WPF 入门教程(三)

19 次查看
# 七.数据绑定
数据绑定是在应用程序 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 直接设置

```xml
<Window x:Class="MyApp.MainWindow"
        xmlns:local="clr-namespace:MyApp">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    
    <!-- 子元素自动继承 -->
    <TextBlock Text="{Binding Message}"/>
</Window>
```

#### 1.2.2 代码中设置

```c#
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainViewModel();
    }
}
```

#### 1.2.3 通过资源设置

```xml
<Window.Resources>
    <local:MainViewModel x:Key="ViewModel"/>
</Window.Resources>

<StackPanel DataContext="{StaticResource ViewModel}">
    <TextBlock Text="{Binding Message}"/>
</StackPanel>
```

#### 1.2.4 继承父容器

```xml
<Window DataContext="{StaticResource ViewModel}">
    <StackPanel>
        <!-- 继承Window的DataContext -->
        <TextBlock Text="{Binding Message}"/>
    </StackPanel>
</Window>
```

### 1.3 DataContext 实际应用
#### 1.3.1 基本数据绑定

```c#
// ViewModel
public class MainViewModel
{
    public string Greeting => "Hello, WPF!";
    public DateTime CurrentTime => DateTime.Now;
}
```


```xml
<StackPanel>
    <TextBlock Text="{Binding Greeting}"/>
    <TextBlock Text="{Binding CurrentTime, StringFormat='Current time: {0:HH:mm:ss}'}"/>
</StackPanel>
```

#### 1.3.2 嵌套对象绑定

```c#
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 };
}

```

```xml
<StackPanel>
    <TextBlock Text="{Binding CurrentUser.Name}"/>
    <TextBlock Text="{Binding CurrentUser.Age}"/>
</StackPanel>
```

#### 1.3.3 集合绑定

```c#
public class MainViewModel
{
    public ObservableCollection<string> Items { get; } = new ObservableCollection<string>
    {
        "Item 1", "Item 2", "Item 3"
    };
}
```


```xml
<ListBox ItemsSource="{Binding Items}"/>
```

### 1.4  MVVM 模式实现


```c#
// 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
)); }
    }
}

```



```xml
<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

```xml
<!-- 设置DataContext -->
<Window.DataContext>
    <local:MainViewModel/>
</Window.DataContext>

<!-- 使用绑定 -->
<TextBlock Text="{Binding Message}"/>
```

#### 2.2.2 ElementName(元素绑定)



```xml
<Slider x:Name="slider" Minimum="0" Maximum="100"/>
<TextBlock Text="{Binding Value, ElementName=slider}"/>
```

#### 2.2.3 StaticResource(资源绑定)

```xml
<Window.Resources>
    <local:User x:Key="user" Name="张三"/>
</Window.Resources>

<TextBlock Text="{Binding Source={StaticResource user}, Path=Name}"/>
```

#### 2.2.4 RelativeSource(相对绑定)



```xml
<!-- 绑定到父元素 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(静态绑定)



```xml
<!-- 绑定到静态属性 -->
<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)

- ​数据流向​​:源 → 目标
- ​​特点​​:当源数据变化时自动更新目标,反之不更新
- ​​适用场景​​:只读数据展示


```xml
<TextBlock Text="{Binding UserName, Mode=OneWay}"/>
```

#### 2.4.2 双向绑定 (TwoWay)
- ​数据流向​​:源 ↔ 目标
- ​​特点​​:源和目标任何一方变化都会影响另一方
- ​​适用场景​​:可编辑表单控件


```xml
<TextBox Text="{Binding UserName, Mode=TwoWay}"/>
```

#### 2.4.3  一次性绑定 (OneTime)
- 数据流向​​:源 → 目标(仅一次)
- ​​特点​​:只在初始化时绑定,后续变化不更新
- ​​适用场景​​:静态数据或性能敏感场景


```xml
<TextBlock Text="{Binding AppVersion, Mode=OneTime}"/>
```

#### 2.4.4 单向到源 (OneWayToSource)
- 数据流向​​:目标 → 源
- ​​特点​​:目标变化时更新源,但源变化不影响目标
- ​​适用场景​​:从UI收集数据


```xml
<Slider Value="{Binding Progress, Mode=OneWayToSource}"/>
```

### 2.5 绑定更新控制
#### 2.5.1 更新触发器 (UpdateSourceTrigger)
- PropertyChanged:属性变化时立即更新
- LostFocus:失去焦点时更新(TextBox 默认)
- Explicit:需手动调用 UpdateSource


```xml
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
```

#### 2.5.2  通知验证 (NotifyOnValidationError)
验证错误时触发事件
```xml
<TextBox Text="{Binding Age, NotifyOnValidationError=True}"/>
```
## 3.集合对象通知
项目结构:
![](https://www.diandiandidi.club:12346/api/note/getImage/15/0fb5c544-0e13-4ef4-904d-5ae3397255a6-.png)

viewModel:


```c#
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:


