WPF binding to Listbox selectedItem - wpf

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.

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.

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;
}
}
}
}

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

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}" />

There is a shorter version to bind to a selected item's property:
<TextBlock Text="{Binding Rules/Name}" />

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.

Related

Binding to DependancyProperty

My XAML won't bind and I can't see why
Using MVVM, I have the following code in my MyView.xaml
<Expander Header="{Binding ThresholdName}" IsExpanded="False" >
<properties:PropertiesVm Name="{Binding ThresholdName}" />
</Expander>
And this has a DataTemplate of MyViewModel.cs
As you can see, I'm using the ThresholdName twice. When I run my application, I can see the ThrehsoldName in the Header of the Expander indicating that the binding at this point is fine. The issue is binding to my "properties" object.
The Properties View is
<Grid>
<TextBlock Text="{Binding Name}" />
</Grid>
And the ViewModel(which inherits from DependancyObject) is
public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(PropertiesVm));
public string Name
{
get { return (Convert.ToString(GetValue(NameProperty))); }
set { SetValue(NameProperty, value); }
}
The error message I get is
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Time; DataItem=null; target element is 'PropertiesVm' (HashCode=6494098); target property is 'Name' (type 'String')
And within my Dictionary, I have set the DataTemplate as
<DataTemplate DataType="{x:Type viewModel:PropertiesVm}">
<view:PropertiesView />
</DataTemplate>
This post suggests adding x:Name but that didn't help
Why does the property Name in my Properties ViewModel never get set? Or can I only do this with a UserControl, binding to code behind?
You've got things a bit about face.
Your Expander content needs to bind to a property of your top level view model which exposes your instance of PropertiesVm. A bit like:
public class TopLevelVm
{
public string ThresholdName { get; }
public PropertiesVm Properties { get; }
}
Then your XAML could be like:
<Expander Header="{Binding ThresholdName}" Content="{Binding }" />
Then the DataTemplate will pick the same DataContext, and your DataTemplate could look like this:
<Grid>
<TextBlock Text="{Binding ThresholdName}" />
<!-- Do something with .Properties -->
</Grid>

Unable to get the binding from textbox and bind combobox from another view model

I am farily new to mvvm so bear with me. I have 2 View models which are inherited namely DBViewModel and PersonViewModel. i would like to add the person object in DBViewModel and bind 2 combobox with observablecollection in PersonViewModel.
public class PersonViewModel
{
private ICommand AddCommand ;
public Person PersonI{get;set;}
public ObservableCollection<Person> EmployeeList{ get; set; }
public ObservableCollection<String> OccupationList{ get; set; }
public PersonViewModel()
{
PersonI = new Person();
this.AddCommand = new DelegateCommand(this.Add);
// get OccupationList and EmployeeList
}
......
}
public class DBViewModel : PersonViewModel
{
public PersonViewModel PersonVM { get; set; }
public PersonViewModel()
{
PersonVM = new PersonViewModel();
}
....
}
<DataTemplate DataType='{x:Type viewModel:DBViewModel}'>
<StackPanel>
<TextBox Text="{Binding PersonI.Name}" />
<ComboBox Name="cboccupation" ItemsSource="{Binding OccupationList}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedItem}" SelectedValuePath="Id"/>
<Button Content="Add" Command="{Binding AddCommand}" />
<DataGrid ItemsSource="{Binding EmployeeList}" CanUserAddRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Occupation">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding OccupationList}"
DisplayMemberPath="Name" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</DataTemplate>
if you want an easier approach I'm thinking you could set a datastore field using blend and bind both controls to that field.
Your bindings are trying to bind to the properties PersonI and OccupationList on the DBViewModel, however those properties do not exist.
You need to point them to the PersonVM.PersonI and PersonVM.OccupationList instead.
<TextBox Text="{Binding PersonVM.PersonI.Name}" />
<ComboBox ItemsSource="{Binding PersonVM.OccupationList}" ... />
For your ComboBox binding inside the DataGrid, that probably will not work because the DataContext of each row in the Grid is a Person object (specified by the DataGrid.ItemsSource), and I don't think Person has a property called OccupationList.
You need to change the Source of your binding to use the object that has the OccupationList property.
For example, if your DataGrid was named MyDataGrid, the following binding for that ComboBox would work
<ComboBox ItemsSource="{Binding
ElementName=MyDataGrid,
Path=DataContext.PersonVM.OccupationList}" ... />
Alternatively, you could use a RelativeSource binding to have it look for the parent DataGrid object without needing to specify a name
<ComboBox ItemsSource="{Binding
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=DataContext.PersonVM.OccupationList}" ... />
As a side note, you seem to be a bit confused about bindings and the DataContext. I like to blog about beginner WPF topics, and would recommend reading What is this "DataContext" you speak of?. I find it has helped many WPF beginners on this site understand the basic binding concept. :)

