WPF 项目中的 MVVM 架构示例

195 阅读7分钟

以下是一个简单的 WPF 项目示例,其中展示了 Models 的作用。在这个例子中,我们将创建一个简单的用户管理系统,其中包含一个 User 模型类来表示用户信息。

1. MVVM 简单示例

首先,我们创建一个名为 UserModel.cs 的文件来表示用户模型:

	// UserModel.cs
	namespace WpfApp.Models
	{
	    public class User
	    {
	        public int Id { get; set; }
	        public string Name { get; set; }
	        public int Age { get; set; }
	 
	        // 假设这里有一些业务逻辑方法
	        public bool IsValidAge()
	        {
	            return Age >= 0 && Age <= 120; // 简单的年龄验证逻辑
	        }
	 
	        // 其他业务逻辑...
	    }
	}

接下来,我们创建一个 UserViewModel.cs 文件来表示与用户模型相关的视图模型。视图模型通常用于在视图(如 XAML 页面)和模型之间提供一个中间层,并包含用于数据绑定的属性。

	// UserViewModel.cs
	using System.Collections.ObjectModel;
	using System.ComponentModel;
	using WpfApp.Models;
	 
	namespace WpfApp.ViewModels
	{
	    public class UserViewModel : INotifyPropertyChanged
	    {
	        private ObservableCollection<User> _users;
	        private User _selectedUser;
	 
	        public UserViewModel()
	        {
	            // 初始化用户列表(这里只是示例,通常你会从数据库或其他数据源加载用户)
	            Users = new ObservableCollection<User>
	            {
	                new User { Id = 1, Name = "Alice", Age = 30 },
	                new User { Id = 2, Name = "Bob", Age = 25 }
	            };
	        }
	 
	        public ObservableCollection<User> Users
	        {
	            get { return _users; }
	            set
	            {
	                _users = value;
	                OnPropertyChanged(nameof(Users));
	            }
	        }
	 
	        public User SelectedUser
	        {
	            get { return _selectedUser; }
	            set
	            {
	                _selectedUser = value;
	                OnPropertyChanged(nameof(SelectedUser));
	            }
	        }
	 
	        public event PropertyChangedEventHandler PropertyChanged;
	 
	        protected void OnPropertyChanged(string propertyName)
	        {
	            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
	        }
	    }
	}

最后,我们在 XAML 文件中创建一个简单的用户界面来显示用户列表。这个 XAML 文件将绑定到 UserViewModel

	<!-- MainWindow.xaml -->
	<Window x:Class="WpfApp.MainWindow"
	        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	        xmlns:local="clr-namespace:WpfApp"
	        mc:Ignorable="d"
	        Title="MainWindow" Height="350" Width="525">
	    <Grid>
	        <ListBox ItemsSource="{Binding Users}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedUser}" />
	        <TextBlock Text="{Binding SelectedUser.Age}" Margin="10,0,0,0" VerticalAlignment="Bottom"/>
	    </Grid>
	</Window>

在 MainWindow.xaml.cs 文件中,我们将设置 DataContext 以使用 UserViewModel

	// MainWindow.xaml.cs
	using WpfApp.ViewModels;
	 
	namespace WpfApp
	{
	    public partial class MainWindow : Window
	    {
	        public MainWindow()
	        {
	            InitializeComponent();
	            DataContext = new UserViewModel();
	        }
	    }
	}

在这个例子中,User 类是一个模型,它封装了用户的数据和业务逻辑(如年龄验证)。UserViewModel 类是一个视图模型,它包含了用于数据绑定的属性(如 Users 和 SelectedUser),并将这些数据暴露给 XAML 视图。最后,MainWindow.xaml 是一个 WPF 视图,它绑定了 UserViewModel 并显示了用户列表和选定用户的年龄。 这个例子展示了 Models 在 WPF 项目中的作用:封装数据和业务逻辑,与视图和视图模型分离,从而提高了代码的可维护性和可扩展性。

