2010-02-24 46 views
8

也许我一直在使用像Cairngorm这样的框架来开发Flex开发太久,但我仍然没有得到MVVM。我知道Cairngorm是一个框架,MVVM是一种设计模式,但我在这里比较的是设计模式的Cairngorms实现,主要是模型视图控制器和命令模式。不要误会我的意思,我认为将视图绑定到视图模型的想法非常好,而且可测试性和设计者 - 程序员工作流的优势非常好。但是有两件事情让我感到困扰:其中一件事是用命令来编写我的所有行为,而这些命令也与我在Cairngorm中的联系。只有在Cairngorm中,他们执行命令模式的方式为您提供了所有命令的集中控制器的好处,除非我错过了某些东西,否则您似乎无法使用MVVM。如果我认为在Cairngorm中执行命令在MVVM中复杂得多最糟糕,我的意思是必须创建私有类来实现ICommand,因为我所做的每件事似乎都太多了。然后你有问题,并不是所有的控件都实现了命令,例如,如果你使用的是一个ListBox,我使用了很多,你运气不好;有解决方法,但各种令人费解。我还是没有得到MVVM!

困扰我的另一件事是视图模型之间的通信。在标准的模型视图控制器中,您收集了所有关于视图观察到的集中模型的信息,但对MVVM来说似乎并不是这种情况,至少在我看到的例子中并不这样。因此,举个例子,如果你有一个控制列表,你用它来选择一个项目,然后用它作为不同视图和后续操作的源代码,我不清楚你是如何在没有集中模型的情况下通知所有变更的。

我知道MVVMFoundation和Tom Ershamam关于WPF Commands Everywhere的工作。叫我老式的,但我认为,为了真正理解一个模式,你必须建立一个从头开始使用它的应用程序。我正在做的是什么,但是一直以来我一直在想我必须错过一些必要的东西,因为我似乎无法平息我脑海中的这个小小的声音,不断告诉我必须有更好的方式。

+0

你太对了! – Faruz 2010-02-24 10:10:27

+0

也许你可以将你的抱怨/怀疑变成一个问题? – 2010-02-24 10:26:53

+0

这个问题是我错过了有关MVVM的东西,还是有更好的方法? – 2010-02-24 10:30:35

回答

3

无论框架/结构/模式,你将永远需要的东西,响应点击一个按钮,工具栏/菜单或纯形式。而且你需要说明是否应该启用按钮/菜单。所以ICommand接口对此很好。 我同意Petoj,你不需要一个新的班级。我编写了一个简单的实现,它带有1或2个委托,一个用于实际响应点击(Execute方法),另一个用于命令的“启用”状态。 这样,ViewModel不会混乱。

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

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

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

3

好写一个新的命令impelements的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; 
    } 
} 
+0

非常感谢,我在Josh Smith的优秀文章中看到了这段代码,并且确实很有帮助,但它仍然使我们为什么需要始终使用命令的问题开放。代码中的事件是背后固有的邪恶还是在某些情况下是合理的? – 2010-02-24 12:02:23

+0

即时专家,但我尽量保持我的视图代码为空,如果我可以,所以据我所知,你不能绑定到一个事件,以便留下命令..(我必须说有一个一个纯粹的XAML视图很好,你只需要在一个地方看) – Peter 2010-02-24 12:29:37

+0

后面的代码不是ViewModel。让视图在ViewModel中执行代码的最简单方法是将命令绑定到gui对象。后台代码中的事件处理程序可以调用ViewModel,但是如果使用绑定,则裁剪出中间人。 – 2010-02-26 03:27:14

0

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

1

直升机胡里奥,

的样子,这是旧的文章,但我真的很喜欢你的问题。

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

命令

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

型号定位

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

实际上没有最佳实践在MVVM中用于像您提到的视图之间的共享模型,但您应该看看dependency injection术语来实现这一点。

问候

+0

关于集中模型的可测试性你是对的,但我仍然认为要共享视图模型之间某些全局元素的状态,中心模型的开发要简单得多。它在难以编码(=更大的错误概率)和易于测试之间取得平衡。 – 2010-08-26 08:52:51

+0

是的,我同意,在某些情况下它是可以接受的,但是为了编写高质量的软件,我们应该遵循一些最佳实践,您可以在这里查看http://misko.hevery.com/2008/08/17/singletons-are -pathological-骗子/。我提供的关于单身人士的链接,但实际上提到的全球化状态,这不是编程的最佳做法。请注意,我提供的链接作者是Google开发者的** Agile Coach **。 – ktutnik 2010-08-26 09:09:22

0

人们讨论有关MVVM使人们显得过于理论和Flex有真正不同的架构都在一起,使得MVVM的人从柔性来有点复杂。

让我给你很简单的例子,

在Flex中,大多是我们创建一个MXML的UI组件,我们有我们的绑定模式,我们主要是写我们的UI组件的事件代码以及非UI组件。例如,WebService,HttpService等,它们不是UI组件,但它们仍然可以在MXML中,并且可以在MXML代码中轻松访问它们。因此,基本上你可以在一个MXML文件中组织模型+视图+控制器,这非常容易组织。

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

为了使事情更容易,MVVM强制您定义不同的文件。

你有一个文件,那就是你的模型本身,例如Customer.cs

你有另一个文件,那是你的视图模型,基本上是型号+命令的组合。并且您在Controller的Executed事件中编写Controller代码。

您有另一个文件,即您的View,view基本上绑定到ViewModel的所有属性,它们是Model或Commands。