当前位置: 首页 > news >正文

第三章 WPF 中实现基于 `INotifyPropertyChanged` 的**状态灯号显示**

WPF 中实现基于INotifyPropertyChanged状态灯号显示

在 WPF 中实现基于INotifyPropertyChanged状态灯号显示(如运行 / 停止、正常 / 异常、在线 / 离线等状态),核心是通过数据模型的属性变更驱动 UI 中灯号的样式(颜色、形状、提示文本)变化,结合多线程实时更新状态,以下是完整、可直接运行的实现方案。

核心思路

  1. 状态模型:定义包含状态类型、灯色、提示文本的属性,实现INotifyPropertyChanged通知 UI 更新。
  2. 灯号 UI:用Ellipse(椭圆)模拟灯号,通过Style+DataTrigger绑定状态属性,自动切换灯色 / 样式。
  3. 后台线程:模拟实时状态变化(如传感器在线状态、设备运行状态),更新模型属性触发灯号刷新。

一、完整实现代码

步骤 1:定义状态枚举和数据模型

先定义状态类型枚举,再创建包含状态属性的模型(核心是INotifyPropertyChanged实现)。

using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; namespace _003WPF_LightState_INotifyPropertyChanged { /// <summary> /// 设备状态枚举(可根据业务扩展) /// </summary> public enum DeviceStatus { Running, // 运行中(绿色) Stopped, // 已停止(灰色) Warning, // 警告(黄色) Error // 异常(红色) } /// <summary> /// 状态灯数据模型(核心:INotifyPropertyChanged) /// </summary> public class StatusLightModel : INotifyPropertyChanged { private DeviceStatus _currentStatus; private string _statusText; /// <summary> /// 当前设备状态(绑定到UI的核心属性) /// </summary> public DeviceStatus CurrentStatus { get => _currentStatus; set { if (_currentStatus != value) { _currentStatus = value; OnPropertyChanged("CurrentStatus"); // 状态变化时自动更新提示文本 UpdateStatusText(); } } } /// <summary> /// 状态提示文本(如“运行中”“异常”) /// </summary> public string StatusText { get => _statusText; set { if (_statusText != value) { _statusText = value; OnPropertyChanged("StatusText"); } } } /// <summary> /// 根据状态更新提示文本 /// </summary> private void UpdateStatusText() { switch (CurrentStatus) { case DeviceStatus.Running: StatusText = "运行中"; break; case DeviceStatus.Stopped: StatusText = "已停止"; break; case DeviceStatus.Warning: StatusText = "警告(负载过高)"; break; case DeviceStatus.Error: StatusText = "异常(连接断开)"; break; default: StatusText = "未知状态"; break; } } // 启动按钮是否可点击 private bool _StartIsEnable = true; /// <summary> /// 实时数值 /// </summary> public bool StartIsEnable { get => _StartIsEnable; set { // 只有值变化时才触发通知,避免无效更新 if (!_StartIsEnable.Equals(value)) { _StartIsEnable = value; OnPropertyChanged("StartIsEnable"); // 自动获取属性名(CallerMemberName特性) } } } // 停止按钮是否可点击 private bool _StopIsEnable = false; /// <summary> /// 实时数值 /// </summary> public bool StopIsEnable { get => _StopIsEnable; set { // 只有值变化时才触发通知,避免无效更新 if (!_StopIsEnable.Equals(value)) { _StopIsEnable = value; OnPropertyChanged("StopIsEnable"); // 自动获取属性名(CallerMemberName特性) } } } #region INotifyPropertyChanged 实现 public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } #endregion } }

步骤 2:设计灯号 UI(MainWindow.xaml)

Ellipse绘制灯号,通过DataTrigger绑定CurrentStatus属性,自动切换灯色和样式:

<Window x:Class="_003WPF_LightState_INotifyPropertyChanged.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:_003WPF_LightState_INotifyPropertyChanged" mc:Ignorable="d" Title="状态灯实时显示" Height="350" Width="400"> <Grid Margin="20"> <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center"> <!-- 状态灯(椭圆模拟) --> <Ellipse x:Name="statusLight" Width="80" Height="80" Margin="0 0 0 20"> <!-- 灯号样式:根据CurrentStatus自动切换颜色 --> <Ellipse.Style> <Style TargetType="Ellipse"> <!-- 默认样式(停止) --> <Setter Property="Fill" Value="LightGray" /> <Setter Property="Stroke" Value="Gray" /> <Setter Property="StrokeThickness" Value="2" /> <!-- 运行中(绿色) --> <Style.Triggers> <DataTrigger Binding="{Binding CurrentStatus}" Value="Running"> <Setter Property="Fill" Value="LimeGreen" /> <Setter Property="Stroke" Value="DarkGreen" /> </DataTrigger> <!-- 警告(黄色) --> <DataTrigger Binding="{Binding CurrentStatus}" Value="Warning"> <Setter Property="Fill" Value="Gold" /> <Setter Property="Stroke" Value="DarkGoldenrod" /> </DataTrigger> <!-- 异常(红色) --> <DataTrigger Binding="{Binding CurrentStatus}" Value="Error"> <Setter Property="Fill" Value="Red" /> <Setter Property="Stroke" Value="DarkRed" /> <!-- 异常时添加闪烁动画 --> <Setter Property="Effect"> <Setter.Value> <DropShadowEffect Color="Red" BlurRadius="10" ShadowDepth="0" /> </Setter.Value> </Setter> </DataTrigger> </Style.Triggers> </Style> </Ellipse.Style> </Ellipse> <!-- 状态文本显示 --> <TextBlock Text="{Binding StatusText}" FontSize="20" HorizontalAlignment="Center" Margin="0 0 0 30" /> <!-- 控制按钮 --> <StackPanel Orientation="Horizontal"> <Button Content="模拟状态变化" Width="120" Height="40" Click="SimulateStatus_Click" IsEnabled="{Binding StartIsEnable}" /> <Button Content="重置为停止" Width="120" Height="40" Click="ResetStatus_Click" IsEnabled="{Binding StopIsEnable}"/> </StackPanel> </StackPanel> </Grid> </Window>

步骤 3:后台线程模拟状态变化(MainWindow.xaml.cs)

通过后台线程随机切换状态,模拟真实场景中的状态更新(如设备运行状态、网络连接状态):

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace _003WPF_LightState_INotifyPropertyChanged { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { private Thread _statusThread; private bool _isSimulating = false; private StatusLightModel _statusModel; public MainWindow() { InitializeComponent(); // 获取数据模型实例 _statusModel = new StatusLightModel(); this.DataContext = _statusModel; // 初始状态设为停止 _statusModel.CurrentStatus = DeviceStatus.Stopped; _statusModel.StopIsEnable = false; _statusModel.StartIsEnable = true; // 窗口关闭时停止线程 Closing += (s, e) => { _statusModel.StopIsEnable = false; _statusModel.StartIsEnable = true; _isSimulating = false; }; } /// <summary> /// 模拟状态变化按钮点击事件(启动后台线程) /// </summary> private void SimulateStatus_Click(object sender, RoutedEventArgs e) { if (_isSimulating) return; _isSimulating = true; _statusModel.StartIsEnable = false; _statusModel.StopIsEnable = true; // 创建后台线程模拟状态变化 _statusThread = new Thread(SimulateStatusChanges) { IsBackground = true, // 后台线程,窗口关闭自动终止 Name = "StatusSimulationThread" }; _statusThread.Start(); } /// <summary> /// 重置状态按钮点击事件 /// </summary> private void ResetStatus_Click(object sender, RoutedEventArgs e) { _isSimulating = false; _statusModel.CurrentStatus = DeviceStatus.Stopped; _statusModel.StopIsEnable = false; _statusModel.StartIsEnable = true; } /// <summary> /// 后台线程:随机切换设备状态(模拟实时状态更新) /// </summary> private void SimulateStatusChanges() { Random random = new Random(); // 状态数组(用于随机选择) DeviceStatus[] statuses = new[] { DeviceStatus.Running, DeviceStatus.Warning, DeviceStatus.Error }; while (_isSimulating) { // 随机选择一个状态 DeviceStatus newStatus = statuses[random.Next(0, statuses.Length)]; // 直接更新模型属性(无需Dispatcher,INotifyPropertyChanged自动处理线程安全) _statusModel.CurrentStatus = newStatus; // 模拟状态持续时间(2~5秒随机) Thread.Sleep(random.Next(2000, 5000)); } } } }

二、关键功能说明

1. 灯号样式逻辑

  • 基础样式:默认灰色(停止状态),通过DataTrigger绑定CurrentStatus属性,自动切换为绿色(运行)、黄色(警告)、红色(异常)。
  • 异常闪烁效果:异常状态时添加DropShadowEffect实现红色光晕,模拟灯号闪烁的视觉效果(如需动态闪烁,可添加DoubleAnimation动画)。

2. 线程安全更新

  • 后台线程直接修改_statusModel.CurrentStatus属性,无需手动调用Dispatcher,因为INotifyPropertyChanged结合 WPF 绑定会自动处理线程安全(WPF 4.5+ 特性)。
  • 线程控制:通过_isSimulating布尔标识优雅停止线程,避免Thread.Abort()带来的线程安全问题。

三、运行效果

  1. 程序启动后,灯号默认显示灰色(停止状态),文本显示 “已停止”。
  2. 点击 “模拟状态变化”,后台线程随机切换状态(运行 / 警告 / 异常),灯号自动切换对应颜色,文本同步更新。
  3. 异常状态时灯号为红色并带有光晕(添加动画后会闪烁),警告为黄色,运行为绿色。
  4. 点击 “重置为停止”,状态恢复为停止,灯号变回灰色。

总结

  1. 状态灯显示的核心是通过INotifyPropertyChanged绑定状态属性,结合DataTrigger自动切换 UI 样式(灯色、动画)。
  2. 后台线程更新状态时,直接修改模型属性即可,WPF 绑定会自动处理线程安全,无需手动调用Dispatcher
  3. 可通过扩展DeviceStatus枚举和DataTrigger样式,轻松适配更多业务状态(如离线、待机等)。

已经附加代码供大家学习参考

http://www.jsqmd.com/news/466501/

相关文章:

  • 2026年靠谱的高分子尼龙轮公司推荐:新能源尼龙轮口碑好的厂家推荐 - 行业平台推荐
  • OpenClaw真的那么神吗?技术架构解密
  • 3月11日打卡
  • OctShop点单收银系统与传统收银系统有何区别
  • Python基于flask的潮服购物服装商城系统的设计与实现
  • CST电磁仿真中的激励设置详解:平面波、离散端口与波端口
  • break,return 和continue
  • MQTT 协议详解
  • 链式二叉树经典题目梳理
  • ffmpeg滤镜学习1
  • [特殊字符] 编辑器里的 AI 助手:DeepSeek 实战教程 未来编程展望
  • 防火墙都装了,勒索病毒咋还跟回家一样随便进?
  • **用Python模拟生物神经网络:从单个神经元到简单前馈网络的实现与可视化**在人
  • AES算法的Verilog实现探索
  • JS生成2027-03-02T00:00:00+08:00格式
  • Python基于flask的养老院健康饮食信息管理系统
  • ‌智慧校园专项资金申报答辩全攻略:打动评委的实用技巧解析‌
  • 2019-2025年我国地级市逐月新房房价数据(Excel/Shp格式)
  • 2026 年上海财税合规管控推荐,让经营更有底气
  • IL-6 Surpass ELISA试剂盒如何用于炎症与疾病机制研究?
  • zlmediakit 配置指南
  • Python基于flask的养老院管理系统的设计与实现膳食
  • JetBrains 新推 AI 开发工具,重塑软件开发格局
  • 2026年热门的MC尼龙棒公司推荐:MC尼龙管/MC尼龙齿轮/MC尼龙滑块专业制造厂家推荐 - 行业平台推荐
  • 新款旅游门票预订导游旅行社研学游景点门票等各类旅游服务周边游多级分佣分销在线核销-ym7K
  • 玩转欧姆龙CP1H功能块】工控老司机教你“偷懒“秘籍
  • AI Agent和Agentic AI别再混为一谈!从概念到落地,这篇讲透了
  • Ansys Dyna模拟:混凝土与金属材料SPH粒子流切割及刀片攻进过程热力耦合与温度场模拟分析
  • 龙芯、飞腾加持!揭秘网闸的“国产化”硬核进化史
  • Python基于flask的养老院系统管理四个角色