2. view 和 VM 如何链接起来

在WPF(Windows Presentation Foundation)项目中,View(视图层)和ViewModel(视图模型层)通常通过数据绑定(Data Binding)和命令(Commands)连接在一起。这种架构模式通常被称为MVVM(Model-View-ViewModel)。下面是一个简单的例子,说明View和ViewModel是如何相互连接的。

首先,我们定义一个简单的ViewModel类,它包含一些属性和一个命令:

csharp复制代码
	// ViewModel层

	using System.ComponentModel;

	using System.Runtime.CompilerServices;

	using System.Windows.Input;

	 

	public class MainWindowViewModel : INotifyPropertyChanged

	{

	    private string _title;

	    private ICommand _sayHelloCommand;

	 

	    public MainWindowViewModel()

	    {

	        Title = "Hello, MVVM!";

	        SayHelloCommand = new RelayCommand(param => SayHello());

	    }

	 

	    public event PropertyChangedEventHandler PropertyChanged;

	 

	    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)

	    {

	        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

	    }

	 

	    public string Title

	    {

	        get { return _title; }

	        set

	        {

	            _title = value;

	            OnPropertyChanged();

	        }

	    }

	 

	    public ICommand SayHelloCommand

	    {

	        get { return _sayHelloCommand; }

	        set

	        {

	            _sayHelloCommand = value;

	            OnPropertyChanged();

	        }

	    }

	 

	    private void SayHello()

	    {

	        // 这里可以添加一些逻辑,比如显示消息框

	        // 但为了简单起见,我们在这里只是更改Title属性

	        Title = "Hello, WPF!";

	    }

	}

	 

	// RelayCommand类是一个简单的ICommand实现,用于在ViewModel中定义命令

	public class RelayCommand : ICommand

	{

	    private readonly Action<object> _execute;

	    private readonly Func<object, bool> _canExecute;

	 

	    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)

	    {

	        _execute = execute;

	        _canExecute = canExecute;

	    }

	 

	    public bool CanExecute(object parameter)

	    {

	        return _canExecute == null || _canExecute(parameter);

	    }

	 

	    public void Execute(object parameter)

	    {

	        _execute(parameter);

	    }

	 

	    public event EventHandler CanExecuteChanged

	    {

	        add { CommandManager.RequerySuggested += value; }

	        remove { CommandManager.RequerySuggested -= value; }

	    }

	}

接下来,我们在XAML中定义一个简单的View,它绑定到上面的ViewModel:

xml复制代码
	<!-- View层 (MainWindow.xaml) -->

	<Window x:Class="WpfApp.MainWindow"

	        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

	        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

	        Title="MainWindow" Height="200" Width="400">

	    <Window.DataContext>

	        <local:MainWindowViewModel />

	    </Window.DataContext>

	    <Grid>

	        <TextBlock Text="{Binding Title}" FontSize="24" VerticalAlignment="Center" HorizontalAlignment="Center"/>

	        <Button Content="Say Hello" Command="{Binding SayHelloCommand}" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="10"/>

	    </Grid>

	</Window>

在上面的XAML代码中,我们做了以下几件事情:

  1. 使用Window.DataContext属性将MainWindowViewModel的实例设置为当前窗口的数据上下文。这意味着在这个窗口中,任何数据绑定都会默认使用这个ViewModel作为数据源。
  2. 使用{Binding Title}TextBlockText属性绑定到ViewModel的Title属性。这样,当Title属性在ViewModel中更改时,TextBlock的文本也会自动更新。
  3. 使用{Binding SayHelloCommand}ButtonCommand属性绑定到ViewModel的SayHelloCommand属性。这样,当用户点击按钮时,SayHelloCommandExecute方法会被调用,进而执行SayHello方法中的逻辑。

通过这种方式,View和ViewModel被紧密地连接在一起,但它们之间保持了松散的耦合。View负责显示数据和响应用户输入,而ViewModel则负责处理业务逻辑和数据验证。这种分离使得代码更加清晰、易于维护和测试。

