WPF 入门教程(三)
七.数据绑定
数据绑定是在应用程序 UI 与业务逻辑之间建立连接的过程,当数据发生变化时自动更新 UI,反之亦然。
1.DataContext快速入门
DataContext 是 WPF 数据绑定的核心概念,它提供了绑定系统默认的数据源。
1.1 DataContext 基础概念
什么是 DataContext?
- DataContext 是 FrameworkElement 的一个属性
- 它是绑定系统默认的数据源
- 具有继承性 - 子元素会继承父元素的 DataContext
- 简化绑定表达式 - 不需要指定 Source
为什么使用 DataContext?
- 减少重复绑定声明
- 实现 MVVM 模式的关键
- 使 UI 与业务逻辑分离
- 提高代码可维护性
1.2 设置 DataContext 的4种方式
1.2.1 XAML 直接设置
<Window x:Class="MyApp.MainWindow"
xmlns:local="clr-namespace:MyApp">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<!-- 子元素自动继承 -->
<TextBlock Text="{Binding Message}"/>
</Window>
1.2.2 代码中设置
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
1.2.3 通过资源设置
<Window.Resources>
<local:MainViewModel x:Key="ViewModel"/>
</Window.Resources>
<StackPanel DataContext="{StaticResource ViewModel}">
<TextBlock Text="{Binding Message}"/>
</StackPanel>
1.2.4 继承父容器
<Window DataContext="{StaticResource ViewModel}">
<StackPanel>
<!-- 继承Window的DataContext -->
<TextBlock Text="{Binding Message}"/>
</StackPanel>
</Window>
1.3 DataContext 实际应用
1.3.1 基本数据绑定
// ViewModel
public class MainViewModel
{
public string Greeting => "Hello, WPF!";
public DateTime CurrentTime => DateTime.Now;
}
<StackPanel>
<TextBlock Text="{Binding Greeting}"/>
<TextBlock Text="{Binding CurrentTime, StringFormat='Current time: {0:HH:mm:ss}'}"/>
</StackPanel>
1.3.2 嵌套对象绑定
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
public class MainViewModel
{
public User CurrentUser { get; } = new User { Name = "Alice", Age = 30 };
}
<StackPanel>
<TextBlock Text="{Binding CurrentUser.Name}"/>
<TextBlock Text="{Binding CurrentUser.Age}"/>
</StackPanel>
1.3.3 集合绑定
public class MainViewModel
{
public ObservableCollection<string> Items { get; } = new ObservableCollection<string>
{
"Item 1", "Item 2", "Item 3"
};
}
<ListBox ItemsSource="{Binding Items}"/>
1.4 MVVM 模式实现
// ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
// MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private string _userName;
public string UserName
{
get => _userName;
set { _userName = value; OnPropertyChanged(nameof(UserName
)); }
}
}
<TextBox Text="{Binding UserName,UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="{Binding UserName}"/>
2.Binding 绑定源
2.1 绑定源基本概念 绑定源(Binding Source)是WPF数据绑定中提供数据的对象
常见绑定源类型:
- DataContext:默认绑定源,具有继承性
- ElementName:绑定到其他UI元素
- StaticResource:绑定到资源字典中的对象
- RelativeSource:基于相对关系的绑定
- Self:绑定到元素自身
- x:Static:绑定到静态属性或字段
2.2 绑定源设置方式
2.2.1 DataContext
<!-- 设置DataContext -->
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<!-- 使用绑定 -->
<TextBlock Text="{Binding Message}"/>
2.2.2 ElementName(元素绑定)
<Slider x:Name="slider" Minimum="0" Maximum="100"/>
<TextBlock Text="{Binding Value, ElementName=slider}"/>
2.2.3 StaticResource(资源绑定)
<Window.Resources>
<local:User x:Key="user" Name="张三"/>
</Window.Resources>
<TextBlock Text="{Binding Source={StaticResource user}, Path=Name}"/>
2.2.4 RelativeSource(相对绑定)
<!-- 绑定到父元素 AncestorType:父级元素类型,AncestorLevel:父级层次 -->
<TextBlock Text="{Binding DataContext.Title,
RelativeSource={RelativeSource AncestorType=StackPanel,AncestorLevel=1}}"/>
<!-- 绑定到自身 -->
<Slider Value="{Binding ActualWidth,
RelativeSource={RelativeSource Self}}"/>
2.2.5 x:Static(静态绑定)
<!-- 绑定到静态属性 -->
<TextBlock Text="{Binding Source={x:Static local:AppConfig.AppName}}"/>
<!-- 绑定到枚举值 -->
<Button Visibility="{Binding Source={x:Static Visibility.Collapsed}}"/>
2.3 绑定源优先级
当同时指定多个绑定源时,优先级顺序为:
1.直接设置的Source 2.RelativeSource 3.ElementName 4.DataContext(默认)
2.4 绑定类型
2.4.1 单向绑定 (OneWay)
- 数据流向:源 → 目标
- 特点:当源数据变化时自动更新目标,反之不更新
- 适用场景:只读数据展示
<TextBlock Text="{Binding UserName, Mode=OneWay}"/>
2.4.2 双向绑定 (TwoWay)
- 数据流向:源 ↔ 目标
- 特点:源和目标任何一方变化都会影响另一方
- 适用场景:可编辑表单控件
<TextBox Text="{Binding UserName, Mode=TwoWay}"/>
2.4.3 一次性绑定 (OneTime)
- 数据流向:源 → 目标(仅一次)
- 特点:只在初始化时绑定,后续变化不更新
- 适用场景:静态数据或性能敏感场景
<TextBlock Text="{Binding AppVersion, Mode=OneTime}"/>
2.4.4 单向到源 (OneWayToSource)
- 数据流向:目标 → 源
- 特点:目标变化时更新源,但源变化不影响目标
- 适用场景:从UI收集数据
<Slider Value="{Binding Progress, Mode=OneWayToSource}"/>
2.5 绑定更新控制
2.5.1 更新触发器 (UpdateSourceTrigger)
- PropertyChanged:属性变化时立即更新
- LostFocus:失去焦点时更新(TextBox 默认)
- Explicit:需手动调用 UpdateSource
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
2.5.2 通知验证 (NotifyOnValidationError)
验证错误时触发事件
<TextBox Text="{Binding Age, NotifyOnValidationError=True}"/>
3.集合对象通知
项目结构:

