1. MVVM 绑定核心概念
A. DataContext(数据上下文)
DataContext 是实现绑定的桥梁 。View 需要知道去哪里寻找它要绑定的属性。当您将 ViewModel 的实例设置为 View 的 DataContext 时,该 View 及其所有子控件都将能够访问 ViewModel 中的公共属性。
B. Binding Path(绑定路径)
绑定路径指定了 DataContext 中您想要连接的特定属性。
cs
<TextBox Text="{Binding UserName}" />
<!-- UserName 就是绑定路径 (Path) -->
C. INotifyPropertyChanged 接口
这是实现数据响应式的关键。如果 ViewModel 中的属性值发生变化,View 必须知道并更新显示。
ViewModel 中的任何属性,如果需要通知 View 它的值已更改,那么 ViewModel 类必须实现 INotifyPropertyChanged 接口,并在属性的 Setter 中触发 PropertyChanged 事件。
D. Binding Mode(绑定模式)
绑定模式决定了数据流动的方向。
| 模式 | 描述 | 常见用途 |
|---|---|---|
| OneWay | 数据从 Source (ViewModel) 流向 Target (View)。ViewModel 改变,View 更新。 | 标签 (TextBlock)、只读显示。 |
| TwoWay | 数据双向流动。ViewModel 改变,View 更新;View 改变(如用户输入),ViewModel 也更新。 | 文本框 (TextBox)、复选框 (CheckBox)。 |
| OneTime | 数据只在初始化时从 Source 流向 Target 一次。之后不再更新。 | 静态或不常变动的数据。 |
| OneWayToSource | 数据从 Target (View) 流向 Source (ViewModel)。 | 较少使用,通常用于需要反向更新的场景。 |
将所有数据和逻辑都塞进一个 ViewModel 违背了单一职责原则 (Single Responsibility Principle, SRP),会导致代码臃肿、难以维护和测试。
解决这个问题的标准 MVVM 模式是使用ViewModel 组合 (ViewModel Composition) ,即嵌套 ViewModel。
解决方案:ViewModel 组合 (Nested ViewModels)
与其让一个巨大的 MainViewModel 包含所有属性,不如将它分解成多个小的、功能专一的 ViewModel,然后让 MainViewModel 引用这些子 ViewModel 的实例。
1.创建数据模型类Screw
数据模型类继承ObservableObject, ObservableObject是广播通知基类,在许多现有的mvvm高级绑定包里,只要将类继承ObservableObject一般都会自动识别为通知广播类,自动引用需要的包和命名空间,但有些时候,我们的项目可能需要用到一些旧框架,例如,我现在在写的项目,它是西门子ug12.0软件的二次开发插件,ug12.0的openg api基架是用net.Frameworw 4.8,许多高级包并不能兼容,这个时候我就需要自己写ObservableObject类来实现这些功能了,其实数据类也可以直接继承INotifyPropertyChanged接口类来工作,但我们是为了模拟像高级包那样进行数据绑定,让代码简洁,所以还是要整一些花活
cs
public class Screw : ObservableObject
{
private string _street ="ok";
public string Street
{
get => _street;
set
{
_street = value;
OnPropertyChanged();
}
}
private string _city="揭阳";
public string City
{
get => _city;
set
{
_city = value;
OnPropertyChanged();
}
}
}
2. 创建主控制器类 ViewModel (MainViewModel)
负责将子 ViewModel 组合在一起。
主控制器类也要继承ObservableObject,面向对象去使用所需的数据类,可以添加很多,这样,我们的项目就可以通过一个主控制器类去管理一大堆数据类,代码会简洁易读
UserName属性不是必须的,只是为了演示,ui界面可同时绑定主控制器属性及其他子数据类
cs
public class MainViewModel : ObservableObject
{
// 实例化子 ViewModel
public Screw AddressInfo { get; } = new Screw(); //new 了一个我们需要加进来的数据类
private string _userName ="cjp";
public string UserName
{
get => _userName;
set
{
_userName = value;
OnPropertyChanged();
}
}
// 假设还有其他子 ViewModel,例如 OrderViewModel, SettingsViewModel 等
// public OrderViewModel CurrentOrder { get; } = new OrderViewModel();
}
3. XAML 绑定实现示例
在 View 中,您通过点号 (.) 来导航到子 ViewModel 的属性。
XML
<Grid Grid.Row="0" Grid.Column="0" Grid.RowSpan="2">
<TextBox HorizontalAlignment="Left" Margin="52,46,0,0" TextWrapping="Wrap" Text="{Binding UserName, Mode=TwoWay}" VerticalAlignment="Top" Width="120" />
<TextBox HorizontalAlignment="Left" Margin="52,100,0,0" TextWrapping="Wrap" Text="{Binding AddressInfo.Street, Mode=TwoWay}" VerticalAlignment="Top" Width="120" />
<TextBox HorizontalAlignment="Left" Margin="52,150,0,0" TextWrapping="Wrap" Text="{Binding AddressInfo.City, Mode=TwoWay}" VerticalAlignment="Top" Width="120" />
</Grid>
三.封装ObservableObject类(简易功能版)
复杂的功能可以根据需要进一步封装,这里只封装两个常用的通知接口
对于 ObservableObject 这样的基类,它通常是为了简化 INotifyPropertyChanged 的实现而创建的。为了使其能够工作,您需要引入以下两个关键命名空间:
System.ComponentModel: 包含INotifyPropertyChanged接口和PropertyChangedEventArgs类。System.Runtime.CompilerServices: 包含CallerMemberName属性,用于简化属性名称的传递。
cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;//必要的引入
using System.Runtime.CompilerServices; // 引入此命名空间以使用 [CallerMemberName]
namespace NX_Openg
{
public class ObservableObject : INotifyPropertyChanged
{
// 必须实现的事件
public event PropertyChangedEventHandler PropertyChanged;
// 触发 PropertyChanged 事件。
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// 设置属性值,并在值发生变化时自动触发 PropertyChanged 事件。
// <returns>如果值发生变化并触发了通知,则返回 true;否则返回 false。</returns>
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
storage = value;
OnPropertyChanged(propertyName);
return true;
}
}
}
使用上述 ObservableObject 基类后,您的 ViewModel 代码会变得更简洁:
可以修改成下面这样
cs
// 继承自 ObservableObject
public class MainViewModel : ObservableObject
{
private string _userName;
// 使用 SetProperty 辅助方法
public string UserName
{
get => _userName;
set => SetProperty(ref _userName, value);
}
// ... 其他属性和逻辑
}
4.数据源绑定
在主窗体.cs后台文件将主控制器绑定到数据源上,
cs
public partial class HomePage : Window
{
public HomePage()
{
InitializeComponent();
this.DataContext = new MainViewModel();//数据绑定
}
}
5.运行测试:
成功绑定到ui
