简体   繁体   English

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

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

I commonly find myself wanting to wrap one or multiple UserControl s into a single one for reusability reasons.出于可重用性的原因,我经常发现自己想将一个或多个UserControl包装成一个。 In other frameworks/libraries this seems to be far more common/trivial to achieve.在其他框架/库中,这似乎更常见/更容易实现。 I take that in most frameworks utilizing Xaml, defining custom controls is not as common due to the MVVM approach, which might be the reason there is no useful documentation for this around There are a lot of vaguely similar questions on this topic, but I failed to find one that concisely elaborates on the ideal way to achieve this.我认为在大多数使用 Xaml 的框架中,由于 MVVM 方法,定义自定义控件并不常见,这可能是没有有用的文档的原因 关于这个主题有很多模糊相似的问题,但我失败了找到一个简明扼要地阐述实现这一目标的理想方法的方法。

Background背景

This is how I would go about in in Blazor.这就是我在 Blazor 中的 go 的方式。 Defining a property with the Parameter property results in a one-way binding and updates the component when that passed parameter changes.使用Parameter属性定义属性会导致单向绑定,并在传递的参数更改时更新组件。

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

If Two way binding is desired, one has to define a corresponding callback explicitly and configure two-way binding inside the parent with the @bind- syntax.如果需要双向绑定,则必须显式定义相应的回调,并使用@bind-语法在父级内部配置双向绑定。

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

In React this is quite similar though the parent has to explicitly pass an event callback as there is no true two-way binding.在 React 中,这非常相似,尽管父级必须显式传递事件回调,因为没有真正的双向绑定。

Issue问题

I can't get my head around how to do this in Xaml.我无法理解如何在 Xaml 中执行此操作。 I take that the usual way to define a property on a custom control is a registered DependencyProperty .我认为在自定义控件上定义属性的常用方法是注册的DependencyProperty My approach so far was:到目前为止,我的方法是:

  • Define a DependencyProperty ;定义一个DependencyProperty
  • Bind that the property's value to the wrapped control's value;将该属性的值绑定到包装控件的值;
    • if it is readonly, use one-way binding;如果是只读的,使用单向绑定;
    • if it is editable from within the custom control, two-way bind to the inner control's value to the property.如果它可以从自定义控件中编辑,则双向绑定到内部控件的属性值。

Here is a sample of a slider that display's its discrete index next to it.这是 slider 的示例,它旁边显示其离散索引。

<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}";
    }

While this approach works for simple controls (in most cases so far) there is a plenty of things disturbing me.虽然这种方法适用于简单的控制(到目前为止在大多数情况下),但有很多事情让我感到不安。

My questions我的问题

  1. Is (one-way or two-way) binding a control's value to its wrappers passed DependencyProperty an issue at all or does it yield any pitfalls? (单向或双向)将控件的值绑定到其传递的DependencyProperty的包装器是一个问题,还是会产生任何陷阱? It appears to me as there is no single source of truth anymore, but the inner control's value as well as the DependencyProperty 's backing field.在我看来,不再有单一的事实来源,而是内部控件的值以及DependencyProperty的支持字段。 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).是否可以确保不了解我的控件实现的人可以可靠地双向绑定我的控件(或单向绑定只读控件/不需要反向绑定时)。
  2. 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语法在这里是正确的方法还是应该给内部控件一个x:Name并通过外部属性的 getter/setter 设置/获取它的值? I noticed children occasionally not updating when using {x:Bind Variable, Mode=OneWay} and invoking the setter inside the code-behind, while doing MyControlsName.Value = did.我注意到孩子们在使用{x:Bind Variable, Mode=OneWay}并在代码隐藏中调用 setter 时偶尔不会更新,同时执行MyControlsName.Value = did。 Or should I even use a entirely different approach.或者我什至应该使用完全不同的方法。
  3. Might INotifyPropertyChanged be feasible here? INotifyPropertyChanged在这里可行吗? This post lists various advantages, but it appears to me as something that's exclusively used for the ViewModel in a MVVM architecture.这篇文章列出了各种优点,但在我看来,它是专门用于 MVVM 架构中的ViewModel的东西。

I really could use some input here.我真的可以在这里使用一些输入。 This seems like something that should be self-explanatory, but the countless ways to implement bindings in Xaml on top of the widely used MVVM approach makes this very obstructive for someone with a background in Blazor.这似乎应该是不言自明的,但是在广泛使用的 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).是否可以确保不了解我的控件实现的人可以可靠地双向绑定我的控件(或单向绑定只读控件/不需要反向绑定时)。

Yes, when you create data binding between property and control.是的,当您在属性和控件之间创建数据绑定时。 This is the bridge where they could connect with each other and receive changes from the other side.这是他们可以相互连接并接收来自另一方的更改的桥梁。 It's reliable unless you manually change the value in code.除非您手动更改代码中的值,否则它是可靠的。

Is the x:Bind syntax the right approach here or should I give the inner control ax:Name and set/get its value through the outer property's getter/setter? x:Bind 语法在这里是正确的方法还是应该给内部控件 ax:Name 并通过外部属性的 getter/setter 设置/获取其值?

Using x:Bind or Binding syntax is the correct way to implement data binding.使用x:BindBinding语法是实现数据绑定的正确方法。

Might INotifyPropertyChanged be feasible here? INotifyPropertyChanged 在这里可行吗?

INotifyPropertyChanged interface is used for data-binding. INotifyPropertyChanged接口用于数据绑定。 When you want to use two-way binding, you will need to implement this interface.当你想使用双向绑定时,你需要实现这个接口。 This is a data-binding implementation.这是一个数据绑定实现。 Generally, MVVM mode uses data-binding, as a result, INotifyPropertyChanged interface is often used in MVVM mode. MVVM模式一般采用数据绑定,因此在MVVM模式下经常使用INotifyPropertyChanged接口。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM