I implemented INotifyPropertyChanged as recommended by many threads.
Implementation 1
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string pName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(pName));
}
}
public class Model : Notifier, IDataErrorInfo
{
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("name_changed"); }
}
}
And the viewmodel consists of the model and command to make changes to model properties.
public class ViewModel : Notifier
{
private Model _model;
public Model Model
{
get { return _model; }
set { _model = value; OnPropertyChanged("model_changed"); }
}
private ICommand _cmd;
public ICommand Command
{
get { return _cmd; }
set { _cmd = value; }
}
public void ExecuteCommand(object para)
{
Console.WriteLine("Command executed");
Model.Name = "new name";
}
}
VM is then binded to the view.
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center" Width="100">
<TextBox.Text>
<Binding Path="Model.Name" Mode="TwoWay" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<ExceptionValidationRule></ExceptionValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
When the command is executed, the TextBox does not get updated to new value.
However, if I implement the INotifyPropertyChanged like this instruction, the binding works.
Implementation 2
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName]string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
}
public class Model : Notifier, IDataErrorInfo
{
private string name;
public string Name
{
get { return name; }
set { SetProperty(ref name, value); }
}
}
What is missed in the first method?
The main problem with Implementation 1 is that the string parameter of your OnPropertyChanged method needs to be the exact property name that is being changed. For your two examples, "model_changed" should be changed to "Model"
and "name_changed" should read "Name". Here are two great techniques to mitigate potential human error with the typing of literal string names:
1. Use the CallerMemberName Attribute
If you are allowed and have access to the System.Runtime.CompilerServices namespace, you can write your base class as such to have the property name automatically passed as the string parameter of the OnPropertyChanged method:
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string pName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(pName));
}
}
Then you can simply call OnPropertyChanged() in your property's getter.
2. Use the nameof keyword
Alternatively, you may simply replace the literal typed property name with nameof(<InsertPropertyNameHere>) which will return the name without any risk of mistyping, like this example:
public class Model : Notifier, IDataErrorInfo
{
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged(nameof(Name)); }
}
}
Please add property name like this.
public class Model : Notifier, IDataErrorInfo
{
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
}
Related
I am trying to implement the MVVM Pattern but unfortunately is taking longer than expected.
I have a ListView populated by an ObservableCollection of ContactsVm, Adding or Removing Contacts works perfectly, the problem comes when trying to change only one Item from this collection by selecting it.
The Xaml where I am setting my bindings:
<ListView ItemsSource="{Binding ContactsToDisplay}"
SelectedItem="{Binding SelectedContact, Mode=TwoWay}"
SeparatorColor="Black"
ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding FirstName}"
Detail="{Binding Id}">
<TextCell.ContextActions>
<MenuItem
Text="Delete"
IsDestructive="true"
Clicked="Delete_OnClicked"
CommandParameter="{Binding .}" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Its cs:
public ContactBookApp()
{
InitializeComponent();
MapperConfiguration config = new MapperConfiguration(cfg => {
cfg.CreateMap<Contact, ContactVm>();
cfg.CreateMap<ContactVm, Contact>();
});
BindingContext = new ContactBookViewModel(new ContactService(), new PageService(), new Mapper(config));
}
private void AddButton_OnClicked(object sender, EventArgs e)
{
(BindingContext as ContactBookViewModel)?.AddContact();
}
private void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
(BindingContext as ContactBookViewModel)?.SelectContact(e.SelectedItem as ContactVm);
}
private void Delete_OnClicked(object sender, EventArgs e)
{
(BindingContext as ContactBookViewModel)?.DeleteContact((sender as MenuItem)?.CommandParameter as ContactVm);
}
}
My ViewModel, here the "problematic" part is the SelectContact method, I am posting the rest in case it helps:
public class ContactBookViewModel : BaseViewModel
{
private readonly IContactService _contactService;
private readonly IPageService _pageService;
private readonly IMapper _mapper;
private ContactVm _selectedContact;
public ObservableCollection<ContactVm> ContactsToDisplay { get; set; }
public ContactVm SelectedContact
{
get => _selectedContact;
set => SetValue(ref _selectedContact, value);
}
public ContactBookViewModel(IContactService contactService, IPageService pageService, IMapper mapper)
{
_contactService = contactService;
_pageService = pageService;
_mapper = mapper;
LoadContacts();
}
private void LoadContacts()
{
List<Contact> contactsFromService = _contactService.GetContacts();
List<ContactVm> contactsToDisplay = _mapper.Map<List<Contact>, List<ContactVm>>(contactsFromService);
ContactsToDisplay = new ObservableCollection<ContactVm>(contactsToDisplay);
}
public void SelectContact(ContactVm contact)
{
if (contact == null)
return;
//None of this approaches works:
//SelectedContact.FirstName = "Test";
//contact.FirstName = "Test;
}
}
}
My ContactVm class:
public class ContactVm : BaseViewModel
{
private string _firstName;
public int Id { get; set; }
public string FirstName
{
get => _firstName;
set => SetValue(ref _firstName, value);
}
}
The BaseViewModel:
public class BaseViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T backingField, T value, [CallerMemberName]string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, value))
return;
backingField = value;
OnPropertyChanged(propertyName);
}
}
As you can see, I am trying to update each selected contact setting its FirstName = "Test", the changed are updated but unfortunately they are not getting reflected in the UI, hope you can help me to find what I am doing wrong.
Thanks in advance!
Your BaseViewModel does not implement the INotifyPropertyChanged interface.
Since you had used MVVM , you could handle the logic diretly in your ViewModel when you select item in listview (you don't need to define ItemSelected event any more) .
private ContactVm _selectedContact;
public ContactVm SelectedContact
{
set
{
if (_selectedContact!= value)
{
_selectedContact= value;
SelectedContact.FirstName="Test";
NotifyPropertyChanged("SelectedContact");
}
}
get { return _selectedContact; }
}
And don't forget to implement the INotifyPropertyChanged to your model and viewmodel.
I guess the NotifyPropertyChangedInvocator attribute is not properly notifying the property changes. But I am not sure about that. Because your BaseViewModel does not implement the INotifyPropertyChanged interface.
The below code works fine for me. This is how I use it in my entire project.
I have directly derived the INotifyPropertyChanged interface in my BaseModel and implemented the property changes.
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ContactVm : BaseModel
{
private string _firstName;
public int Id { get; set; }
public string FirstName
{
get { return _firstName; }
set
{
this._firstName = value;
NotifyPropertyChanged();
}
}
}
This is what I have in my callback.
public void SelectContact(ContactVm contact)
{
if (contact == null)
return;
contact.FirstName = "Test";
}
The only difference is I have implemented property changes for the ObservableCollection in ViewModel too.
public ObservableCollection<ContactVm> ContactsToDisplay
{
get { return _contactsToDisplay; }
set
{
this._contactsToDisplay = value;
NotifyPropertyChanged();
}
}
Note that I have not used your SelectedContact binding in my case. May be as you said that binding would be the issue.
I hope it helps you.
Why can databinding be seen working in the designer:
Click to show image: Databinding seems OK
But runtime shows nothing?
Click to show image: No Data, no usercontrol?
Outline code structure:
ViewModelBase : baseclass inheriting from INotofyPropertychanged
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
}
SiteViewModel : Model class with Id/Name/Description Properties
public class SiteViewModel : ViewModelBase
{
private int _SiteID;
private string _Name;
private string _Description;
public int SiteID
{
get { return _SiteID; }
set { SetProperty(ref _SiteID, value); }
}
public string Name
{
get { return _Name; }
set { SetProperty(ref _Name, value); }
}
public string Description
{
get { return _Description; }
set { SetProperty(ref _Description, value); }
}
}
SitesViewModel: ObservableCollection of SiteViewModel
public class SitesViewModel : ViewModelBase
{
private ObservableCollection<SiteViewModel> _AllSites;
public ObservableCollection<SiteViewModel> AllSites {
get { return _AllSites; }
set { SetProperty<ObservableCollection<SiteViewModel>>(ref _AllSites, value); }
}
public SitesViewModel()
{
AllSites = new ObservableCollection<SiteViewModel>();
for (int count = 1; count <= 3; count++)
{
AllSites.Add(new SiteViewModel { SiteID = count, Name = "Test" + count.ToString(), Description = "Site:" + count.ToString() } );
}
}
}
SiteManagerControl : UserControl with a SitesViewModel property _AllSites
public partial class SiteManagerControl : UserControl
{
private SitesViewModel _AllSites;
public SitesViewModel AllSites
{
get { return _AllSites; } //<-- Breakpoint not hit!
set {
if (_AllSites != value)
{ _AllSites = value;
OnPropertyChanged("AllSites");
}}
}
public SiteManagerControl(){
_AllSites = new SitesViewModel();}
(XAML can be seen in the first linked image above, Note the breakpoint not hit line in the above). The user control is hosted in a Tabcontrol that is part of an ObservableCollection. I don't think this is an issue in the databinding. Will post the code for the tabs if needed.
There are no errors in the Debug Output window to indicate why the databinding is failing.
Your listview DataContext Data binding is with object from class(SitesViewModel)
This class has property named (AllSites) that has oveservable collection property named (AllSites) as well.
so I think you have to fix the ItemSource binding in the list view like this:
ItemsSource="{Binding AllSites.AllSites}"
Will's comment (thanks!) above pointed me in the right direction: Changed MainWindow.xaml to contain:
<DataTemplate DataType="{x:Type vm:SitesViewModel}">
<uc:SitesView></uc:SitesView>
</DataTemplate>
Also followed this : http://codingtales.blogspot.co.uk/2010/02/creating-complete-tabbed-interface-in.html to rework my tab interface
I am implementing a cart in Xamarin.Forms. In my cart page there is a ListView with data. Each of the cell contains a button to select the count of item and amount. In the cart view there is a grand total label.
My problem is the grand total is not updating while the number picker changes. The calculation method is called upon item adding view cell. I know that i need to implement INotifyProperty for this, but I'm unsure of how to do it.
I have a base view model which inherits INotifyProperty that contains an event.
public class BaseViewModel : INotifyPropertyChanged
{
private double _price;
public double Price
{
get
{
return _price;
}
set
{
_price = value;
OnPropertyChanged("Price");}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
View model
public BaseViewModel()
{
App.Instance.ViewModel = this;
TempList = TempList ?? new ObservableCollection<cm_items>();
this.Title = AppResources.AppResource.Cart_menu_title;
this.Price = CartCell.price;
}
As a design methodology, its better to implement MVVM as a subclass and implement it to your ViewModel.
Sample Implementation:
public class ObservableProperty : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I also strongly suggest implementing ICommand as a Dictionary structure like:
public abstract class ViewModelBase : ObservableProperty
{
public Dictionary<string,ICommand> Commands { get; protected set; }
public ViewModelBase()
{
Commands = new Dictionary<string,ICommand>();
}
}
So all todo in your ViewModel is just inherit the ViewModelBase class and use it
class LoginViewModel : ViewModelBase
{
#region fields
string userName;
string password;
#endregion
#region properties
public string UserName
{
get {return userName;}
set
{
userName = value;
OnPropertyChanged("UserName");
}
}
public string Password
{
get{return password;}
set
{
password = value;
OnPropertyChanged("Password");
}
}
#endregion
#region ctor
public LoginViewModel()
{
//Add Commands
Commands.Add("Login", new Command(CmdLogin));
}
#endregion
#region UI methods
private void CmdLogin()
{
// do your login jobs here
}
#endregion
}
Finally: Xaml Usage:
<Entry Placeholder="Username" Text="{Binding UserName}"/>
<Entry Placeholder="Password" Text="{Binding Password}" IsPassword="True"/>
<Button Text="Login" Command="{Binding Commands[Login]}"/>
For example try this view model:
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetPropertyValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (value == null ? field != null : !value.Equals(field))
{
field = value;
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
return true;
}
return false;
}
}
and in inherited classes use it like this:
private int myProperty;
public int MyProperty
{
get { return this.myProperty; }
set { this.SetPropertyValue(ref this.myProperty, value); }
}
When I started Xamarin coding, the MVVM was a bit confusing until I discovered that the PropertyChangedEvent on the ViewModel fired off a signal to the View (ContentPage), and updated the Label/textbox/etc.
For those looking for the 'latest and greatest'... Here's some revised code:
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
and on your property Setter:
public string SomeProperty
{
get { return _somProperty; }
set
{
_someProperty= value;
OnPropertyChanged();
}
}
}
Nice? No? Saves having to pass the property name each time!
I am having an issue getting my model changes updated back into my viewmodel so i can display. In this example i have a label and a button, when i press the button it will execute some business logic, and should update the label on screen. However, when my model changes the view will not. Any Idea on what i am doing wrong here?
View-
<Window.DataContext>
<vm:ViewModel>
</Window.DataContext>
<Grid>
<Label Content="{Binding Path=Name}"/>
<Button Command={Binding UpdateBtnPressed}/>
</Grid>
ViewModel
public ViewModel()
{
_Model = new Model();
}
public string Name
{
get{return _Model.Name;}
set
{
_Model.Name = value;
OnPropertyChanged("Name");
}
}
public ICommand UpdateBtnPressed
{
get{
_UpdateBtn = new RelayCommand(param => UpdateLabelValue());
return _UpdateBtn;
}
private void UpdateLabelValue()
{
_Model.Name = "Value Updated";
}
Model
private string name = "unmodified string";
public string Name
{
get{return name;}
set{name = value;}
}
Try this:
private void UpdateLabelValue()
{
Name = "Value Updated";
}
It seems you've missed to implement the INotifyPropertyChanged interface.
Your model must implement INotifyPropertyChanged such as;
public class Personel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnChanged("Name");}
}
void OnChanged(string pn)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(pn));
}
}
}
I got a view model like this:
public class BaseViewModelTech : INotifyPropertyChanged
{
static string _TechnicianID;
public string TechnicianID
{
get {
return _TechnicianID;
}
set {
_TechnicianID = TechnicianID;
OnPropertyChanged("TechnicianID");
}
}
static string _DeviceID;
public string DeviceID
{
get
{
return _DeviceID;
}
set
{
_DeviceID = DeviceID;
OnPropertyChanged("DeviceID");
}
}
// In ViewModelBase.cs
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
Debug.Fail(msg);
}
}
}
I send it as parameter to my xaml.cs
public partial class BaseView : Window{
BaseViewModelTech viewModel;
public BaseView (BaseViewModelTech vm)
{
InitializeComponent();
viewModel = vm;
}}
what do I write to access it throught xaml using binding?? I failed to understand multiple examples.
Change your code behind of your view slightly:
public partial class BaseView : Window
{
BaseViewModelTech viewModel;
public BaseView (BaseViewModelTech vm)
{
InitializeComponent();
viewModel = vm;
this.DataContext = vm; // <----------- add this
}
}
And then in your XAML you can have something like this:
<TextBlock Text="{Binding TechnicianID}" />
Also note that in your setters you want to do the notification after the property value is changed, not before:
set
{
_DeviceID = DeviceID;
OnPropertyChanged("DeviceID"); // <------ this goes after the member variable change
}
In your case you can't directly refer your ViewModel directly into xaml due to you vm instance being member of your View. So, you should set the DataContext of your view first in code-behind:
public partial class BaseView : Window{
BaseViewModelTech viewModel;
public BaseView (BaseViewModelTech vm)
{
InitializeComponent();
viewModel = vm;
this.DataContext=viewModel;
}}
then in your my xaml.xaml for example for label :
<Label Content="{Binding TechnicianID }"/>