Implementing a simple Master-Detail scenario for WPF in MVVM - wpf

I have a WPF application using MVVM. I have some user controls that should show a Person FirstName, LastName and email in 3 Textbox controls using simple databinding.
The User Control has a simple combobox where the user selects the ID for the user and therefore the Person Record with that ID should be loaded (its data fetched from the database) and then the FirstName, LastName and Email will display in the textBoxes.
I have a Usercontrol with the combobox for the IDs and 3 textboxes for the three properties, a ViewModel Class and a Model class (person Class) with the three properties (FirstName, LastName and Email).
What is the simplest way to implement this behavior using MVVM(preferably)? any samples?

I'm guessing here since your question is a little vague that you're not quite sure how to hook the pieces together. For simplicity's sake let us hook the ViewModel directly to the user control and get it all binding.
As long as your view model is populated with the correct set of People, all the binding below will handle the data and show the correct data. Take note of the two-way binding for the selected item in the combobox. That allows WPF to send back the new selected item to the viewmodel.
In the UserControl's code behind:
public MyUserControl()
{
DataContext = new MyViewModel();
}
In the UserControl's XAML:
<ComboBox ItemsSource="{Binding AllPeople}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
<TextBox Text="{Binding SelectedItem.LastName}" />
<TextBox Text="{Binding SelectedItem.FirstName}" />
<TextBox Text="{Binding SelectedItem.EmailName}" />
Your ViewModel:
private IEnumerable<Person> _allPeople;
public IEnumerable<Person> AllPeople
{
get { return _allPeople; }
set
{
if (_allPeople != value)
{
_allPeople = value;
NotifyPropertyChanged("AllPeople");
}
}
}
private Person _selectedItem;
public Person SelectedItem
{
get { return _selectedItem; }
set
{
if (!_selectedItem != value)
{
_selectedItem = value;
NotifyPropertyChanged("SelectedItem");
}
}
}
private void NotifyPropertyChanged(string propertyName)
{
if ( PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
}
public class Person
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}

Related

mvvm calculated fields

