avalonia-mvvm

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MVVM Patterns in Avalonia

Avalonia中的MVVM模式

Complete guide to MVVM patterns using CommunityToolkit.Mvvm in Avalonia applications.
本指南详细介绍如何在Avalonia应用中使用CommunityToolkit.Mvvm实现MVVM模式。

What I do

我能提供的帮助

  • Guide implementation of ViewModels with ObservableObject base class
  • Show how to create observable properties with [ObservableProperty] attribute
  • Demonstrate command patterns with [RelayCommand] including async and CanExecute
  • Explain View-ViewModel mapping using DataTemplates (critical for navigation)
  • Show dependency injection patterns for passing services to ViewModels
  • 指导使用ObservableObject基类实现ViewModel
  • 演示如何通过[ObservableProperty]特性创建可观察属性
  • 展示使用[RelayCommand]实现命令模式,包括异步命令与CanExecute逻辑
  • 解释如何使用DataTemplates进行View-ViewModel映射(这是导航功能的关键)
  • 演示如何通过依赖注入模式为ViewModel传递服务

When to use me

适用场景

Use this skill when you need to:
  • Create new ViewModels for Avalonia views
  • Implement observable properties that notify the UI of changes
  • Add commands to handle user interactions (button clicks, etc.)
  • Set up navigation patterns with SukiSideMenu or ContentControl
  • Pass services (like ISukiToastManager) between ViewModels
  • Implement validation on ViewModel properties
当你需要以下功能时,可以使用本技能:
  • 为Avalonia视图创建新的ViewModel
  • 实现可通知UI更新的可观察属性
  • 添加命令以处理用户交互(如按钮点击等)
  • 通过SukiSideMenu或ContentControl设置导航模式
  • 在ViewModel之间传递服务(如ISukiToastManager)
  • 为ViewModel属性实现验证逻辑

ViewModel Base Classes

ViewModel基类

Basic ViewModel

基础ViewModel

cs
using CommunityToolkit.Mvvm.ComponentModel;

namespace YourApp.ViewModels;

public partial class ViewModelBase : ObservableObject
{
}
cs
using CommunityToolkit.Mvvm.ComponentModel;

namespace YourApp.ViewModels;

public partial class ViewModelBase : ObservableObject
{
}

Page ViewModel Base

页面ViewModel基类

cs
using CommunityToolkit.Mvvm.ComponentModel;

namespace YourApp.ViewModels;

public abstract partial class PageViewModelBase : ViewModelBase
{
    public abstract string DisplayName { get; }
    public abstract string Icon { get; }
}
cs
using CommunityToolkit.Mvvm.ComponentModel;

namespace YourApp.ViewModels;

public abstract partial class PageViewModelBase : ViewModelBase
{
    public abstract string DisplayName { get; }
    public abstract string Icon { get; }
}

Observable Properties

可观察属性

Basic Observable Property

基础可观察属性

cs
[ObservableProperty]
private string _name = "";
cs
[ObservableProperty]
private string _name = "";

With Validation

带验证的可观察属性

cs
using System.ComponentModel.DataAnnotations;

[ObservableProperty]
[Required(ErrorMessage = "Name is required")]
[MinLength(3, ErrorMessage = "Name must be at least 3 characters")]
private string _name = "";
cs
using System.ComponentModel.DataAnnotations;

[ObservableProperty]
[Required(ErrorMessage = "Name is required")]
[MinLength(3, ErrorMessage = "Name must be at least 3 characters")]
private string _name = "";

With Property Changed Notification

带属性变更通知的可观察属性

cs
[ObservableProperty]
private string _firstName = "";

[ObservableProperty]
private string _lastName = "";

public string FullName => $"{FirstName} {LastName}";

partial void OnFirstNameChanged(string value)
{
    OnPropertyChanged(nameof(FullName));
}

partial void OnLastNameChanged(string value)
{
    OnPropertyChanged(nameof(FullName));
}
cs
[ObservableProperty]
private string _firstName = "";

[ObservableProperty]
private string _lastName = "";

public string FullName => $"{FirstName} {LastName}";

partial void OnFirstNameChanged(string value)
{
    OnPropertyChanged(nameof(FullName));
}

partial void OnLastNameChanged(string value)
{
    OnPropertyChanged(nameof(FullName));
}

Commands

命令

Basic Command

基础命令

cs
[RelayCommand]
private void Save()
{
    // Save logic
}
cs
[RelayCommand]
private void Save()
{
    // Save logic
}

Command with Parameter

带参数的命令

cs
[RelayCommand]
private void ButtonClick(string buttonName)
{
    LastButtonClicked = buttonName;
}
View:
xml
<Button Content="Save" 
        Command="{Binding ButtonClickCommand}" 
        CommandParameter="Save Button" />
cs
[RelayCommand]
private void ButtonClick(string buttonName)
{
    LastButtonClicked = buttonName;
}
视图代码:
xml
<Button Content="Save" 
        Command="{Binding ButtonClickCommand}" 
        CommandParameter="Save Button" />

Async Command

异步命令

