How to bind List to a DependencyProperty

2019-09-21 20:11发布

问题:

I'm trying to learn about DependencyProperty. To do so I want to create a new UserControl which displays a list.

The location of this list must exist in the parent as a property. For this, I only have MainWindow, MainWindowViewModel (these are the parent) and the UserControl (the child) (which is currently using code behind).

In my MainWindow I have

<Grid>
    <uc:RecentList MessageList="{Binding Messages}" />    
</Grid>

And in the code behind

public MainWindow()
{
    InitializeComponent();
    this.DataContext = new MainWindowViewModel();
}

And the ViewModel

public MainWindowViewModel()
{
    this.Messages = new ObservableCollection<string>();
    this.Messages.Add("Item 1");
    this.Messages.Add("Item 2");
    this.T = "hi";
}

public ObservableCollection<string> Messages { get; set; }

In the UserControl I have

<Grid>
    <ListView ItemsSource="{Binding MessageList}"></ListView>
    <TextBlock Text="I'm such text to verify this control is showing" />
</Grid>

And the code behind is

public static readonly DependencyProperty MessageListProperty =
DependencyProperty.Register(
"MessageList", typeof(IEnumerable<string>), typeof(RecentList));

public IEnumerable<string> MessageList
{
    get { return (IEnumerable<string>)GetValue(MessageListProperty); }
    set { SetValue(MessageListProperty, value); }
}

The issue I have is the binding is not working. I can see this in the Output Window, with the Error:

Error 40 : BindingExpression path error: 'MessageList' property not found on 'object' ''MainWindowViewModel' (HashCode=26034861)'. BindingExpression:Path=MessageList; DataItem='MainWindowViewModel' (HashCode=26034861); target element is 'ListView' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')

I understand the issue but I am confused by it. It's looking in the right place (in the MainWindowViewModel) but it is looking I don't understand why the UserControl is looking for the MessageList in the MainWindowViewModel. I guess it's because that is where I set the datacontext but, I also thought it that if I added this.DataContext = this; to the UserControl's constructor then it's wrong (I've tried it, it didn't work either).

Updating my UserControl to

<ListView ItemsSource="{Binding MessageList, RelativeSource={RelativeSource Mode=TemplatedParent}}"></ListView>

Helps in the sense I don't get the error message, but I also don't see the result.

This is what I think is happening when the application loads:

  1. MainWindow loads
  2. MainWindow then see's the UserControl and notes it requires a property.
  3. Before WPF calls the UserControl constructor, it grabs the value of the property. It then initializes the component and automatically pushes the value to the UserControl's property

How can my UserControl use the Parents (MainWindow) property (Messages)

回答1:

The Binding in the UserControl's XAML should have the UserControl instance as its source object, e.g. like this:

<ListView ItemsSource="{Binding MessageList,
    RelativeSource={RelativeSource AncestorType=UserControl}}" />

Alternatively you could set x:Name on the UserControl and use an ElementName binding:

<UserControl ... x:Name="self">
    ...
    <ListView ItemsSource="{Binding MessageList, ElementName=self}" />
    ...
</UserControl>

Besides that you should usually not set the DataContext of the UserControl to itself (like DataContext = this;) because that would effectively prevent inheriting the DataContext from the UserControl's parent element, which is necessary for an "external" binding to work, like:

<uc:RecentList MessageList="{Binding Messages}" />