WPF之INotifyPropertyChanged实现

文章目录

    • 引言
    • INotifyPropertyChanged接口基础
      • 接口定义
      • 工作原理
    • 基本实现方式
      • 标准实现示例
      • CallerMemberName特性
    • 高级实现技术
      • 基类实现
      • 通知多个属性变化
      • 使用PropertyChanging事件
    • MVVM框架中的实现
      • MVVM模式简介
      • MVVM框架中的实现
        • Prism框架
        • MVVM Light框架
        • 自定义MVVM基类
    • 性能优化技术
      • 避免不必要的通知
      • 使用"空字符串"通知所有属性变化
      • 使用WeakEventManager
      • 条件编译
      • 批量更新模式
    • 实际应用示例
      • WPF数据绑定示例
      • MVVM模式示例
    • 调试INotifyPropertyChanged
      • 诊断绑定错误
      • 常见问题与解决方案
    • INotifyPropertyChanged与依赖属性比较
      • 何时使用INotifyPropertyChanged
      • 何时使用依赖属性
    • 总结与最佳实践
      • 总结
      • 最佳实践
    • 学习资源

可以根据Github拉取示例程序运行
GitHub程序演示地址(点击直达)
也可以在本文资源中下载
在这里插入图片描述

引言

在WPF应用程序开发中,数据绑定是最核心的特性之一,它使视图层(UI)和数据层能够自动同步。而INotifyPropertyChanged接口则是实现这种同步机制的基础,它允许对象在属性值变化时通知UI进行更新。本文将深入探讨INotifyPropertyChanged接口的实现与应用,帮助开发者构建响应式的WPF应用程序。

属性变化
触发事件
通知
更新
数据对象
INotifyPropertyChanged
PropertyChanged
绑定系统
UI元素

INotifyPropertyChanged接口基础

接口定义

INotifyPropertyChanged接口是.NET Framework中System.ComponentModel命名空间下的一个简单接口,它只包含一个事件:

// INotifyPropertyChanged接口定义
namespace System.ComponentModel
{// 该接口用于通知客户端属性值已更改public interface INotifyPropertyChanged{// 当属性值更改时发生event PropertyChangedEventHandler PropertyChanged;}// 属性更改事件的委托public delegate void PropertyChangedEventHandler(object sender,         // 发送通知的对象PropertyChangedEventArgs e  // 包含更改的属性名称);
}

工作原理

当一个类实现了INotifyPropertyChanged接口,并在属性值发生变化时触发PropertyChanged事件,WPF的绑定系统就能够捕获这个事件,并自动更新绑定到该属性的UI元素。

UI BindingSystem DataObject 属性值变化 触发PropertyChanged事件 检查受影响的绑定 更新绑定的UI元素 重新渲染 UI BindingSystem DataObject

基本实现方式

标准实现示例

下面是一个实现INotifyPropertyChanged接口的基本示例:

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;namespace WpfDemo
{// 实现INotifyPropertyChanged接口以支持WPF数据绑定public class Person : INotifyPropertyChanged{// INotifyPropertyChanged接口的事件public event PropertyChangedEventHandler PropertyChanged;// 私有字段private string _name;private int _age;// 姓名属性public string Name{get => _name;set{// 检查值是否变化if (_name != value){// 设置新值_name = value;// 通知UI该属性已变化OnPropertyChanged();}}}// 年龄属性public int Age{get => _age;set{if (_age != value){_age = value;OnPropertyChanged();}}}// 属性变化通知方法protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){// 调用事件处理器PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}
}

CallerMemberName特性

在上述示例中,我们使用了CallerMemberName特性,这是C# 5.0引入的一个非常有用的特性,它可以自动获取调用方法的成员名称,无需手动指定属性名称字符串,减少了潜在的错误。

// 不使用CallerMemberName时的写法
protected virtual void OnPropertyChanged(string propertyName)
{PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}// 调用时需要显式指定属性名称
OnPropertyChanged("Name");// 使用CallerMemberName后的简化调用
OnPropertyChanged(); // 编译器会自动传入调用该方法的属性名称

高级实现技术

基类实现

