WPF ListView SelectedValue not being set - wpf

I've looked at a few solutions but nothing has worked yet for me.
I'm using MVVM for this project and have a ListView that I can't set the SelectedItem property.
If this is my (simplified) XAML.
<ListView Name="uxPackageGroups" ItemsSource="{Binding Path=PackageGroups, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" BorderThickness="0"
BorderBrush="#FF0000E8" ScrollViewer.CanContentScroll="True"
SelectedItem="{Binding Path=PackageGroupSelectedItem, Mode=TwoWay}" >
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}" Height="20" Margin="0" Padding="0"/>
</DataTemplate>
</ListView.ItemTemplate>
And I bind it to a PackageGroups in my ViewModel
public PackageGroup PackageGroupSelectedItem {get; set; }
public ObservableCollection<PackageGroup> PackageGroups {get; set; }
private void LoadUI()
{
PackageGroups = Factory.LoadAllPackageGroups())
// if I try to hard-code a pre-selected item here it doesn't work.
// 34 is a valid ID and I see a valid object when stepping through the code
PackageGroupSelectedItem = PackageGroup.LoadByID(db, 34);
}
Anything glaring in my code?
Thanks.

One possible problem is that you're not implementing INotifyPropertyChanged in the PackageGroupSelectedItem property.

I've just came into the same situation and it turned out that my collection item had incorrectly implemented "Equals" method. It doesn't need to implement INotifyPropertyChanged on collection item, but Equals should be implemented correctly...

Related

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. :)

what is the difference between DisplayDataMember and ItemTemplate and when to use one over the other?

As mentioned in the title, I want to know the difference between DisplayDataMember and ItemTemplate. I tried to use both of them together and I got a comiler error that both cannot be used at the same time. I also want to know when to use one over the other.
I am a newbie. If this is not a good question to ask then please forgive me.
DisplayMemberPath and ItemTemplate are two ways for representing data.
First one only lets you allow string representation whereas other lets you customise combobox content as per your need (not only string representation). As error states you can't define both at same time.
Suppose you have TestClass with property say Name.
public class TestClass
{
public string Name { get; set; }
}
Now you bind to ItemsSource of your combobox with collection of objects of this class.
Without DisplayMemberPath and ItemTemplate
<ComboBox ItemsSource="{Binding Objects}"/>
With DisplayMemberPath
<ComboBox ItemsSource="{Binding Objects}" DisplayMemberPath="Name"/>
With ItemTemplate
<ComboBox ItemsSource="{Binding Objects}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<Rectangle Margin="15,0,0,0" Fill="Red"
Width="10" Height="10"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I hope images are self explanatory. Let me know if more clarification required.
Also you can achieve DisplayMemberPath functionality by simply overriding ToString() method on your class since internally it calls ToString() on data item.
public class TestClass
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}

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

How to bind to a property of a property in WPF

I have a listview who's itemssource is a ObservableCollection of MyModel. I am trying to figure how to bind a textbox text's property to the Name property of the model's Owner property
public class Person
{
public string Name { get; set; }
public string Address { get; set; }
//...
}
public class MyModel
{
public string Title { get; set; }
public Person Owner { get; set; }
//...
}
I tried:
<TextBlock Text="{Binding Owner.Name}" />
but that leaves the textblock blank. Whats the proper syntax?
The binding looks fine. I assume that you put the TextBlock into a DataTemplate and attached this to the ListView. If yes, that should work.
To find the error, replace the Binding through a literal to see if you have some rows (The literal must be shown in every line). If not, check the ItemsSource. If yes, check that you have really a Person-object attached to your MyModel-instances and that the Name-property is not null or empty. Check also the output window of VS. There you will see binding-errormessages.
If you have no DataTemplate, here an example:
<ListView ItemsSource="[Your ItemsSource]">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Owner.Name}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Check that the DataContext is set properly and implement INotifyPropertyChanged (raise the event it defines when the value of the property changes.).
Try to use Data Source Wizard form VS 2010
I just add these classes and click, clik, clik
<TextBox Grid.Column="1" Grid.Row="0" Height="23" HorizontalAlignment="Left" Margin="3" Name="nameTextBox" Text="{Binding Path=Owner.Name, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<Slider Margin="81,161.66,66,0" Name="Height" Minimum="0" Maximum="10" Width="400"></Slider>
<Slider Margin="81,1.66,66,0" Name="Width" Minimum="0" Maximum="10" Width="400"/>
<Ellipse Height="{Binding ElementName=Height}" Width="{Binding ElementName=Width}" Fill="Blue" ></Ellipse>

WPF binding to Listbox selectedItem

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.

Resources