【问题标题】:MVVM ViolationsMVVM 违规
【发布时间】:2014-10-09 06:55:44
【问题描述】:

我想澄清一些关于违反 MVVM 的问题。因此,我创建了一个解决方案,其中包含一些项目来演示这些案例。 这是解决方案的定义(项目):

  1. 视图(它是一个 WPF 类库,显然它有视图)
  2. ViewModel(它是一个类库,显然它有视图模型)
  3. 模型(它是一个类库,显然它有模型)
  4. 域(它是一个 Class Libraray 并且它具有应用程序 dataModels)
  5. 核心(它是一个类库,它具有 wpf 的核心,如 RelayCommnd 或 EventToCommand)
  6. 应用程序(它是一个 wpf 应用程序和启动项目)
  7. ExternalCustomControl(它是一个虚构的第三方公司创建的 wpf 自定义控件库)

我为您提供下载整个解决方案以便更好地理解 Here

第一期: 我在 MainWindow.xaml 中有一个 EventToCommand 用于窗口的 Closing 事件,并将其附加到 MainWindowClosingCommand PassEventArgsToCommand 设置为 True,然后,在 MainViewModel 中有一个名为 OnMainWindowClosing

的命令的处理程序
private void OnMainWindowClosing(object parameter)
{
  var arg = parameter as CancelEventArgs;

  // What is the best way to show message dialog to user?
  // Do i have to send message to the View to show the messageBox dialog and then the window send me the answer back to continue?
  // What about IMessageBoxService? Doesn't it violates MVVM?

  // Doesn't following code violates the MVVM? 
  // Cancel the Closing of a Window isnt a UI side duty?
  arg.Cancel = true;

}

并且完全每当您想设置 e.Handlede.Cancel 时都会遇到此问题。那么您知道不需要投射的任何其他方式吗参数CancelEventArgs ?

第二期: 我在 MainWindow.xaml 中有一个 EventToCommand 用于网格的 PreviewMouseDown 事件,并将其附加到 MouseClickCommand PassEventArgsToCommand 设置为 True,然后,在 MainViewModel 中有一个名为 OnMouseClick 的命令的处理程序:

 private void OnMouseClick(object parameter)
{
  //      var arg = parameter as MouseButtonEventArgs; 

  // This is the violation of MVVM : To cast the parameter to MouseButtonEventArgs i have to add a refrence  
  //                                 to PresentationCore.dll in the ViewModel Project  

  // The next and worse step is that in most cases we need to know the Original Source of the event 
  //    (maybe its a StackPanel or a Label or etc) and this again vioaltes the MVVM

  // So Whats the WorkAround?

}

第三期: 我在我的 MainWindow 中使用了第三方控件(Imagine Infragistics 或 DevExpress 或任何其他第三方控件,但这里作为示例,我在我的解决方案中创建了虚构控件作为 ExternalCustomControl 项目)像这样:

    <thirdParty:ThirdPartyCustomControl Grid.Row="1"
                                    ItemsSource="{Binding MyItemsSource,Converter={StaticResource converterKey}}" />

ThirdPartyCustomControl 具有 IEnumarabe&lt;CustomControlDataModel&gt; 类型的属性(CustomControlDataModel 是存在于 ExternalCustomControl 程序集中的类型)但是您知道是否要在 MainViewModel 中创建属性 strong> 对于 CustomControlDataModel 类型的控件,您必须在 ViewModel 项目中添加对 ExternalCustomControl.dll 的引用,这违反了 MVVM,因此我创建了一个名为 MyDataModel 的类型 并将控件的 ItemsSource 绑定到 MainViewModel 中的 MyItemsSource 属性:

    // If i define MyItemsSource as List<CustomControlDataModel> i have to add a refrence to ExternalCustomControl.dll
// and i think its again violate the MVVM (because ExternalCustomControl.dll is a UI Side Controls Assembly) 
public List<MyDataModel> MyItemsSource { get; set; }

所以我将 CustomControlDataModel 类型的属性绑定到 MyDataModel 类型的属性,当然我需要一个转换器:

