【发布时间】: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}";
}
虽然这种方法适用于简单的控件(到目前为止在大多数情况下),但有很多事情让我感到不安。
我的问题
- 是否(单向或双向)将控件的值绑定到其包装传递
DependencyProperty是一个问题,还是会产生任何陷阱?在我看来,不再有单一的事实来源,而是内部控件的值以及DependencyProperty的支持字段。是否可以确保不知道我的控件实现的人可以可靠地双向绑定我的控件(或单向绑定只读控件/不需要反向绑定时)。 -
x:Bind语法在这里是正确的方法还是我应该给内部控件一个x:Name并通过外部属性的 getter/setter 设置/获取它的值?我注意到孩子们在使用{x:Bind Variable, Mode=OneWay}并在代码隐藏中调用setter 时偶尔不会更新,而MyControlsName.Value =确实如此。或者我什至应该使用完全不同的方法。 -
INotifyPropertyChanged在这里可行吗?这个post 列出了各种优点,但在我看来,它是专门用于 MVVM 架构中的ViewModel的东西。
我真的可以在这里使用一些输入。这似乎应该是不言自明的,但是在广泛使用的 MVVM 方法之上在 Xaml 中实现绑定的无数方法使得这对于具有 Blazor 背景的人来说非常困难。
【问题讨论】:
标签: xaml data-binding uwp winui-3