开发流程视频:
https://www.youtube.com/watch?v=CkHyDYeImjY&ab_channel=C%23DesignPro
Git源码: GitHub - CSharpDesignPro/Page-Navigation-using-MVVM: WPF - Page Navigation using MVVM
- 新建工程
新建WPF应用(.NET Framework)
2.新建目录
使用MVVM框架,各目录作用如下:
Styles:用于存放样式资源文件。
Utilties:用于存放自定义控件,命令父类,ViewModel父类,DataTemplate.xaml,通用工具类。
Model:用于存放与业务逻辑或数据相关的类,M层。
View:用于存放用户界面的,V层。
ViewModel:用于存放所有ViewModel类,连接Model、View,VM层。

3.Utilties添加文件
添加RelayCommand.cs,ViewModelBase.cs,DataTemplate.xaml。

3.1RelayCommand.cs
实现ICommand接口,存放命令实际执行的委托与判断命令是否可执行的委托。
代码:
using System;`
`using System.Windows.Input;`
`namespace WpfApp1.Utilties`
`{`
` internal class RelayCommand:ICommand`
` {`
` private Action<object> _execute;`
` private Func<object, bool> _canExecute;`
` /// <summary>`
` /// 将CanExecuteChanged事件的订阅/取消订阅转发给CommandManager.RequerySuggested事件。`
` /// CommandManager.RequerySuggested事件是WPF全局静态事件,每当用户进行鼠标按键/移动、键盘输入等操作时,WPF会自动触发该事件使所有绑定命令执行CanExecute方法。 `
` /// 可通过命令对象的RaiseCanExecuteChanged方法主动触发CanExecuteChanged事件,从而触发 CommandManager.RequerySuggested事件。`
` /// </summary>`
` public event EventHandler CanExecuteChanged`
` {`
` add {CommandManager.RequerySuggested += value; }`
` remove { CommandManager.RequerySuggested -= value; }`
` }`
` public RelayCommand(Action<object> execute, Func<object, bool> canExecute)`
` {`
` _execute = execute;`
` _canExecute = canExecute;`
` }`
` /// <summary>`
` /// CanExecute方法返回值会影响到控件使能`
` /// </summary>`
` public bool CanExecute(object parameter)=>_canExecute==null||_canExecute(parameter);`
` public void Execute(object parameter)=>_execute(parameter);`
` }`
`}`
`
3.2ViewModelBase.cs
实现INotifyPropertyChanged接口,用以在ViewModel层属性变化时通知UI更新。
代码:
using System.ComponentModel;`
`using System.Runtime.CompilerServices;`
`namespace WpfApp1.Utilties`
`{`
` class ViewModelBase:INotifyPropertyChanged`
` {`
` public event PropertyChangedEventHandler PropertyChanged;`
` /// <summary>`
` /// ViewModel层属性值发生变化后调用该方法,触发PropertyChanged事件通知界面重新读取该属性以更新`
` /// </summary>`
` /// <param name="propertyName">属性名称,此处使用了CallerMemberName特性,如果你不传参数,那么编译器会自动把调用这个方法的成员名(比如属性名)作为参数传进来。</param>`
` public void OnPropertyChanged([CallerMemberName]string propertyName=null)`
` {`
` PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));`
` }`
` }`
`}`
`
3.3DataTemplate.xaml
为ViewModel 到 View 的映射提供支持,用于实现界面切换;需要先将此文件添加入App.xaml。
App.xaml代码:
<Application x:Class="WpfApp1.App"`
` xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"`
` xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"`
` xmlns:local="clr-namespace:WpfApp1"`
` StartupUri="MainWindow.xaml">`
` <Application.Resources>`
` <ResourceDictionary>`
` <ResourceDictionary.MergedDictionaries>`
` <ResourceDictionary Source="Utilties/DataTemplate.xaml"/>`
` </ResourceDictionary.MergedDictionaries>`
` </ResourceDictionary>`
` </Application.Resources>`
`</Application>`
`
假设ViewModel目录下存在MainPageViewModel.cs,在View目录下存在MainPage.xaml。
DataTemplate.xaml代码:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"`
` xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"`
` xmlns:viewModel="clr-namespace:WpfApp1.ViewModel"`
` xmlns:view="clr-namespace:WpfApp1.View">`
` <DataTemplate DataType="{x:Type viewModel:MainPageViewModel}">`
` <view:MainPage/>`
` </DataTemplate>`
`</ResourceDictionary>