```c#
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:
```xml
<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>
```
![](https://www.diandiandidi.club:12346/api/note/getImage/15/51590843-043b-4453-8160-27a5e7788057-.png)


## 4.转换器
### 4.1 单值转换器
转换器


```c#
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


```c#
internal class TestViewModel:ViewModelBase
{
    private string _isChecked="Y";// Y或N
    public string IsChecked
    {
        get { return _isChecked; }
        set
        {
            _isChecked = value;
            OnPropertyChanged();
        }
    }
}
```

view

```xml

<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 多值转换器
动态颜色混合


```c#
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省略...
}
```

```xml
<Rectangle>
    <Rectangle.Fill>
        <MultiBinding Converter="{StaticResource ColorMixConverter}">
            <Binding Path="PrimaryColor"/>
            <Binding Path="SecondaryColor"/>
            <Binding Path="BlendWeight"/>
        </MultiBinding>
    </Rectangle.Fill>
</Rectangle>
```

## 5.数据校验
在数据绑定过程中验证数据的有效性

### 5.1 异常校验

```c#
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 实现...
}
```




```xml
<TextBox>
    <TextBox.Text>
        <Binding Path="Age" ValidatesOnExceptions="True" />
    </TextBox.Text>
</TextBox>
```

### 5.2 数据注解校验



```c#
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; }
}
```




```xml
<TextBox Text="{Binding UserName, ValidatesOnDataErrors=True}" />
```

### 5.3 自定义校验规则

```c#
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之间");
    }
}
```


```xml
<TextBox>
    <TextBox.Text>
        <Binding Path="Age">
            <Binding.ValidationRules>
                <local:AgeValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>
```

### 5.4 IDataErrorInfo 接口实现

```c#
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;
        }
    }
}
```




```xml
<TextBox Text="{Binding Name, ValidatesOnDataErrors=True}" />
```

### 5.5 INotifyDataErrorInfo 接口



```c#
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));
    }
}
```




```xml
<TextBox Text="{Binding Email, ValidatesOnNotifyDataErrors=True}" />
```

### 5.6 校验模板



```xml
<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管理可用状态
核心接口


```c#
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 使用内置命令

```xml
<!-- XAML中使用 -->
<Button Command="ApplicationCommands.Paste" Content="粘贴"/>

<!-- 自定义快捷键 -->
<Window.InputBindings>
    <KeyBinding Command="ApplicationCommands.New" Gesture="Ctrl+N"/>
</Window.InputBindings>
```

## 2.自定义命令实现
### 2.1 RelayCommand标准实现


```c#
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);
}
```

使用:

```c#
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 泛型版本实现



```c#
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;
    }
}
```



使用:


```c#
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 基本绑定方式

```xml
<!-- 无参数绑定 -->
<Button Command="{Binding SaveCommand}" Content="保存"/>

<!-- 带参数绑定 -->
<Button Command="{Binding DeleteCommand}" 
        CommandParameter="{Binding SelectedItem}"
        Content="删除选中项"/>
```

### 3.2 输入绑定

```xml
<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"

使用:

```xml
<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 创建
新建目录放置资源文件
![](https://www.diandiandidi.club:12346/api/note/getImage/16/6e925708-0306-4a32-b3d9-f1862c435928-.png)

右键新建资源字典
![](https://www.diandiandidi.club:12346/api/note/getImage/16/6ae738e2-319d-4e4b-b5ec-7b5f46c9fc99-.png)

### 1.2 使用
全局使用:

在App.xaml中添加


```xml
<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>
```


在某个视图中使用:




```xml
<Window.Resources>
        <ResourceDictionary>
            <converter:StringToBoolConverter x:Key="stringToBoolConverter"/>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Resources/Styles/TestWindowDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
```

## 2.样式的基本语法
### 2.1 样式定义

```xml
<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 样式应用

```xml
<Button Style="{StaticResource ButtonStyle}" Content="样式按钮"/>
```

## 3.样式的关键特性
### 3.1 隐式样式
不指定x:Key,自动应用于所有指定类型的控件