在实际开发中,我们通常会创建一个实现了INotifyPropertyChanged接口的基类,然后让其他模型类继承这个基类:

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;namespace WpfDemo
{// 实现INotifyPropertyChanged的基类public abstract class NotifyPropertyBase : INotifyPropertyChanged{// INotifyPropertyChanged接口事件public event PropertyChangedEventHandler PropertyChanged;// 属性变化通知方法protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}// 设置属性值并通知变化的辅助方法protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null){// 如果值没有变化,返回falseif (EqualityComparer<T>.Default.Equals(field, value))return false;// 设置新值field = value;// 通知属性变化OnPropertyChanged(propertyName);return true;}}// 使用基类的示例public class Employee : NotifyPropertyBase{private string _name;private double _salary;public string Name{get => _name;set => SetProperty(ref _name, value);}public double Salary{get => _salary;set => SetProperty(ref _salary, value);}}
}

通知多个属性变化

有时候,更改一个属性可能会影响其他属性的值,这种情况下我们需要通知多个属性的变化:

using System;public class Circle : NotifyPropertyBase
{private double _radius;// 半径属性public double Radius{get => _radius;set{if (SetProperty(ref _radius, value)){// 半径变化后,面积和周长也会变化,需要同时通知OnPropertyChanged(nameof(Area));OnPropertyChanged(nameof(Perimeter));}}}// 面积属性(只读,依赖于半径)public double Area => Math.PI * _radius * _radius;// 周长属性(只读,依赖于半径)public double Perimeter => 2 * Math.PI * _radius;
}

使用PropertyChanging事件

除了PropertyChanged事件外,.NET还提供了PropertyChanging事件,它在属性值即将变化之前触发,可以用于在属性值变化前执行某些操作:

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;public class AdvancedNotifyPropertyBase : INotifyPropertyChanged, INotifyPropertyChanging
{// 属性变化前事件public event PropertyChangingEventHandler PropertyChanging;// 属性变化后事件public event PropertyChangedEventHandler PropertyChanged;// 属性变化前通知方法protected virtual void OnPropertyChanging([CallerMemberName] string propertyName = null){PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));}// 属性变化后通知方法protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}// 设置属性值并通知变化的辅助方法(包含变化前和变化后通知)protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null){// 如果值没有变化,返回falseif (EqualityComparer<T>.Default.Equals(field, value))return false;// 通知属性即将变化OnPropertyChanging(propertyName);// 设置新值field = value;// 通知属性已经变化OnPropertyChanged(propertyName);return true;}
}

MVVM框架中的实现

MVVM模式简介

MVVM (Model-View-ViewModel) 是WPF应用程序中常用的设计模式,它将应用程序分为三个主要部分:

  1. Model:表示业务数据和逻辑
  2. View:表示用户界面
  3. ViewModel:作为View和Model之间的中介

在MVVM模式中,ViewModel几乎总是需要实现INotifyPropertyChanged接口,以便在数据变化时通知View更新。

MVVM框架中的实现

许多MVVM框架(如Prism、MVVM Light、Caliburn.Micro等)都提供了INotifyPropertyChanged的实现基类,使开发者能够更容易地创建符合MVVM模式的应用程序。以下是一些流行MVVM框架中的实现示例:

Prism框架
using Prism.Mvvm;// Prism框架提供的BindableBase基类已实现INotifyPropertyChanged
public class UserViewModel : BindableBase
{private string _username;private string _email;public string Username{get => _username;set => SetProperty(ref _username, value);}public string Email{get => _email;set => SetProperty(ref _email, value);}
}
MVVM Light框架
using GalaSoft.MvvmLight;// MVVM Light提供的ViewModelBase基类
public class ProductViewModel : ViewModelBase
{private string _productName;private decimal _price;public string ProductName{get => _productName;set => Set(ref _productName, value);}public decimal Price{get => _price;set => Set(ref _price, value);}
}
自定义MVVM基类

在没有使用MVVM框架的情况下,我们也可以创建自己的ViewModel基类:

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;namespace CustomMvvm
{// 自定义ViewModel基类public abstract class ViewModelBase : INotifyPropertyChanged{// INotifyPropertyChanged接口实现public event PropertyChangedEventHandler PropertyChanged;// 属性变化通知protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}// 设置属性值protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null){if (EqualityComparer<T>.Default.Equals(storage, value))return false;storage = value;OnPropertyChanged(propertyName);return true;}// 通知多个属性变化protected void OnPropertiesChanged(params string[] propertyNames){if (propertyNames == null)return;foreach (var name in propertyNames){OnPropertyChanged(name);}}}// 使用自定义ViewModel基类的示例public class CustomerViewModel : ViewModelBase{private string _firstName;private string _lastName;private string _fullName;public string FirstName{get => _firstName;set{if (SetProperty(ref _firstName, value)){UpdateFullName();}}}public string LastName{get => _lastName;set{if (SetProperty(ref _lastName, value)){UpdateFullName();}}}public string FullName{get => _fullName;private set => SetProperty(ref _fullName, value);}// 更新全名private void UpdateFullName(){FullName = $"{FirstName} {LastName}";}}
}

