将 WPF UserControl 中的内容绑定到其属性的不同方式的优点/缺点是什么?绑定、属性、缺点、优点

2023-09-08 09:35:48 作者:孤独醉酒人

When starting to work with WPF UserControls, I stumbled upon several ways to bind content of a UserControl to one of its properties.

Here's example C# code for my control:

 public sealed partial class MyUserControl : UserControl
 {
    public MyUserControl()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty TheTextProperty =
        DependencyProperty.Register("TheText",
                                    typeof (string),
                                    typeof(MyUserControl),
                                    new FrameworkPropertyMetadata(0, 
                                        FrameworkPropertyMetadataOptions.
                                             BindsTwoWayByDefault)
            );

    public string TheText
    {
        get { return (string)GetValue(TheTextProperty); }
        set { SetValue(TheTextProperty, value); }
    }
}
wpf studio Actipro WPF Controls wpf studio v15.1.0624官方版

And here are the different ways I found to bind content to this property:

Content uses binding with RelativeSource/AncestorType

<UserControl x:Class="MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
    <StackPanel>
        <TextBox Text="{Binding TheText,
                        RelativeSource={RelativeSource
                                        AncestorType={x:Type UserControl}}}" />
    </StackPanel>
</UserControl>

DataContext of visual tree root is set to UserControl in XAML

<UserControl x:Class="MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
    <StackPanel DataContext="{Binding
                              RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}">
        <TextBox Text="{Binding TheText}" />
    </StackPanel>
</UserControl>

DataContext of visual tree root is set to UserControl in constructor

<UserControl x:Class="MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
    <StackPanel x:Name="VisualTreeRoot">
        <TextBox Text="{Binding TheText}" />
    </StackPanel>
</UserControl>

Here's the constructor:

    public MyUserControl()
    {
        InitializeComponent();
        VisualTreeRoot.DataContext = this;
    }

Last but not least: A warning for other people new to programming UserControls in WPF

The first time I wanted to bind content of a UserControl to one of its properties, I though "hey, let's just set the DataContext of the UserControl directly to itself":

<UserControl x:Class="MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             DataContext="{Binding RelativeSource={RelativeSource Self}}">

Or:

    public MyUserControl()
    {
        InitializeComponent();
        this.DataContext = this;
    }

However, this does not work if a user of the UserControl wants to bind its properties to other binding sources. The UserControl needs to inherit the DataContext from its parent to make this work. By overwriting it as presented above, the bindings won't find their sources anymore.

My final questions:

What are the advantages and disadvantages of each of the presented methods? When should you use which method? Are there more methods?

解决方案

Well in the first case there is no DataContext for the TextBox set to any of it's Parent's. Hence you're having to tell the TextBox where in the VisualTree is the control with that property directly on it. Second case DataContext is set on StackPanel which the TextBox inherit's accordingly. This is better than approach one if you have multiple control's in the StackPanel Setting DataContext on the UserControl itself is not always wrong(via constructor or xaml). I say this because if you have 20 control's out of which 15 that need to use properties defined in it's current UserControl class and 5 that need's the parent of the UserControl's DataContext, You can always use a RelativeSource FindAncestor binding on the minority.

Only "method" I can think of that can show pt3 I mentioned is something like

<!--  Can change type to another userControl too and specify Ancestorlevel  -->
<TextBlock Text="{Binding TheText, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />

^^ This will work fine even if the TextBlock's parent UserControl has itself as it's DataContext

As far as when to use what.

That's just a logical choice, if you have multiple siblings needing the same DataContext, Setting DataContext to their parent is the right answer. I always tend to set DataContext on the Top-most element possible and if any one or two items need variations bind them out accordingly.

If in MVVM, your VM become the DataContext almost always of the Top level item of the View. everything else Bind's directly to the element whose property they need pretty much.