viewModel:
internal class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// [CallerMemberName] 在方法或属性中获取调用该方法或属性的成员名称
/// </summary>
/// <param name="name"></param>
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
internal class User : ViewModelBase
internal class TestViewModel:ViewModelBase
{
private User _selectUser;
public User SelectUser
{
get { return _selectUser; }
set
{
_selectUser = value;
OnPropertyChanged();
}
}
public ObservableCollection<User> Users { get; set; } = new ObservableCollection<User>();
}
model:
internal class User : ViewModelBase
{
private string _username;
public string Username
{
get { return _username; }
set
{
_username = value;
OnPropertyChanged();
}
}
private int _age;
public int Age
{
get { return _age; }
set
{
_age = value;
OnPropertyChanged();
}
}
private string _remark;
public string Remark
{
get { return _remark; }
set
{
_remark = value;
OnPropertyChanged();
}
}
}
view:
<Window x:Class="TestWpfApp.Views.TestWindow"
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:TestWpfApp.Views"
xmlns:viewModel="clr-namespace:TestWpfApp.ViewModels"
mc:Ignorable="d"
Title="TestWindow" Height="400" Width="600">
<Window.DataContext>
<viewModel:TestViewModel/>
</Window.DataContext>
<Grid>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding SelectUser.Remark,UpdateSourceTrigger=LostFocus}" Margin="0,10,0,0"/>
<DataGrid Grid.Row="1" ItemsSource="{Binding Users}" SelectedItem="{Binding SelectUser}"></DataGrid>
</Grid>
</Grid>
</Window>

