在WPF的依赖属性模式中,自动将GetValue() 的返回值转换为预期的返回类型
(Edit Note): 我没把我的问题与一个更简单的示例结合起来,以聚焦于DependencyProperty的重复和冗长代码问题,而 LabeledConainer 和 LabeledTextBox 实际上对DependencyProperty问题来说只是次要的。
我正在使用WPF和 .NET的 C#,并在用依赖属性来构建一些基本控件。我想让这种模式尽量可重复、尽量简单,以便在复制并粘贴一个依赖属性、编辑需要的部分来创建新属性时,尽量少改动。
我有一个 Textbox,在 DockPanel 中附带了一个标签。LabeledContainer 是通用控件,里面包含了标签和控件在 DockPanel 中的所有逻辑,以及用于快速指定标签应出现在控件的左侧、右侧,还是在上方或下方的更多选项,尽管这一切都能工作,我认为与问题无关。
下面是一个简化的示例文件:
namespace MyApp.Controls
{
using ContentType = TextBox;
using MyType = LabeledTextBox;
public partial class LabeledTextBox : LabeledContainer
{
public new ContentType Content { get { return base.Content as ContentType; } set { base.Content = value; } }
public static readonly DependencyProperty SetTextBoxTextProperty = DependencyProperty.Register(
nameof(TextBoxText), typeof(string), typeof(MyType));
public string TextBoxText
{
get { return (string)GetValue(SetTextBoxTextProperty); }
set { SetValue(SetTextBoxTextProperty, value); }
}
}
public LabeledLabel() : base()
{
InitializeComponent();
}
}
我的问题在这行的 (string) 强制转换上
get { return (string)GetValue(SetTextBoxTextProperty); }
并不是因为它会导致错误,而是因为如果我拷贝/粘贴来创建一个新的依赖属性,以暴露诸如 HorizontalAlignment 的 TextBox 之类的东西——这正是我现在经常在这里做的事情——我就得修改它。
有没有某种通用逻辑,可以让我在这里自动将返回类型强制转换为所需类型,因为它已经从属性的类型中得知?是否有某种语法可以引用当前作用域的返回类型?最好是在编译时就能解析,以避免对性能造成任何影响,但我也愿意看看其他解决方案。
更广泛地说,这就是我为基本依赖属性定下的模式(注意使用 MyType 将所有这些属性的注册集中到一行)。我想知道它是否在某些方面不是最优,是否有其他方法可以解决我的问题、进一步减少重复代码,我是不是就这么做错了?
我在业余时间有时会用Rust编程,但我想到该语言中的宏——这本来是解决这些重复的基本依赖属性的显而易见的解决方案——不过对C#是否存在这类工具我并不熟悉。
(Edit):将 'ContentType' using声明改为TextBox,以代替在整理示例代码时错误地使用的标签。
解决方案
与其复制粘贴,不如使用代码生成器:
DependencyPropertyGenerator
依赖属性、路由事件和弱事件源生成器,适用于WPF/UWP/WinUI/Uno/Avalonia/MAUI平台。
用法
```cs using DependencyPropertyGenerator; using System.Windows.Controls;
nullable enable
namespace H.Generators.IntegrationTests;
[DependencyProperty
("IsSpinning", DefaultValue = true, Category = "Category", Description = "Description")] public partial class MyControl : UserControl { // Optional partial void OnIsSpinningChanged(bool oldValue, bool newValue) { } } ``` 将生成:
```cs //HintName: MyControl.Properties.IsSpinning.generated.cs
nullable enable
namespace H.Generators.IntegrationTests { public partial class MyControl { ///
/// Description public static readonly global::System.Windows.DependencyProperty IsSpinningProperty = global::System.Windows.DependencyProperty.Register( name: "IsSpinning", propertyType: typeof(bool), ownerType: typeof(global::H.Generators.IntegrationTests.MyControl), typeMetadata: new global::System.Windows.FrameworkPropertyMetadata( defaultValue: (bool)true, flags: global::System.Windows.FrameworkPropertyMetadataOptions.None, propertyChangedCallback: static (sender, args) => { ((global::H.Generators.IntegrationTests.MyControl)sender).OnIsSpinningChanged( (bool)args.OldValue, (bool)args.NewValue); }, coerceValueCallback: null, isAnimationProhibited: false), validateValueCallback: null);
/// Default value: true ////// <summary> /// Description<br/> /// Default value: true /// </summary> [global::System.ComponentModel.Category("Category")] [global::System.ComponentModel.Description("Description")] public bool IsSpinning { get => (bool)GetValue(IsSpinningProperty); set => SetValue(IsSpinningProperty, value); } partial void OnIsSpinningChanged(); partial void OnIsSpinningChanged(bool newValue); partial void OnIsSpinningChanged(bool oldValue, bool newValue); }} ```
metalama的 [DependencyProperty]
WPF依赖属性
WPF的依赖属性是一个你可以从WPF标记语言中设置并通过一个Binding元素绑定到另一个对象(例如视图模型)的属性。与C#属性不同,你必须使用
DependencyProperty.Register编程注册依赖属性。要把一个依赖属性暴露为C#属性,通常需要编写样板代码,如下例所示:``` class MyClass { public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.Register( nameof(IsEnabled), typeof(bool), typeof(MyClass));
public bool IsEnabled { get { return (bool)GetValue(IsEnabledProperty); } set { SetValue(IsEnabledProperty, value); } }} ```
与其编写这段样板代码,在C#自动属性上添加 [DependencyProperty] 方面,将其转换为WPF依赖属性:
class MyClass { [DependencyProperty] public bool IsEnabled { get; set; } }[DependencyProperty] 方面提供:
- 零样板代码。
- 与Metalama.Patterns.Contracts集成,使用诸如 [NotNull] 或 [Url] 等方面来验证依赖属性(详情请参阅Metalama.Patterns.Contracts)。
- 支持自定义的前置和后置赋值回调。
- 根据属性访问器的可访问性检测可变或只读的依赖属性。
- 处理默认值。