```xml
<Style TargetType="Button">
    <Setter Property="Background" Value="LightGreen"/>
</Style>
```

### 3.2 样式继承
使用BasedOn属性继承其他样式


```xml
<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 动态资源与静态资源

```xml
<!-- 静态资源(设计时解析) -->
<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 主题支持

```xml
<!-- 根据主题加载不同样式 -->
<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="Themes/{x:Static SystemParameters.HighContrast ? 
                                      'HighContrast.xaml' : 'Normal.xaml'}"/>
</ResourceDictionary.MergedDictionaries>
```

## 4.触发器(Triggers)
### 4.1 属性触发器

```xml
<Style TargetType="Button">
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Background" Value="Gold"/>
        </Trigger>
    </Style.Triggers>
</Style>
```

### 4.2 数据触发器

```xml
<Style TargetType="TextBox">
    <Style.Triggers>
        <DataTrigger Binding="{Binding IsValid}" Value="False">
            <Setter Property="Background" Value="Pink"/>
        </DataTrigger>
    </Style.Triggers>
</Style>
```

### 4.3 多条件触发器(MultiTrigger)

```xml
<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)

```xml
<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

```xml
<ControlTemplate x:Key="CustomButtonTemplate" TargetType="Button">
    <!-- 定义控件视觉结构 -->
    <Border Background="LightBlue" CornerRadius="10" Padding="10">
        <ContentPresenter /> <!-- 显示控件的内容(如 Button.Content) -->
    </Border>
</ControlTemplate>
```

#### 1.1.2 应用 ControlTemplate​

```xml
<!-- 方式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)


```xml
<ControlTemplate TargetType="Button">
    <Grid>
        <Ellipse Fill="Red" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>
```

#### 1.2.2 Triggers(触发器)​
用于定义交互状态(如鼠标悬停、按下时的样式变化)。


```xml
<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)。


```xml
<ControlTemplate TargetType="Button">
    <Border Background="{TemplateBinding Background}" 
            CornerRadius="{TemplateBinding Tag}"> <!-- 使用 Tag 传递圆角值 -->
        <ContentPresenter />
    </Border>
</ControlTemplate>
```

### 1.3 常见应用场景​
#### 1.3.1 自定义按钮样式​

