为 Xaml 自定义控件包装器定义可绑定属性的方法 UWP/WinUI

Approach for defining bindable properties for Xaml custom control wrappers for UWP/WinUI

出于可重用性的原因,我经常发现自己想要将一个或多个 UserControl 包装成一个。在其他 frameworks/libraries 中,这似乎要 common/trivial 多得多。 我认为在大多数使用 Xaml 的框架中,由于 MVVM 方法,定义自定义控件并不常见,这可能是没有关于此的有用文档的原因 关于这个主题有很多模糊相似的问题,但我没有找到一个简明扼要地阐述实现这一目标的理想方法的问题。

背景

这就是我在 Blazor 中的处理方式。使用 Parameter 属性 定义 属性 会导致 one-way 绑定并在传递的参数更改时更新组件。

[Parameter]
public bool Value { get; set; }

如果需要双向绑定,则必须显式定义相应的回调并使用 @bind- 语法在 parent 中配置 two-way 绑定。

 [Parameter]
    public EventCallback<bool> ValueChanged { get; set; }
    
    private async Task SetValue(bool value)
    {
        if (Value != value)
        {
            Value = value;
            await ValueChanged.InvokeAsync(value);
        }
    }

在 React 中,这非常相似,尽管 parent 必须显式传递事件回调,因为没有真正的 two-way 绑定。

问题

我不知道如何在 Xaml 中执行此操作。我认为在自定义控件上定义 属性 的通常方法是注册 DependencyProperty。到目前为止我的方法是:

这是一个滑块示例,它在旁边显示了它的离散索引。

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Slider x:Name="IndexSlider"
            Grid.Column="0"
            TickFrequency="1"
            Minimum="0"
            Value="{x:Bind Value, Mode=TwoWay}"
            Maximum="{x:Bind Maximum, Mode=OneWay}"
            Foreground="Blue"/>
        <TextBlock VerticalAlignment="Center"
            HorizontalAlignment="Right"
            Grid.Column="1"
            Margin="6,0,0,0"
            Text="{x:Bind local:WrappedSlider.DiscretePaginate(IndexSlider.Value, Maximum), Mode=OneWay}"/>
    </Grid>

public sealed partial class WrappedSlider : UserControl
    {
        public static readonly DependencyProperty ValueProperty = (/*...*/);

        public double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set
            {
                SetValue(ValueProperty, value);
            }
        }

        public static readonly DependencyProperty MaximumProperty = (/*...*/);

        public double Maximum
        {
            get { return (double)GetValue(MaximumProperty); }
            set
            {
                SetValue(MaximumProperty, value);
            }
        }

        public WrappedSlider()
        {
            InitializeComponent();
        }

        public static string DiscretePaginate(double currentIndex, double upperBound) => $"{currentIndex}/{upperBound}";
    }

虽然这种方法适用于简单的控件(到目前为止在大多数情况下),但有很多事情让我感到不安。

我的问题

  1. (one-way 或 two-way)将控件的值绑定到它的包装器传递 DependencyProperty 是一个问题还是它会产生任何陷阱?在我看来,不再有单一的真实来源,而是内部控件的值以及 DependencyProperty 的支持字段。是否可以确保对我的控件的实现一无所知的人可以可靠地 two-way 到我的控件(或 one-way 绑定 read-only controls/when 不需要反向绑定)。
  2. 这里 x:Bind 语法是正确的方法还是我应该通过外部 属性 的 [= 给内部控件一个 x:Name 和 set/get 它的值81=]?我注意到 children 在使用 {x:Bind Variable, Mode=OneWay} 和在 code-behind 中调用 setter 时偶尔不会更新,而 MyControlsName.Value = 会。或者我什至应该使用完全不同的方法。
  3. INotifyPropertyChanged 在这里可行吗? post 列出了各种优点,但在我看来,它是专用于 MVVM 架构中的 ViewModel

我真的可以在这里使用一些输入。这似乎应该是 self-explanatory,但是在广泛使用的 MVVM 方法之上 Xaml 中实现绑定的无数方法使得这对于具有 Blazor 背景的人来说非常困难。

Can someone who has no idea about my control's implementation be ensured that he can reliably two-way to my control (or one-way bind for read-only controls/when reverse binding is not needed).

是的,当您在 属性 和控件之间创建数据绑定时。这是他们可以相互联系并接收来自另一方的变化的桥梁。除非您手动更改代码中的值,否则它是可靠的。

Is the x:Bind syntax the right approach here or should I give the inner control a x:Name and set/get its value through the outer property's getter/setter?

使用 x:Bind or Binding 语法是实现数据绑定的正确方法。

Might INotifyPropertyChanged be feasible here?

INotifyPropertyChanged 接口用于数据绑定。当你想使用双向绑定时,你需要实现这个接口。这是一个数据绑定实现。通常,MVVM模式使用数据绑定,因此,MVVM模式通常使用INotifyPropertyChanged接口。