尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

004001001_WPF Control 基类 完整深度解析

004001001_WPF Control 基类 完整深度解析 004001001_WPF Control 基类 完整深度解析摘要Control是 WPF 所有可视化控件的共同基类以「逻辑与外观分离」为核心通过ControlTemplate、Style、依赖属性与事件机制实现高度自定义界面。本文结合工业上位机场景系统讲解Control的类定义、核心特性、典型应用与避坑指南帮助开发者构建稳定的 IO 监控、状态显示与参数配置界面。Control是所有 WPF 标准控件的共同基类也是 WPF 控件体系的核心基石。它定义了所有可视化控件共有的属性、方法、事件和行为是你开发自定义控件、统一界面风格、实现复杂交互的基础。一、Control 基类的本质与类定义1.1 在 WPF 类层次结构中的位置Control位于 WPF 类层次结构的核心位置继承链如下plaintextSystem.Object ↳ System.Windows.Threading.DispatcherObject ↳ System.Windows.DependencyObject依赖属性系统 ↳ System.Windows.Media.Visual可视化渲染 ↳ System.Windows.UIElement输入事件、布局 ↳ System.Windows.FrameworkElement样式、数据绑定、布局 ↳ System.Windows.Controls.Control所有控件的基类 ↳ System.Windows.Controls.ContentControl 单内容控件Button、Label ↳ System.Windows.Controls.ItemsControl 多内容控件ListBox、DataGrid ↳ System.Windows.Controls.TextBoxBase 文本控件TextBox ↳ System.Windows.Controls.Primitives.ButtonBase 按钮基类 ↳ ... 所有其他WPF标准控件1.2 官方类定义csharp:namespace System.Windows.Controls { /// summary /// 表示用户界面元素的基类这些元素使用 ControlTemplate 来定义其外观 /// /summary [DefaultProperty(Content)] [Localizability(LocalizationCategory.None, Readability Readability.Unreadable)] public class Control : FrameworkElement { // 核心依赖属性 public static readonly DependencyProperty TemplateProperty; public static readonly DependencyProperty StyleProperty; public static readonly DependencyProperty BackgroundProperty; public static readonly DependencyProperty ForegroundProperty; public static readonly DependencyProperty FontFamilyProperty; public static readonly DependencyProperty FontSizeProperty; public static readonly DependencyProperty FontWeightProperty; public static readonly DependencyProperty IsEnabledProperty; public static readonly DependencyProperty VisibilityProperty; public static readonly DependencyProperty BorderBrushProperty; public static readonly DependencyProperty BorderThicknessProperty; public static readonly DependencyProperty PaddingProperty; public static readonly DependencyProperty HorizontalContentAlignmentProperty; public static readonly DependencyProperty VerticalContentAlignmentProperty; // 构造函数 public Control(); // 核心属性 public ControlTemplate Template { get; set; } public Style Style { get; set; } public Brush Background { get; set; } public Brush Foreground { get; set; } public FontFamily FontFamily { get; set; } public double FontSize { get; set; } public FontWeight FontWeight { get; set; } public bool IsEnabled { get; set; } public Visibility Visibility { get; set; } public Brush BorderBrush { get; set; } public Thickness BorderThickness { get; set; } public Thickness Padding { get; set; } public HorizontalAlignment HorizontalContentAlignment { get; set; } public VerticalAlignment VerticalContentAlignment { get; set; } // 核心方法 public override void OnApplyTemplate(); protected override Size MeasureOverride(Size constraint); protected override Size ArrangeOverride(Size arrangeBounds); public virtual void BeginInit(); public virtual void EndInit(); public bool Focus(); public void UpdateLayout(); // 核心事件 public event RoutedEventHandler Loaded; public event RoutedEventHandler Unloaded; public event DependencyPropertyChangedEventHandler IsEnabledChanged; public event DependencyPropertyChangedEventHandler VisibilityChanged; public event MouseButtonEventHandler MouseDown; public event MouseButtonEventHandler MouseUp; public event MouseEventHandler MouseMove; } }1.3 Control 类的核心设计思想WPF 控件的核心设计原则是“逻辑与外观分离”Control类只负责控件的逻辑行为如点击、状态变化、数据处理控件的外观完全由ControlTemplate定义可以完全自定义逻辑与外观通过TemplateBinding进行绑定这是 WPF 与 WinForms 最大的区别之一也是 WPF 能够实现高度自定义界面的基础。二、Control 基类核心特性详解2.1 最核心的属性Template控件模板Template是Control类最重要的属性没有之一。它决定了控件在界面上的样子。什么是 ControlTemplateControlTemplate是一个 XAML 代码块定义了控件的可视化树结构。你可以完全替换一个控件的模板而不改变它的逻辑行为。示例将 Button 变成一个圆形按钮xaml:Button Content圆形按钮 Width100 Height100 Button.Template ControlTemplate TargetTypeButton Ellipse Fill{TemplateBinding Background} Stroke{TemplateBinding BorderBrush} StrokeThickness2 ContentPresenter HorizontalAlignmentCenter VerticalAlignmentCenter/ /Ellipse /ControlTemplate /Button.Template /Button这个按钮的逻辑行为点击事件、命令绑定完全不变但外观变成了圆形。TemplateBindingTemplateBinding是专门用于控件模板的绑定它将模板中的元素属性绑定到控件的依赖属性上。{TemplateBinding Background}绑定到控件的 Background 属性{TemplateBinding IsEnabled}绑定到控件的 IsEnabled 属性2.2 样式与主题Style 属性Style属性允许你将一组属性值应用到多个控件上实现样式的复用。示例统一所有按钮的样式xaml:Window.Resources Style TargetTypeButton Setter PropertyBackground Value#2196F3/ Setter PropertyForeground ValueWhite/ Setter PropertyFontSize Value14/ Setter PropertyPadding Value10,5/ Setter PropertyBorderThickness Value0/ Setter PropertyTemplate Setter.Value ControlTemplate TargetTypeButton Border Background{TemplateBinding Background} CornerRadius4 ContentPresenter HorizontalAlignmentCenter VerticalAlignmentCenter/ /Border /ControlTemplate /Setter.Value /Setter /Style /Window.Resources !-- 所有按钮都会自动应用上面的样式 -- Button Content启动/ Button Content停止/ Button Content复位/2.3 通用外观属性所有继承自Control的控件都拥有以下通用外观属性属性作用工业场景常用值Background控件背景色绿色运行、红色停机、黄色警告Foreground文本颜色白色、黑色BorderBrush边框颜色灰色、红色BorderThickness边框厚度1、2Padding内边距5,5FontFamily字体“Microsoft YaHei”FontSize字号12、14、16FontWeight字重FontWeights.Bold正常/斜体/粗体/细体等2.4 状态属性属性作用工业场景应用IsEnabled控件是否启用设备运行时禁用参数修改按钮Visibility控件是否可见隐藏未使用的功能模块IsFocused控件是否获得焦点输入框自动聚焦2.5 核心方法1. OnApplyTemplate()当控件的模板被应用时调用这是你访问模板内部元素的唯一时机。csharp:public override void OnApplyTemplate() { base.OnApplyTemplate(); // 获取模板中的元素 var indicator GetTemplateChild(PART_Indicator) as Ellipse; if (indicator ! null) { // 初始化元素 indicator.Fill Brushes.Gray; } }命名规范模板中需要被后台代码访问的元素命名必须以PART_开头。2. MeasureOverride() 和 ArrangeOverride()用于实现自定义布局MeasureOverride(Size constraint)计算控件需要的大小ArrangeOverride(Size arrangeBounds)排列控件内部的子元素2.6 核心事件事件触发时机工业场景应用Loaded控件加载到可视化树时初始化控件数据、启动定时器Unloaded控件从可视化树移除时释放资源、停止定时器、取消事件订阅IsEnabledChangedIsEnabled 属性变化时禁用时改变控件外观MouseDown鼠标按下时自定义按钮点击逻辑三、工业上位机典型应用场景场景 1自定义工业专用控件这是Control类在工业项目中最常用的场景。WPF 标准控件无法满足工业场景的特殊需求如 IO 指示灯、阀门状态、仪表等需要继承Control开发自定义控件。实战实例IO 状态指示灯控件需求显示 PLC 输入输出点的状态绿色表示 ON红色表示 OFF支持闪烁报警。第一步定义自定义控件类csharp:using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Threading; namespace IndustrialVisionTemplate.Controls { /// summary /// IO 状态指示灯控件 /// /summary public class IoIndicator : Control { // 静态构造函数指定默认样式 static IoIndicator() { DefaultStyleKeyProperty.OverrideMetadata( typeof(IoIndicator), new FrameworkPropertyMetadata(typeof(IoIndicator))); } // 依赖属性IO 状态trueONfalseOFF public static readonly DependencyProperty IsOnProperty DependencyProperty.Register( nameof(IsOn), typeof(bool), typeof(IoIndicator), new PropertyMetadata(false, OnIsOnChanged)); public bool IsOn { get (bool)GetValue(IsOnProperty); set SetValue(IsOnProperty, value); } // 依赖属性是否闪烁报警状态 public static readonly DependencyProperty IsBlinkingProperty DependencyProperty.Register( nameof(IsBlinking), typeof(bool), typeof(IoIndicator), new PropertyMetadata(false, OnIsBlinkingChanged)); public bool IsBlinking { get (bool)GetValue(IsBlinkingProperty); set SetValue(IsBlinkingProperty, value); } // 闪烁定时器 private DispatcherTimer _blinkTimer; private bool _isBlinkOn; // 状态变化回调 private static void OnIsOnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control (IoIndicator)d; control.UpdateIndicatorColor(); } private static void OnIsBlinkingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var control (IoIndicator)d; if ((bool)e.NewValue) { control.StartBlinking(); } else { control.StopBlinking(); } } // 启动闪烁 private void StartBlinking() { if (_blinkTimer null) { _blinkTimer new DispatcherTimer(); _blinkTimer.Interval TimeSpan.FromMilliseconds(500); _blinkTimer.Tick (s, e) { _isBlinkOn !_isBlinkOn; UpdateIndicatorColor(); }; } _blinkTimer.Start(); } // 停止闪烁 private void StopBlinking() { _blinkTimer?.Stop(); _isBlinkOn false; UpdateIndicatorColor(); } // 更新指示灯颜色 private void UpdateIndicatorColor() { if (IsBlinking !_isBlinkOn) { // 闪烁时熄灭 SetValue(IndicatorColorPropertyKey, Brushes.Gray); } else if (IsOn) { // ON 状态绿色 SetValue(IndicatorColorPropertyKey, Brushes.LimeGreen); } else { // OFF 状态红色 SetValue(IndicatorColorPropertyKey, Brushes.Red); } } // 只读依赖属性指示灯颜色 private static readonly DependencyPropertyKey IndicatorColorPropertyKey DependencyProperty.RegisterReadOnly( nameof(IndicatorColor), typeof(Brush), typeof(IoIndicator), new PropertyMetadata(Brushes.Gray)); public static readonly DependencyProperty IndicatorColorProperty IndicatorColorPropertyKey.DependencyProperty; public Brush IndicatorColor { get (Brush)GetValue(IndicatorColorProperty); } // 控件卸载时释放资源 public override void OnApplyTemplate() { base.OnApplyTemplate(); Unloaded (s, e) { _blinkTimer?.Stop(); _blinkTimer null; }; } } }第二步定义默认样式Themes/Generic.xamlxaml:ResourceDictionary xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:controlsclr-namespace:IndustrialVisionTemplate.Controls Style TargetTypecontrols:IoIndicator Setter PropertyWidth Value20/ Setter PropertyHeight Value20/ Setter PropertyTemplate Setter.Value ControlTemplate TargetTypecontrols:IoIndicator Ellipse x:NamePART_Indicator Fill{TemplateBinding IndicatorColor} StrokeBlack StrokeThickness1/ /ControlTemplate /Setter.Value /Setter /Style /ResourceDictionary第三步在界面中使用xaml:Window xmlns:controlsclr-namespace:IndustrialVisionTemplate.Controls StackPanel OrientationHorizontal Margin10 TextBlock Text输入1 VerticalAlignmentCenter/ controls:IoIndicator IsOn{Binding Input1State} Margin5,0/ TextBlock Text输入2 VerticalAlignmentCenter Margin20,0,0,0/ controls:IoIndicator IsOn{Binding Input2State} IsBlinking{Binding Input2Alarm} Margin5,0/ TextBlock Text输出1 VerticalAlignmentCenter Margin20,0,0,0/ controls:IoIndicator IsOn{Binding Output1State} Margin5,0/ /StackPanel /Window场景 2统一界面风格通过继承Control的样式实现整个项目界面风格的统一这是工业上位机项目的必备需求。示例工业项目全局控件样式xaml:!-- App.xaml -- Application.Resources !-- 全局 Control 样式所有控件继承 -- Style TargetTypeControl Setter PropertyFontFamily ValueMicrosoft YaHei/ Setter PropertyFontSize Value12/ Setter PropertyForeground Value#333333/ /Style !-- 按钮样式 -- Style TargetTypeButton BasedOn{StaticResource {x:Type Control}} Setter PropertyBackground Value#2196F3/ Setter PropertyForeground ValueWhite/ Setter PropertyPadding Value15,8/ Setter PropertyMinWidth Value80/ Setter PropertyTemplate Setter.Value ControlTemplate TargetTypeButton Border Background{TemplateBinding Background} CornerRadius4 BorderThickness0 ContentPresenter HorizontalAlignmentCenter VerticalAlignmentCenter/ /Border ControlTemplate.Triggers Trigger PropertyIsMouseOver ValueTrue Setter PropertyBackground Value#1976D2/ /Trigger Trigger PropertyIsPressed ValueTrue Setter PropertyBackground Value#0D47A1/ /Trigger Trigger PropertyIsEnabled ValueFalse Setter PropertyBackground Value#BDBDBD/ /Trigger /ControlTemplate.Triggers /ControlTemplate /Setter.Value /Setter /Style !-- 危险按钮样式 -- Style x:KeyDangerButtonStyle TargetTypeButton BasedOn{StaticResource {x:Type Button}} Setter PropertyBackground Value#F44336/ Style.Triggers Trigger PropertyIsMouseOver ValueTrue Setter PropertyBackground Value#D32F2F/ /Trigger /Style.Triggers /Style /Application.Resources场景 3动态创建控件在工业项目中经常需要根据 PLC 的 IO 点数动态创建控件。示例动态创建 IO 监控界面csharp:private void CreateIoControls() { // 从 PLC 获取 IO 点列表 var ioPoints Device.PLC.GetIoPoints(); foreach (var ioPoint in ioPoints) { // 创建 IO 指示灯控件 var indicator new IoIndicator(); indicator.IsOn ioPoint.State; indicator.IsBlinking ioPoint.IsAlarm; // 创建标签 var label new TextBlock(); label.Text ${ioPoint.Name}; label.VerticalAlignment VerticalAlignment.Center; // 添加到布局 var stackPanel new StackPanel(); stackPanel.Orientation Orientation.Horizontal; stackPanel.Margin new Thickness(5); stackPanel.Children.Add(label); stackPanel.Children.Add(indicator); // 绑定数据 indicator.SetBinding(IoIndicator.IsOnProperty, new Binding($IoPoints[{ioPoint.Id}].State)); indicator.SetBinding(IoIndicator.IsBlinkingProperty, new Binding($IoPoints[{ioPoint.Id}].IsAlarm)); IoPanel.Children.Add(stackPanel); } }场景 4控件状态管理通过Control的IsEnabled和Visibility属性实现设备不同状态下的界面控制。示例设备运行时禁用参数修改csharp:private void DeviceStatusChanged(object sender, DeviceStatusChangedEventArgs e) { if (e.NewStatus DeviceStatus.RUNNING) { // 设备运行时禁用所有参数输入控件 ParameterPanel.IsEnabled false; // 隐藏调试按钮 DebugButton.Visibility Visibility.Collapsed; // 启用停止按钮 StopButton.IsEnabled true; // 禁用启动按钮 StartButton.IsEnabled false; } else if (e.NewStatus DeviceStatus.IDLE) { // 设备空闲时启用参数输入 ParameterPanel.IsEnabled true; // 显示调试按钮 DebugButton.Visibility Visibility.Visible; // 禁用停止按钮 StopButton.IsEnabled false; // 启用启动按钮 StartButton.IsEnabled true; } }四、应用注意点与避坑指南1. 绝对不要在构造函数中访问模板元素模板是在OnApplyTemplate()方法中才被应用的构造函数执行时模板还没有加载此时调用GetTemplateChild()会返回 null。❌ 错误写法csharp:public IoIndicator() { // 错误构造函数中模板还没应用 var indicator GetTemplateChild(PART_Indicator) as Ellipse; }✅ 正确写法csharp:public override void OnApplyTemplate() { base.OnApplyTemplate(); // 正确此时模板已经应用 var indicator GetTemplateChild(PART_Indicator) as Ellipse; }2. 不要直接修改模板内部元素的属性应该通过依赖属性和TemplateBinding来控制模板元素的外观不要在后台代码中直接修改模板元素的属性。❌ 错误写法csharp:public void SetColor(Brush color) { var indicator GetTemplateChild(PART_Indicator) as Ellipse; if (indicator ! null) { indicator.Fill color; // 错误直接修改模板元素 } }✅ 正确写法csharp:// 定义依赖属性 public static readonly DependencyProperty IndicatorColorProperty ...; // 在模板中绑定 Ellipse Fill{TemplateBinding IndicatorColor}/ // 修改依赖属性 public void SetColor(Brush color) { IndicatorColor color; }3. 必须在 Unloaded 事件中释放资源自定义控件中使用的定时器、事件订阅、非托管资源必须在Unloaded事件中释放否则会导致内存泄漏。✅ 正确写法csharp:public override void OnApplyTemplate() { base.OnApplyTemplate(); // 订阅 Unloaded 事件 Unloaded (s, e) { // 停止定时器 _blinkTimer?.Stop(); _blinkTimer null; // 取消事件订阅 Device.PLC.IoStateChanged - OnIoStateChanged; }; }4. 不要频繁修改 Template 属性每次修改Template属性都会导致控件重新生成可视化树性能开销很大。如果需要改变控件外观应该通过修改依赖属性来实现。5. 正确使用 MeasureOverride 和 ArrangeOverride如果需要实现自定义布局必须正确实现MeasureOverride和ArrangeOverride方法在MeasureOverride中必须调用每个子元素的Measure方法返回的大小不能超过传入的 constraint在ArrangeOverride中必须调用每个子元素的Arrange方法6. 避免内存泄漏不要在静态变量中引用Control实例及时取消事件订阅定时器不再使用时必须停止非托管资源必须实现IDisposable接口五、总结与速查表5.1 Control 基类核心要点总结Control是 WPF 所有可视化控件的共同基类继承自FrameworkElement统一提供模板、样式、外观、状态、布局和事件等核心能力。Control的核心设计思想是「逻辑与外观分离」逻辑行为由控件类负责外观完全由ControlTemplate定义二者通过TemplateBinding解耦。Template是Control最重要的属性替换模板可以彻底改变控件外观而不影响点击、命令绑定等逻辑行为。自定义工业控件应通过依赖属性暴露状态再由模板中的TemplateBinding驱动界面避免在后台代码中直接修改模板内部元素。模板元素只能在OnApplyTemplate()中访问构造函数执行时模板尚未应用此时调用GetTemplateChild()会返回 null。自定义控件应通过DefaultStyleKeyProperty关联默认样式并在Themes/Generic.xaml中提供默认模板。工业项目应充分利用IsEnabled、Visibility管理设备不同状态下的界面交互避免频繁替换Template造成可视化树重建。必须在Unloaded事件中释放定时器、取消事件订阅、释放非托管资源这是避免工业上位机内存泄漏的关键。5.2 Control 常用成员速查表分类成员用途工业场景推荐值/应用属性Template定义控件外观模板自定义 IO 指示灯、按钮样式属性Style批量复用一组属性设置全局统一按钮、输入框风格属性Background控件背景色绿色运行、红色停机、黄色警告属性Foreground控件文本颜色白色、黑色属性BorderBrush控件边框颜色灰色、红色属性BorderThickness控件边框厚度1、2属性Padding控件内边距5,5按钮 15,8属性FontFamily控件字体Microsoft YaHei属性FontSize控件字号12、14、16属性FontWeight控件字重FontWeights.Bold属性IsEnabled控件是否启用设备运行时禁用参数修改按钮属性Visibility控件是否可见隐藏未使用模块、调试按钮方法OnApplyTemplate()模板应用后获取模板元素初始化PART_Indicator等模板元素方法MeasureOverride(Size)计算控件所需布局尺寸自定义仪表、面板布局方法ArrangeOverride(Size)排列控件内部子元素自定义仪表、面板布局方法Focus()使控件获得焦点输入框自动聚焦方法UpdateLayout()立即刷新布局动态创建控件后刷新界面事件Loaded控件加载到可视化树初始化数据、启动定时器事件Unloaded控件从可视化树移除释放资源、停止定时器、取消订阅事件IsEnabledChangedIsEnabled属性变化时禁用时改变控件外观事件VisibilityChangedVisibility属性变化时显示/隐藏模块联动逻辑5.3 文章摘要Control基类是 WPF 控件体系的核心通过ControlTemplate实现逻辑与外观分离并以依赖属性、样式和事件机制支撑高度自定义的工业界面开发。基于Control可快速构建 IO 指示灯、动态监控面板和设备状态管理界面同时保障控件的稳定性与可维护性是高质量 WPF 工业上位机开发的关键基石。
返回列表