大型 MVVM 模板
为了省去您每次创建 WPF/MVVM 项目时都要重复进行的工作,我将所有内容都打包到了这里。
- 下载 mvvm-template 源代码
- 下载可运行的可执行文件。无代码
- 下载 MVVM_Template_Project__MVVM_Light_5 (无 EXE)
- 下载 MVVM_Template_Project__MVVM_Light_5
项目是用 VS 2013 构建的。代码有完整的注释,您可以直接阅读,很容易就能明白代码是如何工作的。
欢迎下载并试用这段代码。
如果您下载并觉得它有用,请回来投票或评论。 :)
注意:我已经更新了代码。现在您可以只下载代码(2MB),或者只下载可执行文件来查看结果(无代码,440KB)。(这些是针对 MVVM Light 4.x.x 版本)
注意 2 (10月29日):我已经为 MVVM Light 5 更新了二进制文件和代码示例。请阅读最后的说明以了解更改。
这应该可以解决您下载了代码但缺少必要引用 DLL 的问题。
注意 3 (2016年4月21日):已更新源代码,您可以在 V2 zip 文件中获取。
引言
如果您曾经想开始一个小型(或大型)项目,并对 WPF-MVVM 所涉及的繁琐的“管道”工作感到厌烦,那么这篇文章就是为您准备的。
如果您是 MVVM 的新手,并想快速了解您可以实现的目标,请务必下载代码并仔细阅读。代码有完整的注释,涵盖了许多不同的主题。
那么,这里有什么?
大部分内容都不是我原创的发明,但这里是将许多不同的东西整合到了一起,因此当您想开始一个项目时,可以快速使用它,然后根据需要替换或删除不需要的内容。
以下是内容的快速概述
- MVVM Light & .NET 4.5
- 基础 ViewModel 中的 INPC 实现。
- Menu
- 选项卡控件(MVVM + 每个视图的选项卡绑定)
- 枚举
- 用于模拟和原型设计的随机数据
- 使用命令将按钮绑定到 ViewModel
- 启用/禁用按钮
- 验证(文本框/输入字段)
- Converters
- 样式
- DataTemplates
- 资源(图片、文本文件)
- 扩展方法
- 随机助手
- DataService & DI(来自 MVVM Light)
- 单元测试
您可能不会使用所有这些功能,但最好将它们放在那里,然后移除不需要的部分,而不是试图记住如何让 Resharper 与 MVVM Light 良好协作,如何使 `RaisePropertyChanged` 在不提供属性名称作为 `string` 的情况下工作,需要引用哪些文件等等。
我被说服了,下一步是什么?
下载代码,运行解决方案,然后开始动手。在接下来的章节中,我将重点介绍包含的内容、原因以及如何使用它们(如果需要)。
红色药丸还是蓝色药丸?
请教我基础知识,牵着我的手带我四处逛逛。
--> 从背景知识开始。
当您还在艰难地编写第一个“Hello World!
”时,我已经在收集 Microsoft MVP 了。
--> 直接跳转到感兴趣的部分。
背景
我一直听说的 MVVM 是什么?好吧,正如右边的图片所示,它是一种模式,可以将您的视图与模型解耦,并使一切都易于单元测试。换句话说,就是减少修复错误代码的时间,而有更多时间在夏威夷晒太阳。
MVVM 的优点(除其他外)是 View(在 XAML 中)与 ViewModel(您的 C# 代码)之间的强大绑定能力,这旨在使编码人员摆脱对设计工作方式的担忧,也使设计人员摆脱对代码工作方式的担忧。您可以制作原型,告诉您的开发人员:我需要一个具有 `int` 类型属性 `Foo` 和 `string` 类型属性 `Bar` 的类。然后您去告诉您的设计人员:我需要一个看起来像那样的页面,您将绑定到:一个 `int` 类型的属性 `Foo`,以及一个 `string` 类型的属性 `Bar`。
我对 WPF/MVVM 唯一的问题是启动编码所需的“管道”工作量。常见示例包括
- 如何添加验证规则……
- 如何定义转换器……
- 命令语法又是什么……
- 如何绑定到 ViewModel……
- Messenger 如何工作……
通常,一旦我解决了这些问题,我就会花一些时间模拟设计时的数据,等等。您应该已经明白我的意思了……大量的“管道”工作……
总体结构
- 辅助(所有杂项文件夹的顶层)
- Converters
- 助手(扩展方法等)
- 资源(文件、dll、图片等)
- 验证
- 设计时(所有设计时文件的中心文件夹)
- 模型
- ViewModels
- 视图
逐级解析(项目)树
属性
我已将 _Annotations.cs_ 文件添加到 Properties 文件夹,以便于调用 `RaisePropertyChanged` 而无需指定正在更改的属性。这样可以避免在您更改属性名称但忘记更改 `RaisePropertyChanged` 字符串参数中的名称时,发生简单但烦人且难以发现的错误。
- 感谢 Nico 的数字足迹
Converters
这些类基本上继承自 `IValueConverter`。想法很简单,您将输入提供给 `Convert` 方法,然后您就可以控制输出。
这有什么用?
- 您有一个 `DateTime` 属性,只想显示年份。
- 您有一个数字属性,用于告知用户有多少独角兽,您希望能够根据该数字说出“
没有独角兽
”、“一只独角兽
”或“一些独角兽
”。
例如
class CommentsConverter : IValueConverter
{
/// <summary>
/// An example for a converter that will return
/// "Warning: Wild Unicorns found on premise."
/// if the word "unicorn" exists in the string.
/// </summary>
public object Convert(object value,Type targetType,object parameter,CultureInfo culture)
{
var input_string = value as string;
if (input_string.IsNullOrEmpty())
return "The cake is a lie.";
if (input_string.Contains("unicorn"))
return "Warning: Wild Unicorns found on premise.";
return "This following text is Unicorn free: [" + input_string + "]";
}
/// <summary>
/// It it makes sense to convert back, IMPLEMENT the following method as well !!!!
/// </summary>
public object ConvertBack
(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
要使用上面的转换器,请在 XAML 中添加此代码
<TextBlock
Text="{Binding SomeProperty, Converter={StaticResource CommentsConverter}}"
/>
辅助函数
这里是所有帮助您完成工作但本身并不重要的小类。
Enums.cs
您可以将所有枚举放在一个地方,这样在您想查看它们时就不需要去寻找。
MessengerClasses
这与 MVVM Light 一起使用,用于从一个视图发送消息到另一个视图。此类仅包含您将作为消息发送的一组特定类的集合。请参阅代码以获取完整的文档示例。
Random_Helper
如果您厌倦了在调用 `rand.GetNext()` 获取随机数之前先写 `Random rand= new Random();`(并且希望不要忘记在紧密循环中这样做,因为您会得到相同的数字),那么我向您介绍:`Random_Helper`。
它拥有您能想到到的所有随机方法,甚至更多。优点是您可以直接使用它们,添加它们,或删除您不喜欢的,而不是自己创建它们来获取一些随机数据。
所有方法都已记录,请查看。一些方法包括
RandomName
RandomDate
RandomPhone
RandomWeather
RandomBool
- 感谢 Shemesh 的这个想法。
StringExtensionMethods
如果您讨厌写:`string.IsNullOrEmpty(some_string)`,并且喜欢 Linq 语法,例如:`some_string.IsNullOrEmpty()`,您会喜欢这些。
扩展方法基本上是语法糖,它允许您像访问类的常规方法/属性一样访问 `static` 方法。如果您从未听说过它们,请参阅 MSDN 文档。
Validation_helper
这个想法基于这样的假设:您的验证需要在多个验证(不同类)中执行重复的操作。我们可以将所有重复的代码移到一个静态助手类中,然后从它那里调用方法。
资源
您所有的杂项文件很可能都会放在这里。在这个例子中,我有一个图片,一个文本文件,我的样式和数据模板,以及另一个类(`ApexGrid`)我也将使用它。
ApexGrid.cs
普通的 XAML 网格很糟糕。Apex Grid 将允许您替换以下代码
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
</Grid>
用
<a:ApexGrid Rows="Auto,Auto,*,28" Columns="Auto,200" >
<!-- Your code goes here -->
</a:ApexGrid>
- 感谢 Dave Kerr
DataTemplates.cs
在这里,我们将 `ViewModel` 粘合到 `View`。请参阅源代码以获取更多示例和旧式模板化的选项。
<!-- Here I'm binding ViewModels to Views, so the content -->
<!-- control will know what to display (on the view) when -->
<!-- it encounters a ViewModel -->
<DataTemplate DataType="{x:Type vm:About_ViewModel}">
<views:About_View />
</DataTemplate>
Styles.cs
将所有样式声明在一个地方,以赋予应用程序一致的外观,并避免编写重复的代码,这些代码最终会中断且不同步。
<!-- Lets define a template for all of our labels for example: -->
<Style TargetType="Label" x:Key="MyLabelTemplate">
<Setter Property="Foreground" Value="Yellow" />
<Setter Property="Margin" Value="5"></Setter>
</Style>
验证
与转换器类似,这些类扩展了 `ValidationRule`。它们会接收一个对象,运行一些逻辑,然后返回一个 `true`/`false` 的结果,这可能会触发一个样式(如果您愿意)。(在此模板中,它将显示一个闪烁的红色感叹号,您可以在“_Styles.cs_”中找到它,如果您想了解实现方式)。
class Example_Validation : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var str = value as string; // Convert to our string
var sb = new StringBuilder(); // For the error messages
var valid = true; // Rather obvious
if (str.Length < 5)
{
valid = false;
sb.AppendLine("String too short.");
}
return new ValidationResult(valid, sb.ToString());
}
}
设计时
您可以在这里放置所有设计时类。在此模板中,我只是重用了 Laurent Bugnion 和他的 MVVM Light 模板。
基本上,它保存了 `DataService` 的设计时实现,并返回假的本地数据,而不是通常会调用数据库或网络的真实 `DataService`。
模型
在这里,您将拥有所有模型类。如果您问什么是模型,您应该阅读一些关于 MVVM 的内容,但基本上它是所有与数据库或 Web 交互的逻辑,并且完全不关心您如何将其呈现给用户。
在此示例中,您会找到 `IDataService` 接口、实现(`DataService`)以及一个 _Person.cs_ 类,我用它来模拟人员信息,但在真实的程序中,这些信息将来自数据库。
ViewModels
在这里,您将拥有所有的 ViewModels,它们位于 View 和 Model 之间,充当缓冲区,避免将 UI 与逻辑混淆。模板中的所有 ViewModels 都已完整注释,如果您感兴趣,可以阅读它们的作用。
快速概述
- _About_ViewModel.cs_:我的信息,但您可能需要类似的东西
- _Main_ViewModel.cs_:MainWindow 将绑定的属性
- _MVVMLight_ViewModel.cs_:Laurent 的示例,保留在此以便您了解他所做的
- _MyBase_ViewModel.cs_:我们所有的自定义 ViewModel 都继承自它,以支持 INPC“魔法”
- _Random_ViewModel.cs_:第一个选项卡,包含随机数据
- _ValidationsConverters_ViewModel.cs_:第二个选项卡,包含验证和转换器的示例
ViewModelLocator
:Laurent 的定位器。它用作静态实例以获取其他 ViewModel
视图
这些是包含 UI 的文件。每个 View 都是一个 XAML 文件和一个 _Xaml.cs_ 代码隐藏文件。唯一带有代码隐藏的 View 是 About,原因是我想链接回 CodeProject。根据您对 MVVM 模式“纯粹性”的看法,您可能会喜欢或讨厌这一点。
进入道场
MainWindow 和 Main_ViewModel
这是我们程序的入口点。ViewModel(又名 VM)将其他 ViewModel 作为属性持有,View 只是将每个 `TabItem` 绑定到一个 VM。
窗口由一个 `DockPanel` 组成,顶部停靠一个 `Menu`,底部停靠一个 `StatusBar`,最后一个子元素 `TabControl` 占据了其余空间。
`Menu` 中的 `MenuItems` 使用 `Commands` 绑定到 `ApplicationCommands`(剪切、复制、粘贴)或绑定到其他 VM 中的实际方法。为了访问这些其他 VM,我们使用 `MVVM Light` 的 messenger 类,发送一种类型的消息,其他 VM 将注册接收该消息并将其路由到其自身的命令。
请查看代码中的示例和注释。以下是一个代码片段
<Grid x:Name="LayoutRoot">
<DockPanel >
<!-- -->
<Menu DockPanel.Dock="Top" ... >
<!-- Default application commands: -->
<MenuItem Header="_Default commands" IsTabStop="False">
<MenuItem Command="ApplicationCommands.Copy" />
<!-- ... -->
</MenuItem>
<MenuItem Header="_Custom commands" IsTabStop="False">
<MenuItem Header="Refresh 7 people"
Command="{Binding RefreshPeopleMenu_Command}"
CommandParameter="7" />
<!-- ... -->
</MenuItem>
</Menu>
<StatusBar DockPanel.Dock="Bottom" ...>
<StatusBarItem HorizontalAlignment="Right">
<TextBlock Name="StatBarText" Text="{Binding StatusBarMessage}" />
</StatusBarItem>
</StatusBar>
<TabControl>
<TabItem Header="Random Helper">
<ContentControl Content="{Binding Random_VM}" />
</TabItem>
<!-- ... -->
</TabControl>
</DockPanel>
在 `Main_ViewModel` 构造函数中,我们注册侦听类型为 `StatusMessage` 的消息,以便可以轻松地从应用程序中的任何 VM 设置 `StatusBar` 文本。
Messenger.Default.Register<StatusMessage>(this, msg => StatusBarMessage=msg.NewStatus);
Random_View 和 Random_ViewModel
顶部有一个 `ComboBox`,它绑定到一个 `enum` 示例。没什么特别的。
底部部分包含一个 `DataGrid`,其中包含一些 `Person` 类型的随机对象(您可以在 _Models_ 文件夹中找到该类)。除了绑定到对象的属性和显示该对象的不同属性外,有趣的是 Interaction.Triggers。请查看代码以获取更多注释,但基本上它使用 Blend 的 DLL 使您的控件能够响应单击事件。
在我们的例子中,双击 `DataGrid` 将打开一个 `MessageBox`,其中包含您点击的那个人的姓名,当然您也可以执行任何您想做的事情。额外的好处是,您可以获得用于将参数与命令一起传递的语法(在我们的示例中,我们将其绑定到选定的项)。
接下来,我们有一个关于如何使用命令的 `CanExecute` 来启用/禁用按钮的示例。在这种情况下,它只是一个简单的检查,即传递的参数大于 3,因此如果您在 `ComboBox` 中选择值“`1”,按钮将被禁用。
为了确保在选择“`1”时立即禁用该按钮,我们将调用 `CommandManager.InvalidateRequerySuggested();` 绑定到的属性。这将刷新所有绑定,并因此检查该按钮的 `can execute` 状态并禁用它。请查看代码注释以获取更多信息。
与主 VM 中的情况一样,我们使用以下方式将刷新人员的命令注册到 messenger:
Messenger.Default.Register<RefreshPeople>
(this, (msg) => Execute_RefreshPeople(msg.PeopleToFetch));
这使我们能够重用此命令,并从 `Menu`(它是 `Main_ViewModel` 的一部分)中使用它,而无需硬编码调用。
最后,当按钮被点击,并且刷新人员的命令被触发时,我们正在向 messenger 发送一条消息来设置状态。我已经将该方法设置为 `Messenger` 类上的 `static` 方法,因此您可以从任何 VM 调用它,传递您想要设置为 `StatusBar` 的 `string`。代码如下:
private void Execute_RefreshPeople(int arg)
{
PeopelCollection = new ObservableCollection<Person>(_dataService.GetPeople(arg));
var msg = arg + " people refreshed.";
StatusSetter.SetStatus(msg); // <-- This sends the message
}
ICommand
这里我使用一种约定,即在同一时间声明和初始化命令,从而省去了在构造函数中初始化命令的麻烦。下面是一个例子
public ICommand SomeMethod_Command
{
get
{
return _someMethodCommand ??
(_someMethodCommand =
new RelayCommand<int>(Execute_SomeMethod,
CanExecute_SomeMethod));
}
}
private ICommand _someMethodCommand;
ValidationsConvertes_View 和 ValidationsConvertes_ViewModel
验证
这些的目的是让用户知道他们的输入有问题。您可以使用它来验证电子邮件,确保某些字段不为空等等。前面已经简要介绍过 了。在我的 _Styles.xaml_(位于 _Auxiliary\Resources_ 下)中,我定义了一个名为“_myErrorTemplate_”的样式,它以“Control
”(基本上是所有东西)为目标。其目的是将其用作其他样式(在我的例子中,用于 `TextBox`、`CheckBox` 和 `ComboBox`)的基础,以便所有输入字段都应用相同的样式。它会闪烁三次感叹号并将控件边框设置为红色,以便用户知道有问题。它还将工具提示设置为包含有用的消息,以便用户在将鼠标悬停在控件上时理解问题所在。
因此,上面的带有两个输入字段的部分负责这一点。
Converters
第二部分涵盖了转换器。前面也已经介绍过 了,因此我将简要说明。
您有一个 `TextBox`,您可以在其中输入,还有两个 `TextBlock`,它们将实时显示转换器接收到的内容以及它返回的内容。该转换器将响应三种状态:空字符串,以及字符串是否包含“unicorn
”一词。请随意尝试并查看代码。
按钮启用/禁用示例
是的,我知道,你们是忍者,并且可能已经注意到这在 `Random_ViewModel` 中已经做过了。恭喜。现在继续阅读。
与在 `viewmodel`(代码)中执行检查和设置状态相反,这里是通过 XAML 使用 `Style.Triggers` 完成的。
其思想是,您可以获取您的控件,并在 XAML 中声明当某些事件为 `true` 或 `false` 时会发生什么。您可以设置一个触发器,或多个触发器(代码中同时包含这两个示例,因此请查看代码以获取更多信息)。
这是一个带有按钮的单个触发器的示例。如果 `ViewModel` 上的名为 `ShouldBeEnabledProperty` 的属性为 `true`,则按钮启用。如果为 `false`,则禁用。您可以在代码中触发此操作,假设您的属性是 INPC 并且会触发属性更改,那么当它发生时,按钮将自动更新到正确的状态。
<Button Content="Shiny name here"
Command="{Binding Path=Some_Command}" >
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShouldBeEnabledProperty}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
ICommand
此 `ViewModel` 使用不同的约定来定义和初始化 ViewModel 中的命令。声明一个简单的 `ICommand` 自动属性,然后在构造函数中像这样初始化它:
public Your_ViewModel(IDataService dataService)
{
...
// Inside the constructor:
SomeMethod_Command = new RelayCommand(Execute_SomeMethod, CanExecute_SomeMethod);
...
}
// Outside, like a normal C# Property:
public ICommand SomeMethod_Command { get; private set; }
我建议您选择您最喜欢的一种,并坚持下去。
MvvmLight_View 和 MvvmLight_ViewModel
这是 Laurent Bugnion 和他的 MVVM Light 模板 随附的默认模板。我将其保留为一个选项卡,以便您可以查看 Nuget 包的默认设置并查看他的代码。
我有幸与他进行了简短的接触,他似乎是一个很棒的人。尽管他非常忙,但他非常有帮助 :)。
About_View 和 About_ViewModel
我为另一个项目创建了这个,我认为如果您想有一个“关于”视图,这会是一个不错的选择。`ViewModel` 相当无聊(一个属性作为其他属性的存根),因为它不应该做太多事情。
有趣的点在于
- 将图像(资源)嵌入到您的 View 中
- 有一个将在 Web 浏览器中打开的链接(计算机的默认浏览器)。这主要是在 View 中有一个超链接,像这样:
<TextBlock >
CodeProject:
<Hyperlink NavigateUri="https://codeproject.org.cn/Members/_Noctis_"
RequestNavigate="NavigateToCP">
link
</Hyperlink>.
</TextBlock>
这将调用代码隐藏中的此方法,该方法将启动一个新进程并转到链接
private void NavigateToCP(object sender, RequestNavigateEventArgs e)
{
System.Diagnostics.Process.Start("https://codeproject.org.cn/Members/_Noctis_");
}
单元测试
没有单元测试,我认为您的项目注定会失败,并且不应该上线。我在解决方案中添加了一个简单的单元测试项目,这样您就没有借口不自己设置一个。它有一些非常简单的测试方法,将从 `Random_Helper` 类获取一些随机数据,并进行一些基本验证。
如果您从未进行过测试,请去读几本书,并加入进来。这将改变您的编码方式,并在将来为您节省大量时间。
其他重要部分
ViewModelLocator
这是 MVVM Light 中“动态”获取和检索视图和 ViewModel 的核心。您应该记住五点:
- 将此添加到您的 _App.xaml_ 中
In App.xaml: <Application.Resources> <vm:ViewModelLocatorTemplate xmlns:vm="clr-namespace:MVVM_Template_Project.ViewModels" x:Key="Locator" /> </Application.Resources>
- 将此添加到每个视图中,以便它知道其数据上下文
DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}"
- 在 `ViewModelLocator` 构造函数中将该类注册到 `SimpleIoc`(简单控制反转)
SimpleIoc.Default.Register<Main_ViewModel>();
- 在 `ViewModelLocator` 中有一个带有 `ViewModel` 的属性
public Main_ViewModel Main { get { return ServiceLocator.Current.GetInstance<Main_ViewModel>(); } }
- 请确保在 _DataTemplates.xaml_ 中添加一个条目,以将 `ViewModel` 绑定到 `View`(根据 Digitalbeach 的要求 :))。它应该看起来像:
<DataTemplate DataType="{x:Type vm:Your_ViewModel_Namel}"> <views:Your_View_Name/> </DataTemplate>
注意:`ViewModelLocator` 和 `Main_ViewModel` 中的重复属性
由于我没有将默认 `MainWindow` 用于所有内容,并且因为我有一个在 `ViewModelLocator` 中使用的 `Main_ViewModel`,所以您必须在 **`ViewModelLocator` 和 `Main_ViewModel` 中都** 定义您的 `ViewModels` 属性。它们都将返回相同的实例,但原因如下:
- 当您在 VIEW 中进行绑定时,您使用的是 `ViewModelLocator` 的属性。
- 当您在 `MainWindow` 中进行绑定时,您使用的是 `Main_ViewModel` 的属性。
例如
<TabItem Header="Some name">
<ContentControl Content="{Binding Some_VM}" />
</TabItem>
上面的代码使用的是 `Main_ViewModel` 的 `Some_VM` 属性,但当您在 View 的 XAML 中绑定它时
DataContext="{Binding Test_VM, Source={StaticResource Locator}}"
您使用的是 `ViewModelLocator` 属性。如果您觉得命名不同有助于理解,可以随意更改。
如果您不使用选项卡,或者不打算在主 ViewModel 中进行绑定,您可以从 `Main_ViewModel` 中删除所有属性,只保留在 `ViewModelLocator` 中。
Annotations.cs
我已将 _Annotations.cs_ 文件添加到 Properties 文件夹,以便于调用 `RaisePropertyChanged` 而无需指定正在更改的属性。原因是为了避免在您决定更改属性名称但忘记更改 `RaisePropertyChanged` 字符串参数中的名称时,发生简单但烦人且难以发现的错误。
基类
接下来,我创建了 `MyBase_ViewModel`,它扩展了 GalaSoft 的 `ViewModelBase`,并包含了 `RaisePropertyChanged` 和 `RaisePropertyChanging`,以便上述功能适用于所有派生自它的类。**请注意**:您的 ViewModel 应继承自 `Use MyBase_ViewModel : ViewModelBase` 而不是直接继承自 `ViewModelBase`。
应用程序图标
要设置应用程序图标,只需转到项目的属性,然后在 **Application** 选项卡下,将 Icon 设置为您想要的任何图标(注意,它必须是“_.ico_”文件,但您可以轻松地在 Google 上搜索并找到免费的在线网站,它们会将您的 _jpg_ 或 _png_ 转换为图标。这里有两个:
总结
这项任务比我最初预期的要长(信不信由你,我最初打算写一个完全不同的项目),但我相信这对各地的开发者都会非常有帮助。
您可以在代码的注释中,以及在“_Readme.md_”文件中找到以上大部分内容以及更多内容。
如果您觉得这篇文章有帮助,请投票,留言,并随时在此文章下回帖。 :)
更新
MVVM-Light 5 (2014年10月29日)
Laurent Bugnion 已将 MVVM-Light 库更新到 5.0 版本。我注意到了 2 个主要的(可能破坏性的?)更改。
- `RaisePropertyChanging` 已被移除(由于迁移到可移植类库),因此必须从基类中移除。您可以在 此处 阅读更多关于我的问题和他回复的内容。
- 中继命令 `CanExecute` 已损坏(出于相同原因),并且需要一些变通方法才能使其再次工作。您可以在 此帖子 中阅读更多相关信息。修复的核心是将该命令的命名空间从“`GalaSoft.MvvmLight.Command”更改为“`GalaSoft.MvvmLight.CommandWpf”。
无论如何,如果您获取更新的代码示例,库已经更新,并且更改已引入,所以您可以直接使用它 :)。
2014年10月5日
我对代码进行了一些更新。更改包括:
- 将 3 个 ViewModel 设为 sealed 以避免 CA2214 警告。它们上方有注释,因此您可以轻松知道为什么这样做。您可以在我在评论中对该问题的回答 此处 阅读更多内容。
- 我已更新 MVVM Light 和 `CommonServiceLocator` 以消除 ReSharper 显示的警告(尽管一切仍然正常)。现在它应该可以干净整洁地构建了。
- 因此,我再次上传了代码,所以下载文件是不同的。
历史
- 2016年4月21日:修正了源代码中的一些拼写错误,重新措辞了一些注释,简化了一些布尔逻辑,根据评论请求在文章中添加了一个 `ViewModelLocator` 部分,并上传了新代码。(其中包含有关使用字符串插值的说明,假设您使用的是最新的 C#。)
- 2014年10月29日:将 MVVM-Light 更新到 5.0 版本
- 2014年10月5日:更新了库和代码
- 2014年5月4日:首次发布