4.转换器
4.1 单值转换器
转换器
namespace TestWpfApp.Converters
{
public class StringToBoolConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string strValue)
{
return strValue == "Y";
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? "Y" : "N";
}
}
}
viewModel
internal class TestViewModel:ViewModelBase
{
private string _isChecked="Y";// Y或N
public string IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged();
}
}
}
view
<Window x:Class="TestWpfApp.Views.TestWindow"
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:TestWpfApp.Views"
xmlns:viewModel="clr-namespace:TestWpfApp.ViewModels"
mc:Ignorable="d"
xmlns:converter="clr-namespace:TestWpfApp.Converters"
Title="TestWindow" Height="400" Width="600">
<Window.DataContext>
<viewModel:TestViewModel/>
</Window.DataContext>
<Window.Resources>
<converter:StringToBoolConverter x:Key="stringToBoolConverter"/>
</Window.Resources>
<Grid>
<CheckBox IsChecked="{Binding IsChecked,Converter={StaticResource stringToBoolConverter}}" Content="是否选择"/>
</Grid>
</Window>
4.2 多值转换器
动态颜色混合
public class ColorMixConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
Color color1 = (Color)values[0];
Color color2 = (Color)values[1];
double weight = values.Length > 2 ? (double)values[2] : 0.5;
return Color.FromRgb(
(byte)(color1.R * weight + color2.R * (1 - weight)),
(byte)(color1.G * weight + color2.G * (1 - weight)),
(byte)(color1.B * weight + color2.B * (1 - weight))
);
}
// ConvertBack省略...
}
<Rectangle>
<Rectangle.Fill>
<MultiBinding Converter="{StaticResource ColorMixConverter}">
<Binding Path="PrimaryColor"/>
<Binding Path="SecondaryColor"/>
<Binding Path="BlendWeight"/>
</MultiBinding>
</Rectangle.Fill>
</Rectangle>
5.数据校验
在数据绑定过程中验证数据的有效性
5.1 异常校验
public class User : INotifyPropertyChanged
{
private int _age;
public int Age
{
get => _age;
set
{
if (value < 0 || value > 120)
throw new ArgumentOutOfRangeException("年龄必须在0-120之间");
_age = value;
OnPropertyChanged();
}
}
// INotifyPropertyChanged 实现...
}
<TextBox>
<TextBox.Text>
<Binding Path="Age" ValidatesOnExceptions="True" />
</TextBox.Text>
</TextBox>
5.2 数据注解校验
using System.ComponentModel.DataAnnotations;
public class User
{
[Required(ErrorMessage = "用户名不能为空")]
[StringLength(20, MinimumLength = 3, ErrorMessage = "用户名长度3-20个字符")]
public string UserName { get; set; }
[Range(0, 120, ErrorMessage = "年龄必须在0-120之间")]
public int Age { get; set; }
}
<TextBox Text="{Binding UserName, ValidatesOnDataErrors=True}" />
5.3 自定义校验规则
public class AgeValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo culture)
{
if (!int.TryParse(value?.ToString(), out int age))
return new ValidationResult(false, "必须输入数字");
return age >= 0 && age <= 120
? ValidationResult.ValidResult
: new ValidationResult(false, "年龄必须在0-120之间");
}
}
<TextBox>
<TextBox.Text>
<Binding Path="Age">
<Binding.ValidationRules>
<local:AgeValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
5.4 IDataErrorInfo 接口实现
public class User : IDataErrorInfo, INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
public string Error => null; // 不实现整个对象的校验
public string this[string columnName]
{
get
{
if (columnName == nameof(Name))
{
if (string.IsNullOrWhiteSpace(Name))
return "姓名不能为空";
if (Name.Length < 2)
return "姓名至少2个字符";
}
return null;
}
}
}
<TextBox Text="{Binding Name, ValidatesOnDataErrors=True}" />
5.5 INotifyDataErrorInfo 接口
public class User : INotifyDataErrorInfo, INotifyPropertyChanged
{
private string _email;
public string Email
{
get => _email;
set
{
_email = value;
OnPropertyChanged();
ValidateEmail();
}
}
private void ValidateEmail()
{
ClearErrors(nameof(Email));
if (string.IsNullOrWhiteSpace(Email))
{
AddError(nameof(Email), "邮箱不能为空");
return;
}
if (!Regex.IsMatch(Email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"))
AddError(nameof(Email), "邮箱格式不正确");
}
// INotifyDataErrorInfo 实现...
private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
public bool HasErrors => _errors.Any();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
if (_errors.ContainsKey(propertyName))
return _errors[propertyName];
return null;
}
private void AddError(string propertyName, string error)
{
if (!_errors.ContainsKey(propertyName))
_errors[propertyName] = new List<string>();
if (!_errors[propertyName].Contains(error))
{
_errors[propertyName].Add(error);
OnErrorsChanged(propertyName);
}
}
private void ClearErrors(string propertyName)
{
if (_errors.ContainsKey(propertyName))
{
_errors.Remove(propertyName);
OnErrorsChanged(propertyName);
}
}
private void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
}
<TextBox Text="{Binding Email, ValidatesOnNotifyDataErrors=True}" />
5.6 校验模板
<ControlTemplate x:Key="ValidationErrorTemplate">
<DockPanel>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder/>
</Border>
<TextBlock Foreground="Red" Text="!" FontWeight="Bold" Margin="5,0"/>
</DockPanel>
</ControlTemplate>
<TextBox Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}"
Text="{Binding Age, ValidatesOnExceptions=True}"/>
八.命令
命令模式概念:
- 解耦:将操作请求与执行分离
- 复用:同一命令可绑定到多个控件
- 状态管理:通过CanExecute管理可用状态 核心接口
public interface ICommand
{
// 当命令的可执行状态改变时触发
event EventHandler CanExecuteChanged;
// 判断命令是否可执行
bool CanExecute(object parameter);
// 执行命令逻辑
void Execute(object parameter);
}
1.内置命令体系
1.1 命令库分类
| 命令库 | 包含命令示例 | 适用场景 |
|---|---|---|
| ApplicationCommands | Cut, Copy, Paste, New, Open | 应用程序通用操作 |
| NavigationCommands | BrowseBack, BrowseForward, Refresh | 导航相关操作 |
| ComponentCommands | MoveLeft, MoveRight, ScrollPageUp | 组件操作 |
| MediaCommands | Play, Pause, Stop, Record | 多媒体控制 |
| EditingCommands | AlignLeft, IncreaseFontSize | 富文本编辑 |
1.2 使用内置命令
<!-- XAML中使用 -->
<Button Command="ApplicationCommands.Paste" Content="粘贴"/>
<!-- 自定义快捷键 -->
<Window.InputBindings>
<KeyBinding Command="ApplicationCommands.New" Gesture="Ctrl+N"/>
</Window.InputBindings>
2.自定义命令实现
2.1 RelayCommand标准实现
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
private EventHandler _canExecuteChanged;
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
_canExecuteChanged += value;
CommandManager.RequerySuggested += value;
}
remove
{
_canExecuteChanged -= value;
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter) => _canExecute?.Invoke(parameter) ?? true;
public void Execute(object parameter) => _execute(parameter);
public void RaiseCanExecuteChanged() => _canExecuteChanged?.Invoke(this, EventArgs.Empty);
}
使用:
internal class TestViewModel:ViewModelBase
{
public ICommand TestClickCommand { get; set; }
public TestViewModel()
{
TestClickCommand = new RelayCommand(TestClick);
}
private void TestClick(object parameter)
{
MessageBox.Show("Hello World!"+ parameter.ToString());
}
}
2.2 泛型版本实现
public class RelayCommand<T> : ICommand
{
private readonly Action<T> _execute;
private readonly Func<T, bool> _canExecute;
public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (parameter != null && !(parameter is T))
return false;
return _canExecute?.Invoke((T)parameter) ?? true;
}
public void Execute(object parameter)
{
if (CanExecute(parameter))
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
使用:
internal class TestViewModel:ViewModelBase
{
public ICommand TestClickCommand { get; set; }
public TestViewModel()
{
TestClickCommand = new RelayCommand<string>(TestClick);
}
private void TestClick(string parameter)
{
MessageBox.Show("Hello World!"+ parameter);
}
}
3.命令绑定技术
3.1 基本绑定方式
<!-- 无参数绑定 -->
<Button Command="{Binding SaveCommand}" Content="保存"/>
<!-- 带参数绑定 -->
<Button Command="{Binding DeleteCommand}"
CommandParameter="{Binding SelectedItem}"
Content="删除选中项"/>
3.2 输入绑定
<Window.InputBindings>
<!-- 键盘快捷键 -->
<KeyBinding Command="{Binding SearchCommand}" Gesture="Ctrl+F"/>
<!-- 鼠标手势 -->
<MouseBinding Command="{Binding OpenCommand}" Gesture="Ctrl+LeftClick"/>
</Window.InputBindings>
4.事件转命令
Nuget安装:Microsoft.Xaml.Behaviors.Wpf
引入命名空间:xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
使用:
<Label Content="点我一下">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding TestClickCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Label>
九.样式
样式(Style)是WPF中用于统一管理控件外观和行为的强大工具,它允许开发者集中定义控件的属性设置,实现UI的一致性和可维护性。
1.样式资源创建和使用
1.1 创建
新建目录放置资源文件

右键新建资源字典

1.2 使用
全局使用:
在App.xaml中添加
<Application x:Class="TestWpfApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpfApp"
StartupUri="./views/TestWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/Styles/TestWindowDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
在某个视图中使用:
<Window.Resources>
<ResourceDictionary>
<converter:StringToBoolConverter x:Key="stringToBoolConverter"/>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/Styles/TestWindowDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
2.样式的基本语法
2.1 样式定义
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!--引入其他资源字典-->
<!--
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/Templates/CommonConverterDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
-->
<Style x:Key="buttonStyle" TargetType="Button">
<Setter Property="Background" Value="LightBlue"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="10,5"/>
</Style>
</ResourceDictionary>
2.2 样式应用
<Button Style="{StaticResource ButtonStyle}" Content="样式按钮"/>
3.样式的关键特性
3.1 隐式样式
不指定x:Key,自动应用于所有指定类型的控件
<Style TargetType="Button">
<Setter Property="Background" Value="LightGreen"/>
</Style>
3.2 样式继承
使用BasedOn属性继承其他样式
<Style x:Key="BaseButtonStyle" TargetType="Button">
<Setter Property="Background" Value="LightBlue"/>
</Style>
<Style x:Key="SpecialButtonStyle" TargetType="Button" BasedOn="{StaticResource BaseButtonStyle}">
<Setter Property="Foreground" Value="Red"/>
</Style>
3.3 动态资源与静态资源
<!-- 静态资源(设计时解析) -->
<Style x:Key="StaticStyle" TargetType="Button">
<Setter Property="Background" Value="{StaticResource BrushResource}"/>
</Style>
<!-- 动态资源(运行时解析) -->
<Style x:Key="DynamicStyle" TargetType="Button">
<Setter Property="Background" Value="{DynamicResource BrushResource}"/>
</Style>
3.4 主题支持
<!-- 根据主题加载不同样式 -->
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes/{x:Static SystemParameters.HighContrast ?
'HighContrast.xaml' : 'Normal.xaml'}"/>
</ResourceDictionary.MergedDictionaries>
4.触发器(Triggers)
4.1 属性触发器
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Gold"/>
</Trigger>
</Style.Triggers>
</Style>
4.2 数据触发器
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding IsValid}" Value="False">
<Setter Property="Background" Value="Pink"/>
</DataTrigger>
</Style.Triggers>
</Style>
4.3 多条件触发器(MultiTrigger)
<Style TargetType="Button">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsEnabled" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" Value="Gold"/>
</MultiTrigger>
</Style.Triggers>
</Style>
4.4 事件触发器(EventTriggers)
<Style TargetType="Button">
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:0.2"
Storyboard.TargetProperty="Opacity"
To="0.8"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
十.模板
1.ControlTemplate
ControlTemplate 用于完全自定义控件的外观(视觉结构),但不改变其功能(如 Button 的点击事件仍然有效)。
核心特点:
- 仅修改 UI 表现,不影响控件逻辑(如事件、命令)。
- 适用于所有继承自 Control 的 WPF 控件(如 Button、CheckBox、Slider)。
- 通常结合 Style 使用,通过 Template 属性应用
模板也类似于样式可以进行继承
1.1 基本语法
1.1.1 定义 ControlTemplate
<ControlTemplate x:Key="CustomButtonTemplate" TargetType="Button">
<!-- 定义控件视觉结构 -->
<Border Background="LightBlue" CornerRadius="10" Padding="10">
<ContentPresenter /> <!-- 显示控件的内容(如 Button.Content) -->
</Border>
</ControlTemplate>
1.1.2 应用 ControlTemplate
<!-- 方式1:直接通过 Template 属性应用 -->
<Button Template="{StaticResource CustomButtonTemplate}" Content="Click Me" />
<!-- 方式2:通过 Style 应用(推荐) -->
<Style TargetType="Button">
<Setter Property="Template" Value="{StaticResource CustomButtonTemplate}" />
</Style>
1.2 关键组成部分
1.2.1 ContentPresenter
显示控件的 Content(如 Button.Content、Label.Content)
<ControlTemplate TargetType="Button">
<Grid>
<Ellipse Fill="Red" />
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
1.2.2 Triggers(触发器)
用于定义交互状态(如鼠标悬停、按下时的样式变化)。
<ControlTemplate TargetType="Button">
<Border x:Name="border" Background="LightGray" CornerRadius="5">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="LightBlue" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
1.2.3 TemplateBinding
将模板中的属性绑定到控件的依赖属性(如 Background、FontSize)。
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding Tag}"> <!-- 使用 Tag 传递圆角值 -->
<ContentPresenter />
</Border>
</ControlTemplate>
1.3 常见应用场景
1.3.1 自定义按钮样式
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/Styles/TestWindowDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
<ControlTemplate x:Key="btn" TargetType="Button">
<Border x:Name="border" Background="{TemplateBinding Background}" CornerRadius="4" BorderThickness="{TemplateBinding BorderThickness}"
Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"
BorderBrush="{TemplateBinding BorderBrush}"
>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightGray" TargetName="border"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="Red" TargetName="border"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Button Template="{StaticResource btn}" Tag="8" Width="120" Height="36" Content="按钮"/>
</Grid>

