我可以在 WPF 上使用 MVVM 模板来解决这个问题吗?

Posted

技术标签:

【中文标题】我可以在 WPF 上使用 MVVM 模板来解决这个问题吗?【英文标题】:Can I use MVVM Template on WPF to resolve this? 【发布时间】:2014-03-11 16:19:16 【问题描述】:

我一直在阅读有关 WPF 上的 MVVM 的信息。只要我理解正确,在 MVVM 中我应该将模型(要显示的数据)与显示它的方式(视图)分开,使用 ModelView 来链接两者,对吧?

所以我想做的是使用DataSet 作为模型,其中 n 个字段来自数据库。字段名称、类型和顺序是可变的(从 SQL Server 视图中选择)。

视图将是一个带有 Datagrid 的 WPF 表单。

所以我应该以一种迭代数据集中每个字段的方式构建我的模型视图,并根据字段类型格式化数据,以便稍后在网格上显示。

在 WPF 上使用 MVVM 模板可以做到这一点吗?

提前致谢

更新

我将尝试根据 LordTakkera cmets 阐明我的场景和我尝试做的事情。 我正在使用 .NET 技术构建客户端-服务器分层应用程序。客户端和服务器将在不同的机器上,与 WCF 通信。 一些不同的客户会使用这个软件,所以假设这个场景: 客户 1、2 和 3 需要在网格上显示销售报告。

客户 1 需要:销售日期、客户名称和销售导入。

客户 2 需要:销售日期、供应商名称和销售导入。

客户 3 想要在他的客户表中添加一个数字字段,该字段表示每个客户的预期销售额,并希望在销售报告中显示它。

如果我使用强数据类型,我需要为每个客户端提供一个二进制文件,我是这样吗? 我想要的是为客户端 1、2 和 3 使用相同的二进制文件,根据数据库数据更改销售报告的输出。

为什么?原因很多:

1) 消除错误:如果我在某个类中发现错误,我可以纠正它,重新编译并将纠正后的二进制文件提供给我的所有客户。

2) 可扩展性:如果我向系统中添加了客户 1 要求的某些功能,我可以将该功能提供给我的所有客户。

3) 灵活性:我可以使用相同的二进制文件为每个客户端更改软件的行为。

我了解强类型的优势,实际上我会在某些情况下使用强类型。但在其他情况下,灵活性对我来说很重要。 那么,我的选择是什么?

谢谢!

更新 2

在@LordTakkera 和此页面中的许多人的帮助下,我设法开始了我的应用程序的开发。现在我有了我的模型、我的 wcf 服务、我的客户以及我的第一个 ViewVode 和 View。现在我正在尝试为应用程序添加更多复杂性,为我的项目添加多个 XAML 视图。如果我继续遵循 MVVM 模式,则此页面与插入所有其他视图的主页之间的导航也应该使用 viewModel 进行管理(对于我阅读的内容,它称为应用程序 viewModel)。我的想法是有一个容器 MainPage ,其中包含将出现子视图的区域。我还希望将子视图放在单独的 XAML 文件中,因为有些视图非常复杂,我会丢失视图!问题是在带有主容器页面的 MVVM 模式上找不到 WPF 导航的单个示例代码。我能找到的所有示例代码:

1) 使用外部工具包,如 Prism 或 MVVM ligth,现在我不想搞乱新事物!

2) 每个视图的所有控件都是在单个 XAML 上创建的。

我需要一些代码方面的帮助来创建我的应用程序的基本布局,并且想知道我的计划是否可行。谢谢!

【问题讨论】:

广泛说明:codeproject.com/Articles/30905/WPF-DataGrid-Practical-Examples 与 MVVM 无关,但是,忘记DataSet。它只是一个美化的Dictionary<string,object>,它迫使你进行各种可怕的stringly typed hacks 和强制转换,并引入了一堆丑陋且容易出错的代码。创建一个适当的强类型数据模型,并将您的 UI 数据绑定到该模型的适当属性。 对不起,我不明白。正如我提到的,我的想法是使用 MVVM 将 SQL Server 视图的结果显示到 GridView 中。因此,如果我理解正确,您告诉我我需要创建一个强类型类来存储 DataSet 中的数据。但在这种情况下,如果视图发生变化(例如我添加了一个字段),我必须修改模型类、视图模型和视图并重新编译我的所有代码!这对我来说听起来很疯狂!没有办法创建抽象模型、视图模型和视图吗?谢谢! 【参考方案1】:

