如何将 System.Windows.Controls.Control 中的公共基础 class 用于不同的超级 class,例如 Button、TextBox、TextBlock

How to use common base class from System.Windows.Controls.Control for the different superclasses like Button, TextBox,TextBlock

我有一个来自 System.Windows.Controls.Control 的基础 class,它根据来自外部的数据更改 Visibility、Enabled、Background、Foreground 属性。

当我像下面这样使用 class 时

public class RsdDesignBase : Button
    {
....
}

它适用于按钮控件。我想对 TextBox、Image、TextBlock 等其他控件使用相同的 class,但如果我这样使用,我需要为所有其他控件复制粘贴相同的代码。

有没有办法使用我的 RsdDesignBase class 作为其他控件的基础 class?或者任何其他方式来做到这一点。

我会在下面粘贴整个 class。它所做的是等待 DataTag 对象的更改,当它们更改它更改某些属性时。例如,如果 _enabledTag.Value 为 0,则禁用控件。

public class RsdDesignButtonBase : Button
{
    private DataTag _visibilityTag;
    private DataTag _enabledTag;
    private DataTag _appearanceTag;
    public TagScriptObject TagScriptObject { get; set; }
    private readonly Timer _timer;

    protected RsdDesignButtonBase()
    {
        Loaded += RSD_ButtonBase_Loaded;
        Unloaded += OnUnloaded;
        _timer = new Timer(1000);
        _timer.Elapsed += TimerOnElapsed;
    }

    private void TimerOnElapsed(object sender, ElapsedEventArgs e)
    {
        Dispatcher.BeginInvoke(new Action(() =>
        {
            var background = Background;
            var foreground = Foreground;
            Background = foreground;
            Foreground = background;
        }), DispatcherPriority.Render);

    }

    private void OnUnloaded(object sender, RoutedEventArgs e)
    {
        if (_enabledTag != null) _enabledTag.DataChanged -= EnabledTagOnDataChanged;
        if (_visibilityTag != null) _visibilityTag.DataChanged -= VisibilityTagOnDataChanged;
        if (_appearanceTag != null) _appearanceTag.DataChanged -= AppearanceTagOnDataChanged;
    }

    private void RSD_ButtonBase_Loaded(object sender, RoutedEventArgs e)
    {
        DependencyPropertyDescriptor desc =
            DependencyPropertyDescriptor.FromProperty(FrameworkElement.TagProperty, typeof(FrameworkElement));
        desc.AddValueChanged(this, TagPropertyChanged);
        TagPropertyChanged(null, null);
    }

    private void TagPropertyChanged(object sender, EventArgs e)
    {
        if (Tag == null) return;
        TagScriptObject = JsonConvert.DeserializeObject<TagScriptObject>(Tag.ToString());

        if (TagScriptObject?.VisibilityProperty?.TagId > 0)
        {
            _visibilityTag =
                GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == TagScriptObject.VisibilityProperty?.TagId);
            if (_visibilityTag != null)
            {
                _visibilityTag.DataChanged += VisibilityTagOnDataChanged;
                VisibilityTagOnDataChanged(null, null);
            }
        }