1.3.2 自定义 CheckBox(开关样式)
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/Styles/TestWindowDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
<ControlTemplate TargetType="CheckBox" x:Key="checkBox">
<Border Width="40" Height="20" Background="LightGray" CornerRadius="10">
<Ellipse x:Name="thumb" HorizontalAlignment="Left" Width="20" Fill="White" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="thumb" Property="HorizontalAlignment" Value="Right" />
<Setter TargetName="thumb" Property="Fill" Value="Green" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<CheckBox Template="{StaticResource checkBox}"/>
</Grid>

1.3.3 自定义 ProgressBar
<Window.Resources>
<ControlTemplate TargetType="ProgressBar" x:Key="pb">
<Grid>
<Rectangle Fill="LightGray" RadiusX="5" RadiusY="5" />
<Rectangle x:Name="progressBar" Fill="Blue" RadiusX="5" RadiusY="5"
HorizontalAlignment="Left" Width="{TemplateBinding Value}" />
</Grid>
</ControlTemplate>
</Window.Resources>
<Grid>
<ProgressBar Maximum="100" Value="50" Template="{StaticResource pb}" Width="100" Height="30"/>
</Grid>

2.DataTemplate
DataTemplate 用于定义数据对象如何可视化显示,通常用于 ItemsControl(如 ListBox、ListView)或 ContentControl(如 ContentPresenter)。
核心特点
数据驱动 UI:自动将数据对象渲染为可视化元素。 支持数据绑定:可绑定到对象的属性(如 Person.Name)。 可复用:可定义为资源,供多个控件共享
2.1 基本语法
2.1.1 定义 DataTemplate
<DataTemplate DataType="{x:Type model:User}" x:Key="listBoxItem">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Username}" FontWeight="Bold" />
<TextBlock Text="{Binding Age}" Margin="5,0,0,0" />
</StackPanel>
</DataTemplate>
2.1.2 应用 DataTemplate
<ListBox ItemsSource="{Binding Users}" ItemTemplate="{StaticResource ResourceKey=listBoxItem}"/>
2.2 关键组成部分
2.2.1 DataType 属性
指定模板适用的数据类型(可省略,但推荐显式声明)
<DataTemplate DataType="{x:Type local:Product}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
2.2.2 数据绑定({Binding})
绑定到数据对象的属性(如 {Binding Name})
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Age}" />
</StackPanel>
</DataTemplate>
2.2.3 ItemTemplate 和 ContentTemplate
- ItemTemplate:用于 ItemsControl(如 ListBox)。
- ContentTemplate:用于 ContentControl(如 Button、Label)。
3.ItemsPanelTemplate
ItemsPanelTemplate 是 WPF 中用于定义 ItemsControl 及其派生类(如 ListBox、ListView、ComboBox 等)内部布局面板的模板。
主要作用:
- 控制 ItemsControl 中项的布局方式
- 替换默认的布局面板(如 StackPanel)
- 实现自定义的布局效果
3.1 不同 ItemsControl 的默认 ItemsPanel
| 控件类型 | 默认 ItemsPanel |
|---|---|
| ItemsControl | StackPanel (Orientation=Vertical) |
| ListBox | VirtualizingStackPanel (Orientation=Vertical) |
| ComboBox | StackPanel (Orientation=Vertical) |
| ListView | VirtualizingStackPanel (Orientation=Vertical) |
| Menu | WrapPanel (Orientation=Horizontal) |
| StatusBar | DockPanel |
3.2 使用
水平列表
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<!-- 把默认的垂直排列改成水平排列 -->
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBoxItem>项目1</ListBoxItem>
<ListBoxItem>项目2</ListBoxItem>
<ListBoxItem>项目3</ListBoxItem>
</ListBox>
网格布局
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- 使用网格布局,每行3列 -->
<UniformGrid Columns="3"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Button Content="按钮1"/>
<Button Content="按钮2"/>
<!-- 更多项目... -->
</ItemsControl>
4.HierarchicalDataTemplate
HierarchicalDataTemplate 是 WPF 中专门用于显示层级数据的模板,主要用于 TreeView、Menu 等可以展示层级结构的控件。
与普通 DataTemplate 的区别:
| 特性 | DataTemplate | HierarchicalDataTemplate |
|---|---|---|
| 适用场景 | 平铺数据 | 层级数据 |
| 子项支持 | 不支持自动绑定子项 | 支持通过 ItemsSource 绑定子项 |
| 典型控件 | ListBox, ComboBox | TreeView, Menu |
4.1 基本 TreeView 绑定
<TreeView ItemsSource="{Binding Departments}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Department}"
ItemsSource="{Binding Employees}">
<TextBlock Text="{Binding DeptName}" FontWeight="Bold"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Employee}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Photo}" Width="16"/>
<TextBlock Text="{Binding Name}" Margin="5,0"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
4.2 多级层级结构
<TreeView ItemsSource="{Binding Company}">
<TreeView.Resources>
<!-- 第一级:公司 -->
<HierarchicalDataTemplate DataType="{x:Type local:Company}"
ItemsSource="{Binding Departments}">
<TextBlock Text="{Binding CompanyName}" Foreground="Blue"/>
</HierarchicalDataTemplate>
<!-- 第二级:部门 -->
<HierarchicalDataTemplate DataType="{x:Type local:Department}"
ItemsSource="{Binding Teams}">
<TextBlock Text="{Binding DeptName}" Foreground="Green"/>
</HierarchicalDataTemplate>
<!-- 第三级:团队 -->
<HierarchicalDataTemplate DataType="{x:Type local:Team}"
ItemsSource="{Binding Members}">
<TextBlock Text="{Binding TeamName}"/>
</HierarchicalDataTemplate>
<!-- 第四级:成员 -->
<DataTemplate DataType="{x:Type local:Employee}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
4.3 案例-查看html大纲
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// [CallerMemberName] 在方法或属性中获取调用该方法或属性的成员名称
/// </summary>
/// <param name="name"></param>
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Model
public class HtmlHeadingNode:ViewModelBase
{
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
OnPropertyChanged();
}
}
private int _level;
public int Level
{
get { return _level; }
set
{
_level = value;
OnPropertyChanged();
}
}
public ObservableCollection<HtmlHeadingNode> Children { get; set; } = new ObservableCollection<HtmlHeadingNode>();
}
ViewModel
internal class TestViewModel:ViewModelBase
{
public ObservableCollection<HtmlHeadingNode> HeadingNodes { get; set; } = new ObservableCollection<HtmlHeadingNode>();
public ICommand LoadHtmlFileCommand { get; set; }
private string _htmlFilePath = string.Empty;
public string HtmlFilePath
{
get { return _htmlFilePath; }
set
{
_htmlFilePath = value;
OnPropertyChanged();
}
}
public TestViewModel()
{
LoadHtmlFileCommand = new RelayCommand<object>(LoadHtmlFile);
}
private void LoadHtmlFile(object o)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "HTML文件 (*.html;*.htm)|*.html;*.htm|所有文件 (*.*)|*.*";
openFileDialog.FilterIndex = 1;
openFileDialog.RestoreDirectory = true;
if (openFileDialog.ShowDialog() == true)
{
try
{
HtmlFilePath = openFileDialog.FileName;
string htmlContent = File.ReadAllText(HtmlFilePath);
HeadingNodes.Clear();
ParseHtmlHeadings(htmlContent);
}
catch (Exception ex)
{
MessageBox.Show($"读取文件时出错: {ex.Message}");
}
}
}
private void ParseHtmlHeadings(string htmlContent)
{
// 略
}
}
RelayCommand
略
View
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="36"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Html文件大纲" FontSize="16" FontWeight="Bold"/>
<Grid Grid.Row="1" Margin="0,5,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding HtmlFilePath}" IsReadOnly="True" VerticalContentAlignment="Center" Padding="5,0"/>
<Button Grid.Column="1" Command="{Binding LoadHtmlFileCommand}" Content="打开" Margin="5,0,0,0" Width="75"/>
</Grid>
<TreeView Grid.Row="2" Margin="0,5,0,0" ItemsSource="{Binding HeadingNodes}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type model:HtmlHeadingNode}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Title}" Foreground="Red"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
效果:

