我仍然没有得到MVVM!

时间:2010-02-24 10:07:26

标签: wpf design-patterns mvvm

也许我一直在使用像Cairngorm这样的Frameworks进行Flex开发太长时间,但我仍然没有得到MVVM。我知道Cairngorm是一个框架而MVVM是一种设计模式,但我在这里比较的是Cairngorms设计模式的实现,主要是模型视图控制器和命令模式。不要误解我的意思,我认为将视图绑定到视图模型的想法很棒,并且可测试性和设计器 - 程序员工作流程的优势很大。但是有两件事让我感到烦恼:一件是用Commands编写我的所有动作,顺便说一句,这也让我与Cairngorm接壤。只有在Cairngorm中,他们实现命令模式的方式才能让你拥有一个集中控制器用于所有命令,除非我遗漏了一些东西,否则你似乎无法使用MVVM。如果我认为实现Cairngorm中的命令在MVVM中的复杂性是最糟糕的,我的意思是必须创建实现ICommand的私有类,因为我做的一切似乎都太多了。然后你遇到的问题是并非所有的控件都实现了命令,例如,如果你使用的是ListBox,我经常使用它,你运气不好;有一些解决方法,但各种错综复杂。

困扰我的另一件事是View Models之间的沟通。在标准的模型视图控制器中,您可以收集视图观察到的集中模型的所有信息,但MVVM似乎不是这种情况,至少在我看到的示例中没有。因此,例如,如果您有一个带有列表的控件,您可以使用该列表选择一个项目,然后将该项目用作不同视图和后续操作的源,我不清楚如何在没有集中模型的情况下通知所有人。

我知道MVVMFoundation和Tom Ershamam关于WPF Commands Everywhere的工作。称我为老式,但我认为为了真正理解模式,你必须构建一个从头开始使用它的应用程序。这就是我正在做的事情,但是我一直在想我一定会错过一些必不可少的东西,因为我似乎无法在我脑海中保持这个小小的声音,一直告诉我必须有一个更好的方法。

5 个答案:

答案 0 :(得分:3)

好好编写一个新命令来阻止ICommand似乎有点过分了看看这个类: VB.NET:     公共类RelayCommand         实现ICommand

#Region " Declarations"
    Private mCanExecute As Predicate(Of Object)
    Private mExecute As Action(Of Object)
#End Region

#Region " Constructors"
    Public Sub New(ByVal canExecute As Predicate(Of Object), ByVal execute As Action(Of Object))
        mCanExecute = canExecute
        mExecute = execute
    End Sub

    Public Sub New(ByVal execute As Action(Of Object))
        mCanExecute = Nothing
        mExecute = execute
    End Sub
#End Region

#Region " Events"
    Public Custom Event CanExecuteChanged As EventHandler Implements System.Windows.Input.ICommand.CanExecuteChanged
        AddHandler(ByVal value As EventHandler)
            AddHandler CommandManager.RequerySuggested, value
        End AddHandler
        RemoveHandler(ByVal value As EventHandler)
            RemoveHandler CommandManager.RequerySuggested, value
        End RemoveHandler
        RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
            Throw New ApplicationException("Can't raise custom command!")
        End RaiseEvent
    End Event
#End Region

#Region " Public Methods"
    Public Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
        If (mCanExecute Is Nothing) Then
            Return True
        End If
        Return mCanExecute(parameter)
    End Function

    Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
        mExecute(parameter)
    End Sub
#End Region

End Class

C#

public class RelayCommand : ICommand
{

    #region Declarations
    private Predicate<object> mCanExecute;
    private Action<object> mExecute;
    #endregion

    #region Constructors
    public RelayCommand(Predicate<object> canExecute, Action<object> execute)
    {
        mCanExecute = canExecute;
        mExecute = execute;
    }

    public RelayCommand(Action<object> execute)
    {
        mCanExecute = null;
        mExecute = execute;
    }
    #endregion

    #region Events
    public event EventHandler CanExecuteChanged {
        add {
            CommandManager.RequerySuggested += value;
        }
        remove {
            CommandManager.RequerySuggested -= value;
        }
    }
    #endregion

    #region Public Methods
    public bool CanExecute(object parameter)
    {
        if ((mCanExecute == null)) {
            return true;
        }
        return mCanExecute(parameter);
    }

    public void Execute(object parameter)
    {
        mExecute(parameter);
    }
    #endregion

}

并使用它只是暴露一个ICommand类型的属性,该属性返回一个带有委托给函数的新RelayCommand ......

vb.net

Private mDeleteCommand As ICommand

Public ReadOnly Property DeleteCommand() As ICommand
    Get
        If (mDeleteCommand Is Nothing) Then
            mDeleteCommand = New RelayCommand(AddressOf CanDeleteTodo, AddressOf DeleteTodo)
        End If
        Return mDeleteCommand
    End Get
End Property

C#