        if (TagScriptObject?.EnableProperty?.TagId > 0)
        {
            _enabledTag =
                GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == TagScriptObject.EnableProperty?.TagId);
            if (_enabledTag != null)
            {
                _enabledTag.DataChanged += EnabledTagOnDataChanged;
                EnabledTagOnDataChanged(null, null);
            }
        }

        if (TagScriptObject?.AppearanceProperty?.TagId > 0)
        {
            _appearanceTag =
                GlobalVars.AllDataTagList.FirstOrDefault(t => t.Id == TagScriptObject.AppearanceProperty?.TagId);
            if (_appearanceTag != null && !_appearanceTag.IsEventHandlerRegistered(null))
            {
                _appearanceTag.DataChanged += AppearanceTagOnDataChanged;
                AppearanceTagOnDataChanged(null, null);
            }
        }
    }

    private void AppearanceTagOnDataChanged(object source, EventArgs args)
    {
        _timer.Enabled = false;
        _ = Dispatcher.BeginInvoke(new Action(() =>
        {
            double tagValue;
            bool result = true;
            if (_appearanceTag.VarType == VarType.Bit)
            {
                tagValue = _appearanceTag.TagValue ? 1 : 0;
            }
            else
            {
                result = double.TryParse(_appearanceTag.TagValue.ToString(), out tagValue);
            }

            if (result)
            {
                foreach (var controlColor in TagScriptObject.AppearanceProperty.ControlColors)
                {
                    if (tagValue >= controlColor.RangeMin &&
                        tagValue <= controlColor.RangeMax)
                    {
                        Background =
                            new BrushConverter().ConvertFromString(controlColor.Background) as SolidColorBrush;
                        Foreground =
                            new BrushConverter().ConvertFromString(controlColor.Foreground) as SolidColorBrush;
                        _timer.Enabled = controlColor.Flashing == ConfirmEnum.Yes;
                        break;
                        
                    }
                }
            }
        }), DispatcherPriority.Render);
    }

    private void EnabledTagOnDataChanged(object source, EventArgs args)
    {
        _ = Dispatcher.BeginInvoke(new Action(() =>
        {
            if (_enabledTag != null)
            {
                if (TagScriptObject.EnableProperty.IsRangeSelected)
                {
                    double tagValue;
                    bool result = true;
                    if (_enabledTag.VarType == VarType.Bit)
                    {
                        tagValue = _enabledTag.TagValue ? 1 : 0;
                    }
                    else
                    {
                        result = double.TryParse(_enabledTag.TagValue.ToString(), out tagValue);
                    }

                    if (result)
                    {
                        if (tagValue >= TagScriptObject.EnableProperty.RangeFrom &&
                            tagValue <= TagScriptObject.EnableProperty.RangeTo)
                        {
                            IsEnabled = TagScriptObject.EnableProperty.DefaultValue;
                        }
                        else
                        {
                            IsEnabled = !TagScriptObject.EnableProperty.DefaultValue;
                        }
                    }
                }
                else
                {
                    if (_enabledTag.IsNumeric || _enabledTag.VarType == VarType.Bit)
                    {
                        var bitArray = _enabledTag.GetBitArray();
                        var singleBit = TagScriptObject.EnableProperty.SingleBit;
                        if (bitArray.Count > singleBit)
                        {
                            if (bitArray[singleBit])
                            {
                                IsEnabled = TagScriptObject.EnableProperty.DefaultValue;
                            }
                            else
                            {
                                IsEnabled = !TagScriptObject.EnableProperty.DefaultValue;
                            }
                        }
                    }
                }
            }
        }), DispatcherPriority.Render);
    }

    private void VisibilityTagOnDataChanged(object source, EventArgs args)
    {
        _ = Dispatcher.BeginInvoke(new Action(() =>
        {
            if (_visibilityTag != null)
            {
                if (TagScriptObject.VisibilityProperty.IsRangeSelected)
                {
                    double tagValue;
                    bool result = true;
                    if (_visibilityTag.VarType == VarType.Bit)
                    {
                        tagValue = _visibilityTag.TagValue ? 1 : 0;
                    }
                    else
                    {
                        result = double.TryParse(_visibilityTag.TagValue.ToString(), out tagValue);
                    }

                    if (result)
                    {
                        if (tagValue >= TagScriptObject.VisibilityProperty.RangeFrom &&
                            tagValue <= TagScriptObject.VisibilityProperty.RangeTo)
                        {
                            Visibility = TagScriptObject.VisibilityProperty.DefaultValue
                                ? Visibility.Visible
                                : Visibility.Hidden;
                        }
                        else
                        {
                            Visibility = TagScriptObject.VisibilityProperty.DefaultValue
                                ? Visibility.Collapsed
                                : Visibility.Visible;
                        }
                    }
                }
                else
                {
                    if (_visibilityTag.IsNumeric || _visibilityTag.VarType == VarType.Bit)
                    {
                        var bitArray = _visibilityTag.GetBitArray();
                        var singleBit = TagScriptObject.VisibilityProperty.SingleBit;
                        if (bitArray.Count > singleBit)
                        {
                            if (bitArray[singleBit])
                            {
                                Visibility = TagScriptObject.VisibilityProperty.DefaultValue
                                    ? Visibility.Visible
                                    : Visibility.Hidden;
                            }
                            else
                            {
                                Visibility = TagScriptObject.VisibilityProperty.DefaultValue
                                    ? Visibility.Hidden
                                    : Visibility.Visible;
                            }
                        }
                    }
                }
            }
        }), DispatcherPriority.Render);
    }
}

