65.9K
CodeProject 正在变化。 阅读更多。
Home

大型 MVVM 模板

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (52投票s)

2014年5月4日

CPOL

19分钟阅读

viewsIcon

116390

downloadIcon

8846

为了省去您每次创建 WPF/MVVM 项目时都要重复进行的工作,我将所有内容都打包到了这里。

项目是用 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 pattern

背景

我一直听说的 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` 字符串参数中的名称时,发生简单但烦人且难以发现的错误。

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
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> 
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` 相当无聊(一个属性作为其他属性的存根),因为它不应该做太多事情。

有趣的点在于

  1. 将图像(资源)嵌入到您的 View 中
  2. 有一个将在 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 的核心。您应该记住五点:

  1. 将此添加到您的 _App.xaml_ 中
    In App.xaml:
      <Application.Resources>
          <vm:ViewModelLocatorTemplate 
              xmlns:vm="clr-namespace:MVVM_Template_Project.ViewModels"
              x:Key="Locator" />
      </Application.Resources> 
  2. 将此添加到每个视图中,以便它知道其数据上下文
    DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}" 
  3. 在 `ViewModelLocator` 构造函数中将该类注册到 `SimpleIoc`(简单控制反转)
    SimpleIoc.Default.Register<Main_ViewModel>(); 
  4. 在 `ViewModelLocator` 中有一个带有 `ViewModel` 的属性
    public Main_ViewModel Main
    {
        get
        {
            return ServiceLocator.Current.GetInstance<Main_ViewModel>();
        }
    }  
  5. 请确保在 _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 个主要的(可能破坏性的?)更改。

  1. `RaisePropertyChanging` 已被移除(由于迁移到可移植类库),因此必须从基类中移除。您可以在 此处 阅读更多关于我的问题和他回复的内容。
  2. 中继命令 `CanExecute` 已损坏(出于相同原因),并且需要一些变通方法才能使其再次工作。您可以在 此帖子 中阅读更多相关信息。修复的核心是将该命令的命名空间从“`GalaSoft.MvvmLight.Command”更改为“`GalaSoft.MvvmLight.CommandWpf”。

无论如何,如果您获取更新的代码示例,库已经更新,并且更改已引入,所以您可以直接使用它 :)。

2014年10月5日

我对代码进行了一些更新。更改包括:

  1. 将 3 个 ViewModel 设为 sealed 以避免 CA2214 警告。它们上方有注释,因此您可以轻松知道为什么这样做。您可以在我在评论中对该问题的回答 此处 阅读更多内容。
  2. 我已更新 MVVM Light 和 `CommonServiceLocator` 以消除 ReSharper 显示的警告(尽管一切仍然正常)。现在它应该可以干净整洁地构建了。
  3. 因此,我再次上传了代码,所以下载文件是不同的。

历史

  • 2016年4月21日:修正了源代码中的一些拼写错误,重新措辞了一些注释,简化了一些布尔逻辑,根据评论请求在文章中添加了一个 `ViewModelLocator` 部分,并上传了新代码。(其中包含有关使用字符串插值的说明,假设您使用的是最新的 C#。)
  • 2014年10月29日:将 MVVM-Light 更新到 5.0 版本
  • 2014年10月5日:更新了库和代码
  • 2014年5月4日:首次发布
© . All rights reserved.