020.WPF MVVM数据绑定底层原理类封装

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 的实现而创建的。为了使其能够工作,您需要引入以下两个关键命名空间:

  1. System.ComponentModel : 包含 INotifyPropertyChanged 接口和 PropertyChangedEventArgs 类。
  2. 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

相关推荐
黑夜中的潜行者5 小时前
构建高性能 WPF 大图浏览器:TiledViewer 技术解密
性能优化·c#·.net·wpf·图形渲染
yy7634966689 小时前
WPF样式入门:5分钟学会自定义Button样式
wpf
她说彩礼65万9 小时前
WPF路由事件作用
wpf
LcVong9 小时前
WPF DataGrid 全属性详解(分类整理+实用说明)
wpf
Greyscarf10 小时前
WPF使用MxDraw云图插件入门
wpf·mxdraw云图·mxdraw
执笔论英雄1 天前
【大模型推理】VLLM 引擎使用
wpf·vllm
LateFrames1 天前
动画性能比对:WPF / WinUI3 / WebView2
wpf·webview·用户体验·winui3
阿湯哥2 天前
多智能体架构深度解析:企业落地如何选择Skills与SubAgents?
架构·wpf
源之缘-OFD先行者2 天前
自研 WPF 鸟情图表:性能与灵活的双重突破
wpf