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;
}
Related
I am trying to update a textblock on the view by databinding to a property in the viewmodel (the datacontext for the view).
In the code below; when SelectedItem changes, I want the textblock text to update with the value of the Name property on SelectedItem.
In an attempt to achieve this I have set the binding source to the property that is changing and the binding path to the data I want to update the textblock with.
I.e. I am expecting that the binding engine will see a change on the binding Source (SelectedItem) and pull the data from the binding Path (SelectedItem.Name).
http://msdn.microsoft.com/en-us/library/ms746695.aspx
Setting the SelectedItem raises INPC but the text does not update.
public class ViewModel
{
public IConfiguration Configuration { get; set;}
}
public class Configuration : IConfiguration, INotifyPropertyChanged
{
public Item SelectedItem
{
get { return _item;}
set
{
_item = value;
ItemName = _item.Name;
RaisePropertyChangedEvent("SelectedItem");
}
}
public string ItemName
{
get { return _itemName;}
set
{
_itemName= value;
RaisePropertyChangedEvent("ItemName");
}
}
}
public class Item
{
public string Name { get; set;}
}
I know that changes on Configuration are seen because this works:
<TextBlock Text="{Binding Configuration.ItemName}"/>
But this does not:
<TextBlock Text="{Binding Path=Name, Source=Configuration.SelectedItem}"/>
And nor does this:
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name, Source=Configuration.SelectedItem}"/>
I'm assuming that this should be straightforward - what have I missed?
I've never actually seen anyone use Binding.Source before, so I don't know much about it. But my guess is that it's not dynamic. When you create your binding, it's grabbing a reference to the object specified in your Source, and then that's it: it uses that same reference for the lifetime of the binding.
Why make this complicated? Just use Path. That's the normal way of doing binding, and it's dynamic all the way -- what you're doing is exactly what Path is intended for.
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name}"/>
This is probably working, you just can not see it. The Binding engine has not been notified that the Name property of the Item object has changed.
Try implementing the INotifyPropertyChanged interface on the Item class as well (raising the PropertyChanged event as necessary)
This will work for your third binding situation, and also for a similar definition as below
<TextBlock DataContext="{Binding Path=Configuration.SelectedItem}" Text="{Binding Path=Name}"/>
But for a simpler fix, this should work:
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name}" />
Edit:
public class Configuration : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private Item _SelectedItem = null;
public Item SelectedItem
{
get
{
return _SelectedItem;
}
set
{
_SelectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
public class Item
{
public string Name { get; set; }
}
Then in a Command Execute somewhere I have this:
Configuration.SelectedItem = new Item() { Name = "test" };
Which updates the TextBlock in the View fine:
<TextBlock Text="{Binding Path=Configuration.SelectedItem.Name}" />
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.
I have a gridview were I define some columns, like this...
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyProp}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
I bind my gridview to a collection and implemts INotifyPropertyChanged in the property MyProp. This works well and any changes of MyProp are reflected to the gridview.
If I add another column that is bound to the object itself I dont get any notifications/updates. My code...
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource myConverter}}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
I think I need something like INotifyPropertyChanged for the object but I have no idea how to do this. Any suggestions?
Yes, the actual instance itself never changes - only its properties.
Presumably your converter relies on a bunch of properties from the object you've bound to? If so, you could use a MultiBinding and change your converter to an IMultiValueConverter. Then you can bind to all the dependent properties that might cause the TextBlock to update.
Make the object impletment the interface INotifyPropertyChanged
Here is an example from MSDN
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerName = String.Empty;
private string companyNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer()
{
customerName = "no data";
companyNameValue = "no data";
phoneNumberValue = "no data";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CompanyName
{
get {return this.companyNameValue;}
set
{
if (value != this.companyNameValue)
{
this.companyNameValue = value;
NotifyPropertyChanged("CompanyName");
}
}
}
public string PhoneNumber
{
get { return this.phoneNumberValue; }
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged("PhoneNumber");
}
}
}
}
I have created a user control "SearchControl"(which will be reused further in other screens as well.
SearchControl ->
<usercontrol name="SearchControl"......>
<stackpanel orientation="horizontal"...>
<TextBox Text"{Binding Path=UserId}"...>
<Button Content="_Search" ....Command="{Binding Path=SearchCommand}"..>
</stackpanel>
</usercontrol>
public partial class SearchControl : UserControl
{
public SearchControl()
{
InitializeComponent();
DataContext=new UserViewModel();
}
}
I then use this control in a window "UserSearch"
<window name="UserSearch".............
xmlns:Views="Namespace.....Views">
<Grid>
<Grid.RowDefinitions>
<RowDefinition..../>
<RowDefinition..../>
<RowDefinition..../>
<RowDefinition..../>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition..../>
<ColumnDefinition..../>
</Grid.ColumnDefinitions>
<Views:SearchControl Grid.Row="0" Grid.Colspan="2"/>
<TextBlock Text="User Id" Grid.Row="1" Grid.Column="0"..../>
<TextBox Text="{Binding Path=UserId}" Grid.Row="1" Grid.Column="1".../>
<TextBlock Text="First Name" Grid.Row="2" Grid.Column="0"..../>
<TextBox Text="{Binding Path=FirstName}" Grid.Row="2" Grid.Column="1".../>
<TextBlock Text="Last Name" Grid.Row="3" Grid.Column="0"..../>
<TextBox Text="{Binding Path=LastName}" Grid.Row="3" Grid.Column="1".../>
</Grid>
</window>
public partial class UserSearch : Window
{
public UserSearch()
{
InitializeComponent();
DataContext=new UserViewModel();
}
}
What I am aimimg for:
When I enter UserId inthe textbox in SearchControl and click on Search button, the resulting record which is retieved should be displayed in the textboxes for UserId, FirstName, LastName
class UserViewModel:INotifyPropertyChanged
{
DBEntities _ent; //ADO.Net Entity set
RelayCommand _searchCommand;
public UserViewModel()
{
_ent = new DBEntities();
}
public string UserId {get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
public ICommand SearchCommand
{
get
{
if(_searchCommand == null)
{
_searchCommand = new RelayCommand(param = > this.Search());
}
return _searchCommand;
}
}
public void Search()
{
User usr = (from u in _ent
where u.UserId = UserId
select u).FirstOrDefault<User>();
UserId = usr.UserId;
FirstName = usr.FirstName;
LastName = usr.LastName;
OnPropertyChanged("UserId");
OnPropertyChanged("FirstName");
OnPropertyChanged("LastName");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertChangedEventArgs(propertyName);
}
}
Here as I am using two separate instances of the UserViewModel for the SearchControl and UserSearch, even though I retieve the record for the particular user on searching by UserId, I am unable to bind the properties UserId, FullName , LastName with the respective textboxes...How do I fix this problem??
1) Don't let the View initialize the presentation model, it should be the other way round. The presentation model is the object of interest, not the particular view.
public interface IView
{
void SetModel(IPresentationModel model);
}
publiv class View : UserControl, IView
{
public void SetModel(IPresentationModel model)
{
DataContext = model;
}
}
public class PresentationModel : IPresentationModel
{
public PresentationModel(IView view)
{
view.SetModel(this);
}
}
2) Don't set the data context of the subview in the code behind file. Usually, the view that uses the subview sets the data context in the xaml file.
3) Usually each view has its own presentation model. The presentation model should have one type of view. That means that different views of a single presentation model may differ in appearance but not in functionality (in your case one view is used to search, the other one is used to display and edit data). So, you have vialoted the Single Responsibilty Principle.
4) Abstract your data access layer, otherwise you won't be able to unit test your presentation model (because it needs access to the data base directly). Define an repository interface and implementation:
public interface IUserRepository
{
User GetById(int id);
}
public class EntityFrameworkUserRepository : IUserRepository
{
private readonly DBEntities _entities;
public EntityFrameworkUserRepository(DBEntities entities)
{
_entities = entities;
}
public User GetById(int id)
{
return _entities.SingleOrDefault(u => u.UserId == id);
}
}
5) Don't use FirstOrDefault because an ID is unique, so there must not be several users for one id. SingleOrDefault (used in the code snippet above) throws an exception if more than one result is found but returns null if none is found.
6) Bind directly to your entity:
public interface IPresentationModel
{
User User { get; }
}
<StackPanel DataContext="{Binding Path=User}">
<TextBox Text="{Binding Path=FirstName}" />
<TextBox Text="{Binding Path=LastName}" />
</StackPanel>
7) Use the CommandParameter to provide the user id you are searching for directly with your command.
<TextBox x:Name="UserIdTextBox">
<Button Content="Search" Command="{Binding Path=SearchCommand}"
CommandParameter="{Binding ElementName=UserIdTextBox, Path=Text}" />
public class PresentationModel
{
public ICommand SearchCommand
{
// DelegateCommand<> is implemented in some of Microsoft.BestPractices
// assemblies, but you can easily implement it yourself.
get { return new DelegateCommand<int>(Search); }
}
private void Search(int userId)
{
_userRepository.GetById(userId);
}
}
8) If only data binding causes issues, look at the following website to get some ideas how to debug wpf data bindings: http://beacosta.com/blog/?p=52
9) Don't use strings that contain property names. Once you refactor your code and properties change their names, to will have a stressful time finding all property names in strings and fixing them. Use lambda expressions instead:
public class PresentationModel : INotifiyPropertyChanged
{
private string _value;
public string Value
{
get { return _value; }
set
{
if (value == _value) return;
_value = value;
RaisePropertyChanged(x => x.Value);
}
}
public PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(Expression<Func<PresentationModel, object>> expression)
{
if (PropertyChanged == null) return;
var memberName = ((MemberExpression)expression.Body).Member.Name;
PropertyChanged(this, new PropertyChangedEventArgs(memberName));
}
}
I wish you the best to solve your problem and I hope that I could help you a little bit.
Best Regards
Oliver Hanappi
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; }
}