如果您从 RsdDesignButtonBase class 派生自 FrameworkElement:

public class RsdDesignBase : FrameworkElement
{
    ...
}

...您应该能够为 TextBoxImageTextBlock 和任何其他 FrameworkElement 扩展和自定义它,例如:

public class TextBlock : RsdDesignBase {}

据我所知,您的控件做了两(三)件事情:

  • 它为控件设置特定的布局(可见性、背景等)
  • 它处理很多(反)序列化和处理 JSON 数据。
  • 如果某些数据可用或不可用,return 中的一些处理会修改 UI 属性(例如 Hide/Show)。

遵循“关注点分离”的有用原则 - 不是因为它听起来很学术或者 'awesome',而是因为你不会陷入耦合过于紧密的代码中 - 我宁愿推荐将所有这些逻辑放入 Attached Property 或一组附加属性中。并将控件作为第一个参数传递。

您不必更改很多实现,并且几乎可以将它用于派生自 Control 甚至 FrameworkElement

的所有 WPF 元素

https://docs.microsoft.com/en-us/dotnet/desktop/wpf/advanced/attached-properties-overview?view=netframeworkdesktop-4.8

如果我没理解错的话,您想为 Button、TextBox、Image 和 TextBlock(可能还有更多)添加一些功能并为所有 class 重用该代码,对吗?

您现在正在做的是在继承树的底部添加一个 Base。这样您就无法与其他 classes 共享它。理想情况下,您可能想要更改 System.Windows.Controls.Control,但那是 .NET Framework 的一部分,因此您无法更改它...

这是继承的缺点...

我看到的唯一可能是使用合成:

  • 创建一个包含您想要的逻辑的 class。我们称它为 RsdDesign。不需要超级class。它看起来很像您的 RsdDesignButtonBase。
  • 为每个要添加此功能的控件创建一个后代
  • 为这些后代提供类型为``RsdDesign```` 的私有成员。
  • 将控件的所有适用方法连接到成员。
    public class RsdDesign
    {
        private DataTag _visibilityTag;
        private DataTag _enabledTag;
        private DataTag _appearanceTag;
        public TagScriptObject TagScriptObject { get; set; }
        private readonly Timer _timer;
        private System.Windows.Controls.Control _parentControl
    
        protected RsdDesign(System.Windows.Controls.Control parentControl)
        {
            _parentControl = parentControl;
            _parentControl.Loaded += RSD_ButtonBase_Loaded;
            _parentControl.Unloaded += OnUnloaded;
            _timer = new Timer(1000);
            _timer.Elapsed += TimerOnElapsed;
        }
    
        // The rest of your RsdDesignButtonBase implementation
        // ...
    }
    
    public class RsdDesignButton: Button
    {
        private RsdDesign _design;
    
        public RsdDesignButton(...)
        {
            _design = new RsdDesign(this);
        }
    
        // You may need to hook some of the methods explicitly like this:
        private void EnabledTagOnDataChanged(object source, EventArgs args)
        {
            _design.EnabledTagOnDataChanged(source, args);
        }
    }

我还没有尝试过,但也许这个想法可以帮助您找到解决方案。