You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何复用基于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/>
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 21:39:06