5.HeaderedTemplate
5.1 基本概念
HeaderedItemsControl 是 WPF 中一类特殊的控件,它们同时具有标题(Header)和内容项(Items)两部分。常见的派生控件包括:
- TreeViewItem
- MenuItem
- Expander
- GroupItem
- TabItem
5.2 核心模板类型
5.2.1 HeaderedItemsControl 模板结构
<HeaderedItemsControl>
<HeaderedItemsControl.Header> <!-- 标题部分 -->
<!-- 可以是任意内容 -->
</HeaderedItemsControl.Header>
<HeaderedItemsControl.Items> <!-- 内容项部分 -->
<!-- 子项集合 -->
</HeaderedItemsControl.Items>
</HeaderedItemsControl>
5.2.2 HeaderTemplate
用于定义如何显示标题部分的模板
<HeaderedItemsControl.HeaderTemplate>
<DataTemplate>
<!-- 标题的视觉呈现 -->
<Border Background="LightBlue" Padding="5">
<TextBlock Text="{Binding}" FontWeight="Bold"/>
</Border>
</DataTemplate>
</HeaderedItemsControl.HeaderTemplate>
5.2.3 ItemTemplate
用于定义如何显示内容项的模板(与普通 ItemsControl 相同
<HeaderedItemsControl.ItemTemplate>
<DataTemplate>
<!-- 每个子项的视觉呈现 -->
<TextBlock Text="{Binding Name}" Margin="5,2"/>
</DataTemplate>
</HeaderedItemsControl.ItemTemplate>
十一.MVVM框架
1.Mvvmlight
主要特点
- 轻量级:核心功能精简,不包含过多复杂功能
- 简单易用:学习曲线平缓,适合快速开发
- 消息传递机制:提供 Messenger 类实现组件间通信
- 依赖注入:简单的 IOC 容器支持
- 命令实现:简化 ICommand 接口的实现
1.1 Mvvm入门
1.1.1 安装
Nuget搜索Mvvmlight进行安装,该框架已经停止维护。
1.1.2 Model层
public class PersonModel:ViewModelBase
{
private int _id;
public int Id
{
get { return _id; }
set { Set(ref _id, value); }
}
private string _name;
public string Name
{
get { return _name; }
set { Set(ref _name, value); }
}
private int _age;
public int Age
{
get { return _age; }
set { Set(ref _age, value); }
}
// 手动通知
//public int Age
//{
// get { return _age; }
// set { _age = value;RaisePropertyChanged(); }
//}
}
1.1.3 ViewModel层
public class TestMvvmViewModel:ViewModelBase
{
private PersonModel _person=new PersonModel()
{
Id = 1,
Name = "张三",
Age = 18
};
public PersonModel Person
{
get { return _person; }
set { Set(ref _person, value); }
}
public RelayCommand NoParamCommand => new RelayCommand(() =>
{
MessageBox.Show($"Id:{_person.Id},Name:{_person.Name},Age:{_person.Age}","无参数命令调用");
});
}
1.1.4 View层
<Window x:Class="TestWpfApp.Views.TestMvvmWindow"
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:TestWpfApp.Views"
mc:Ignorable="d"
xmlns:viewModel="clr-namespace:TestWpfApp.ViewModels"
Title="TestMvvmWindow" Height="450" Width="800">
<Window.DataContext>
<viewModel:TestMvvmViewModel/>
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Person.Name}"/>
<TextBox Text="{Binding Person.Age}"/>
<Button Content="查看" Command="{Binding NoParamCommand}"/>
</StackPanel>
</Window>
1.1.5 效果

