MVVM 模式 #

MVVM 概述 #

什么是 MVVM? #

MVVM (Model-View-ViewModel) 是一种软件架构模式,专门用于分离用户界面 (UI) 和业务逻辑。

text
┌─────────────────────────────────────────────────────────────┐
│                    MVVM 架构                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────────────┐                                       │
│   │      View       │  用户界面层                           │
│   │   (XAML 页面)   │  - 显示数据                           │
│   └────────┬────────┘  - 用户交互                           │
│            │                                                │
│            │ 数据绑定                                       │
│            │                                                │
│   ┌────────▼────────┐                                       │
│   │   ViewModel     │  视图模型层                           │
│   │  (C# 类)        │  - 业务逻辑                           │
│   └────────┬────────┘  - 状态管理                           │
│            │           - 命令处理                           │
│            │                                                │
│            │ 数据访问                                       │
│            │                                                │
│   ┌────────▼────────┐                                       │
│   │      Model      │  数据模型层                           │
│   │  (数据实体)      │  - 数据结构                           │
│   └─────────────────┘  - 数据访问                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

MVVM 的优势 #

text
┌─────────────────────────────────────────────────────────────┐
│                    MVVM 优势                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ✅ 关注点分离                                              │
│     - UI 与业务逻辑分离                                     │
│     - 代码职责清晰                                          │
│     - 易于理解和维护                                        │
│                                                             │
│  ✅ 可测试性                                                │
│     - ViewModel 可单元测试                                  │
│     - 不依赖 UI 框架                                        │
│     - 测试驱动开发友好                                      │
│                                                             │
│  ✅ 可复用性                                                │
│     - ViewModel 可跨平台复用                                │
│     - 多个 View 可共享 ViewModel                            │
│     - 组件化开发                                            │
│                                                             │
│  ✅ 设计师与开发者协作                                      │
│     - 设计师专注 XAML                                       │
│     - 开发者专注 ViewModel                                  │
│     - 并行开发                                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

MVVM 各层职责 #

text
┌─────────────────────────────────────────────────────────────┐
│                    各层职责                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  View (视图):                                               │
│  ├── XAML 文件定义 UI 结构                                  │
│  ├── .xaml.cs 代码隐藏文件                                  │
│  ├── 数据绑定到 ViewModel                                   │
│  ├── 处理 UI 特定逻辑(动画、视觉效果)                      │
│  └── 不应包含业务逻辑                                       │
│                                                             │
│  ViewModel (视图模型):                                       │
│  ├── 实现 INotifyPropertyChanged                            │
│  ├── 暴露属性供 View 绑定                                   │
│  ├── 实现 ICommand 处理用户操作                             │
│  ├── 调用 Model 层获取/保存数据                             │
│  └── 不应引用 UI 控件                                       │
│                                                             │
│  Model (模型):                                              │
│  ├── 数据实体类                                             │
│  ├── 数据访问逻辑                                           │
│  ├── 业务规则验证                                           │
│  └── 服务接口                                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

ViewModel 基础 #

INotifyPropertyChanged 接口 #

csharp
public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

ViewModelBase 实现 #

csharp
public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
            return false;

        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

创建 ViewModel #

csharp
public class MainViewModel : ViewModelBase
{
    private string _title;
    private string _username;
    private bool _isLoading;

    public string Title
    {
        get => _title;
        set => SetProperty(ref _title, value);
    }

    public string Username
    {
        get => _username;
        set => SetProperty(ref _username, value);
    }

    public bool IsLoading
    {
        get => _isLoading;
        set => SetProperty(ref _isLoading, value);
    }

    public MainViewModel()
    {
        Title = "主页";
        Username = string.Empty;
    }
}

关联 View 和 ViewModel #

csharp
public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
        BindingContext = new MainViewModel();
    }
}
xml
<ContentPage.BindingContext>
    <local:MainViewModel />
</ContentPage.BindingContext>

Command 模式 #

ICommand 接口 #

csharp
public interface ICommand
{
    event EventHandler CanExecuteChanged;
    
    bool CanExecute(object parameter);
    
    void Execute(object parameter);
}

Command 实现 #