Binding to a viewmodel property in a DataTemplate

I'm fairly new to XAML but enjoying learning it. The thing I'm really struggling with is binding a property to an element in a DataTemplate.
I have created a simple WPF example to, (hopefully,) explain my problem.
I this example I am trying to bind the Visibility property of a CheckBox in a DataTemplate to a Property in my viewmodel. (Using this scenario purely for learning/demo.)
I have a simple DataModel named Item, but is of little relevance in this example.
class Item : INotifyPropertyChanged
{
// Fields...
private bool _IsRequired;
private string _ItemName;
And a fairly simple View Model named ItemViewModel.
class ItemViewModel : INotifyPropertyChanged
{
private ObservableCollection<Item> _Items;
private bool _IsCheckBoxChecked;
private bool _IsCheckBoxVisible;
public ObservableCollection<Item> Items
{
get { return _Items; }
set { _Items = value; }
}
public bool IsCheckBoxChecked
{
get { return _IsCheckBoxChecked; }
set
{
if (_IsCheckBoxChecked == value)
return;
_IsCheckBoxChecked = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxChecked"));
PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxVisible"));
}
}
}
public bool IsCheckBoxVisible
{
get { return !_IsCheckBoxChecked; }
set
{
if (_IsCheckBoxVisible == value)
return;
_IsCheckBoxVisible = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxVisible"));
}
(Constructors and INotifyPropertyChanged implementation omitted for brevity.)
Controls laid out in MainPage.xaml as follows.
<Window.Resources>
<local:VisibilityConverter x:Key="VisibilityConverter"/>
</Window.Resources>
<Window.DataContext>
<local:ItemViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<CheckBox x:Name="checkBox" Content="Hide CheckBoxes" FontSize="14" IsChecked="{Binding IsCheckBoxChecked, Mode=TwoWay}" />
<ListView ItemsSource="{Binding Items}" HorizontalContentAlignment="Stretch" >
<ListView.ItemTemplate >
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ItemName}"/>
<CheckBox Grid.Column="1" Visibility="{Binding IsCheckBoxVisible, Converter={StaticResource VisibilityConverter}}" >
<CheckBox.DataContext>
<local:ItemViewModel/>
</CheckBox.DataContext>
</CheckBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" Margin="4,4,0,0">
<TextBlock Text="IsCheckBoxVisible:"/>
<TextBlock Text="{Binding IsCheckBoxVisible}" Margin="4,0,0,0" FontWeight="Bold" />
</StackPanel >
<Button Content="Button" Visibility="{Binding IsCheckBoxVisible, Converter={StaticResource VisibilityConverter}}" Margin="4,4,4,4"/>
</StackPanel>
</Grid>
The 'Hide CheckBoxes' checkbox is bound to IsCheckBoxChecked and is used to update IsCheckBoxVisible. I've also added a couple of extra controls below the DataTemplate to prove, (to myself,) the everything works.)
I have also implemented Jeff Wilcox's value converter. (Thank you.) http://www.jeff.wilcox.name/2008/07/visibility-type-converter/
When I run the app, checking and unchecking the 'Hide Checkbox', controls outside the DataTemplate function as expected but, alas, the Checkbox inside the data template remains unchanged.
I have had success with:
IsVisible="{Binding IsChecked, Converter={StaticResource VisibilityConverter}, ElementName=checkBox}"
But I'm not just trying mimic another control but make decisions based on a value.
I would REALLY appreciate any help or advice you can offer.
Thank you.
When you are in a DataTemplate, your DataContext is the data templated object, in this case an Item. Thus, the DataContext of the CheckBox in the DataTemplate is an Item, not your ItemViewModel. You can see this by your <TextBlock Text="{Binding ItemName}"/>, which binds to a property on the Item class. The Binding to IsCheckBoxVisible is trying to find a property called IsCheckBoxVisible on Item.
There are a couple of ways around this, but by far the easiest is to do this:
On your Window (in the xaml), give it and x:Name. Eg:
<Window [...blah blah...]
x:Name="MyWindow">
Change your binding to look like this:
<CheckBox Grid.Column="1"
Visibility="{Binding DataContext.IsCheckBoxVisible, ElementName=MyWindow, Converter={StaticResource VisibilityConverter}}">
We're using the Window as the source for the Binding, then looking at its DataContext property (which should be your ItemViewModel, and then pulling off the IsCheckBoxVisible property.
Another option, if you want something fancier, is to use a proxy object to reference your DataContext. See this article on DataContextProxy.

Silverlight DataBinding MVVM

Im having some problems with setting up databinding in Silverlight.
Im trying to use the MVVM approach and found some nice examples, So Ive created my View and my ViewModel, I created some classes Im going to use to contain the data and one to populate the classes.
Firstly my ViewModel looks like:
public class MainPageVM : INotifyPropertyChanged
{
ObservableCollection<Item> Items;
public MainPageVM()
{
InitializeItems InitItems = new InitializeItems();
InitItems.GenerateItemList(out Items);
RaiseProertyChanged("Items");
}
public string test = "Binding Test";
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void RaiseProertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
And then in my View i have :
<UserControl.Resources>
<viewmodel:MainPageVM x:Key="ViewModel" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource ViewModel}">
<StackPanel>
<TextBlock Text="{Binding test}"/>
<ListBox ItemsSource="{Binding Items}"
Width="200"
Height="200">
<ListBoxItem Width="190" Height="20">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ItemName}"/>
<TextBlock Text="-"/>
<TextBlock Text="{Binding ItemID}"/>
</StackPanel>
</ListBoxItem>
</ListBox>
</StackPanel>
</Grid>
I added breakpoints and I know my ObservableCollection that im trying to bind to is being populated but nothing binds, in the error window im just getting xxx property doesnt exist in MainPageVM.
Any advice here would be great as im a bit lost as to what could be going on, and this is my first silverlight application.
Thanks
Items needs to be a public property. Same with your test field. In Silverlight you can only bind to public properties.
Also, typically in the Setter of those properties you raise the property changed event. This tells the Silverlight runtime to refresh the controls that are bound to that property with the new values of that property.

