如何复用基于System.Windows.Controls.Control的自定义基类至Button、TextBox等WPF控件
解决WPF控件DataTag逻辑复用的最优方案
这个问题我太熟悉了——继承控件基类确实能搞定单个控件的需求,但要把这套逻辑复用给TextBox、Image、TextBlock等不同控件时,重复代码简直是噩梦。别担心,WPF里有两个更优雅的方案,能让你把这套DataTag控制逻辑一次性复用给所有FrameworkElement,不用再复制粘贴代码:附加属性(Attached Property) 或者 Blend行为(Behavior)。我给你详细拆解最优实现方式。
为什么不用继承?
继承的问题在于强耦合:你只能给Button的子类用这套逻辑,而TextBox、Image这些控件有自己的继承链,不可能都继承自你的Button基类。附加属性和行为是松耦合的,能给任意元素“挂载”功能,完美解决复用问题。
方案一:WPF原生附加属性(无额外依赖)
这是最轻量化的方案,不需要引用任何第三方库,完全用WPF原生特性实现。我们把原来Button基类里的逻辑,封装到一个静态类中,通过附加属性或直接监听控件的Loaded事件来初始化。
实现代码
using System; using System.Collections.Generic; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Media; using System.Timers; using Newtonsoft.Json; public static class RsdControlProperties { // 存储每个控件对应的Timer和DataTag,避免内存泄漏 private static readonly Dictionary<FrameworkElement, Timer> _elementTimers = new(); private static readonly Dictionary<FrameworkElement, DataTag> _visibilityTags = new(); private static readonly Dictionary<FrameworkElement, DataTag> _enabledTags = new(); private static readonly Dictionary<FrameworkElement, DataTag> _appearanceTags = new(); // 定义附加属性:一键启用/禁用DataTag控制逻辑 public static readonly DependencyProperty IsRsdControlEnabledProperty = DependencyProperty.RegisterAttached( "IsRsdControlEnabled", typeof(bool), typeof(RsdControlProperties), new PropertyMetadata(false, OnIsRsdControlEnabledChanged)); public static void SetIsRsdControlEnabled(DependencyObject obj, bool value) => obj.SetValue(IsRsdControlEnabledProperty, value); public static bool GetIsRsdControlEnabled(DependencyObject obj) => (bool)obj.GetValue(IsRsdControlEnabledProperty); private static void OnIsRsdControlEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not FrameworkElement element) return; if ((bool)e.NewValue) { // 初始化逻辑 element.Loaded += OnElementLoaded; element.Unloaded += OnElementUnloaded; } else { // 清理逻辑 OnElementUnloaded(element, null); element.Loaded -= OnElementLoaded; element.Unloaded -= OnElementUnloaded; } } private static void OnElementLoaded(object sender, RoutedEventArgs e) { var element = sender as FrameworkElement; if (element == null) return; // 监听Tag属性变化(你原来的配置通过Tag传递) var tagDescriptor = DependencyPropertyDescriptor.FromProperty(FrameworkElement.TagProperty, typeof(FrameworkElement)); tagDescriptor.AddValueChanged(element, OnTagPropertyChanged); OnTagPropertyChanged(element, null); // 初始化Timer var timer = new Timer(1000); timer.Elapsed += (s, args) => { element.Dispatcher.BeginInvoke(new Action(() => { if (element is Control control) { var tempBackground = control.Background; control.Background = control.Foreground; control.Foreground = tempBackground; } }), DispatcherPriority.Render); }; _elementTimers[element] = timer; } private static void OnElementUnloaded(object sender, RoutedEventArgs e) { var element = sender as FrameworkElement; if (element == null) return; // 注销所有事件,避免内存泄漏 if (_enabledTags.TryGetValue(element, out var enabledTag)) { enabledTag.DataChanged -= OnEnabledTagChanged; _enabledTags.Remove(element); } if (_visibilityTags.TryGetValue(element, out var visibilityTag)) { visibilityTag.DataChanged -= OnVisibilityTagChanged; _visibilityTags.Remove(element); } if (_appearanceTags.TryGetValue(element, out var appearanceTag)) { appearanceTag.DataChanged -= OnAppearanceTagChanged; _appearanceTags.Remove(element); } if (_elementTimers.TryGetValue(element, out var timer)) { timer.Stop(); timer.Dispose(); _elementTimers.Remove(element); } // 移除Tag属性监听 var tagDescriptor = DependencyPropertyDescriptor.FromProperty(FrameworkElement.TagProperty, typeof(FrameworkElement)); tagDescriptor.RemoveValueChanged(element, OnTagPropertyChanged); } private static void OnTagPropertyChanged(object sender, EventArgs e) { var element = sender as FrameworkElement; if (element == null || element.Tag == null) return; var tagScript = JsonConvert.DeserializeObject<TagScriptObject>(element.Tag.ToString()); if (tagScript == null) return; // 绑定VisibilityTag if (tagScript.VisibilityProperty?.TagId > 0) { var visibilityTag = GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == tagScript.VisibilityProperty.TagId); if (visibilityTag != null) { _visibilityTags.TryGetValue(element, out var oldTag); oldTag?.DataChanged -= OnVisibilityTagChanged; _visibilityTags[element] = visibilityTag; visibilityTag.DataChanged += OnVisibilityTagChanged; OnVisibilityTagChanged(element, null); } } // 绑定EnabledTag if (tagScript.EnableProperty?.TagId > 0) { var enabledTag = GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == tagScript.EnableProperty.TagId); if (enabledTag != null) { _enabledTags.TryGetValue(element, out var oldTag); oldTag?.DataChanged -= OnEnabledTagChanged; _enabledTags[element] = enabledTag; enabledTag.DataChanged += OnEnabledTagChanged; OnEnabledTagChanged(element, null); } } // 绑定AppearanceTag if (tagScript.AppearanceProperty?.TagId > 0) { var appearanceTag = GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == tagScript.AppearanceProperty.TagId); if (appearanceTag != null) { _appearanceTags.TryGetValue(element, out var oldTag); oldTag?.DataChanged -= OnAppearanceTagChanged; _appearanceTags[element] = appearanceTag; appearanceTag.DataChanged += OnAppearanceTagChanged; OnAppearanceTagChanged(element, null); } } } #region DataTag事件处理逻辑(移植你原来的代码,替换this为element) private static void OnVisibilityTagChanged(object source, EventArgs args) { var element = source as FrameworkElement; if (element == null || !_visibilityTags.TryGetValue(element, out var visibilityTag)) return; element.Dispatcher.BeginInvoke(new Action(() => { var tagScript = JsonConvert.DeserializeObject<TagScriptObject>(element.Tag.ToString()); if (tagScript?.VisibilityProperty == null) return; double tagValue; bool parseSuccess = true; if (visibilityTag.VarType == VarType.Bit) tagValue = visibilityTag.TagValue ? 1 : 0; else parseSuccess = double.TryParse(visibilityTag.TagValue.ToString(), out tagValue); if (!parseSuccess) return; if (tagScript.VisibilityProperty.IsRangeSelected) { element.Visibility = tagValue >= tagScript.VisibilityProperty.RangeFrom && tagValue <= tagScript.VisibilityProperty.RangeTo ? (tagScript.VisibilityProperty.DefaultValue ? Visibility.Visible : Visibility.Hidden) : (tagScript.VisibilityProperty.DefaultValue ? Visibility.Collapsed : Visibility.Visible); } else { if (visibilityTag.IsNumeric || visibilityTag.VarType == VarType.Bit) { var bitArray = visibilityTag.GetBitArray(); var targetBit = tagScript.VisibilityProperty.SingleBit; if (bitArray.Count > targetBit) { element.Visibility = bitArray[targetBit] ? (tagScript.VisibilityProperty.DefaultValue ? Visibility.Visible : Visibility.Hidden) : (tagScript.VisibilityProperty.DefaultValue ? Visibility.Hidden : Visibility.Visible); } } } }), DispatcherPriority.Render); } private static void OnEnabledTagChanged(object source, EventArgs args) { var element = source as FrameworkElement; if (element == null || !_enabledTags.TryGetValue(element, out var enabledTag)) return; element.Dispatcher.BeginInvoke(new Action(() => { var tagScript = JsonConvert.DeserializeObject<TagScriptObject>(element.Tag.ToString()); if (tagScript?.EnableProperty == null) return; double tagValue; bool parseSuccess = true; if (enabledTag.VarType == VarType.Bit) tagValue = enabledTag.TagValue ? 1 : 0; else parseSuccess = double.TryParse(enabledTag.TagValue.ToString(), out tagValue); if (!parseSuccess) return; if (tagScript.EnableProperty.IsRangeSelected) { element.IsEnabled = tagValue >= tagScript.EnableProperty.RangeFrom && tagValue <= tagScript.EnableProperty.RangeTo ? tagScript.EnableProperty.DefaultValue : !tagScript.EnableProperty.DefaultValue; } else { if (enabledTag.IsNumeric || enabledTag.VarType == VarType.Bit) { var bitArray = enabledTag.GetBitArray(); var targetBit = tagScript.EnableProperty.SingleBit; if (bitArray.Count > targetBit) { element.IsEnabled = bitArray[targetBit] ? tagScript.EnableProperty.DefaultValue : !tagScript.EnableProperty.DefaultValue; } } } }), DispatcherPriority.Render); } private static void OnAppearanceTagChanged(object source, EventArgs args) { var element = source as FrameworkElement; if (element is not Control control || !_appearanceTags.TryGetValue(element, out var appearanceTag) || !_elementTimers.TryGetValue(element, out var timer)) return; element.Dispatcher.BeginInvoke(new Action(() => { var tagScript = JsonConvert.DeserializeObject<TagScriptObject>(element.Tag.ToString()); if (tagScript?.AppearanceProperty == null) return; timer.Enabled = false; double tagValue; bool parseSuccess = true; if (appearanceTag.VarType == VarType.Bit) tagValue = appearanceTag.TagValue ? 1 : 0; else parseSuccess = double.TryParse(appearanceTag.TagValue.ToString(), out tagValue); if (!parseSuccess) return; foreach (var colorConfig in tagScript.AppearanceProperty.ControlColors) { if (tagValue >= colorConfig.RangeMin && tagValue <= colorConfig.RangeMax) { control.Background = new BrushConverter().ConvertFromString(colorConfig.Background) as SolidColorBrush; control.Foreground = new BrushConverter().ConvertFromString(colorConfig.Foreground) as SolidColorBrush; timer.Enabled = colorConfig.Flashing == ConfirmEnum.Yes; break; } } }), DispatcherPriority.Render); } #endregion }
使用方式
在XAML里给任意控件添加附加属性即可,完全不用修改控件的继承关系:
<!-- 给Button用 --> <Button local:RsdControlProperties.IsRsdControlEnabled="True" Tag="{Binding ButtonTagScriptJson}"/> <!-- 给TextBox用 --> <TextBox local:RsdControlProperties.IsRsdControlEnabled="True" Tag="{Binding TextBoxTagScriptJson}"/> <!-- 给Image用 --> <Image local:RsdControlProperties.IsRsdControlEnabled="True" Tag="{Binding ImageTagScriptJson}"/>
方案二:Blend行为(更清晰的生命周期管理)
如果你习惯MVVM开发,Blend行为是更优雅的选择——它有现成的Attach/Detach生命周期,代码结构更清晰。需要先通过NuGet安装Microsoft.Xaml.Behaviors.Wpf(微软官方维护的库)。
实现代码
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Media; using System.Timers; using Microsoft.Xaml.Behaviors; using Newtonsoft.Json; public class RsdControlBehavior : Behavior<FrameworkElement> { private DataTag _visibilityTag; private DataTag _enabledTag; private DataTag _appearanceTag; private Timer _flashTimer; protected override void OnAttached() { base.OnAttached(); AssociatedObject.Loaded += OnLoaded; AssociatedObject.Unloaded += OnUnloaded; _flashTimer = new Timer(1000); _flashTimer.Elapsed += OnFlashTimerElapsed; } protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.Loaded -= OnLoaded; AssociatedObject.Unloaded -= OnUnloaded; CleanupTags(); _flashTimer.Stop(); _flashTimer.Dispose(); } private void OnLoaded(object sender, RoutedEventArgs e) { var tagDescriptor = DependencyPropertyDescriptor.FromProperty(FrameworkElement.TagProperty, typeof(FrameworkElement)); tagDescriptor.AddValueChanged(AssociatedObject, OnTagChanged); OnTagChanged(null, null); } private void OnUnloaded(object sender, RoutedEventArgs e) { CleanupTags(); var tagDescriptor = DependencyPropertyDescriptor.FromProperty(FrameworkElement.TagProperty, typeof(FrameworkElement)); tagDescriptor.RemoveValueChanged(AssociatedObject, OnTagChanged); } private void CleanupTags() { _visibilityTag?.DataChanged -= OnVisibilityTagChanged; _enabledTag?.DataChanged -= OnEnabledTagChanged; _appearanceTag?.DataChanged -= OnAppearanceTagChanged; _visibilityTag = null; _enabledTag = null; _appearanceTag = null; } private void OnTagChanged(object sender, EventArgs e) { if (AssociatedObject.Tag == null) return; var tagScript = JsonConvert.DeserializeObject<TagScriptObject>(AssociatedObject.Tag.ToString()); if (tagScript == null) return; // 绑定VisibilityTag if (tagScript.VisibilityProperty?.TagId > 0) { _visibilityTag = GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == tagScript.VisibilityProperty.TagId); _visibilityTag?.DataChanged += OnVisibilityTagChanged; OnVisibilityTagChanged(null, null); } // 绑定EnabledTag if (tagScript.EnableProperty?.TagId > 0) { _enabledTag = GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == tagScript.EnableProperty.TagId); _enabledTag?.DataChanged += OnEnabledTagChanged; OnEnabledTagChanged(null, null); } // 绑定AppearanceTag if (tagScript.AppearanceProperty?.TagId > 0) { _appearanceTag = GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == tagScript.AppearanceProperty.TagId); _appearanceTag?.DataChanged += OnAppearanceTagChanged; OnAppearanceTagChanged(null, null); } } #region 事件处理逻辑(和附加属性方案一致,替换element为AssociatedObject) private void OnFlashTimerElapsed(object sender, ElapsedEventArgs e) { AssociatedObject.Dispatcher.BeginInvoke(new Action(() => { if (AssociatedObject is Control control) { var temp = control.Background; control.Background = control.Foreground; control.Foreground = temp; } }), DispatcherPriority.Render); } private void OnVisibilityTagChanged(object source, EventArgs args) { // 逻辑和附加属性方案的OnVisibilityTagChanged完全一致,替换element为AssociatedObject } private void OnEnabledTagChanged(object source, EventArgs args) { // 逻辑和附加属性方案的OnEnabledTagChanged完全一致,替换element为AssociatedObject } private void OnAppearanceTagChanged(object source, EventArgs args) { // 逻辑和附加属性方案的OnAppearanceTagChanged完全一致,替换element为AssociatedObject } #endregion }
使用方式
在XAML里通过Interaction.Behaviors挂载行为:
<Window xmlns:i="http://schemas.microsoft.com/xaml/behaviors"> <Button> <i:Interaction.Behaviors> <local:RsdControlBehavior/>
相关产品推荐
相关产品推荐

