【问题标题】:Approach for defining bindable properties for Xaml custom control wrappers for UWP/WinUI为 UWP/WinUI 的 Xaml 自定义控件包装器定义可绑定属性的方法
【发布时间】:2022-01-13 09:31:38
【问题描述】:

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

背景

这就是我在 Blazor 中的工作方式。使用 Parameter 属性定义属性会导致单向绑定,并在传递的参数更改时更新组件。

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

如果需要双向绑定,则必须明确定义相应的回调,并使用@bind- 语法在父级内部配置双向绑定。

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

在 React 中这非常相似,尽管父级必须显式传递事件回调,因为没有真正的双向绑定。

问题

我无法理解如何在 Xaml 中执行此操作。我认为在自定义控件上定义属性的常用方法是注册DependencyProperty。到目前为止,我的方法是:

  • 定义一个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. 是否(单向或双向)将控件的值绑定到其包装传递DependencyProperty 是一个问题,还是会产生任何陷阱?在我看来,不再有单一的事实来源,而是内部控件的值以及DependencyProperty 的支持字段。是否可以确保不知道我的控件实现的人可以可靠地双向绑定我的控件(或单向绑定只读控件/不需要反向绑定时)。
  2. x:Bind 语法在这里是正确的方法还是我应该给内部控件一个x:Name 并通过外部属性的 getter/setter 设置/获取它的值?我注意到孩子们在使用{x:Bind Variable, Mode=OneWay} 并在代码隐藏中调用setter 时偶尔不会更新,而MyControlsName.Value = 确实如此。或者我什至应该使用完全不同的方法。
  3. INotifyPropertyChanged 在这里可行吗?这个post 列出了各种优点,但在我看来,它是专门用于 MVVM 架构中的 ViewModel 的东西。

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

【问题讨论】:

    标签: xaml data-binding uwp winui-3


    【解决方案1】:

    能否确保不了解我的控件实现的人能够可靠地双向访问我的控件(或单向绑定只读控件/不需要反向绑定时)。

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

    这里的 x:Bind 语法是正确的方法还是应该给内部控件一个 x:Name 并通过外部属性的 getter/setter 设置/获取它的值?

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

    INotifyPropertyChanged 在这里可行吗?

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

    【讨论】:

    • 谢谢,这涵盖了我的大部分问题;关于我的第一个问题的前半部分有什么建议吗?我注意到通过运行时调用的某些事件调用的异步调用(例如,导航到页面后获取数据)在通过常规 getter 调用时无法启动状态更新。虽然显式命名和调用控件有效,但它并不总是理想的。另一方面,两者都没有定义私有DependencyProperty
    • @Beltway 请使用您正在使用的代码 sn-p 发布另一个问题,以便我们专注于那个问题。
    猜你喜欢
    • 2016-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-29
    • 1970-01-01
    • 2011-05-11
    • 1970-01-01
    相关资源
    最近更新 更多