WPF 入门教程(三)

2026-05-16 00:59 176 阅读

七.数据绑定

数据绑定是在应用程序 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");
     });