cs
[RelayCommand]
private async Task LoadDataAsync()
{
    IsLoading = true;
    await Task.Delay(1000);
    IsLoading = false;
}
cs
[RelayCommand]
private async Task LoadDataAsync()
{
    IsLoading = true;
    await Task.Delay(1000);
    IsLoading = false;
}

Command with CanExecute

带CanExecute的命令

cs
[ObservableProperty]
private bool _isDataLoaded;

[RelayCommand(CanExecute = nameof(CanSave))]
private void Save()
{
    // Save logic
}

private bool CanSave() => IsDataLoaded;

partial void OnIsDataLoadedChanged(bool value)
{
    SaveCommand.NotifyCanExecuteChanged();
}
cs
[ObservableProperty]
private bool _isDataLoaded;

[RelayCommand(CanExecute = nameof(CanSave))]
private void Save()
{
    // Save logic
}

private bool CanSave() => IsDataLoaded;

partial void OnIsDataLoadedChanged(bool value)
{
    SaveCommand.NotifyCanExecuteChanged();
}

Critical Pattern: DataTemplates

关键模式:DataTemplates

When using navigation patterns where ViewModels are displayed in a ContentControl (like SukiSideMenu), you MUST define DataTemplates to map ViewModels to their corresponding Views.
In SukiWindow:
xml
<suki:SukiWindow.DataTemplates>
    <DataTemplate DataType="vm:HomePageViewModel">
        <views:HomePage />
    </DataTemplate>
    <DataTemplate DataType="vm:SettingsPageViewModel">
        <views:SettingsPage />
    </DataTemplate>
</suki:SukiWindow.DataTemplates>
当使用ViewModel在ContentControl(如SukiSideMenu)中展示的导航模式时,必须定义DataTemplates以实现ViewModel到对应View的映射。
在SukiWindow中:
xml
<suki:SukiWindow.DataTemplates>
    <DataTemplate DataType="vm:HomePageViewModel">
        <views:HomePage />
    </DataTemplate>
    <DataTemplate DataType="vm:SettingsPageViewModel">
        <views:SettingsPage />
    </DataTemplate>
</suki:SukiWindow.DataTemplates>

Dependency Injection

依赖注入

Passing Services to ViewModels

为ViewModel传递服务

Parent ViewModel:
csharp
public partial class MainWindowViewModel : ViewModelBase
{
    private readonly ISukiToastManager _toastManager;

    public MainWindowViewModel()
    {
        _toastManager = new SukiToastManager();
        Pages = new ObservableCollection<PageViewModelBase>
        {
            new NotificationsPageViewModeler)
        };
    }
}
Child ViewModel:
csharp
public partial class NotificationsPageViewModel : PageViewModelBase
{
    private readonly ISukiToastManager _toastManager;

    public NotificationsPageViewModel(ISukiToastManager toastManager)
    {
        _toastManager = toastManager;
    }
}
父ViewModel:
csharp
public partial class MainWindowViewModel : ViewModelBase
{
    private readonly ISukiToastManager _toastManager;

    public MainWindowViewModel()
    {
        _toastManager = new SukiToastManager();
        Pages = new ObservableCollection<PageViewModelBase>
        {
            new NotificationsPageViewModeler)
        };
    }
}
子ViewModel:
csharp
public partial class NotificationsPageViewModel : PageViewModelBase
{
    private readonly ISukiToastManager _toastManager;

    public NotificationsPageViewModel(ISukiToastManager toastManager)
    {
        _toastManager = toastManager;
    }
}

Common Mistakes to Avoid

常见误区

  1. Forgetting DataTemplates - Navigation won't work without ViewModel-to-View mapping
  2. Not using partial class - CommunityToolkit.Mvvm requires partial keyword
  3. Missing OnPropertyChanged - Computed properties won't update without manual notification
  4. Not calling NotifyCanEuteChanged - Commands with CanExecute won't re-evaluate automatically
  5. Using wrong field naming - Observable property fields must start with underscore and be private
  1. 忘记定义DataTemplates - 没有ViewModel到View的映射,导航功能将无法工作
  2. 未使用partial类 - CommunityToolkit.Mvvm要求ViewModel必须是partial类
  3. 遗漏OnPropertyChanged调用 - 计算属性不会自动更新,需要手动通知
  4. 未调用NotifyCanExecuteChanged - 带CanExecute的命令不会自动重新执行状态评估
  5. 字段命名错误 - 可观察属性的字段必须以下划线开头且为私有

Best Practices

最佳实践

  1. Always use partial keyword on ViewModels
  2. Use [ObservableProperty] instead of manual INotifyPropertyChanged
  3. Use [RelayCommand] instead of manual ICommand implementation
  4. Define DataTemplates in the root window for navigation scenarios
  5. Pass services through constructor injection
  6. Use computed properties for derived values
  7. Implement validation attributes on properties need validation
  1. ViewModel始终使用partial关键字
  2. 使用[ObservableProperty]替代手动实现INotifyPropertyChanged
  3. 使用[RelayCommand]替代手动实现ICommand
  4. 在根窗口中定义DataTemplates以支持导航场景
  5. 通过构造函数注入传递服务
  6. 使用计算属性处理派生值
  7. 在需要验证的属性上实现验证特性