正如 HighCore 所指出的,DataSet 不是一个很好的模型选择,但除此之外这听起来很对,不过我会从你的帖子中提出一些担忧。

    你提到你有变量“类型”,这是一个很大的危险信号。如果您有变量类型,您将如何在数据网格中拥有一致的列?现在,如果您的意思是要为每种类型使用不同的数据网格(可能使用类型化的数据模板),那就没问题了。

    当您说“格式化数据”时,我希望您的意思是“将其存储在数据对象中”。 WPF 绑定到对象的属性,因此您的数据网格应如下所示:

    <DataGrid ItemsSource=Binding DataItems>
       <DataGrid.Columns>
          <DataGridTextColumn Header="Name" Binding="Binding Name"/>
          ...
       </DataGrid.Columns>
    </DataGrid>
    

基本上,确保您绑定到从您的模型中填充的对象。 ViewModel 绝对是应该执行此转换的对象。

更新

对于不理解您的应用程序的目的,我深表歉意,在该特定应用程序中,您所谈论的内容更有意义。话虽如此,我会尽力解决您的疑虑:

    一般来说,如果底层数据上下文或对象发生变化,您应该需要更改模型/视图/视图模型。 C# 是强类型的,WPF 的全部意义在于拥有声明性、强类型的 UI。我了解您正在尝试显示“通用”数据,但除非您正在执行“SELECT *”类型查询,否则无论如何您都需要更改一些代码。如果您知道要显示的视图是什么,那么在修改/添加字段时更改所有代码是完全可以接受的(甚至是可取的,因为它会迫使您考虑它影响的任何地方的更改)。

    变量或“弱”类型是危险信号,因为如上所述,C# 是一种强类型语言。强类型有很多优点,其中最少的是良好的编译时错误检查。如果您想处于弱类型环境中,请三思而后行您使用的是哪种语言。

    这又回到了通用数据。您可以使用类型化的数据模板来完成一些基于类型的“格式化”更改,但这是在多态(相对于弱类型)场景中。您可以使用转换器来执行您描述的货币格式,但我会考虑您正在尝试做的事情并确保它是您想要的方向。

更新 2 对于这种情况,强类型应该没问题。实际上,您正在与 WCF 服务进行通信,该服务根据定义是强类型的(这是 DataContracts 的重点,如果您不这样做,请修复它并为自己省去很多麻烦)。所以我们知道您的模型(WCF 服务)具有强类型数据对象,具有“销售日期”、“客户名称”、“销售导入”、“供应商名称”和“每个客户的销售额”等属性。现在我们只需要三个视图(甚至是一个灵活视图)来完成您的三个场景。

您当然不需要三个二进制文件,即使使用三视图方法(您只需要一种方法来选择哪个视图处于活动状态)。您甚至可以有一个应用程序配置来确定使用哪个“客户端”,或者通过 MEF 加载您的视图。一种灵活的视图方法需要做更多的工作,因为您必须绑定列集合而不是静态定义它(顺便说一下,这是一个问题,我目前正在为我的一个项目工作)。

简而言之,有很多方法可以让不同的视图出现。我不建议根据数据更改视图,因为您的用户不会期望它,而且实施起来会非常混乱。如果您希望“客户端 1”只能访问“视图 1”,我会在应用程序配置中设置它,或者只分发“视图 1”插件并使用 MEF 加载它。如果所有客户端都应该能够看到所有视图,则需要某种组合框或导航栏。

更新 3 (可能)为同一视图模型拥有多个视图,尤其是同一模型的多个视图模型,是使用 MVVM 的原因之一!我很高兴你能发现这一点。关于您的问题:

关于你的第一条评论:

    您的模型(在 WCF 端)不会了解用户过滤器,除非您的请求包含它们。包含这些信息没有问题(实际上我会鼓励它降低网络利用率),但在设计服务合同时需要注意这一点。

    查看模型始终(据我所知无一例外)位于客户端。客户端“模型”成为您方案中的 WCF 代理。

至于你的第二个:

    绝对有一种方法可以从您的数据库创建数据模型类,事实上,我会鼓励这样做。 .NET 有一个名为 Entity Framework 的库(当前版本为 6.02,在 Nuget 上可用),它将自动生成具有自动外键导航和许多其他功能 (MSDN) 的数据库“对象”。警告一句,EF 类一般不能通过 WCF 直接发送(由于导航属性,递归会破坏序列化程序)。因此,您想要发送给客户端的任何内容都应该作为手动创建的 DataContract 发送,并且服务器上的一个类在两者之间进行转换。

    我不知道链接,但是拥有多个视图非常简单,您只需使用 Content Presenter 并在想要更改时设置支持的 UserControl 属性。它也可以通过使用选项卡控件来完成,可能还有无数其他方式。:

    <ContentPresenter Content="Binding CurrentView"/>
    

    请参阅上面关于将 EF 类与 WCF 结合使用的说明。即使您不使用 EF,通常也建议使用自定义构建的数据对象作为您的合同。这样一来,您只需发送所需的信息,您的合约库就不会被数据库/模型对象弄得一团糟。