```xml
<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>
```
![](https://www.diandiandidi.club:12346/api/note/getImage/16/7922ebcf-e609-4ec5-87bb-1edf280b2c97-.png)
#### 1.3.2 自定义 CheckBox(开关样式)​
    
```xml
<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>
```
![](https://www.diandiandidi.club:12346/api/note/getImage/16/343f5348-010c-4d0a-bc0e-4507230a0ad7-.png)

#### 1.3.3 自定义 ProgressBar​

```xml
 <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>
```
![](https://www.diandiandidi.club:12346/api/note/getImage/16/d6b763ee-dc80-4e9d-81ce-1c21e52a679d-.png)

## 2.DataTemplate 
DataTemplate 用于定义​​数据对象如何可视化显示​​,通常用于 ItemsControl(如 ListBox、ListView)或 ContentControl(如 ContentPresenter)。

核心特点​​

数据驱动 UI​​:自动将数据对象渲染为可视化元素。
支持数据绑定​​:可绑定到对象的属性(如 Person.Name)。
可复用​​:可定义为资源,供多个控件共享

### 2.1 基本语法​
#### 2.1.1 定义 DataTemplate​

```xml
<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​

```xml
<ListBox ItemsSource="{Binding Users}" ItemTemplate="{StaticResource ResourceKey=listBoxItem}"/>
```

### 2.2 关键组成部分​
#### 2.2.1 DataType 属性​
指定模板适用的数据类型(可省略,但推荐显式声明)

```xml
<DataTemplate DataType="{x:Type local:Product}">
    <TextBlock Text="{Binding Name}" />
</DataTemplate>
```

#### 2.2.2 数据绑定({Binding})​
绑定到数据对象的属性(如 {Binding Name})

```xml
<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 使用
水平列表


```xml
<ListBox>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <!-- 把默认的垂直排列改成水平排列 -->
            <StackPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    
    <ListBoxItem>项目1</ListBoxItem>
    <ListBoxItem>项目2</ListBoxItem>
    <ListBoxItem>项目3</ListBoxItem>
</ListBox>
```



网格布局


```xml
<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 绑定

```xml
<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 多级层级结构

```xml
<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:


```c#
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


```c#
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


```c#
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


```c#
<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>
```

效果:
![](https://www.diandiandidi.club:12346/api/note/getImage/16/c9f798e6-830d-4307-9c4c-7928c3ce4c30-.png)

## 5.HeaderedTemplate 
### 5.1 基本概念
HeaderedItemsControl 是 WPF 中一类特殊的控件,它们同时具有​​标题(Header)​​和​​内容项(Items)​​两部分。常见的派生控件包括:

- TreeViewItem
- MenuItem
- Expander
- GroupItem
- TabItem

### 5.2 核心模板类型
#### 5.2.1  HeaderedItemsControl 模板结构



```xml
<HeaderedItemsControl>
    <HeaderedItemsControl.Header>  <!-- 标题部分 -->
        <!-- 可以是任意内容 -->
    </HeaderedItemsControl.Header>
    
    <HeaderedItemsControl.Items>   <!-- 内容项部分 -->
        <!-- 子项集合 -->
    </HeaderedItemsControl.Items>
</HeaderedItemsControl>
```

#### 5.2.2 HeaderTemplate
用于定义如何显示标题部分的模板


```xml
<HeaderedItemsControl.HeaderTemplate>
    <DataTemplate>
        <!-- 标题的视觉呈现 -->
        <Border Background="LightBlue" Padding="5">
            <TextBlock Text="{Binding}" FontWeight="Bold"/>
        </Border>
    </DataTemplate>
</HeaderedItemsControl.HeaderTemplate>
```

#### 5.2.3 ItemTemplate
用于定义如何显示内容项的模板(与普通 ItemsControl 相同


```xml
<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进行安装,该框架已经停止维护。
![](https://www.diandiandidi.club:12346/api/note/getImage/28/f82e68af-fa27-4f50-89a7-2f4092f2bd41-.png)
**1.1.2 Model层**
```c#
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层**

```c#
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层**
```xml
<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 效果**
![](https://www.diandiandidi.club:12346/api/note/getImage/28/509a4002-4278-405f-a25b-e4158361b2c9-.png)

### 1.2 消息机制
有时候多个ViewModel之间需要传递消息进行交互,例如A窗口点击按钮更新B窗口数据。

```c#
// 发送消息
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默认是一个单例的
```c#
// 注册服务
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进行安装
![](https://www.diandiandidi.club:12346/api/note/getImage/28/3fe44d7c-20d9-4a5e-940d-2e2ad1e9ce53-.png)

**2.1.2 ViewModel层**
```c#
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层**

```xml
<StackPanel>
    <TextBox Text="{Binding Title}"/>
    <Button Content="打印标题" HorizontalAlignment="Left" Command="{Binding GetTitleCommand}"/>
</StackPanel>
```
![](https://www.diandiandidi.club:12346/api/note/getImage/28/282ca2db-fb08-412a-ba22-8219443c93ec-.png)

### 2.2 特性
特性在`.net framework`可能无法使用,`.net core`正常运行

```c#
// 如果需要继承其他类,请在这里添加该特性
// [INotifyPropertyChanged]
public partial class MainViewModel:ObservableObject
{
    /// <summary>
    /// 自动生成属性、通知
    /// </summary>
    [ObservableProperty]
    private string _title;

    [RelayCommand]
    private void Click()
    {
        MessageBox.Show(Title);
    }
}
```
```xml
 <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**
![](https://www.diandiandidi.club:12346/api/note/getImage/28/ba9db925-4543-4492-8c95-a495b78be6a1-.png)

**2.3.2 修改 App.xaml.cs**
```c#
 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**

```c#
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**
```c#
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = App.Current.ServiceProvider?.GetService<MainViewModel>();
    }
}
```

**2.3.5 View**
```xml
<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 弱引用机制**

发送端:
```c#
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();
    }
}
```
接收端:
```c#
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);
    }
}
```
记得在客户端卸载消息
```c#
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 响应消息**
消息通用类:
```c#
 public class MyMessage:RequestMessage<string>
 {
     public object? Data { get; set; }
 }
```

发送:
```c#
 MyMessage myMessage = new()
 {
     Data = Name,
 };
 var result = WeakReferenceMessenger.Default
     .Send(new ValueChangedMessage<MyMessage>(myMessage), "name_token");
 Console.WriteLine(result.Value.Response);
```

接收:
```c#
 Messenger.Register<ClientViewModel,ValueChangedMessage<MyMessage>,string>
     (this, "name_token", (vm, msg) =>
     {
         Name = msg.Value.Data.ToString();

         msg.Value.Reply("ok");
     });
```















© 2026 XyzRgb.space · 本页内容为个人学习记录,如有错漏欢迎指正。