csharp
public class MainViewModel : ViewModelBase
{
    public ICommand SubmitCommand { get; }
    public ICommand NavigateCommand { get; }

    public MainViewModel()
    {
        SubmitCommand = new Command(OnSubmit, CanSubmit);
        NavigateCommand = new Command<string>(OnNavigate);
    }

    private void OnSubmit()
    {
    }

    private bool CanSubmit()
    {
        return !string.IsNullOrEmpty(Username);
    }

    private void OnNavigate(string page)
    {
    }
}

带参数的 Command #

csharp
public class ListViewModel : ViewModelBase
{
    public ICommand ItemSelectedCommand { get; }
    public ICommand DeleteCommand { get; }

    public ListViewModel()
    {
        ItemSelectedCommand = new Command<Item>(OnItemSelected);
        DeleteCommand = new Command<Item>(OnDelete, CanDelete);
    }

    private void OnItemSelected(Item item)
    {
        if (item != null)
        {
        }
    }

    private void OnDelete(Item item)
    {
        Items.Remove(item);
    }

    private bool CanDelete(Item item)
    {
        return item != null && item.CanDelete;
    }
}

Command 绑定 #

xml
<StackLayout>
    <Entry Text="{Binding Username}" />
    
    <Button Text="提交"
            Command="{Binding SubmitCommand}" />
    
    <Button Text="导航"
            Command="{Binding NavigateCommand}"
            CommandParameter="DetailPage" />
</StackLayout>

刷新 Command 可执行状态 #

csharp
public class MainViewModel : ViewModelBase
{
    private string _username;

    public string Username
    {
        get => _username;
        set
        {
            if (SetProperty(ref _username, value))
            {
                (SubmitCommand as Command)?.ChangeCanExecute();
            }
        }
    }

    public ICommand SubmitCommand { get; }

    public MainViewModel()
    {
        SubmitCommand = new Command(OnSubmit, CanSubmit);
    }

    private bool CanSubmit()
    {
        return !string.IsNullOrWhiteSpace(Username);
    }
}

AsyncCommand #

csharp
public class AsyncCommand : ICommand
{
    private readonly Func<Task> _execute;
    private readonly Func<bool> _canExecute;
    private bool _isExecuting;

    public event EventHandler CanExecuteChanged;

    public AsyncCommand(Func<Task> execute, Func<bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return !_isExecuting && (_canExecute?.Invoke() ?? true);
    }

    public async void Execute(object parameter)
    {
        if (!CanExecute(parameter))
            return;

        try
        {
            _isExecuting = true;
            await _execute();
        }
        finally
        {
            _isExecuting = false;
            RaiseCanExecuteChanged();
        }
    }

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}
csharp
public class MainViewModel : ViewModelBase
{
    public ICommand LoadDataCommand { get; }

    public MainViewModel()
    {
        LoadDataCommand = new AsyncCommand(LoadDataAsync);
    }

    private async Task LoadDataAsync()
    {
        IsLoading = true;
        try
        {
            var data = await _dataService.GetDataAsync();
            Items = new ObservableCollection<Item>(data);
        }
        finally
        {
            IsLoading = false;
        }
    }
}

数据绑定进阶 #

属性依赖 #

csharp
public class OrderViewModel : ViewModelBase
{
    private decimal _subtotal;
    private decimal _taxRate;

    public decimal Subtotal
    {
        get => _subtotal;
        set
        {
            if (SetProperty(ref _subtotal, value))
            {
                OnPropertyChanged(nameof(Tax));
                OnPropertyChanged(nameof(Total));
            }
        }
    }

    public decimal TaxRate
    {
        get => _taxRate;
        set
        {
            if (SetProperty(ref _taxRate, value))
            {
                OnPropertyChanged(nameof(Tax));
                OnPropertyChanged(nameof(Total));
            }
        }
    }

    public decimal Tax => Subtotal * TaxRate;

    public decimal Total => Subtotal + Tax;
}

集合属性 #

csharp
public class ListViewModel : ViewModelBase
{
    private ObservableCollection<Item> _items;
    private Item _selectedItem;

    public ObservableCollection<Item> Items
    {
        get => _items;
        set => SetProperty(ref _items, value);
    }

    public Item SelectedItem
    {
        get => _selectedItem;
        set => SetProperty(ref _selectedItem, value);
    }