如果我们想要在 cs 中动态绑定而不是在 xml 中通过标签的方式,则可以:

在WPF应用程序中,如果您不想在XAML文件的<Window.DataContext>中直接指定ViewModel,而是希望在后台代码(隐藏代码文件,通常是.xaml.cs文件)中动态指定,您可以按照以下步骤进行操作:

  1. 确保您的ViewModel已经注册到依赖注入容器(如果使用了依赖注入)
    如果您的应用程序使用了依赖注入(例如通过Microsoft.Extensions.DependencyInjection),确保MainWindowViewModel已经被注册为服务。这样,您就可以在后台代码中通过依赖注入来获取它。

  2. 在后台代码中设置DataContext
    在窗口的构造函数或Loaded事件中,您可以设置DataContext属性。这里有一个简单的例子:

    csharp复制代码
    	public partial class MainWindow : Window
    
    	{
    
    	    private readonly MainWindowViewModel _viewModel;
    
    	 
    
    	    // 如果使用了依赖注入,可以通过构造函数注入ViewModel
    
    	    public MainWindow(MainWindowViewModel viewModel, IServiceProvider serviceProvider) // 注意:通常不会直接注入IServiceProvider,这里只是为了示例
    
    	    {
    
    	        InitializeComponent();
    
    	        _viewModel = viewModel;
    
    	        this.DataContext = _viewModel;
    
    	    }
    
    	 
    
    	    // 或者,如果没有使用构造函数注入,可以在Loaded事件中获取ViewModel并设置DataContext
    
    	    public MainWindow()
    
    	    {
    
    	        InitializeComponent();
    
    	        this.Loaded += MainWindow_Loaded;
    
    	    }
    
    	 
    
    	    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    
    	    {
    
    	        // 这里假设您有一个方法来获取ViewModel的实例,比如通过ServiceLocator模式或直接实例化
    
    	        var viewModel = GetViewModelInstance();
    
    	        this.DataContext = viewModel;
    
    	    }
    
    	 
    
    	    private MainWindowViewModel GetViewModelInstance()
    
    	    {
    
    	        // 这里是获取ViewModel实例的逻辑,可能是通过依赖注入容器,也可能是直接new一个实例
    
    	        // 例如,如果使用了Microsoft.Extensions.DependencyInjection,您可能需要一个方式来访问IServiceProvider
    
    	        // 但通常,您会在应用程序的启动过程中设置DataContext,而不是在这里手动获取IServiceProvider
    
    	        // 这个例子只是为了说明如何在没有直接依赖注入到构造函数的情况下获取ViewModel
    
    	        return new MainWindowViewModel(); // 或者通过其他方式获取
    
    	    }
    
    	}
    

    注意:在上面的代码中,直接在MainWindow的构造函数中注入MainWindowViewModelIServiceProvider通常不是最佳实践。这只是一个示例来说明如何通过构造函数注入来获取ViewModel。在实际应用中,您可能会使用某种形式的依赖注入设置,在应用程序启动时配置服务,并在需要时通过构造函数注入、属性注入或其他方式获取服务。

  3. 使用依赖注入容器
    如果您的应用程序使用了依赖注入容器,并且已经配置了MainWindowViewModel,那么您应该在应用程序的启动过程中(例如在App.xaml.csOnStartup方法中)获取MainWindow的实例,并设置其DataContext。这通常涉及到创建或获取MainWindow的实例,并通过依赖注入容器获取MainWindowViewModel的实例,然后设置DataContext。但是,请注意,直接在App.xaml.cs中设置MainWindowDataContext可能不是最佳实践,因为这违反了MVVM模式的某些原则。更好的做法是让MainWindow自己负责获取和设置其DataContext,这通常是通过构造函数注入或属性注入来实现的。然而,在某些情况下,您可能需要在App.xaml.cs中进行一些额外的配置或初始化工作,然后再将控制权交给MainWindow