private ICommand mDeleteCommand;
public ICommand DeleteCommand {
    get {
        if ((mDeleteCommand == null)) {
            mDeleteCommand = new RelayCommand(CanDeleteTodo, DeleteTodo);
        }
        return mDeleteCommand;
    }
}

答案 1 :(得分:3)

无论框架/架构/模式如何,您总是需要能够响应按钮单击,工具栏/菜单或普通表单上的内容。如果应该启用按钮/菜单,你需要一些东西。所以ICommand接口很适合这个。 我同意Petoj,你不需要新课。我写了一个简单的实现,需要1或2个委托,一个用于实际响应click(Execute方法),另一个用于命令的“enabled”状态。 这样,ViewModel就不会混乱。

但我同意这不是一个集中的命令库。但你真的想要一个吗?我更喜欢将特定于app的一部分的命令放在相应的视图模型中,并在应该通知应用程序的其余部分时引发适当的事件。

对于列表框,我将SelectedItem属性绑定到ViewModel上的属性。使用INotifyPropertyChanged,代码的任何部分都可以对更改做出反应。

ViewModels之间的通信是一个很好的问题。如果在同一屏幕上需要不同的视图,则可以使用包含每个视图的视图模型的“超级”视图模型。 那里有很多MVVM框架。我使用了Mark Smith's MVVM helpers的部分,它非常轻巧且有用。

答案 2 :(得分:1)

Helo Julio,

看起来这是一个老帖子,但我真的很喜欢你的问题。

最近我也是一名flex程序员和一名WPF。我很了解Cairngorm (让Say [C])框架,学会了使用Parsley Framework使用Presentation model,并且在WPF中我意识到Presentation Model已经改为MVVM模式。

<强>命令

[C]中的命令与MVVM中的命令不同,[C]中的命令作为Command Pattern更令人满意,其中[C]控制器充当Invoker,因此在[C]中命令实际上可以支持Chain,Undo,Transaction等。在MVVM Command中远不是Command模式。在MVVM中使用Command的主要思想是因为ICommand是与操作绑定的唯一方法。在Flex中,您可以使用click="{viewmodel.doOperation()}"轻松地将方法绑定到事件,但不能在WPF中绑定。

模型定位器

将应用程序状态集中在像[C]这样的单一模型定位器上是一种不好的做法。另一方面,如果你这样做,你将失去“轻松”单元测试代码的机会。如果您的模型定位器包含大量较小的模型,那么您的代码越依赖您的测试就越困难,而您已经对代码进行了大量依赖。而且,使用单身人士的最可怕的事情是不可能嘲笑。因此,如果您的模型定位器非单元测试友好,那么您的单元测试过程可能会充满痛苦。

实际上没有在MVVM的上下文中使用的最佳实践,就像你提到的那样在视图之间共享模型,但你应该看一下dependency injection术语来实现它。

最好的问候

答案 3 :(得分:0)

好的,这样给这个帖子一些关闭以供将来参考。首先非常感谢你的答案。 RelayCommand真是个好主意;它确实简化了很多事情,使事情易于测试和使用。看起来这是要走的路。绑定到SelectedItem似乎也解决了ItemsControl中缺少命令支持的问题。关于ViewModels之间的通信,我不相信拥有超级视图模型可以解决我的问题,因为它将模型与我的可视树联系在一起。此外,我还没有找到一种方法在这个结构中有一个干净的,对象无关的方式来在不同层次结构中的所有视图模型之间进行通信。所以我要尝试的是首先创建一个集中模型,它是一个实现INotifyPropertyChanged接口的单例。然后,ViewModel可以拥有此模型的实例,并使用我们的老朋友Observer模式对其进行传播,以传播相应的属性更改。似乎工作正常,虽然我有点担心循环引用。你觉得怎么样?

答案 4 :(得分:0)

讨论MVVM的人们认为它看起来太理论化了,而且flex的结构真的不同,这使得MVVM对于来自flex的人来说并不复杂。

让我举一个非常简单的例子,

在Flex中,我们主要为UI Component创建一个MXML,我们有可绑定模型,我们主要在UI Component和非UI组件的事件中编写代码。例如,WebService,HttpService等,它们不是UI组件,但它们仍然可以在MXML中,并且可以在MXML代码中轻松访问它们。

基本上你可以在一个MXML文件中轻松组织Model + View + Controller。

在Silverlight中,在XAML中,您只能将UI元素作为要修改的页面/用户控件的子元素。有一些限制,XAML允许您将非UI元素仅放在资源中,并且您添加的资源的类型变量在XAML后面的代码中不易访问,您必须调用find资源来访问代码。

为了简化操作,MVVM会强制您定义不同的文件。

您有一个文件,即您的模型本身,例如Customer.cs

您有另一个文件,即ViewModel,它基本上是Model + Commands的组合。然后在Command的Executed事件中编写Controller代码。

您有另一个文件,即View,视图基本上绑定到ViewModel的所有属性,它们是模型或命令。