1.2 消息机制
有时候多个ViewModel之间需要传递消息进行交互,例如A窗口点击按钮更新B窗口数据。
// 发送消息
Messenger.Default.Send(user);
// 指定目标进行发送
Messenger.Default.Send<User,TargetView>(user);
// 接收消息
Messenger.Default.Register<User>(this, user => {
// 处理消息
});
// 记得在窗体关闭时取消注册
Messenger.Default.Unergister<User>(this);
// 使用消息token发送接收消息
Messenger.Default.Send(user,nameof(TargetView));
Messenger.Default.Register<User>(this,nameof(TargetView), user => {
});
1.3 IOC 容器
SimpleIoc默认是一个单例的
// 注册服务
SimpleIoc.Default.Register<IDataService, DataService>();
// 获取实例
var dataService = SimpleIoc.Default.GetInstance<IDataService>();
// 构造函数注入
SimpleIoc.Default.Register<Random>();
SimpleIoc.Default.Register<TestMvvmViewModel>();
var viewModel = SimpleIoc.Default.GetInstance<TestMvvmViewModel>();
DataContext = viewModel;
private readonly Random _random;
public TestMvvmViewModel(Random random)
{
_random = random;
}
2.mvvmtoolkit
2.1 Mvvm入门
2.1.1 Nuget安装
Nuget搜索CommunityToolkit.Mvvm进行安装

