【问题标题】:How to use common base class from System.Windows.Controls.Control for the different superclasses like Button, TextBox,TextBlock如何将 System.Windows.Controls.Control 中的公共基类用于不同的超类,如 Button、TextBox、TextBlock
【发布时间】:2021-09-19 20:07:11
【问题描述】:

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

当我使用下面的类时

public class RsdDesignBase : Button
    {
....
}

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

有没有办法将我的 RsdDesignBase 类用作其他控件的基类?或任何其他方式来做到这一点。

我将在下面粘贴整个班级。它所做的是等待 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);
    }
}

【问题讨论】:

  • 您可能需要重新考虑“根据外部数据更改 Visibility、Enabled、Background、Foreground 属性” 是否值得作为基类来实现。检查附加的行为,这可能会导致在 xaml 中增加一两行,但如果您决定继续,与您现在必须做的工作相比,所需的工作要少得多。您可能还想了解 WPF 的不同 base classes(最好找到一个好的教程/解释它们的书籍),看看您的想法有多糟糕。

标签: c# wpf inheritance wpf-controls


【解决方案1】:

如果我理解正确,您想为 Button、TextBox、Image 和 TextBlock(可能还有更多)添加一些功能,并将该代码重用于所有类,对吗?

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

这是继承的缺点...

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

  • 创建一个包含所需逻辑的类。我们称之为RsdDesign。不需要超类。它看起来很像您的 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);
        }
    }

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

【讨论】:

  • 这完全实现了我想做的事情。现在我可以创建更小的设计类并在不同的控件上使用它们。
【解决方案2】:

如果您从 RsdDesignButtonBase 派生自 FrameworkElement 类:

public class RsdDesignBase : FrameworkElement
{
    ...
}

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

public class TextBlock : RsdDesignBase {}

【讨论】:

  • 如果控件将管理文本、模板和字体,您应该说 Control 类更好。 wpf.2000things.com/2014/10/10/…
  • WPF 中的子类化控件确实是最后的自定义方法。凭借所有 WPF 灵活性(样式、模板、附加行为等),除非控件非常不同,否则很少需要自定义控件。
  • “很少需要自定义控件”?嗯...这是一个大胆的声明,让您想知道您一直在开发什么样的应用程序。
  • @mm8,在过去的 10 年里,我在 WPF 中制作了大约 10 个控件,这些控件要么不存在(例如图形),要么解决了非常具体的问题。同时,我还发现了许多不需要的按钮、标签、列表等等。与子类化是主要自定义方法的 winforms 相比,它很少
  • 这个控件实际上是在运行时从外部 xaml 创建的。我知道这没有意义,但我正在开发一个可设计的 scada 程序。 github.com/icsharpcode/WpfDesigner 这用于设计 xaml。然后当它被保存时,我正在运行时创建控件。我将数据保存为 json 标记每个控件,然后在创建附加功能时对其进行解析。我将尝试使用附加属性。 TextBlock 不是什么大问题,我可以用不同的方式实现它。
【解决方案3】:

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

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

遵循“关注点分离”这一有用的原则——不是因为它听起来很学术或“很棒”,而是因为你不会陷入一堆过于紧密耦合的代码——我宁愿建议将所有将此逻辑转换为Attached Property 或一组附加属性。并将控件作为第一个参数传递。

您不必更改很多实现,您可以将它用于几乎所有派生自 Control 甚至 FrameworkElement 的 WPF 元素

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-23
    • 1970-01-01
    • 2012-10-15
    • 2015-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多