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));
}
}
}
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.
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"); }
}
}
I've a problem with DataGrid in Prism MVVM.
When I edited entity in other window, then I create new window with DataGrid is not updated. Only run application again help. It's my code:
<DataGrid Name="ClientsTable" IsReadOnly="True" ItemsSource="{Binding Path=ListOfClients, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" Margin="22,10,22,55" Width="800"/>
Part of ViewModel for this window:
public ListOfClientsViewModel(IClientService clientService, IEventAggregator eventAggregator)
{
this.clientService = clientService;
this.eventAggregator = eventAggregator;
ListOfClients.AddRange(clientService.GetAllClientsForList());
}
private ObservableCollection<ClientForList> listOfClients = new ObservableCollection<ClientForList>();
public ObservableCollection<ClientForList> ListOfClients
{
get { return listOfClients; }
set { SetProperty(ref listOfClients, value); }
}
And part of model from collection. It's in other project.
public class ClientForList : INotifyPropertyChanged
{
private string id;
private string name;
private string firstname;
private string lastname;
private string city;
private DateTime createdDate;
[DisplayName("Numer klienta")]
public string Id
{
get { return id; }
set
{
if (value != id)
{
id = value;
OnPropertyChanged();
}
}
}
[DisplayName("Nazwa")]
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
OnPropertyChanged();
}
}
}
[DisplayName("ImiÄ™")]
public string Firstname
{
get { return firstname; }
set
{
if (value != firstname)
{
firstname = value;
OnPropertyChanged();
}
}
}
[.....]
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And, when I edit entity in other window, then I open window with DataGrid by:
void ExecuteListOfClients()
{
ListOfClients listOfClientsWindow = new ListOfClients();
listOfClientsWindow.DataContext = new ListOfClientsViewModel(IClientService, EventAggregator);
listOfClientsWindow.ShowDialog();
}
And data in DataGrid is old. When I restart application data is actual. Help!
Instead of
set { SetProperty(ref listOfClients, value); }
do
set { SetProperty(listOfClients, value); }
and instead of
ListOfClients.AddRange(clientService.GetAllClientsForList());
do
ListOfClients = clientService.GetAllClientsForList().ToObservableCollection();
You may need to implement the extension ToObservableCollection().
Another solution is to change the property ListOfClients as a normal IList and then implment INotifyPropertyChanged inside your view model
Ok, I solved part of the problem.
I have cut the window creation without the manual creation of the ViewModel on:
void ExecuteListOfClients()
{
ListOfClients listOfClientsWindow = new ListOfClients();
listOfClientsWindow.ShowDialog();
}
And in xaml ListOfClients.xaml I changed to:
prism:ViewModelLocator.AutoWireViewModel="True"
And now, after reopening the window, it shows refreshed data;)
But there is still a problem. I've done the LoadData method, which is done in the constructor and assigns data to the ObservableCollection. It works. I have set a button for it and after editing the client I click to load new data and unfortunately nothing happens. In the future I want this method to be called from the EventAggregator after editing the client, but for now I have made a button to test and unfortunately it does not work.
It works. The problem was not in the WPF itself, nor was the view strangely enough. The problem lay in EntityFramework. I had to modify the command choosing data from the database to the collection:
var clients = db.Clients.AsNoTracking().ToList();
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 want to pass a value from MainWindow into my UserControl! I passed a value to my UserControl and the UserControl showed me the value in a MessageBox, but it is not showing the value in a TextBox. Here is my code:
MainWindow(Passing Value To UserControl)
try
{
GroupsItems abc = null;
if (abc == null)
{
abc = new GroupsItems();
abc.MyParent = this;
abc.passedv(e.ToString(), this);
}
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
UserControl
public partial class GroupsItems : UserControl
{
public MainWindow MyParent { get; set; }
string idd = "";
public GroupsItems()
{
InitializeComponent();
data();
}
public void passedv(string id, MainWindow mp)
{
idd = id.ToString();
MessageBox.Show(idd);
data();
}
public void data()
{
if (idd!="")
{
MessageBox.Show(idd);
texbox.Text = idd;
}
}
}
EDIT(using BINDING and INotifyProperty )
.....
public GroupsItems()
{
InitializeComponent();
}
public void passedv()
{
textbox1.Text = Text;
}
}
public class Groupitm : INotifyPropertyChanged
{
private string _text = "";
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Problem here is with reference.
When you create new object in code behind, new object will be created and this is not the same object which you have in xaml code. So you should use following code:
<local:GroupsItems x:Name="myGroupsItems"/>
and in code behind you don't have to create new object. You should use object that you added in XAML:
...
myGroupsItems.MyParent = this;
myGroupsItems.passedv(e.ToString(), this);
...
Here is example solution (sampleproject).
You are calling data in the constructor when idd is still "" which results in the text box still being empty. Changing the MyParent property does not change that. Only passedv does. But at that point you do not have the parent set. Just call data in passedv, too.
Try this:
public partial class GroupsItems : UserControl
{
//properties and methods
private string idd="";
public string IDD
{
get{return idd;}
set{
idd=value;
textBox1.Text=idd;
}
}
//other properties and methods
}
Usage:
In your Main form:
abc = new GroupsItems();
abc.IDD="sometext";
MainGrid1.Children.Add(abc); //Grid or any other container for your UserControl
In your Binding example, your GroupItem class looks ok, except that you need to pass in the name of the changed property:
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged("Text");
}
}
}
Now, in GroupsItems, you shouldn't be accessing the TextBox. In WPF, we manipulate the data, not the UI... but as we use Binding objects to data bind the data to the UI controls, they automatically update (if we correctly implement the INotifyPropertyChanged interface).
So first, let's add a data property into your code behind (which should also implement the INotifyPropertyChanged interface) like you did in your GroupItem class:
private GroupItem _item = new GroupItem();
public GroupItem Item
{
get { return _item; }
set
{
if (value != _item)
{
_item = value;
NotifyPropertyChanged("Item");
}
}
}
Now let's try using a Binding on a TextBox.Text property:
<TextBox Text="{Binding Item.Text}" />
See how we bind the Text property of your GroupItem class to the TextBox.Text property... now all we need to do is to change the value of the Item.Text property and watch it update in the UI:
<Button Content="Click me" Click="Button_Click" />
...
private void Button_Click(object sender, RoutedEventArgs e)
{
Item.Text = "Can you see me now?";
}
Alternatively, you could put this code into your passedv method if you are calling that elsewhere in your project. Let me know how you get on.
UPDATE >>>
In your GroupItem class, try changing the initialization to this:
private string _text = "Any text value";
Can you now see that text in the UI when you run the application? If not, then try adding/copying the whole Text property into your code behind and changing the TextBox declaration to this:
<TextBox Text="{Binding Text}" />
If you can't see the text value now, you've really got problems... you have implemented the INotifyPropertyChanged interface in your code behind haven't you?