I am using the MVVM light toolkit for a wpf application which talks to a wcf server.
The wcf server returns a Person object (proxy object).
This person object has several fields, name, surname, etc etc.
My viewmodel calls the webservice, and then gets a return of this model.
My view is bound to viewmodel's model, and the fields correctly bound to each ui textbox.
all cool in the shade, system functions nicely.
two fields on the model are DateOfBirth, and NationalIDNumber
(fyi: in south africa you can derive a persons date of birth from an ID number)
So after the user inputs or updtes the NationalIdNumber (if available) I would like the DOB to be determined as well.
But the DOB must still be mapped to the initial field that was returned from the WCF service, so I cant just bind it to the NationalIdNumber with a converter. It needs to stay bound to DOB field of the wcf proxy so that it can get persisted back.
how best should i implement this?
If this was a non mvvm project, i would just put a event on the IDNumber text fields so that if it looses focus, try calculate a dob from it (might not always be possible if text in it is rubbish) and then overwrite the value of the Dob textbox.
I was thinking of just tweaking the Person objects NationalIdNumber setter, but this will get removed the minute I update the webservice reference
Thanks
You can have Person property in your view model:
ViewModel:
public class PersonViewModel : INotifyPropertyChanged
{
Person person = new Person();
public Person Person
{
get
{
return person;
}
set
{
person = value;
NotifyPropertyChanged("Person");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View:
<TextBox Text="{Binding Person.NationalIDNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Height="23" HorizontalAlignment="Left" Margin="128,98,0,0"
Name="textBox1" VerticalAlignment="Top" Width="120" />
So whenever you update Person's properties, Person's setter will be called.
...
Edit:
Using MvvmLight:
ViewModel:
public class PersonViewModel : ViewModelBase
{
Person person = new Person();
public Person Person
{
get
{
return person;
}
set
{
person = value;
RaisePropertyChanged("Person");
}
}
}
View:
<TextBox Text="{Binding Person.NationalIDNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Height="23" HorizontalAlignment="Left" Margin="128,98,0,0"
Name="textBox1" VerticalAlignment="Top" Width="120" />
public class PropertyHelpersViewModel : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
if(text != value)
{
text = value;
RaisePropertyChanged("Text");
}
}
}
protected void RaisePropertyChanged(string propertyName)
{
var handlers = PropertyChanged;
if(handlers != null)
handlers(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}

How to bind a textbox.Text property in WPF MMVM design

So I'm trying to learn the MVVM design patter in WPF, I want to do the following:
In external class I've got a ObservableCollection _students that is bound to a listview on the WPF window using MVVM design pattern. The listview shows only the Student's name and Age.
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string Course { get; set; }
public DateTime JoiningDate { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Student> _students;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ObservableCollection<Student> Students
{
get
{
return _students;
}
set
{
_students = value;
NotifyPropertyChanged("Students");
}
}
All good, but I want to put a TextBox and set it to show the listview's selected item's course property. This means I must
get the listview's selected index (ok)
bind the textbox.Text property to Students[that index].Course
I'm stuck at 2. Any help?
i would solve this by another way.
Take a look at this post
.
Another way would be that your ViewModel contains a Student-property(e.g. SelectedStudent) which is bind to the SelctedItem of the listView. Then you can handel this by
{Binding Path=SelectedStudent.Course}
Assume you bind the listview to a collection of type SampleData like below:
SampleData.cs
public class SampleData
{
public int Id { get; set; }
public string Text { get; set; }
public decimal Value { get; set; }
}
Then you bind the ListView ItemsSource to a collection. WIt does not matter if you bind ItemsSource property to a property on ViewModel or you bind it in code-behind like the code below.
var source = new List<SampleData>();
source.Add(new SampleData() { Id = 1, Text = "AAA" });
source.Add(new SampleData() { Id = 2, Text = "BBB" });
source.Add(new SampleData() { Id = 3, Text = "CCC" });
source.Add(new SampleData() { Id = 4, Text = "DDD" });
source.Add(new SampleData() { Id = 5, Text = "EEE" });
You can bind TextBox's Text property to the SelectedItem directly on the View.
<StackPanel Orientation="Horizontal">
<ListView x:Name="listView1" />
<TextBox Text="{Binding ElementName=listView1, Path=SelectedItem.Text}" />
</StackPanel>

Selected Value Combo Box not binding

Ok so I have spent hours now trying to figure this out and I cant.
I have the below combo box which is binding correctly to my collection of data.
<ComboBox Name="cbx" Width="250" Height="25"
Visibility="{Binding Path=IsComboBox,Converter={StaticResource BoolConverter}}"
ItemsSource="{Binding Path=Answers}"
SelectedValuePath="AnswerId"
SelectedItem="{Binding Path=SelectedAnswer, Mode=TwoWay}"
DisplayMemberPath="Answer"/>
The Selected Item however is not populating top my Selected Answer property. I put a textbox on the form and bound it to SelectedAnswer.Answer and that is binding to the answer correctly.
For some reason though my combo box will not bind the selected answer
I have read something about the layout of the combo box property and tried changing that, also stepped through the getter and setter of the property to ensure it is not clearing down (which is not as it will bind to the text box)
Please help with this.
SurveyAnswer:
public class SurveyAnswer : INotifyPropertyChanged
{
private Guid answerId;
public Guid AnswerId
{
get { return answerId; }
set {
answerId = value;
NotifyPropertyChanged("AnswerId");
}
}
private string answer;
public string Answer
{
get { return answer; }
set {
answer = value;
NotifyPropertyChanged("Answer");
}
}
public Guid SurveyLineID { get; set; }
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set {
isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
#region NotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
I think you need to change SelectedItem to SelectedValue. Sometimes that order of parameters matters as well.
<ComboBox Name="cbx" Width="250" Height="25"
Visibility="{Binding Path=IsComboBox,Converter={StaticResource BoolConverter}}"
ItemsSource="{Binding Path=Answers}"
SelectedValue="{Binding Path=SelectedAnswer, Mode=TwoWay}"
DisplayMemberPath="Answer" SelectedValuePath="AnswerId"/>
This is helpful:
http://johnpapa.net/binding-to-silverlight-combobox-and-using-selectedvalue-selectedvaluepath-and-displaymemberpath

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 - Create Treeview

I have list of type field that can represent a hierarchy: List MyFields
public class Field
{
public Field(string name, string value)
{
this.Name = name;
this.Value = value;
}
public string Name { get; set; }
public string Value { get; set; }
public IList<Field> SubFields { get; set; }
}
How can i bind MyFields to a TreeView?
EDIT:
I forgot, i want to eg. show the value in a message box when clicking on the item.
Set the TreeViews ItemsSource to the Property you want to bind with.
You can create a HierarchicalDataTemplate which should be placed in the resources of your TreeView or at an higher level, make sure to set the DataType to your class to make it apply.
Something like this for example:
<HierarchicalDataTemplate DataType="{x:Type data:Field}"
ItemsSource="{Binding SubFields}">
<ContentControl MouseDoubleClick="TreeViewItem_MouseDoubleClick">
<TextBlock Text="{Binding Name}"/>
</ContentControl>
</HierarchicalDataTemplate>
private void TreeViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
Field field = (sender as FrameworkElement).DataContext as Field;
MessageBox.Show(field.Value.ToString());
}
You also need a root-elements list to which you can bind the ItemsSource of the TreeView itself.

Resources