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

相关推荐
聆风吟º2 小时前
CANN hccl 深度解析:异构计算集群通信库的跨节点通信与资源管控实现逻辑
人工智能·wpf·transformer·cann
无心水10 小时前
分布式定时任务与SELECT FOR UPDATE:从致命陷阱到优雅解决方案(实战案例+架构演进)
服务器·人工智能·分布式·后端·spring·架构·wpf
LZL_SQ12 小时前
HCCL测试框架中AllReduce边界条件测试设计深度剖析
wpf·cann
User_芊芊君子1 天前
【分布式训练】CANN SHMEM跨设备内存通信库:构建高效多机多卡训练的关键组件
分布式·深度学习·神经网络·wpf
就是有点傻2 天前
WPF按钮走马灯效果
wpf
zuozewei2 天前
虚拟电厂聚合商平台安全技术体系深度解读
安全·wpf
极客智造2 天前
WPF 自定义控件:AutoGrid 实现灵活自动布局的网格控件
wpf
极客智造2 天前
WPF Grid 布局高效扩展:GridHelpers 附加属性工具类全解析
wpf
张人玉2 天前
WPF 多语言实现完整笔记(.NET 4.7.2)
笔记·.net·wpf·多语言实现·多语言适配
暖馒3 天前
深度剖析串口通讯(232/485)
开发语言·c#·wpf·智能硬件