2.1.2 ViewModel层
public class MainViewModel: ObservableObject
{
private string _title = "Hello, World!";
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
//public string Title
//{
// get => _title;
// set{
// _title = value;
// OnPropertyChanged();
// }
//}
public RelayCommand GetTitleCommand { get; private set; }
public MainViewModel()
{
GetTitleCommand = new RelayCommand(() =>
{
MessageBox.Show(Title);
});
}
}
2.1.3 View层
<StackPanel>
<TextBox Text="{Binding Title}"/>
<Button Content="打印标题" HorizontalAlignment="Left" Command="{Binding GetTitleCommand}"/>
</StackPanel>

2.2 特性
特性在.net framework可能无法使用,.net core正常运行
// 如果需要继承其他类,请在这里添加该特性
// [INotifyPropertyChanged]
public partial class MainViewModel:ObservableObject
{
/// <summary>
/// 自动生成属性、通知
/// </summary>
[ObservableProperty]
private string _title;
[RelayCommand]
private void Click()
{
MessageBox.Show(Title);
}
}
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<TextBox Text="{Binding Title,UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="按钮" Command="{Binding ClickCommand}" HorizontalAlignment="Left"/>
</StackPanel>
2.3 ICO容器
CommunityToolkit.Mvvm不自带Ioc功能,需要使用IOC官方建议使用Microsoft.Extensions.DependencyInjecti
2.3.1 安装Microsoft.Extensions.DependencyInjecti

