WPF Demos

Posted by Andy Feng on June 27, 2025

Callback to ShareService

日志 -> 共享服务

flowchart
edit[Edit View]
main[Main View]
statusbar[Status Bar]
edit --用户保存出错-->main--设置status-->statusbar

主窗口 MainWindow.xaml 中status bar绑定到message

<StatusBar Name="MainStatusBar" DockPanel.Dock="Bottom">
    <TextBlock Text="{Binding StatusMessage}" />
</StatusBar>

主窗口 ViewModel 中定义 StatusMessage 属性

private string _statusMessage;
public string StatusMessage
{
    get => _statusMessage;
    set
    {
        _statusMessage = value;
        OnPropertyChanged();
    }
}

EditViewModel把错误信息传给主窗口 ViewModel 你有几个选择:

方法 1:使用事件回调 callback in viewmodel

在 EditViewModel 中定义:

public Action<string>? SetStatusMessage;
...
public void Save(){
	...
	catch (Exception ex)
	{
	    SetStatusMessage?.Invoke($"保存失败:{ex.Message}");
	}
}

在创建 EditViewModel 时,注入主窗口方法:

var vm = new AddQuestionViewModel(...);
vm.SetStatusMessage = msg => MainViewModel.StatusMessage = msg;

方法 2:使用共享服务(如 IStatusService

定义一个服务类,或另一个viewmodel

public interface IStatusService
{
    string Message { get; set; }
}

public class StatusService : IStatusService, INotifyPropertyChanged
{
    private string _message;
    public string Message
    {
        get => _message;
        set { _message = value; OnPropertyChanged(); }
    }

    public event PropertyChangedEventHandler? PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string? name = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

App.xaml.cs 里注册:

services.AddSingleton<IStatusService, StatusService>();

然后在 MainWindow 和各 ViewModel 中注入 IStatusService 并绑定 MessageStatusBar

Bonus:显示几秒后自动清除

你可以这样实现:

SetStatusMessage?.Invoke("保存成功!");
Task.Delay(3000).ContinueWith(_ => SetStatusMessage?.Invoke(""));

CodeBehind to MVVM

列表->编辑,再返回

flowchart
list[List View]
edit[Edit View]
list--用户选择一个-->edit
edit--用户点击返回-->list

way1: code behind

main window

public partial class MainWindow : Window
{
	public MainWindow(IServiceProvider serviceProvider, ResultViewModel result)
	{
	}
	public void LoadView<T>() where T : UserControl
	{
	    this.pnlContent.Children.Clear();
	    var view = this._rootScope.ServiceProvider.GetRequiredService<T>();
	    this.pnlContent.Children.Add(view);
	}
}

list view code behind

public partial class ListView : UserControl
{
	private MainWindow _mainWindow;
	private readonly EditView _editQuiView;
	public ListQuizView(MainWindow mainWindow, EditView editView)
	{
		InitializeComponent();
		_mainWindow = mainWindow;
		_editView = editView;   
	}
	private async Task Edit(ListViewModel vm)
	{
	    if (vm == null) return;
	    // 跳转到编辑页面
	    await _editViewModel.Load(vm.Id);
	    _editView.DataContext = _editViewModel;
	    _mainWindow.LoadView<EditView>(_editView);
	}
	private void OnBackClicked(object sender, RoutedEventArgs e)
    {
        _mainWindow.LoadView(_editView);
    }
}

在edit view添加button

<Button Content="Back" Margin="5" Click="OnBackClicked" />

在edit view code behind添加

private void OnBackClicked(object sender, RoutedEventArgs e)
{
	_mainWindow.LoadView(_listView);
}

way2: MVVM

EditViewModel 中添加 BackCommand

public ICommand BackCommand { get; }

public EditQuestionViewModel(
    )
{
    ...
    BackCommand = new RelayCommand(Back); // <--- 新增
}

// 新增的方法:Back
private void Back()
{
	var listView = App.ServiceProvider.GetRequiredService<ListView>();
	_mainWindow.LoadView(_listView);
}

EditView.xaml 中绑定按钮到命令

<Button Content="Back" Margin="5" Click="OnBackClicked" />

改成

<Button Content="Back" Margin="5" Command="{Binding BackCommand}"/>

Add Command

给view添加一个 download 按钮和调用方法,使用MVVM

为ListCategoryView的 download 按钮添加方法,调用dao的AddOrUpdateCategories()。 要求使用MVVM,优雅的代码。

绑定按钮到命令

修改ListCategoryView,添加 btnDownloadCategory 按钮的 XAML:

<Button x:Name="btnDownloadCategory"
        Content="Download Category"
        HorizontalAlignment="Left"
        Margin="176,15,0,0"
        VerticalAlignment="Top"
        Width="142"
        Command="{Binding DownloadCommand}" />

给view添加独立的 ViewModel 类

public class ListCategoryViewModel : INotifyPropertyChanged
{
    private readonly ICategoryDao _categoryDao;

    public ListCategoryViewModel(ICategoryDao categoryDao)
    {
        _categoryDao = categoryDao;
        DownloadCommand = new AsyncRelayCommand(DownloadCategoriesAsync);
    }

    public ICommand DownloadCommand { get; }

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged(string propertyName) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    private async Task DownloadCategoriesAsync()
    {
        var externalCategories = await LoadFromExternalSourceAsync();

        await _categoryDao.AddOrUpdateCategories(SourceEnum.SourceA, externalCategories);
        _resultService.SetSuccess("Categories downloaded and updated.");
    }

    // 假设从网络/API 或本地文件加载外部数据
    private async Task<ICollection<Category>> LoadFromExternalSourceAsync()
    {
        await Task.Delay(500); // 模拟延迟
        return new List<Category>
        {
            new Category { Name = "History", ShortName = "history" },
            new Category { Name = "Science", ShortName = "science" }
        };
    }
}

ListCategoryView.xaml.cs 中注入 ViewModel

将它作为一个字段并传入 DataContext

public partial class ListCategoryView : UserControl
{
    private readonly ListCategoryPageViewModel _viewModel;

    public ListCategoryView(MainWindow mainWindow,
                            ListCategoryPageViewModel viewModel)
    {
        InitializeComponent();
        _viewModel = viewModel;
        this.DataContext = _viewModel;
    }
}

DI 注册依赖

配置

Program.csApp.xaml.cs 中:

services.AddTransient<ListCategoryViewModel>();

FAQ

References