How to get TextBox inside DataTemplate in a ListBox to notify the ViewModel on value change

What I need to find is when a textbox's value is changing or the dropdown's value changes inside my datatemplate item, I need to be notified in my ViewModel.cs.
So basically as a user edits a textbox inside the listbox, the viewmodel will be notified as the values are changing.
The reason is I need to go through all my Entries and update something as items inside the listbox's datatemplate change.
Any suggetion?
I have the following in my XAML.
<ListBox x:Name="EntriesListBox"
ItemsSource="{Binding Path=Entries}"
Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="EntriesPropertyName"
Width="215"
Margin="0,0,5,0"
SelectedItem="{Binding Path=Property, Mode=TwoWay}"
ItemsSource="{Binding Source={StaticResource DataContextProxy},Path=DataSource.EntityTypeProperties}" />
<TextBox x:Name="EntriesPropertyValue"
Width="215"
Margin="0,0,5,0"
Text="{Binding Path=Value, Mode=TwoWay, BindsDirectlyToSource=True}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The following is in my VM (ViewModel.cs)
public ObservableCollection<Entry> Entries { get; set; }
The following is in my business object (Entry.cs)
public class Entry
{
public PropertyItem Property { get; set; }
public string Value { get; set; }
}
On your binding, set the UpdateSourceTrigger... Also implement INotifyPropertyChanged
Provided that you have setup your view model class properly (by implementing INotifyPropertyChanged), following is what you may want to do:
<TextBox x:Name="EntriesPropertyValue"
Width="215"
Margin="0,0,5,0"
Text="{Binding Path=Value, Mode=TwoWay, BindsDirectlyToSource=True, UpdateSourceTrigger=PropertyChanged}" />
This seems to work. Any reason not to do it this way?
private void EntriesPropertyValue_TextChanged(object sender, TextChangedEventArgs e)
{
(sender as TextBox).GetBindingExpression(TextBox.TextProperty).UpdateSource();
this.ViewModel.UpdateFinalQuery();
}

Resources