性能优化技术

避免不必要的通知

在实现INotifyPropertyChanged时,应避免不必要的通知,只有在属性值确实发生变化时才触发PropertyChanged事件:

public string Name
{get => _name;set{// 检查值是否变化,避免不必要的通知if (_name != value){_name = value;OnPropertyChanged();}}
}

使用"空字符串"通知所有属性变化

在某些情况下,我们可能需要通知对象的所有属性都已变化,这时可以使用空字符串作为属性名:

// 通知所有属性已变化
public void RefreshAllProperties()
{// 传递空字符串表示所有属性都已变化OnPropertyChanged(string.Empty);
}

不过,这种方法会导致所有绑定都重新计算,可能会影响性能,应谨慎使用。

使用WeakEventManager

在某些情况下,PropertyChanged事件可能会导致内存泄漏,特别是当订阅者比发布者生命周期更长时。使用WeakEventManager可以避免这个问题:

using System.ComponentModel;
using System.Windows;public class WeakNotifyPropertyChanged : INotifyPropertyChanged
{// 显式实现事件,防止外部直接订阅event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged{add => WeakEventManager<WeakNotifyPropertyChanged, PropertyChangedEventArgs>.AddHandler(this, "PropertyChanged", value);remove => WeakEventManager<WeakNotifyPropertyChanged, PropertyChangedEventArgs>.RemoveHandler(this, "PropertyChanged", value);}// 触发事件protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){WeakEventManager<WeakNotifyPropertyChanged, PropertyChangedEventArgs>.DeliverEvent(this, "PropertyChanged", new PropertyChangedEventArgs(propertyName));}
}

条件编译

在调试时启用更多验证,但在发布版本中禁用,可以提高性能:

protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{#if DEBUG// 在调试版本中进行额外验证if (propertyName == null)throw new ArgumentNullException(nameof(propertyName));#endifif (EqualityComparer<T>.Default.Equals(field, value))return false;field = value;OnPropertyChanged(propertyName);return true;
}

批量更新模式

当需要更新多个属性但只想发送一个通知时,可以使用批量更新模式:

public class BatchNotifyModel : INotifyPropertyChanged
{public event PropertyChangedEventHandler PropertyChanged;private bool _isBatchUpdate = false;private HashSet<string> _pendingNotifications = new HashSet<string>();// 开始批量更新public void BeginUpdate(){_isBatchUpdate = true;_pendingNotifications.Clear();}// 结束批量更新并发送通知public void EndUpdate(){_isBatchUpdate = false;// 发送所有待处理的通知foreach (var propertyName in _pendingNotifications){OnPropertyChanged(propertyName);}_pendingNotifications.Clear();}// 属性变化通知protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){if (_isBatchUpdate){// 批量更新模式下,只记录属性名,不立即通知_pendingNotifications.Add(propertyName);}else{// 正常模式下,立即通知PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}// 使用示例public void UpdateCustomer(string firstName, string lastName, string email){BeginUpdate();FirstName = firstName;LastName = lastName;Email = email;EndUpdate();}// 属性定义省略...
}

实际应用示例

WPF数据绑定示例

下面是一个使用INotifyPropertyChanged接口实现数据绑定的完整示例:

// Model类定义
using System.ComponentModel;
using System.Runtime.CompilerServices;namespace WpfBindingDemo
{// 用户模型类public class User : INotifyPropertyChanged{private string _username;private string _email;private int _age;public event PropertyChangedEventHandler PropertyChanged;public string Username{get => _username;set{if (_username != value){_username = value;OnPropertyChanged();}}}public string Email{get => _email;set{if (_email != value){_email = value;OnPropertyChanged();}}}public int Age{get => _age;set{if (_age != value){_age = value;OnPropertyChanged();}}}protected void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}
}// MainWindow.xaml.cs
using System.Windows;namespace WpfBindingDemo
{public partial class MainWindow : Window{// 创建一个用户对象作为DataContextprivate User _currentUser = new User{Username = "张三",Email = "zhangsan@example.com",Age = 25};public MainWindow(){InitializeComponent();// 设置DataContextthis.DataContext = _currentUser;}// 更新按钮点击事件处理private void UpdateButton_Click(object sender, RoutedEventArgs e){// 更新用户属性 - 由于实现了INotifyPropertyChanged,UI会自动更新_currentUser.Username = usernameTextBox.Text;_currentUser.Email = emailTextBox.Text;_currentUser.Age = int.Parse(ageTextBox.Text);MessageBox.Show("用户信息已更新!");}}
}

对应的XAML代码:

<Window x:Class="WpfBindingDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="INotifyPropertyChanged示例" Height="350" Width="500"><Grid Margin="20"><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions><!-- 标题 --><TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"Text="用户信息编辑" FontSize="20" FontWeight="Bold"Margin="0,0,0,20"/><!-- 用户名 --><TextBlock Grid.Row="1" Grid.Column="0" Text="用户名:" Margin="0,5,10,5"VerticalAlignment="Center"/><TextBox x:Name="usernameTextBox" Grid.Row="1" Grid.Column="1" Margin="0,5"Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}"/><!-- 邮箱 --><TextBlock Grid.Row="2" Grid.Column="0" Text="邮箱:" Margin="0,5,10,5"VerticalAlignment="Center"/><TextBox x:Name="emailTextBox" Grid.Row="2" Grid.Column="1" Margin="0,5"Text="{Binding Email, UpdateSourceTrigger=PropertyChanged}"/><!-- 年龄 --><TextBlock Grid.Row="3" Grid.Column="0" Text="年龄:" Margin="0,5,10,5"VerticalAlignment="Center"/><TextBox x:Name="ageTextBox" Grid.Row="3" Grid.Column="1" Margin="0,5"Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}"/><!-- 更新按钮 --><Button Grid.Row="4" Grid.Column="1" Content="更新信息"Click="UpdateButton_Click" HorizontalAlignment="Left"Margin="0,15,0,0" Padding="10,5"/><!-- 信息预览 --><Border Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2"BorderBrush="LightGray" BorderThickness="1" Margin="0,20,0,0"Padding="10"><StackPanel><TextBlock Text="实时预览:" FontWeight="Bold" Margin="0,0,0,10"/><TextBlock><Run Text="用户名: "/><Run Text="{Binding Username, Mode=OneWay}" FontWeight="Bold"/></TextBlock><TextBlock><Run Text="邮箱: "/><Run Text="{Binding Email, Mode=OneWay}" FontWeight="Bold"/></TextBlock><TextBlock><Run Text="年龄: "/><Run Text="{Binding Age, Mode=OneWay}" FontWeight="Bold"/></TextBlock></StackPanel></Border></Grid>
</Window>

MVVM模式示例

下面是一个使用MVVM模式的示例,展示了INotifyPropertyChanged在ViewModel中的应用:

// ViewModelBase.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;namespace MvvmDemo
{// ViewModel基类public abstract class ViewModelBase : INotifyPropertyChanged{public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null){if (EqualityComparer<T>.Default.Equals(field, value))return false;field = value;OnPropertyChanged(propertyName);return true;}}
}// PersonViewModel.cs
using System.Windows.Input;namespace MvvmDemo
{// 人员ViewModelpublic class PersonViewModel : ViewModelBase{private string _firstName;private string _lastName;private string _fullName;private int _age;public string FirstName{get => _firstName;set{if (SetProperty(ref _firstName, value)){UpdateFullName();}}}public string LastName{get => _lastName;set{if (SetProperty(ref _lastName, value)){UpdateFullName();}}}public string FullName{get => _fullName;private set => SetProperty(ref _fullName, value);}public int Age{get => _age;set => SetProperty(ref _age, value);}// 更新全名private void UpdateFullName(){FullName = $"{FirstName} {LastName}";}// 构造函数public PersonViewModel(){// 初始化属性FirstName = "张";LastName = "三";Age = 30;}}
}// MainWindow.xaml.cs
using System.Windows;namespace MvvmDemo
{public partial class MainWindow : Window{public MainWindow(){InitializeComponent();// 设置DataContext为ViewModelthis.DataContext = new PersonViewModel();}}
}

对应的XAML:

<Window x:Class="MvvmDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="MVVM示例" Height="350" Width="500"><Grid Margin="20"><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions><!-- 标题 --><TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"Text="人员信息" FontSize="20" FontWeight="Bold"Margin="0,0,0,20"/><!-- 姓氏 --><TextBlock Grid.Row="1" Grid.Column="0" Text="姓氏:" Margin="0,5,10,5"VerticalAlignment="Center"/><TextBox Grid.Row="1" Grid.Column="1" Margin="0,5"Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}"/><!-- 名字 --><TextBlock Grid.Row="2" Grid.Column="0" Text="名字:" Margin="0,5,10,5"VerticalAlignment="Center"/><TextBox Grid.Row="2" Grid.Column="1" Margin="0,5"Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}"/><!-- 年龄 --><TextBlock Grid.Row="3" Grid.Column="0" Text="年龄:" Margin="0,5,10,5"VerticalAlignment="Center"/><TextBox Grid.Row="3" Grid.Column="1" Margin="0,5"Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}"/><!-- 信息预览 --><Border Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"BorderBrush="LightGray" BorderThickness="1" Margin="0,20,0,0"Padding="10"><StackPanel><TextBlock Text="人员信息预览:" FontWeight="Bold" Margin="0,0,0,10"/><TextBlock><Run Text="全名: "/><Run Text="{Binding FullName, Mode=OneWay}" FontWeight="Bold"/></TextBlock><TextBlock><Run Text="年龄: "/><Run Text="{Binding Age, Mode=OneWay}" FontWeight="Bold"/></TextBlock></StackPanel></Border></Grid>
</Window>

调试INotifyPropertyChanged

诊断绑定错误

当数据绑定不工作时,通常是因为PropertyChanged事件没有正确触发。可以通过以下方法诊断:

  1. 使用DebuggerBreakpoints在OnPropertyChanged方法处设置断点
  2. 使用PresentationTraceSources.TraceLevel附加属性设置绑定跟踪级别
  3. 使用Snoop或其他WPF调试工具检查绑定
<!-- 设置绑定跟踪级别 -->
<TextBox Text="{Binding Name, PresentationTraceSources.TraceLevel=High}"/>

常见问题与解决方案

  1. 属性值更改但UI不更新

    • 检查是否正确实现了INotifyPropertyChanged接口
    • 确保在属性setter中调用了OnPropertyChanged方法
    • 确认属性名称拼写正确
  2. 绑定路径错误

    • 检查绑定表达式是否正确
    • 确认DataContext设置正确
  3. PropertyChanged事件触发但UI不更新

    • 检查绑定模式(Mode=OneWay或TwoWay)
    • 确认UI线程访问是否正确(跨线程问题)
  4. 命名问题

    • 使用nameof操作符避免硬编码字符串
    • 确保属性名与OnPropertyChanged中的参数匹配

INotifyPropertyChanged与依赖属性比较

WPF提供了两种主要的属性变更通知机制:INotifyPropertyChanged接口和依赖属性系统。以下是它们的比较:

属性变更通知
INotifyPropertyChanged
依赖属性
适用于POCO类
轻量级
手动实现通知
WPF控件使用
自动变更通知
支持附加属性

何时使用INotifyPropertyChanged

  • 数据模型类和ViewModel类
  • 不需要依赖属性其他功能(如值继承、动画等)的场景
  • 希望保持类的简单,不依赖于WPF框架

何时使用依赖属性

  • 创建自定义WPF控件
  • 需要支持样式、模板、动画等WPF功能
  • 需要属性值继承的场景

总结与最佳实践

总结

INotifyPropertyChanged接口是WPF数据绑定的核心机制之一,它允许对象在属性值变化时通知UI进行更新。通过正确实现这个接口,我们可以创建响应式的应用程序,使UI与数据保持同步。

最佳实践

  1. 创建基类:创建一个实现INotifyPropertyChanged的基类,以便在多个模型类中重用代码

  2. 使用CallerMemberName:利用CallerMemberName特性自动获取属性名称,避免硬编码字符串

  3. 避免不必要的通知:只在属性值真正变化时才触发PropertyChanged事件

  4. 使用SetProperty辅助方法:简化属性setter代码,并确保一致性

  5. 正确处理关联属性:当一个属性变化影响其他属性时,确保通知所有受影响的属性

  6. 考虑性能:对于频繁变化的属性,考虑使用批量更新模式,减少通知次数

  7. 测试属性通知:编写单元测试确保PropertyChanged事件正确触发

  8. 使用工具简化实现:考虑使用Fody.PropertyChanged等工具自动注入PropertyChanged通知代码

学习资源

  1. Microsoft Docs - INotifyPropertyChanged接口

  2. Microsoft Docs - 数据绑定概述

  3. CodeProject - WPF中的高级数据绑定

  4. GitHub - WPF样例集合

  5. Fody.PropertyChanged - 自动注入属性变更通知

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/80995.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MCP教程系列】SpringBoot 搭建基于 Spring AI 的 SSE 模式 MCP 服务

原文地址&#xff1a;https://developer.aliyun.com/article/1662946 在当今快速发展的AI技术背景下&#xff0c;如何高效地集成模型能力成为开发者关注的重点。本文将手把手教你如何基于 Spring AI 搭建支持 SSE&#xff08;Server-Sent Events&#xff09;模式的 MCP 服务 相…

springboot集成langchain4j实现票务助手实战

前言 看此篇的前置知识为langchain4j整合springboot&#xff0c;以及springboot集成langchain4j记忆对话。 Function-Calls介绍 langchain4j 中的 Function Calls&#xff08;函数调用&#xff09;是一种让大语言模型&#xff08;LLM&#xff09;与外部工具&#xff08;如 A…

MySQL-数据库分布式XA事务

准备 innodb存储引擎开启支持分布式事务 set global innodb_support_axonMySQL数据库XA事务的SQL语法如下&#xff1a; XA {START| BEGIN} xid {JOIN | RESUME} XA END xid {SUSPEND [ FOR MIGRATE]} XA PREPARE xid XA COMMIT xid [ONE PHASE] XA ROLLBACK xid XA RECOVER 完…

SAP 运维-冷门问题解决办法

1.SAP Fiori帮助菜单链接如何配置&#xff1f; 答&#xff1a; 执行事务代码HELP_CONFIG&#xff0c;选择对应的Fiori部署模式&#xff0c;配置帮助菜单下的URL链接。 检查配置的帮助菜单&#xff0c;执行事务代码/N//UI2/FLP_CUS_CONF 或者SR13进行查看配置状态与修改。

新型智慧园区技术架构深度解析:数字孪生与零碳科技的融合实践

&#x1f3ed;在杭州亚运村零碳园区&#xff0c;光伏板与氢燃料大巴构成的能源网络&#xff0c;正通过数字孪生技术实现智能调度。这不仅是格力电器与龙源电力在新能源领域的创新实践&#xff0c;更是智慧园区4.0时代的标杆案例。当AI算法开始接管能源调度&#xff0c;当BIM建模…

Java转Go日记(三十六):简单的分布式

1.1.1. 简单的分布式server 目前分布式系统已经很流行了&#xff0c;一些开源框架也被广泛应用&#xff0c;如dubbo、Motan等。对于一个分布式服务&#xff0c;最基本的一项功能就是服务的注册和发现&#xff0c;而利用zk的EPHEMERAL节点则可以很方便的实现该功能。EPHEMERAL节…

机器学习笔记——特征工程

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本笔记介绍机器学习中常见的特征工程方法、正则化方法和简要介绍强化学习。 文章目录 特征工程&#xff08;Fzeature Engineering&#xff09;1. 特征提取&#xff…

在 Ubuntu 20.04.6 LTS 中将 SCons 从 3.1.2 升级到 4.9.1

在 Ubuntu 20.04.6 LTS 中将 SCons 从 3.1.2 升级到 4.9.1&#xff0c;可以通过以下步骤完成&#xff1a; 方法 1&#xff1a;使用 pip 安装&#xff08;推荐&#xff09; 步骤 1&#xff1a;卸载旧版本 SCons # 如果通过 apt 安装的旧版本&#xff0c;先卸载 sudo apt remov…

LeetCode热题100--234.回文链表--简单

1. 题目 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xf…

【markdown】介绍如何在markdown中绘制流程图

在 Markdown 中编写流程图主要通过 ​​Mermaid 语法​​实现&#xff08;多数平台如 GitHub、VS Code、Typora 已原生支持&#xff09;。以下是详细方法&#xff1a; 1. 基础流程图​​ 语法结构 用 mermaid 包裹代码块&#xff0c;指定方向后定义节点和连接线&#xff1a…

Java中使用自定义序列化器:自动添加View字段的实现与应用

Java 中 BigDecimal 序列化器:自动添加 View 返回字段的实现与应用 在 Java 开发过程中,数据的序列化与反序列化是非常重要的环节。当我们处理数值类型数据,特别是BigDecimal类型时,有时需要在序列化输出中添加额外的视图字段,以满足前端展示或者特定业务需求。本文将通过…

Java类一文分解:JavaBean,工具类,测试类的深度剖析

解锁Java类的神秘面纱&#xff1a;从JavaBean到测试类的深度剖析 前言一、JavaBean 类&#xff1a;数据的守护者&#xff08;一&#xff09;JavaBean 类是什么&#xff08;二&#xff09;JavaBean 类的特征&#xff08;三&#xff09;JavaBean 类的使用场景&#xff08;四&…

机器学习-- 线性回归、逻辑回归

线性回归 线性回归是一种统计方法,用于发现变量之间的关系。在机器学习背景下,线性回归可找出特征(Feature)与标签(Lable)之间的关系。 例如,假设我们想要根据汽车的重量预测汽车的每加仑汽油行驶里程(mpg),并且我们有以下数据集: 线性回归方程 Linear regressi…

Lua再学习

因为实习的项目用到了Lua&#xff0c;所以再来深入学习一下 函数 函数的的多返回值 Lua中的函数可以实现多返回值&#xff0c;实现方法是再return后列出要返回的值的列表&#xff0c;返回值也可以通过变量接收到&#xff0c;变量不够也不会影响接收对应位置的返回值 Lua中传…

TCP协议十大核心特性深度解析:构建可靠传输的基石

TCP&#xff08;传输控制协议&#xff09;作为互联网的"交通指挥官"&#xff0c;承载着全球80%以上的网络流量。本文将深入解析TCP协议的十大核心特性&#xff0c;通过原理剖析、流程图解和实战案例&#xff0c;揭示其如何实现高效可靠的数据传输。 一、面向连接的可…

基于 Spring Boot 瑞吉外卖系统开发(十三)

基于 Spring Boot 瑞吉外卖系统开发&#xff08;十三&#xff09; 查询套餐 在查询套餐信息时包含套餐的分类名&#xff0c;分类名称在category表中&#xff0c;因此这里需要进行两表关联查询。 自定义SQL如下&#xff1a; select s.* ,c.name as category_name from setmeal…

华为IP(6)

VLAN聚合 VLAN聚合产生的技术背景 在一般是三层交换机中&#xff0c;通常采用一个VLAN接口的方式实现广播域之间的互通&#xff0c;这在某些情况下导致了IP地址的浪费 因为一个VLAN对应的子网中&#xff0c;子网号、子网广播地址、子网网关地址不能用作VLAN内的主机IP地址&a…

深度解析IP静态的工作原理,IP静态的应用场景又哪些?

一、什么是IP静态&#xff1f; 当我们谈到“IP静态”时&#xff0c;大家可能首先想到的是与“动态IP”相对的概念。确实如此&#xff0c;静态IP是一种固定分配的IP地址&#xff0c;也就是说&#xff0c;在特定时间内&#xff0c;分配给你的IP地址不会有所更改——无论你完成多…

docker(四)使用篇一:docker 镜像仓库

前文我们已经介绍了 docker 并安装了 docker&#xff0c;下面我们将正式步入使用环节&#xff0c;本章是第一个使用教学&#xff1a;docker 镜像仓库。 一、什么是镜像仓库 所谓镜像仓库&#xff0c;其实就是负责存储、管理和分发镜像的仓库&#xff0c;并且建立了仓库的索引…

单片机开发软件

目录 纯编码 vscode Ardunio Keil 1. 集成化开发环境&#xff08;IDE&#xff09; 2. 多架构芯片支持 3. 高效的代码生成与优化 4. 强大的调试与仿真功能 5. 丰富的库函数与生态系统 6. 教育与企业级适用性 典型应用场景 半编码半图形化 STM32CUBEIED 1. 图形化配置…