2.3.2 修改 App.xaml.cs
public partial class App : Application
{
public App()
{
InitializeComponent();
ServiceProvider = ConfigureServices();
}
public new static App Current = (App)Application.Current;
public IServiceProvider? ServiceProvider { get; set; }
private IServiceProvider ConfigureServices()
{
IServiceCollection services = new ServiceCollection();
// 每调用一次就会创建一个新的实例
// services.AddTransient<DialogWindow>();
// 整个应用程序生命周期内只创建一个实例
services.AddSingleton<Random>();
// 每个请求/作用域(Scope)内共享一个实例
//services.AddScoped<UserModel>();
return services.BuildServiceProvider();
}
}
2.3.3 修改ViewModel
public partial class MainViewModel:ObservableObject
{
private readonly Random _ran;
public MainViewModel()
{
}
public MainViewModel(Random ran)
{
_ran = ran;
}
[RelayCommand]
private void GetRandomNumber()
{
int number = _ran.Next(1, 100);
MessageBox.Show($"Random number: {number}");
}
}
2.3.4 View.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = App.Current.ServiceProvider?.GetService<MainViewModel>();
}
}
2.3.5 View
<Window x:Class="TestWpfCore.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:TestWpfCore"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<d:Window.DataContext>
<local:MainViewModel/>
</d:Window.DataContext>
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<Button Content="获取随机数" Command="{Binding GetRandomNumberCommand}" HorizontalAlignment="Left"/>
</StackPanel>
</Window>
2.4 消息机制
2.4.1 弱引用机制
发送端:
public partial class MainViewModel:ObservableObject
{
[ObservableProperty]
private string _name="张三";
[RelayCommand]
void SendMessage()
{
WeakReferenceMessenger.Default.Send(new ValueChangedMessage<string>(Name), "name_token");
}
[RelayCommand]
void OpenClient()
{
new ClientWindow().Show();
}
}
接收端:
public partial class ClientViewModel: ObservableRecipient
{
[ObservableProperty]
private string _name = "李四";
public ClientViewModel()
{
IsActive=true;
}
protected override void OnActivated()
{
Messenger.Register<ClientViewModel,//收件人
ValueChangedMessage<string>, // 消息类型
string// token类型
>(this, "name_token", (vm, msg) =>
{
Name = msg.Value;
});
}
public void Unloaded()
{
Messenger.UnregisterAll(this);
}
}
记得在客户端卸载消息
public partial class ClientWindow : Window
{
public ClientWindow()
{
InitializeComponent();
var vm = App.Current.ServiceProvider?.GetService<ClientViewModel>();
DataContext = vm;
Closed += (s, e) =>
{
vm ?.Unloaded();
};
}
}
2.4.2 响应消息 消息通用类:
public class MyMessage:RequestMessage<string>
{
public object? Data { get; set; }
}
发送:
MyMessage myMessage = new()
{
Data = Name,
};
var result = WeakReferenceMessenger.Default
.Send(new ValueChangedMessage<MyMessage>(myMessage), "name_token");
Console.WriteLine(result.Value.Response);
接收:
Messenger.Register<ClientViewModel,ValueChangedMessage<MyMessage>,string>
(this, "name_token", (vm, msg) =>
{
Name = msg.Value.Data.ToString();
msg.Value.Reply("ok");
});