【问题标题】:UWP Custom Control, Can't Use Binding, only x:Bind, How To Adapt?UWP自定义控件,不能使用Binding,只能x:Bind,如何适配?
【发布时间】:2018-09-17 14:45:52
【问题描述】:

我在 UWP 中创建了一个自定义控件,但是我遇到的问题是微软也提供了自定义控件,所以我将使用 UWP 社区工具包 OrbitView 作为示例。以下 3 个绑定有效:

<Grid>
    <TextBlock Text="{Binding MyProperty}"/>
    <TextBlock Text="{x:Bind PageViewModel.MyProperty, Mode=OneWay}"/>
    <Toolkit:OrbitView MinItemSize="{x:Bind PageViewModel.MyProperty, Mode=OneWay}" />
</Grid>

标准控件 (TextBlock) 可与 Binding 或 x:Bind 一起使用。但是如果我想在自定义控件上使用 Binding:

<Toolkit:OrbitView MinItemSize="{Binding MyProperty}" />

它没有。在此处和网络上搜索我正在努力弄清楚发生了什么以及为什么。一般的解决方案似乎是只使用 x:Bind。但是,我想将自定义控件放在 UserControl 中,因为我希望在运行时使用 XamlReader 从外部 Xaml 文件加载它的选项。我的理解是你不能在这种情况下使用 x:Bind ,因为它需要在编译时存在。有什么解决方案可以完成我在 UWP 中的目标吗?

【问题讨论】:

  • 这很奇怪,因为自定义控件应该具有相同的 DataContext。只是为了测试,您可以将自定义控件上的 DataContext 指向提供的 ViewModel 并尝试一下,即&lt;Toolkit:OrbitView DataContext={Binding} /&gt; 或类似的东西。您认为它应该起作用是对的,所以我不确定这里发生了什么。我会说,如果 x:Bind 工作正常,你最好使用它,因为它更快。
  • 使用绑定时,DependencyControl 属性上的设置器不会触发。 Binding 采用 DependencyObject 查找 SetValue 方法并直接绑定到它。 DependendencyObjects 是他们自己的面包。
  • 至于文本块,我理解 DataContext 是一样的,但我不明白为什么 Toolkit:OrbitView 看不到它。您如何确定它不具有约束力? OrbitView 上的什么不工作?我只是问,因为如果您正在查看用于绑定的 setter,正如我所提到的,您不会在那里看到它。
  • 另一种不应该采用这种方式的解决方法是仅 xBind 到 DataContext。 &lt;Toolkit:OrbitView DataContext="{x:Bind PageViewModel}" /&gt; 这样你的内部 UserControl '应该' 仍然像往常一样绑定到 DataContext 。我承认,即使这样可以解决问题,我也想知道并理解为什么它一开始就不起作用。我几乎可以肯定,它应该按照您使用&lt;Toolkit:OrbitView MinItemSize="{Binding MyProperty}" /&gt; 的方式工作,从技术上讲,您做得对,所以我很困惑。也请发布 OrbitView XAML。
  • 依赖属性包装器使用 x:Bind 触发。您是说他们不会使用 Binding 触发,这可以解释很多,因为我一直在使用它来查看它是否有效。我的 dp 包装器设置器在自定义控件中运行其他代码,所以这就是为什么它们从来没有工作过,对于 OrbitView,我只是在设置器上放了一个断点,然后用 x:Bind 观察它,但不是用 Binding。所以我想这里的谜团已经解决了,谢谢。我确实想出了如何在 UserControl 中使用 x:Bind,所以我想我会这样做。

标签: c# xaml data-binding uwp


【解决方案1】:

好的,这就是如果您在 setter 中运行其他代码时它无法正常工作的原因以及如何让它工作。

这是实现依赖属性并在 setter 上执行代码的正确方法。这是在 UWP 项目中完成的,因为您的问题是 UWP,但对于任何项目类型的所有依赖属性具有完全相同的原则。

public sealed partial class MainPage : Page
{
    public MainPage() => InitializeComponent();        

    public int SomeValue
    {
        get => (int)GetValue(SomeValueProperty);
        set => SetValue(SomeValueProperty, value);
    }

    public static readonly DependencyProperty SomeValueProperty = //Notice this is static. It's bound to an internal static hash table of some sort; I use to know exactly but forgot.
        DependencyProperty.Register(
            nameof(SomeValue), /*The name of the property to register against. 
                               * The static version is always the name of the property ended with Property
                               * i.e. SomeValue property is SomeValueProperty dependency property */                                                 
            typeof(int), //This is the type used to describe the property.
            typeof(MainPage), //This is the type the dependency property relates to. 

            /* Below this is the magic. It's where we supply the property meta data and can be delivered different ways.
            * For this example I will supply only the default value and the event we want to use when the value is changed.
            * Note: 
            * The event we supply is fired ONLY if the value is changed. This event is what we need to use to handle changes in the setter to cover binding operations as well. */
            new PropertyMetadata(default(int),  /* This is the default value the dependency property will have. 
                                                * It can be whatever you decide but make sure it works with the same type or you'll most likely get an error. */

                /* This is the event fired when the value changes.
                 * Note: Dependency properties binding and events always operate on the UI thread. Cross threading will throw exceptions. */
                new PropertyChangedCallback((s, e) =>
                {
                    var mainPage = s as MainPage; //The sender of the callback will always be of the type it's from.

                    /* The values given from the e argument for OldValue and NewValue should be of type int in this example...
                     * but since we can't gaurantee the property is setup properly before here I always add a check. */
                    if (e.OldValue is int oldValue) { }
                    if (e.NewValue is int newValue) 
                    { 
                         /* Now do what you want with the information.  This is where you need to do custom work instead of using the setter.
                          * Note: If you need to work on something in the MainPage remember this is a static event and you'll need to refer to the sender or s value in this case.
                          * I've converted s to the variable mainPage for easy referencing here. */
                         mainPage.MyCustomControl.Value = newValue;  //Note: The custom control should bind to this as well via XAML making this pointless. I set the value here just for educational purposes.
                    }
                })));

}

依赖属性不应该看起来那么可怕,实际上也不是,但是我添加了很多 cmets 来帮助您更轻松地浏览它。希望这会有所帮助:)

【讨论】:

  • 我将重新设计我的自定义控件以不使用包装器设置器并改用它。谢谢!
  • 没问题;乐意效劳。还查看了个人资料,继续努力。
猜你喜欢
  • 2017-02-07
  • 1970-01-01
  • 1970-01-01
  • 2012-09-10
  • 1970-01-01
  • 1970-01-01
  • 2019-07-29
  • 2020-08-05
  • 1970-01-01
相关资源
最近更新 更多