    public ICommand AddCommand { get; }
    public ICommand RemoveCommand { get; }

    public ListViewModel()
    {
        Items = new ObservableCollection<Item>();
        
        AddCommand = new Command(OnAdd);
        RemoveCommand = new Command<Item>(OnRemove);
    }

    private void OnAdd()
    {
        Items.Add(new Item { Title = $"Item {Items.Count + 1}" });
    }

    private void OnRemove(Item item)
    {
        if (item != null)
        {
            Items.Remove(item);
        }
    }
}

服务注入 #

依赖注入概述 #

text
┌─────────────────────────────────────────────────────────────┐
│                    依赖注入                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  传统方式:                                                  │
│  ViewModel 直接创建 Service 实例                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  ViewModel                                          │   │
│  │  ├── new DataService()     ❌ 紧耦合                │   │
│  │  └── new NavigationService() ❌ 难以测试            │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  依赖注入:                                                  │
│  通过构造函数注入依赖                                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  ViewModel                                          │   │
│  │  ├── IDataService _dataService     ✅ 接口依赖      │   │
│  │  └── INavigationService _navService ✅ 易于模拟     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

定义服务接口 #

csharp
public interface IDataService
{
    Task<IEnumerable<Item>> GetItemsAsync();
    Task<Item> GetItemAsync(int id);
    Task<bool> SaveItemAsync(Item item);
    Task<bool> DeleteItemAsync(int id);
}

public interface INavigationService
{
    Task NavigateToAsync(string route);
    Task GoBackAsync();
}

public interface IDialogService
{
    Task ShowAlertAsync(string title, string message, string cancel);
    Task<bool> ShowConfirmAsync(string title, string message, string accept, string cancel);
}

实现服务 #

csharp
public class DataService : IDataService
{
    private readonly HttpClient _httpClient;

    public DataService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<IEnumerable<Item>> GetItemsAsync()
    {
        var response = await _httpClient.GetAsync("api/items");
        response.EnsureSuccessStatusCode();
        var json = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<IEnumerable<Item>>(json);
    }

    public async Task<Item> GetItemAsync(int id)
    {
        var response = await _httpClient.GetAsync($"api/items/{id}");
        response.EnsureSuccessStatusCode();
        var json = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<Item>(json);
    }

    public async Task<bool> SaveItemAsync(Item item)
    {
        var json = JsonConvert.SerializeObject(item);
        var content = new StringContent(json, Encoding.UTF8, "application/json");
        var response = await _httpClient.PostAsync("api/items", content);
        return response.IsSuccessStatusCode;
    }

    public async Task<bool> DeleteItemAsync(int id)
    {
        var response = await _httpClient.DeleteAsync($"api/items/{id}");
        return response.IsSuccessStatusCode;
    }
}

配置依赖注入 #

csharp
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddServices(this IServiceCollection services)
    {
        services.AddSingleton<IDataService, DataService>();
        services.AddSingleton<INavigationService, NavigationService>();
        services.AddSingleton<IDialogService, DialogService>();
        
        services.AddTransient<MainViewModel>();
        services.AddTransient<DetailViewModel>();
        services.AddTransient<SettingsViewModel>();
        
        return services;
    }
}
csharp
public partial class App : Application
{
    public static IServiceProvider ServiceProvider { get; private set; }

    public App()
    {
        InitializeComponent();

        var services = new ServiceCollection();
        services.AddServices();
        ServiceProvider = services.BuildServiceProvider();

        MainPage = new AppShell();
    }
}

在 ViewModel 中使用依赖注入 #

csharp
public class MainViewModel : ViewModelBase
{
    private readonly IDataService _dataService;
    private readonly INavigationService _navigationService;
    private readonly IDialogService _dialogService;

    public MainViewModel(
        IDataService dataService,
        INavigationService navigationService,
        IDialogService dialogService)
    {
        _dataService = dataService;
        _navigationService = navigationService;
        _dialogService = dialogService;

        LoadCommand = new AsyncCommand(LoadDataAsync);
    }

    public ICommand LoadCommand { get; }

