37

Can anyone help with the following - been playing about with this but can't for the life of me get it to work.

I've got a view model which contains the following properties;

public ObservableCollection<Rule> Rules { get; set; }
public Rule SelectedRule { get; set; }

In my XAML I've got;

<ListBox x:Name="lbRules" ItemsSource="{Binding Path=Rules}" 
         SelectedItem="{Binding Path=SelectedRule, Mode=TwoWay}">
<ListBox.ItemTemplate>
    <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Name:" />
                <TextBox x:Name="ruleName">
                    <TextBox.Text>
                        <Binding Path="Name" UpdateSourceTrigger="PropertyChanged" />
                    </TextBox.Text>
                </TextBox>
            </StackPanel>
    </DataTemplate>
</ListBox.ItemTemplate>

Now the ItemsSource works fine and I get a list of Rule objects with their names displayed in lbRules.

Trouble I am having is binding the SelectedRule property to lbRules' SelectedItem. I tried binding a textblock's text property to SelectedRule but it is always null.

<TextBlock Text="{Binding Path=SelectedRule.Name}" />

The error I'm seeing in the output window is: BindingExpression path error: 'SelectedRule' property not found.

Can anyone help me with this binding - I can't see why it shouldn't find the SelectedRule property.

I then tried changing the textblock's text property as bellow, which works. Trouble is I want to use the SelectedRule in my ViewModel.

<TextBlock Text="{Binding ElementName=lbRules, Path=SelectedItem.Name}" />

Thanks very much for your help.

Kjartan
  • 18,591
  • 15
  • 71
  • 96
Oli Baylis
  • 373
  • 1
  • 3
  • 4

6 Answers6

27

First off, you need to implement INotifyPropertyChanged interface in your view model and raise the PropertyChanged event in the setter of the Rule property. Otherwise no control that binds to the SelectedRule property will "know" when it has been changed.

Then, your XAML

<TextBlock Text="{Binding Path=SelectedRule.Name}" />

is perfectly valid if this TextBlock is outside the ListBox's ItemTemplate and has the same DataContext as the ListBox.

arconaut
  • 3,227
  • 2
  • 27
  • 36
  • That's it, silly me, I hadn't implemented the interface on that view model. Been banging my head against a wall all morning on that one! Thanks very much for your help. Also thanks very much to everyone else, especially Yacoder, for taking the time to have a look at this. – Oli Baylis Jan 06 '10 at 14:27
  • 1
    Thanks from me as well. Just had the exact problem. I implemented the interface but forgot to raise the event. – A Jackson Sep 19 '11 at 21:05
13

Inside the DataTemplate you're working in the context of a Rule, that's why you cannot bind to SelectedRule.Name -- there is no such property on a Rule. To bind to the original data context (which is your ViewModel) you can write:

<TextBlock Text="{Binding ElementName=lbRules, Path=DataContext.SelectedRule.Name}" />

UPDATE: regarding the SelectedItem property binding, it looks perfectly valid, I tried the same on my machine and it works fine. Here is my full test app:

XAML:

<Window x:Class="TestWpfApplication.ListBoxSelectedItem"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="ListBoxSelectedItem" Height="300" Width="300"
    xmlns:app="clr-namespace:TestWpfApplication">
    <Window.DataContext>
        <app:ListBoxSelectedItemViewModel/>
    </Window.DataContext>
    <ListBox ItemsSource="{Binding Path=Rules}" SelectedItem="{Binding Path=SelectedRule, Mode=TwoWay}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Name:" />
                    <TextBox Text="{Binding Name}"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Window>

Code behind:

namespace TestWpfApplication
{
    /// <summary>
    /// Interaction logic for ListBoxSelectedItem.xaml
    /// </summary>
    public partial class ListBoxSelectedItem : Window
    {
        public ListBoxSelectedItem()
        {
            InitializeComponent();
        }
    }


    public class Rule
    {
        public string Name { get; set; }
    }

    public class ListBoxSelectedItemViewModel
    {
        public ListBoxSelectedItemViewModel()
        {
            Rules = new ObservableCollection<Rule>()
            {
                new Rule() { Name = "Rule 1"},
                new Rule() { Name = "Rule 2"},
                new Rule() { Name = "Rule 3"},
            };
        }

        public ObservableCollection<Rule> Rules { get; private set; }

        private Rule selectedRule;
        public Rule SelectedRule
        {
            get { return selectedRule; }
            set
            {
                selectedRule = value;
            }
        }
    }
}
Massimiliano
  • 16,770
  • 10
  • 69
  • 112
  • Thanks for both replies. I see what you mean but I'm not sure if I fully understand. The thing is, the DataTemplate works fine - I understand that the context within the template is the Rule. It's the binding of the SelectedItem of lbRules to my SelectedRule property which I'm having trouble with. Are you saying that by setting the ItemSource I'm also indirectly setting the DataContext of the listbox? Sorry if I didn't make that clear. I was just using the binding to a textblock to help me debug through the problem. – Oli Baylis Jan 06 '10 at 11:53
  • Thanks very much for your reply. Your answer was very useful to me in getting to my solution. As it turns out my problem was that I wasn't implementing InotifyPropertyChanged which is why I've marked Arconaut's reply as the answer. Thanks for your time. One thing that does interest me though is why your solution, doesn't implement the INotifyPropertyChanged interface but yet still works... Anyway, thanks again for your time. – Oli Baylis Jan 06 '10 at 14:30
  • 1
    You're right, mine will only work one way, UI -> VM. To make VM -> UI binding work I will need the INotifyPropertyChanged. – Massimiliano Jan 06 '10 at 14:47
3

Yocoder is right,

Inside the DataTemplate, your DataContext is set to the Rule its currently handling..

To access the parents DataContext, you can also consider using a RelativeSource in your binding:

<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ____Your Parent control here___ }}, Path=DataContext.SelectedRule.Name}" />

More info on RelativeSource can be found here:

http://msdn.microsoft.com/en-us/library/system.windows.data.relativesource.aspx

Arcturus
  • 26,677
  • 10
  • 92
  • 107
0

For me, I usually use DataContext together in order to bind two-depth property such as this question.

<TextBlock DataContext="{Binding SelectedRule}" Text="{Binding Name}" />

Or, I prefer to use ElementName because it achieves bindings only with view controls.

<TextBlock DataContext="{Binding ElementName=lbRules, Path=SelectedItem}" Text="{Binding Name}" />

Youngjae
  • 24,352
  • 18
  • 113
  • 198
0

There is a shorter version to bind to a selected item's property:

<TextBlock Text="{Binding Rules/Name}" />

alexW
  • 63
  • 4
-7

since you set your itemsource to your collection, your textbox is tied to each individual item in that collection. the selected item property is useful in this scenario if you were trying to do a master-detail form, having 2 listboxes. you would bind the second listbox's itemsource to the child collection of rules. in otherwords the selected item alerts outside controls that your source has changed, internal controls(those inside your datatemplate already are aware of the change.

and to answer your question yes in most circumstances setting the itemsource is the same as setting the datacontext of the control.

ecathell
  • 1,030
  • 13
  • 25
  • 4
    "setting the itemsource is the same as setting the datacontext of the control" - please don't post nonsense :) – arconaut Jan 06 '10 at 14:12