Reputation: 3
I have a simple wpf project that consists of a listbox that contains ObservableCollection of Employees and 3 text boxes.
I made a "model" class as well that will link property objects like Employee to the MainWindow. The property for the Employee class is called "CurrentEmployee".
I initialize a Model object to the Window and name it "boundModel". When the selection is changed on the listbox, I'd like the "boundModel.CurrentEmployee" to change to whichever employee was selected, and for the 3 text boxes to also change to the employee's properties. What I have currently sets the "boundModel.CurrentEmployee" to whatever name is selected in the listbox. When the listbox selection changes so does boundModel.CurrentEmployee! Unfortunately, it doesn't update on the MainWindow!
I want to bind boundModel.CurrentEmployee to the 3 text boxes. I'd like to do this so that when I create another class, I can have that class link to some more UI controls through the Model class. Is this either possible, or efficient in any way? My windows forms background seems almost useless here!
Thanks in advance!
Main Window Backend
public partial class MainWindow : Window
{
Model boundModel;
public MainWindow()
{
boundModel = new Model();
InitializeComponent();
this.DataContext = boundModel;
lb_Employees.ItemsSource = boundModel.EmployeeCollection;
}
private void lb_Employees_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
boundModel.CurrentEmployee = (Employee)lb_Employees.SelectedItem;
}
}
Main Window XAML
<ListBox x:Name="lb_Employees" SelectionChanged="lb_Employees_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="tb_Name" Text="{Binding boundModel.Name}"/>
<TextBox x:Name="tb_Age" Text="{Binding Age}"/>
<TextBox x:Name="tb_Kids" Text="{Binding HasKids}"/>
Employee
class Employee : System.ComponentModel.INotifyPropertyChanged
{
private string name;
private int age;
private bool hasKids;
public string Name
{
get{return name;}
set
{
name = value;
Notify("Name");
}
}
public int Age
{
get{return age;}
set
{
age = value;
Notify("Age");
}
}
public bool HasKids
{
get{return hasKids;}
set
{
hasKids = value;
Notify("HasKids");
}
}
public Employee(string name, int age, bool haskids)
{
this.Name = name;
this.Age = age;
this.HasKids = haskids;
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void Notify(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
Model
class Model : System.ComponentModel.INotifyPropertyChanged
{
public ObservableCollection<Employee> EmployeeCollection = new ObservableCollection<Employee>();
private Employee currentemployee;
public Employee CurrentEmployee
{
get {return currentemployee;}
set
{
currentemployee = value;
Notify("Employee");
}
}
public Model()
{
EmployeeCollection.Add(new Employee("Tom Smith", 40, true));
EmployeeCollection.Add(new Employee("Beth Smith", 38, true));
EmployeeCollection.Add(new Employee("Steph Rodriguez", 25, false));
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void Notify(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
Upvotes: 0
Views: 2110
Reputation: 37066
The name of this property is CurrentEmployee
, not Employee
.
public Employee CurrentEmployee
{
get {return currentemployee;}
set
{
currentemployee = value;
//Notify("Employee");
Notify("CurrentEmployee");
}
}
boundModel
is a private field. You can't bind to a private anything, and you can't bind to any kind of field.
It is also (or should be) the same object as DataContext
. These Bindings go to DataContext
to look for the property named in their Paths. So if you want to bind to DataContext.CurrentEmployee.Name
, do so:
<TextBox x:Name="tb_Name" Text="{Binding CurrentEmployee.Name}"/>
<TextBox x:Name="tb_Age" Text="{Binding CurrentEmployee.Age}"/>
<TextBox x:Name="tb_Kids" Text="{Binding CurrentEmployee.HasKids}"/>
I'd actually recommend getting rid of boundModel
and doing this (I wouldn't name my viewmodel Model, but you already did):
public MainWindow()
{
InitializeComponent();
ViewModel = new Model();
}
public Model ViewModel {
get { return DataContext as Model; }
set { DataContext = value; }
}
And I'd bind ItemsSource in the XAML, and set CurrentEmployee by binding as well:
<ListBox
x:Name="lb_Employees"
ItemsSource="{Binding EmployeeCollection}"
SelectedItem="{Binding CurrentEmployee}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Whoops, another way to simplify: That ItemTemplate isn't doing enough to justify its existence. You can use DisplayMemberPath
instead:
<ListBox
x:Name="lb_Employees"
ItemsSource="{Binding EmployeeCollection}"
SelectedItem="{Binding CurrentEmployee}"
DisplayMemberPath="Name"
/>
Now that we've dispensed with all of the places where the code-behind interacts directly with the viewmodel, we could dispense with the ViewModel
property as well. It's no longer needed. Codebehind isn't a bad thing, but if you do WPF properly you do often find that there's little need to do anything there.
Upvotes: 1