avalonia-mvvm
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMVVM 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
常见误区
- Forgetting DataTemplates - Navigation won't work without ViewModel-to-View mapping
- Not using partial class - CommunityToolkit.Mvvm requires partial keyword
- Missing OnPropertyChanged - Computed properties won't update without manual notification
- Not calling NotifyCanEuteChanged - Commands with CanExecute won't re-evaluate automatically
- Using wrong field naming - Observable property fields must start with underscore and be private
- 忘记定义DataTemplates - 没有ViewModel到View的映射,导航功能将无法工作
- 未使用partial类 - CommunityToolkit.Mvvm要求ViewModel必须是partial类
- 遗漏OnPropertyChanged调用 - 计算属性不会自动更新,需要手动通知
- 未调用NotifyCanExecuteChanged - 带CanExecute的命令不会自动重新执行状态评估
- 字段命名错误 - 可观察属性的字段必须以下划线开头且为私有
Best Practices
最佳实践
- Always use partial keyword on ViewModels
- Use [ObservableProperty] instead of manual INotifyPropertyChanged
- Use [RelayCommand] instead of manual ICommand implementation
- Define DataTemplates in the root window for navigation scenarios
- Pass services through constructor injection
- Use computed properties for derived values
- Implement validation attributes on properties need validation
- ViewModel始终使用partial关键字
- 使用[ObservableProperty]替代手动实现INotifyPropertyChanged
- 使用[RelayCommand]替代手动实现ICommand
- 在根窗口中定义DataTemplates以支持导航场景
- 通过构造函数注入传递服务
- 使用计算属性处理派生值
- 在需要验证的属性上实现验证特性