    private async Task LoadDataAsync()
    {
        IsLoading = true;
        try
        {
            var items = await _dataService.GetItemsAsync();
            Items = new ObservableCollection<Item>(items);
        }
        catch (Exception ex)
        {
            await _dialogService.ShowAlertAsync("错误", ex.Message, "确定");
        }
        finally
        {
            IsLoading = false;
        }
    }
}

项目结构 #

推荐的项目结构 #

text
MyApp/
├── Models/                      # 数据模型
│   ├── Item.cs
│   └── User.cs
│
├── ViewModels/                  # 视图模型
│   ├── ViewModelBase.cs
│   ├── MainViewModel.cs
│   ├── DetailViewModel.cs
│   └── SettingsViewModel.cs
│
├── Views/                       # 视图
│   ├── MainPage.xaml
│   ├── MainPage.xaml.cs
│   ├── DetailPage.xaml
│   └── DetailPage.xaml.cs
│
├── Services/                    # 服务
│   ├── IDataService.cs
│   ├── DataService.cs
│   ├── INavigationService.cs
│   └── NavigationService.cs
│
├── Converters/                  # 值转换器
│   ├── BoolToColorConverter.cs
│   └── DateFormatConverter.cs
│
├── Helpers/                     # 辅助类
│   └── AsyncCommand.cs
│
├── Resources/                   # 资源
│   ├── Styles/
│   └── Images/
│
├── App.xaml
├── App.xaml.cs
└── AppShell.xaml

MVVM 最佳实践 #

1. 保持 ViewModel 纯净 #

csharp
public class BadViewModel : ViewModelBase
{
    private async void OnSubmit()
    {
        await Application.Current.MainPage.DisplayAlert("提示", "成功", "确定");
    }
}

public class GoodViewModel : ViewModelBase
{
    private readonly IDialogService _dialogService;

    public GoodViewModel(IDialogService dialogService)
    {
        _dialogService = dialogService;
    }

    private async Task OnSubmitAsync()
    {
        await _dialogService.ShowAlertAsync("提示", "成功", "确定");
    }
}

2. 使用异步方法 #

csharp
public class MainViewModel : ViewModelBase
{
    public ICommand LoadCommand { get; }

    public MainViewModel()
    {
        LoadCommand = new AsyncCommand(LoadAsync);
    }

    private async Task LoadAsync()
    {
        IsLoading = true;
        try
        {
            var data = await _dataService.GetDataAsync();
            Items = new ObservableCollection<Item>(data);
        }
        finally
        {
            IsLoading = false;
        }
    }
}

3. 避免在属性 setter 中执行耗时操作 #

csharp
public class BadViewModel : ViewModelBase
{
    private string _searchText;
    public string SearchText
    {
        get => _searchText;
        set
        {
            SetProperty(ref _searchText, value);
            Search(value);
        }
    }

    private void Search(string text)
    {
    }
}

public class GoodViewModel : ViewModelBase
{
    private string _searchText;
    public string SearchText
    {
        get => _searchText;
        set => SetProperty(ref _searchText, value);
    }

    public ICommand SearchCommand { get; }

    public GoodViewModel()
    {
        SearchCommand = new Command<string>(Search);
    }

    private void Search(string text)
    {
    }
}

4. 使用消息中心进行跨组件通信 #

csharp
public class Messages
{
    public const string UserLoggedIn = "UserLoggedIn";
    public const string UserLoggedOut = "UserLoggedOut";
    public const string DataUpdated = "DataUpdated";
}

public class LoginViewModel : ViewModelBase
{
    public ICommand LoginCommand { get; }

    public LoginViewModel()
    {
        LoginCommand = new AsyncCommand(LoginAsync);
    }

    private async Task LoginAsync()
    {
        var result = await _authService.LoginAsync(Username, Password);
        if (result.Success)
        {
            MessagingCenter.Send(this, Messages.UserLoggedIn, result.User);
        }
    }
}

public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {
        MessagingCenter.Subscribe<LoginViewModel, User>(this, Messages.UserLoggedIn, 
            (sender, user) =>
        {
            CurrentUser = user;
        });
    }
}

下一步 #

现在你已经掌握了 MVVM 模式,接下来学习 样式与资源,了解如何美化你的应用界面!

最后更新:2026-03-29