简体   繁体   中英

How to bind multiple properties in a UserControl

Let's say we have a UserControl like this:

<UserControl x:Class="...>
    <StackPanel>
        <TextBlock Name="TextBlock1" />
        <TextBlock Name="TextBlock2" />
        <TextBlock Name="TextBlock3" />
        ...
        <TextBlock Name="TextBlock10" />
    </StackPanel>
</UserControl>

And we have properties defined like this:

public string Text1 { get; set; }
public string Text2 { get; set; }
public string Text3 { get; set; }
...
public string Text10 { get; set; }

And know I want to bind all those TextBlocks to all those properties. There are obviously multiple ways of doing this and I wonder about the (dis-)advantages of the different ones. Let's make a list:

  1. My first approach was this:

     <TextBlock Name="TextBlock1" Text="{Binding Path=Text1, RelativeSource={RelativeSource AncestorType=UserControl}}" /> 

    This works and is fairly simple, but it is a lot of redundant code if I have to type it for all the TextBlocks. In this example we could just copy-paste, but the UserControl could be more complex.

  2. When I googled the problem I found this solution:

     <UserControl ... DataContext="{Binding RelativeSource={RelativeSource Self}}"> <TextBlock Name="TextBlock1" Text="{Binding Path=Text1}" /> 

    This looks pretty clean in the xaml, however DataContext could be used otherwise. So if someone uses this UserControl and changes the DataContext, we are screwed.

  3. Another common solution seems to be this:

     <UserControl ... x:Name="MyUserControl"> <TextBlock Name="TextBlock1" Text="{Binding Path=Text1, ElementName=MyUserControl}" /> 

    This has the same problem as 2. though, Name could be set from somewhere else.

  4. What if we write our own MarkupExtension?

     public class UserControlBindingExtension : MarkupExtension { public UserControlBindingExtension() { } public UserControlBindingExtension(string path) { this.Path = path; } private Binding binding = null; private string path; [ConstructorArgument("path")] public string Path { get { return path; } set { this.path = value; binding = new Binding(this.path); binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(UserControl), 1); } } public override object ProvideValue(IServiceProvider serviceProvider) { if(binding == null) return null; return binding.ProvideValue(serviceProvider); } } 

    Now we can do something like this:

     <UserControl ... xmlns:self="clr-namespace:MyProject"> <TextBlock Name="TextBlock1" Text="{self:UserControlBinding Path=Text1}" 

    Neat! But I'm not conviced if my implementation is bulletproof and I would prefer not writing my own MarkupExtension.

  5. Similar to 4. we could do this:

     public class UserControlBindingHelper : MarkupExtension { public UserControlBindingHelper() { } public UserControlBindingHelper(Binding binding) { this.Binding = binding; } private Binding binding; [ConstructorArgument("binding")] public Binding Binding { get { return binding; } set { binding = value; binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(UserControl), 1); } } public override object ProvideValue(IServiceProvider serviceProvider) { if (binding == null) return null; return binding.ProvideValue(serviceProvider); } } 

    Which would result in code like this:

     <TextBlock Name="TextBlock1" Text="{self:UserControlBindingHelper {Binding Text1}}" /> 
  6. We could do it in code!

     private void setBindingToUserControl(FrameworkElement element, DependencyProperty dp, string path) { Binding binding = new Binding(path); binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(StateBar), 1); element.SetBinding(dp, binding); } 

    And then we do this:

     setBindingToUserControl(this.TextBlock1, TextBlock.Text, "Text1"); 

    Fairly nice, but it makes the xaml harder to read, since the information about the bindings is missing now.

  7. What other good/interesting options are there?

So what is the way to go in which situation? Did I make a mistake somewhere?
To specify the question:
Are all those ways correct? Are some superior to others?

It looks like you've made a lot of good tests here! As you've probably already noticed, most of your ways of doing it are working, but i would recommend using your first approach. While it might look a bit repetitive, it is very explicit and fairly simple to maintain. It also makes your code easily readable by anyone who's not so expert in xaml.

You already know the issues with solution 2 and 3. Creating your own markup extension is a bit overkill(at least in this case) and makes your code harder to understand. Solution 6 works fine but as you said, it makes it impossible to know to what the textblock is bind to within the xaml.

Just use #2, and assume the DataContext is correct. Thats the standard. It's the responsibility of the person who uses the User Control to ensure that the DataContext is set correctly.

Anything else is just adding unnecessary complexity.

See ReSharper WPF error: "Cannot resolve symbol "MyVariable" due to unknown DataContext" .

Its common practice for anyone using a User Control to set up the DataContext to RelativeSource Self see How do I bind to RelativeSource Self?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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