更新 4 1. 请记住,您不希望将数据库对象直接公开给 WCF,并且您尤其不希望对 EF 对象执行此操作(因为导航属性)。所以你可以:(A)为你的 DAL 使用 EF 并转换为 WCF 的数据契约对象(推荐)或(B)创建你自己的模型对象,这可能与数据契约对象相同(尽管我仍然会保留它们分开)并通过 ADO.NET 或类似的东西执行 DAL。如您所述,如果您使用 EF,数据库 CRUD 操作非常容易完成。

    如果数据不同,您只需要不同的视图模型。如果您的视图模型(主要)由不同视图显示不同的大量对象组成,那么我认为没有理由在多个视图模型中复制代码。经验法则是:视图模型应该独立于视图。只要您的 VM 中没有任何特定于视图的代码,您就可以将它重用于尽可能多的视图。

    你的最后一个问题很大,虽然我很乐意给你我的意见,但你会发现如果你问 100 位工程师这个问题,他们都会给你不同的答案(好像你已经找到了)。

所以,首先,你是绝对正确的。使用 MVVM(或任何设计模式)会增加您必须编写的代码量,有时甚至会显着增加(尤其是当您刚刚开始一个 MVVM 项目时,样板代码的数量有时看起来绝对荒谬)。

然而,设计模式的重点不是让我们的代码更小,而是让它更可扩展、可维护和可测试。具体到 MVVM,您可以将多个视图放在同一个视图模型上(如您所见),并且能够将多个视图模型放在同一个模型上。这意味着如果你想添加一个视图或视图模型,你不必重写一大堆代码。此外,视图模型和模型都易于测试,因为它们不了解 UI。

总的来说,我的优秀设计规则是使用常识!。如果将来可能会更改/扩展,那么使用设计模式几乎总是值得额外的代码。如果我只是想在一个小时内把它扔掉,谁在乎呢?

就像我说的,设计模式的适用性以及何时允许偏差在软件工程中是一个巨大的争论。我鼓励你只做有意义的事情(当然要考虑未来),并在你错了时进行重构。

如果您有其他问题,请告诉我。

【讨论】:

1) 如果来自 SQL 视图的数据发生变化,那么对于不强制我修改模型、视图和视图模型的数据集,Wich 是替代方案吗? 2)为什么变量类型是一个危险信号? 2)“格式化数据”是指根据视图中的字段类型更改数据在网格上的显示方式。如果字段是货币,则显示带有货币符号等的数字。谢谢! 非常感谢LordTakkera 为澄清我的疑惑所做的培训。我编辑了这个问题以包括我的场景的一些细节。你怎么想的?再次感谢! @ercpap 我尝试解决您上面的更新。如果我能提供其他任何东西,请告诉我! 好的。我理解你的观点。我不知道我可以有一个模型、一个视图模型和多个视图。这似乎是一种有趣的方法,使客户可以对同一报告使用多个视图。所以要创建我的销售报告,我应该:1)在数据库上创建 sp 或视图; 2)为存储数据创建强类型类模型; 3)根据用户过滤器填充模型; 4)在WCF上创建一个Contract来暴露模型的某些部分; 5)创建我的ViewModel(在服务器上?还是在客户端?); 6)在客户端创建多个视图,让用户选择要使用的视图,对吗? 3 关于previuos 评论的问题:1) 有没有办法自动化从数据库视图或SP 创建数据模型类的过程?或者我需要手动创建它们? 2)您能否提供一些链接与多个视图的示例并在它们之间进行选择? 3)我应该使用将我的模型类直接暴露给 WCF,还是需要为此使用 WCF 特定类?再次感谢您!

以上是关于我可以在 WPF 上使用 MVVM 模板来解决这个问题吗?的主要内容,如果未能解决你的问题,请参考以下文章

wpf mvvm DataGird 按钮事件

WPF - MVVM:SelectionChanged 后的 ComboBox 值

WPF中的MVVM模式简单实现

使用 MVVM 对集合进行 WPF 分组

具有多个图像的 WPF DataGrid RowDetailsTemplate (MVVM)

WPF使用MVVM设计模式 问题