public object Convert(object value, Type targetType, object parameter, c     System.Globalization.CultureInfo culture)
{
  // Imagine when the source data (MyDataModel) is huge (for example 1 milion) it (this dummy Conversion)
  // affects the performance

  if (value is List<MyDataModel>)
  {
    var result = new List<CustomControlDataModel>();

    (value as List<MyDataModel>).ForEach(myVal =>
      {
        var custDataModel = new CustomControlDataModel();
        custDataModel.ID = myVal.ID;
        custDataModel.Name = myVal.Name;
        custDataModel.Age = myVal.Age;

        result.Add(custDataModel);
      });

    return result;
  }
  return value;
}

问题是您是否知道比这种虚拟转换更好的方法,或者您通常将第三方程序集添加到您的视图和视图模型中?

这些是我遇到的问题,如果您知道其他问题并与大家分享您的专业知识,如果您添加更多内容,我将不胜感激。

更新

对于第一期的MessageBox部分,我建议使用此链接 MesageBox

谢谢。

【问题讨论】:

  • UserControls should NOT have view models. 这是一种代码气味。你可以说出来,因为你在做这件事时遇到了问题。您还将 UI 问题推到了视图模型中。 UI 问题在 代码隐藏 中。 MVVM != 没有代码隐藏。
  • @会不会是这个问题

标签: c# wpf design-patterns mvvm


【解决方案1】:

很好的问题!

1) 我个人认为你是对的,使用服务违反了 MVVM。几周前我写了一篇关于这个确切主题的很长的文章,标题为Implementing Dialog Boxes in MVVM。在那篇文章中,我为 MVVM 对话框的整体问题提供了一个“纯粹”的解决方案,但我花了 11 页来解释我是如何得出这个设计的。幸运的是,实际实现非常简单,类似于数据模板,支持您指定的多项目设计,并且可以与 3rd 方库一起使用。读一读,我总是很欣赏客观的反馈。

2) 如果您使用的是 MVVM Lite,那么 EventToCommand 允许您指定参数转换器。这是一个示例,我使用它将窗口鼠标移动消息参数转换为我的视图模型中的等效表示:

<i:EventTrigger EventName="MouseMove">
    <cmd:EventToCommand Command="{Binding ElementName=_this, Mode=OneWay, Path=MouseMoveCommand}" PassEventArgsToCommand="True" EventArgsConverter="{StaticResource MouseEventArgsConverter}" />
</i:EventTrigger>

3) 如果我正确理解了您的问题,我会添加对视图和视图模型项目的引用,至少当这是我的项目结构时。老实说,虽然我通常将我的视图和视图模型放在同一个项目中,例如MyProject.UI,所有内容均按类别文件夹排序。我在为一家大型国际公司工作的合同中看到了这一点,实际上它工作得非常好,因为您通常同时编辑视图及其对应的视图模型;在解决方案窗口中并排放置它们确实使整个开发过程更容易。显然,一些纯粹主义者不太喜欢它,但我个人认为,只要您仍然严格遵守该架构,只需将它们放在同一个项目中就不会破坏 MVVM。我也从来没有在单元测试等方面产生任何问题,您只需要创建视图模型。

【讨论】:

  • 在同一个项目中拥有视图和视图模型绝对不会违反任何 MVVM 概念。有些人喜欢有一个充满视图的项目,而另一个用于视图模型的项目,但我个人的项目是在模块级别而不是在视图/视图模型级别构建的。
【解决方案2】:

我的结束代码看起来像这样,我不认为这违反了 mvvm

xaml

<Window>
 <i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <cmd:EventToCommand Command="{Binding ClosingCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
  </i:Interaction.Triggers>

