databinding combobox in Dataform Silverlight using MVVM in Update - silverlight

I have Master/Detail – datagrid/dataform and after select item it shows in dataform for update, but I have a problem with databinding or populating combox with departments and set SelectedEmployee.departmentid as selectedvalue.
Here 2 questions now:
1. In EmployeeViewModel this code just doesn’t work and the question why?
private ObservableCollection<department> _departments;
public ObservableCollection<department> Departments
{
get { return _departments; }
set
{
_departments = value;
RaisePropertyChanged("Departments");
}
}
But this code works fine
private ObservableCollection<department> _departments;
public ObservableCollection<department> Departments
{
get {
if (_departments == null)
{
_departments = new ObservableCollection<department> {
new department()
{ id = 1, departmentname = "Technical " + 1, },
new department()
{ id = 2, departmentname = "Technical " + 2, },
new department()
{ id = 3, departmentname = "Technical " + 3, }
};
}
return _departments;
}
set
{
_departments = value;
RaisePropertyChanged("Departments");
}
}
2. Behavior of Combobox inside and outside DataForm is different. Outside it works, inside it doesn’t. I think here need to use Source in ItemsSource, but I don’t know how. So there is another question is how to fix it?
employeeView.xaml
<navigation:Page xmlns:local="clr-namespace:departmentTechManager"
x:Class="departmentTechManager.Views.employeeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="820" d:DesignHeight="780"
Title="employees"
Style="{StaticResource PageStyle}"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mvvmlightcmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL4"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:dataformtoolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
DataContext="{Binding employeeStatic, Source={StaticResource Locator}}">
<data:DataGrid Grid.Row="0" x:Name="dgEmployees" CanUserSortColumns="true"
IsReadOnly="true" AutoGenerateColumns="true"
ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee, Mode=TwoWay}" Margin="0,36,-123,0"></data:DataGrid>
<dataformtoolkit:DataForm x:Name="dfDetails"
CurrentItem="{Binding SelectedEmployee}" AutoGenerateFields="False"
CommitButtonContent="Save" CommandButtonsVisibility="Edit, Commit, Cancel">
<dataformtoolkit:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataformtoolkit:DataField Label="name">
<TextBox Text="{Binding name, Mode=TwoWay}" /></dataformtoolkit:DataField>
<dataformtoolkit:DataField Label="departments">
<ComboBox ItemsSource="{Binding Departments}"
DisplayMemberPath="departmentname"
SelectedValuePath="id"
SelectedValue="{Binding Path=SelectedEmployee.departmentid, Mode=TwoWay}" />
</dataformtoolkit:DataField>
</StackPanel>
</DataTemplate>
</dataformtoolkit:DataForm.EditTemplate>
<i:Interaction.Triggers><i:EventTrigger EventName="EditEnded">
<mvvmlightcmd:EventToCommand Command="{Binding SaveEmployeesCommand}"/>
</i:EventTrigger></i:Interaction.Triggers>
</dataformtoolkit:DataForm>
In ViewModelLocator.cs:
public ViewModelLocator()
{
_sp = ServiceProviderBase.Instance;
Createdepartment();
Createemployee();
}
#region EmployeeViewModel
private static EmployeeViewModel _employee;
public static EmployeeViewModel employeeStatic
{
get
{
if (_employee == null)
{
Createemployee();
}
return _employee;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public EmployeeViewModel employee
{
get
{
return employeeStatic;
}
}
public static void Clearemployee()
{
//do it later
//_employee.Cleanup();
_employee = null;
}
public static void Createemployee()
{
if (_employee == null)
{
_employee = new EmployeeViewModel(_sp.PageConductor, _sp.EmployeeDataService);
}
}
#endregion
#region DepartmentViewModel
private static DepartmentViewModel _department;
public static DepartmentViewModel departmentStatic
{
get
{
if (_department == null)
{
Createdepartment();
}
return _department;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public DepartmentViewModel department
{
get
{
return departmentStatic;
}
}
public static void Cleardepartment()
{
//do it later
//_department.Cleanup();
_department = null;
}
public static void Createdepartment()
{
if (_department == null)
{
_department = new DepartmentViewModel(_sp.PageConductor, _sp.DepartmentDataService);
}
}
#endregion
Can somebody help me?
combox is just empty. but now i can populate it with Departments like so:
departmentTechManagerDomainService.metadata.cs
[MetadataTypeAttribute(typeof(employee.employeeMetadata))]
public partial class employee
{
[Include]
public department department { get; set; }
public Nullable<int> departmentid { get; set; }
public string name { get; set; }
}
departmentTechManagerDomainService.cs
public IQueryable<employee> GetEmployees()
{return this.ObjectContext.employees.Include("department").OrderBy(e=>e.name);}
here is ViewModel code:
private ObservableCollection<department> _departments;
public ObservableCollection<department> Departments
{
get { return _departments; }
set
{
_departments = value;
RaisePropertyChanged("Departments");
}
}
private department _selectedDepartment;
public department SelectedDepartment
{
get { return _selectedDepartment; }
set
{
_selectedDepartment = value;
RaisePropertyChanged("SelectedDepartment");
}
}
private void InitializeModels()
{
Employees = new ObservableCollection<employee>();
SelectedEmployee = new employee();
NewEmployee = new employee();
//new
Departments = new ObservableCollection<department>();
SelectedDepartment = new department();
}
private void GetEmployeesCallback(IEnumerable<employee> employees)
{
if (employees != null)
{
foreach (var employee in employees)
{
Employees.Add(employee);
//new
if (!Departments.Contains(employee.department))
Departments.Add(employee.department);
}
if (Employees.Count > 0)
{
SelectedEmployee = Employees[0];
}
}
}
I make Departments distinct, but here is only departments those already have been selected, but here aren't those that haven't been selected yet, and still combobox is not populated with departments in DataForm. ?!

2nd question - it looks like combobox inside and outside of dataform receives different DataContext and hence trying to locate Departments properties on different sources. It is not clear how to fix it since you haven't shown most of your ViewModels.
Pay attention to the VS output window, it usually provides very detailed info regarding binding errors and I'm assuming there are binding errors in your case.
Try modifying your department related bindings in following way:
<ComboBox ItemsSource="{Binding DataContext.Departments, RelativeSoruce={RelativeSource AncestorType={x:Type localViews:employeeView}}}" />
Where localViews should be an xml-namespace for departmentTechManager.Views. Try the same trick for SelectedItem binding.

I've got the solution for this question. here it is.

In Edit Template, Source must need to be mention with ViewModel Name
<ComboBox Grid.Row="2" Grid.Column="1"
ItemsSource="{Binding Path=Accounts, Source={StaticResource MyAccountViewModel}, Mode=TwoWay}" />

Related

Select one combo box value on the basis of another combo box value

I am new to WPF application development. I want to get floors on the basis of blocks. I have one combo box of blocks and another combo box of floors. When I select any block in one combo box, the other combo box should display the floors of the select block.
This is the combo box layout:
<ComboBox Grid.Row="0" Grid.Column="0" Width="100"
Margin="0,0,0,10" Height="35"
Loaded="FrameworkElement_OnLoaded"
SelectedValuePath ="Id"
SelectedValue ="{Binding SelectedBlockId, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectionChanged="Selector_OnSelectionChanged" ItemsSource="{Binding Blocks}" />
<ComboBox Grid.Row="0" Grid.Column="3" Width="100"
Margin="0,0,0,10" Height="35"
Loaded="FrameworkElement_OnLoaded"
SelectedValuePath ="Id"
SelectedValue ="{Binding SelectedFloorId, Mode=TwoWay}"
DisplayMemberPath="Name"
SelectionChanged="Selector_OnSelectionChanged" ItemsSource="{Binding Floors}" />
Here's a slightly different example.
Xaml...
<!-- language: xaml -->
<Label Name="FavoriteFoodLbl" Grid.Column="0" Grid.Row="13">Favorite Food</Label>
<ComboBox Name="FavoriteFoodCombo" Grid.Column="1" Grid.Row="13" ItemsSource="{Binding Foods}" SelectedItem="{Binding FavoriteFood, UpdateSourceTrigger=PropertyChanged}" />
<Label Name="FavoriteFlavourLbl" Grid.Column="2" Grid.Row="13">Favorite Flavour</Label>
<ComboBox Name="FavoriteFlavourCombo" Grid.Column="3" Grid.Row="13" ItemsSource="{Binding Flavours}" />
View model/code-behind code...
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> Foods { get; set; } = new ObservableCollection<string>() { "Pizza", "Ice Cream", "Soup" };
private string _favoriteFood;
public string FavoriteFood
{
get { return _favoriteFood; }
set
{
_favoriteFood = value;
switch (_favoriteFood)
{
case "Pizza":
_flavours = new ObservableCollection<string>(PizzaToppings);
break;
case "Ice Cream":
_flavours = new ObservableCollection<string>(IceCreamFlavours);
break;
case "Soup":
_flavours = new ObservableCollection<string>(SoupFlavours);
break;
default:
_flavours = new ObservableCollection<string>();
break;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("FavoriteFood"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Flavours"));
}
}
public List<string> PizzaToppings { get; set; } = new List<string>() { "Margarita", "Pepperoni", "Meat Feast" };
public List<string> IceCreamFlavours { get; set; } = new List<string>() { "Vanilla", "Strawberry", "Chocolate" };
public List<string> SoupFlavours { get; set; } = new List<string>() { "Tomato", "Leek and Potato", "Chicken" };
private ObservableCollection<string> _flavours = null;
public ObservableCollection<string> Flavours
{
get
{
return _flavours;
}
set
{
_flavours = value;
RaisePropertyChanged();
}
}
public string FavoriteFlavour { get; set; }
Change out the case statement for something appropriate.
Does this help?
What you need to do is to establish master-detail relationship between Block and Floor models and correctly bind them to you View (comboboxes).
So supposing your Floor and Block have two properties ID and Description, your model should look like this:
public class Floor
{
public int Id { get; set; }
public string Description { get; set; }
}
public class Block
{
public int Id { get; set; }
public int Description { get; set; }
// notice Floors collection inside each block
public IList<Floor> Floors { get; set; }
public Block()
{
Floors = new List<Floor>();
}
}
Your ViewModel will contain two ObservableCollections and one property to store currently selected block. Notice SelectedBlock property setter: when the property is updated, the Floors collection gets recreated with new values.
public const string BlocksPropertyName = "Blocks";
private ObservableCollection<Block> _blocks = null;
public ObservableCollection<Block> Blocks
{
get
{
return _blocks;
}
set
{
_blocks = value;
RaisePropertyChanged(BlocksPropertyName);
}
}
public const string SelectedBlockPropertyName = "SelectedBlock";
private Block _selectedBlock = null;
public Block SelectedBlock
{
get
{
return _selectedBlock;
}
set
{
_selectedBlock = value;
RaisePropertyChanged(SelectedBlockPropertyName);
if (_selectedBlock != null)
{
Floors = new ObservableCollection<Floor>(_selectedBlock.Floors);
}
}
}
public const string FloorsPropertyName = "Floors";
private ObservableCollection<Floor> _floors = null;
public ObservableCollection<Floor> Floors
{
get
{
return _floors;
}
set
{
_floors = value;
RaisePropertyChanged(FloorsPropertyName);
}
}
In your XAML you just bind both ComboBoxes to corrispective collections:
<ComboBox ItemsSource="{Binding Blocks}"
SelectedItem="{Binding SelectedBlock}" />
<ComboBox ItemsSource="{Binding Floors}" />
Please refer to the following blog post for an example of how to implement cascading ComboBoxes in WPF using the MVVM pattern: https://blog.magnusmontin.net/2013/06/17/cascading-comboboxes-in-wpf-using-mvvm/
You could basically just replace the Countries and the Cities types from the sample code with your Block and Floor types.

how to make listbox elements as selected from DataContext when ListBox is binded from code behind

I am trying to set some of the ListBox elements as selected when binding through DataContext. ListBox is binded through code behind.
I am binding my listbox on user control's constructor
TradesListBox.ItemsSource = config.OfType<Trade>().ToList();
XAML below is a part of UserControl whose DisplayMemberPath property is being set through a constructor as shown in line above, while I am trying to set SelectedItem property from DataContext that is being passed through owing window. But SelectedItem are not being displayed
<Label Grid.Row="1" Grid.Column="0" Target="{Binding ElementName=TradesListBox}" Style="{StaticResource LabelStyle}" FontSize="18" HorizontalAlignment="Right">_Trades</Label>
<ListBox Grid.Row="1" Grid.Column="1" Name="TradesListBox" HorizontalAlignment="Stretch" Height="70" Margin="2" DisplayMemberPath="ConfigValue" SelectedItem="{Binding Trade.ConfigValue}" SelectionMode="Multiple" />
private List<Trade> trade;
[DataMember]
public virtual List<Trade> Trade
{
get
{
if (trade == null)
trade = new List<Trade>();
return trade;
}
set
{ trade = value == null ? new List<Trade>() : value; }
}
I think items are selected but not highlighted, if so then try to set Focus on ListBox and see if it works for you.
TradesListBox.ItemsSource = config.OfType<Trade>().ToList();
// Let say you want to Select first and second item
TradesListBox.SelectedItems.Add(TradesListBox.Items[0]);
TradesListBox.SelectedItems.Add(TradesListBox.Items[1]);
// Set focus on ListBox
TradesListBox.Focus();
Hi If you are using MVVM then you can bind SelectedItem of ListBox like
xaml
<ListBox ItemsSource="{Binding Students}" SelectedItem="{Binding SelectedStudent}" DisplayMemberPath="Name"></ListBox>
.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
ViewModel
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
Students = new ObservableCollection<Student>();
Students.Add(new Student { Name = "abc", Age = 20 });
Students.Add(new Student { Name = "pqr", Age = 30 });
Students.Add(new Student { Name = "xyz", Age = 40 });
SelectedStudent = Students[0];
}
public ObservableCollection<Student> Students { get; set; }
Student selectedStudent;
public Student SelectedStudent
{
get { return selectedStudent; }
set { selectedStudent = value; Notify("SelectedStudent"); }
}
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
CustomType
public class Student:INotifyPropertyChanged
{
string name;
public string Name {
get
{ return name; }
set
{
name = value;
Notify("Name");
}
}
int age;
public int Age
{
get
{ return age; }
set
{
age = value;
Notify("Age");
}
}
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Remember the reference of SelectedItem must be same as that one of the item in Collection binded to ItemsSource
>Edit
<ListBox Grid.Row="1" Grid.Column="1" Name="TradesListBox" HorizontalAlignment="Stretch"
Height="70" Margin="2" DisplayMemberPath="ConfigValue"
SelectedValue="{Binding Trade.ConfigValue}"
SelectedValuePath="ConfigValue" SelectionMode="Multiple" />

Binding ComboBox in ListBox

I cant figure this one out. If I just have the combo-box by itself not embedded in a list box it will populate and selected the values I need.
The XAML
<ComboBox Name="comboBox1"
Height="23"
DataContext="{Binding Combox}"
ItemsSource="{Binding Comboxes}"
DisplayMemberPath="PV"
SelectedValuePath="PK"
SelectedItem="{Binding SelectedItem}"
VerticalAlignment="Top"
Width="120" />
The Code Behind
public MainWindow()
{
InitializeComponent();
DataAttribute d = new DataAttribute(2, "blue");
Combox c = new Combox();
c.SelectedItem = d;
c.Comboxes.Add(new DataAttribute(1, "red"));
c.Comboxes.Add(new DataAttribute(3, "Black"));
c.Comboxes.Add(c.SelectedItem);
comboBox1.DataContext = c;
}
Class for holding data
public class Combox: INotifyPropertyChanged
{
public Combox()
{
Comboxes = new List<DataAttribute>();
}
private DataAttribute _selectedItem;// = new DataAttribute(-1, "NA");
public List<DataAttribute> Comboxes { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public DataAttribute SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem == value) return;
_selectedItem = value;
OnPropertyChanged("SelectedValue");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class DataAttribute
{
public DataAttribute() { }
public DataAttribute(int pk, string pv)
{
PK = pk;
PV = pv;
}
public int PK { get; set; }
public string PV { get; set; }
public override string ToString()
{
return PV;
}
}
All that works fine, but as soon as I try to create a list of com boxes in the listbox nothing. I can see the combo but no data. How on earth to you bind to it is XAML?
public MainWindow()
{
InitializeComponent();
List<Combox> com = new List<Combox>();
DataAttribute d = new DataAttribute(2, "blue");
Combox c = new Combox();
c.SelectedItem = d;
c.Comboxes.Add(new DataAttribute(1, "red"));
c.Comboxes.Add(new DataAttribute(3, "Black"));
c.Comboxes.Add(c.SelectedItem);
com.Add(c);
lstTest.ItemSource = com;
}
As here is the XAML with a listbox. It no longer binds...
<ListBox Name="lstTest" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<ComboBox Name="comboBox1"
DataContext="{Binding Combox}"
ItemsSource="{Binding Comboxes}"
DisplayMemberPath="PV"
SelectedValuePath="PK"
SelectedItem="{Binding SelectedItem}"
VerticalAlignment="Top"
Width="120" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I am stumped as it was hard enough trying to figure out how to just get the selected object to appear without the list-box...
Inside the items of an ItemsControl the DataContext is the currectly templated item, if you want to get to a list that is to be used as the ItemsSource for the ComboBoxes you normally need to modify the bindings to use another source, e.g. RelativeSource or ElementName. (This is the case for one list that is to be used for all ComboBoxes)
In this case, where the list seems to be part of the item, you should only need to remove the binding on the DataContext as the DataContext is already the item (an instance of Combox).

WPF Data Binding and Templates

I am working on a project and I want to have a list box with a custom data template populate with user data. My question is, when I click on an item in the list box, how can I tell what item I selected? Basically, if I select "kevin", I want to display his data. If I select Dave, I want to display his data. I do not know how to get the data our after it is bound...
EDIT: I found a really great tutorial that covers this. A very hidden gem.
http://msdn.microsoft.com/en-us/library/aa480224.aspx
Bind SelectedItem of the ComboBox to any property.
<Window x:Class="ComboBoxSelectedItemBinding.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="500">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListBox x:Name="st"
ItemsSource="{Binding Path=Customers,Mode=TwoWay}" IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding Path=SelectedCustomer,Mode=TwoWay}"
Margin="0,38,0,80">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Text="{Binding Path=SelectedCustomer.Name}" Grid.Column="1" VerticalAlignment="Center" Margin="5"></TextBlock>
</Grid>
public partial class Window1 : Window, INotifyPropertyChanged
{
private ObservableCollection<Customer> customers;
public ObservableCollection<Customer> Customers
{
get
{
return customers;
}
set
{
customers = value;
NotifyPropertyChanged("Customers");
}
}
private Customer selectedCustomer;
public Customer SelectedCustomer
{
get
{
return selectedCustomer;
}
set
{
selectedCustomer = value;
NotifyPropertyChanged("SelectedCustomer");
}
}
public Window1()
{
Customers = new ObservableCollection<Customer>();
Customers.Add(new Customer() { ID = 1, Name = "Ravi", Salary = 1000 });
Customers.Add(new Customer() { ID = 99, Name = "Alex", Salary = 3000 });
Customers.Add(new Customer() { ID = 123, Name = "Steve", Salary = 100 });
Customers.Add(new Customer() { ID = 31, Name = "Alice", Salary = null });
InitializeComponent();
DataContext = this;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
public class Customer:INotifyPropertyChanged
{
private int id;
public int ID
{
get
{
return id;
}
set
{
id = value;
NotifyPropertyChanged("ID");
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
NotifyPropertyChanged("Name");
}
}
private decimal? salary;
public decimal? Salary
{
get
{
return salary;
}
set
{
salary = value;
NotifyPropertyChanged("Salary");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
The SelectedIndex property of the ListBox will correspond to the index of the selected item in the data source. So assuming you have bound to an IList you should be able to just use myDataSource[myListBox.SelectedIndex]. I'm assuming you aren't trying to support multiselect, in which case you can use the same concept, but the implementation is more complicated.
Depending if you let the user select one or many item in your ListBox you can just loop all Item and check if it selected. Here is a little example C# example (could be done in VB.NET):
for (int i = 0; i < MyListBox.Items.Count; i++)
{
if(MyListBox.Items[i].Selected)
//Do what you want with the item with : MyListBox.Items[i].Value
}
It sounds like you will only have one item selected at a time. If so, then I prefer to bind the ListBox.SelectedItem to a property on my ViewModel. If we assume that you bound the listbox to a collection of Person classes, then the property on the ViewModel would be of type Person. The listbox will set this property to the selected instance of Person.
Then, just bind the other portion of the UI that show's Kevin's data to the property on the ViewModel.
As long as you have INotifyPropertyChanged implemented on the properties, your UI should update automatically as you change the selected item in the listbox.

WPF Master-Details view with Listbox and Combobox with Binding

I've been looking around for an answer to my question for a few days now, but am not able to find a solution.
The problem is that the combobox updates the Test object in User class with the the previous selected Users'.
i.e. you select user2 and user2 has test2, you then select user5 that has test5. Now if you select user2 again, it will show that it has test5.
Here is some code. I have two classes Users and Tests. And two ObservableCollections for each of those. This is how I've got them setup:
public class User
{
public string Name { get; set; }
public int test { get; set; }
public test userTest { get; set; }
}
public class test
{
public int ID { get; set; }
public String Name { get; set; }
}
public class ListOfTests:ObservableCollection<test>
{
public ListOfTests()
{
for (int i = 0; i < 4; i++)
{
test newTest = new test();
newTest.ID = i;
newTest.Name = "Test " + i;
Add(newTest);
}
}
}
public class ListOfUsers: ObservableCollection<User>
{
public ListOfUsers()
{
ListOfTests testlist = new ListOfTests();
for (int i = 0; i < 10; i++)
{
User newUser = new User();
newUser.Name = "User " + i;
newUser.ID = i;
newUser.userTest = testlist[i];
Add(newUser);
}
}
}
And the XAML is:
<Window x:Class="ComboboxTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ComboboxTest"
Title="Window1" Height="300" Width="300">
<StackPanel x:Name="SP1">
<StackPanel.Resources>
<local:ListOfTests x:Key="ListOfTests" />
</StackPanel.Resources>
<ListBox ItemsSource="{Binding}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True"/>
<TextBox Text="{Binding Path=Name}" Foreground="Black" />
<TextBox Text="{Binding Path=userTest}" />
<ComboBox SelectedItem="{Binding Path=userTest}"
SelectedValue="{Binding Path=userTest.ID}"
ItemsSource="{Binding Source={StaticResource ListOfTests}}"
DisplayMemberPath="Name"
SelectedValuePath="ID"
Foreground="Black" />
</StackPanel>
Now if I change the Binding on the SelectedItem to "{Binding Path=userTest, Mode=OneWay}" then it works, but i can not change the it manually.
Here is a kicker thought... If I target .Net 4.0 (VS2010) then it works fine...
Can anyone please help me find a solution to this?
If I'm understanding your question, it sounds like that WPF isn't being notified when the value of a property changes. You can get around this by implementing the INotifyPropertyChanged interface. For example, the User class would look something like this:
public class User : INotifyPropertyChanged
{
private string name = string.Empty;
public string Name
{
get { return this.name; }
set
{
this.name = value;
this.OnPropertyChanged("Name");
}
}
private int test = 0;
public int Test
{
get { return this.test; }
set
{
this.test = value;
this.OnPropertyChanged("Test");
}
}
private test userTest = null;
public test UserTest
{
get { return this.userTest; }
set
{
this.userTest = value;
this.OnPropertyChanged("UserTest");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler eh = this.PropertyChangd;
if(null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
You should probably do the same for your test class, as well.
WPF will watch for when the PropertyChanged event is fired, and updates any affected bindings as needed. This should cause the selected item in the ComboBox to change back to the test for user2.
Update: OK, I think I got this working. I think that you're missing part of the code in what you posted (like what the DataContext for the Window is), but here's what I got working:
I created a class called ViewModel, which is set to the DataContext of the main Window. Here's its code:
class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
for(int i = 0; i < 4; i++)
{
this.tests.Add(new Test()
{
ID = i,
Name = "Test " + i.ToString(),
});
}
for(int i = 0; i < 4; i++)
{
this.users.Add(new User()
{
Name = "User " + i.ToString(),
ID = i,
UserTest = this.tests[i],
});
}
}
private ObservableCollection<User> users = new ObservableCollection<User>();
public IEnumerable<User> Users
{
get { return this.users; }
}
private ObservableCollection<Test> tests = new ObservableCollection<Test>();
public IEnumerable<Test> Tests
{
get { return this.tests; }
}
private User currentUser = null;
public User CurrentUser
{
get { return this.currentUser; }
set
{
this.currentUser = value;
this.OnPropertyChanged("CurrentUser");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
var eh = this.PropertyChanged;
if(null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
I moved the creation of the two lists to code. One thing I noticed in your sample is that one instance of ListOfTests was used as the ItemsSource of the ComboBox, while another instance was used to build ListOfUsers. I'm not sure if that was part of the problem or not, but it is better to just have one list of tests.
The XAML for the main Window is the following:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ListBox ItemsSource="{Binding Path=Users}"
SelectedItem="{Binding Path=CurrentUser}"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True">
</ListBox>
<TextBox Text="{Binding Path=CurrentUser.Name}" />
<TextBox Text="{Binding Path=CurrentUser.UserTest.Name}" />
<ComboBox ItemsSource="{Binding Path=Tests}"
SelectedItem="{Binding Path=CurrentUser.UserTest}"
DisplayMemberPath="Name" />
</StackPanel>
</Window>
The key to getting things working is the CurrentUser property. It is bound to ListBox.SelectedItem, and ComboBox.SelectedItem is bound to CurrentUser.UserTest. This will change the selection in the ComboBox to represent the test of the user selected in the ListBox.
I got this all working using Visual Studio 2008 SP1, so hopefully it will work for you as well. If you have any problems getting this working, let me know and I'll see what I can do.
Andy,
Here is a more readable extract from the code I have now.
public class User : INotifyPropertyChanged
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
private int iD;
public int ID
{
get
{
return iD;
}
set
{
iD = value;
OnPropertyChanged("ID");
}
}
private test userTest;
public test UserTest
{
get
{
return userTest;
}
set
{
userTest = value;
OnPropertyChanged("UserTest");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler eh = this.PropertyChanged;
if (null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
Looks better than in the comments.
Regards
Corne

Resources