mainviewmodel.cs

    public ICommand ClosingCommand
    {
        get
        {
            return this._closingCommand ?? (this._closingCommand = new DelegateCommand<CancelEventArgs>((args) =>
                {
                    //i set a property in app.xaml.cs when i shut down the app there with
                    //Application.Current.Shutdown();
                    if (App.IsShutDown) return;


                if (this.HasChanges)
                {
                    var result = _msgService.Show("blup blup", "blup", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);

                    if (result == MessageBoxResult.No)
                    {
                        args.Cancel = true;
                    }
                }
                else
                {                      
                    var result = MessageBox.Show("Blup blup", "blup", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);

                    if (result == MessageBoxResult.No)
                    {
                        args.Cancel = true;
                    } 
                }                    
            }));
        }
    }

第三个问题(第三方控件):如果控件需要某种类型的集合,我不明白您的问题,然后通过您的 VM 公开这些集合。

第二个问题:好不好说。我用这样的东西,我会说是 mvvm 之类的 ;)

 <DataGrid x:Name="myGrd">
   <i:Interaction.Triggers>
      <i:EventTrigger EventName="MouseDoubleClick">
         <Commanding:EventToCommand  Command="{Binding Path=OpenCommand}" 
                                     CommandParameter="{Binding ElementName=myGrd, Path=SelectedItem}"/>
      </i:EventTrigger>
    </i:Interaction.Triggers>

最后我做了 mvvm,但始终牢记做事简单

【讨论】:

  • 您的 messagebox.show 肯定违反了 mvvm 并注意这个问题与实施无关,非常感谢
  • 应该是 _msgService :) 我只是乱写了
【解决方案3】:

对于您的第二个问题,我认为您只需要重新考虑您要实现的目标以及原因。

如果您在网格上连接处理程序,然后根据在代码中单击该网格的哪个特定 UI 元素来做出决定,那么您可能做错了。一个名为OnMouseClickCommand 也有点代码味道,因为该命令什么也没说。命令类似于UserSelectedCommandGrapefuitSliceRequestedCommand ...即比一般的“单击某物”更具体。

您想尝试将“某事”分解为逻辑,以便在那时发出明确而明确的命令,而不是尝试使用 MouseClickEventArgs 并在代码中决定这意味着什么 - 您的 UI 应该决定什么这意味着,并向您的虚拟机发出命令。

因此,您的各个 UI 元素应该具有命令和绑定,而不是尝试在布局 UI 级别设置命令。如果单击Image,则意味着特定的东西,如果单击DataGrid 的一行,则意味着特定的东西,如果拖动Slider,则意味着特定的东西。创建您的 XAML,以便它触发这些特定命令,而不是将责任推到一个模糊的“我的整个 ui 被单击,现在我将使用代码找出究竟是什么”的思维方式。

【讨论】:

  • 第一个示例:假设我需要一个 DragDrop 功能,并基于此我想在 viewmodel 中执行一些操作并最终在数据库中插入一些内容
  • 第二个示例:假设我有一个可编辑的数据网格,其中包含两个事件 RecordUpdating 和 RecordUpdated,当用户更改记录中的某些内容时,RecordUpdating 将触发并且此事件的事件处理程序中的整个操作完全是业务类型操作,因此它在视图模型中但您需要将事件 arg 传递给视图模型以取消或继续更新操作。非常感谢
  • 第一个例子:拖放绝对是一个 UI 操作,所以我会在代码隐藏中处理。一旦我围绕一个元素(或确定什么移动到哪里),我就会触发一个事件(从代码隐藏)来改变作为 ItemSource 绑定的基础集合,无论它是有元素的拖动。
  • 第二个例子:是什么导致 RecordUpdating 事件触发,这是你控制的东西吗?是检测到数据网格中记录的更新吗?您的意思是这是对数据库的更新吗?我猜是这样,因为从 VM 属性更新各个行只是数据绑定的问题。因此,假设您正在谈论更新数据库,是什么阻止您处理取消操作而不必知道视图事件参数中包含的细节?启动数据库操作后,您打算如何取消它?
猜你喜欢
  • 2014-07-31
  • 2010-11-01
  • 1970-01-01
  • 2019-07-21
  • 1970-01-01
  • 1970-01-01
  • 2021-02-26
  • 2012-11-25
  • 2016-01-30
相关资源
最近更新 更多