MVVM Datagrid Binding SelectedItem not updating - wpf

I'm new to WPF and MVVM and i've an applicaton that uses Entity Framework to connect to database and a datagrid to show the users of the application.
The users CRUD operations are made in a separate window and not in the datagrid.
My problems are related with the update of datagrid.
The insert operation is ok but the update is not.
View 1 (Users List):
<DataGrid Grid.Row="1"
ItemsSource="{Binding Users, Mode=TwoWay}"
SelectedItem="{Binding SelectedUser, Mode=TwoWay}"
AutoGenerateColumns="False"
CanUserAddRows="False">
</DataGrid>
ViewModel :
class UserListViewModel: NotificationClass
{
UserDBContext _db = null;
public UserListViewModel()
{
_db = new UserDBContext();
Users = new ObservableCollection<User>(_db.User.ToList());
SelectedUser = Users.FirstOrDefault();
}
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get { return _users; }
set
{
_users = value;
OnProprtyChanged();
}
}
private User _selectedUser;
public User SelectedUser
{
get
{
return _selectedUser;
}
set
{
_selectedUser = value;
OnProprtyChanged();
}
}
public RelayCommand Edit
{
get
{
return new RelayCommand(EditUser, true);
}
}
private void EditUser()
{
try
{
UserView view = new UserView();
view.DataContext = SelectedUser;
view.ShowDialog();
if (view.DialogResult.HasValue && view.DialogResult.Value)
{
if (SelectedUser.Id > 0){
User updatedUser = _db.User.First(p => p.Id == SelectedUser.Id);
updatedUser.Username = SelectedUser.Username; //this doesn't do nothing, object is already with the new username ?!
}
_db.SaveChanges();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
after _db.SaveChanges(), datagrid should not be updated ?
Model:
class UserDBContext: DbContext
{
public UserDBContext() : base("name=DefaultConnection")
{
}
public DbSet<User> User { get; set; }
}
View 2 (User detail)
public partial class UserView : Window
{
public UserView()
{
InitializeComponent();
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
}
User object
class User: NotificationClass
{
public int Id { get; set; }
public string Username { get; set; }
public string CreatedBy { get; set; }
public DateTime? CreatedOn { get; set; }
}
NotificationClass
public class NotificationClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnProprtyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
if i close and open view 1, the new username is updated..
could someone help ? thanks

Just implementing INotifyPropertyChanged isn't enough, you have to explicitly invoke PropertyChanged (or in your case OnPropertyChanged) when a property changed.
See also https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification
You can do it like so
class User : NotificationClass
{
private int _id;
private string _username;
private string _createdBy;
private DateTime? _createdOn;
public int Id
{
get => _id;
set
{
if (value == _id) return;
_id = value;
OnPropertyChanged();
}
}
public string Username
{
get => _username;
set
{
if (value == _username) return;
_username = value;
OnPropertyChanged();
}
}
public string CreatedBy
{
get => _createdBy;
set
{
if (value == _createdBy) return;
_createdBy = value;
OnPropertyChanged();
}
}
public DateTime? CreatedOn
{
get => _createdOn;
set
{
if (value.Equals(_createdOn)) return;
_createdOn = value;
OnPropertyChanged();
}
}
}

it worked ! many thanks #nosale !
what about the change made to SelectedUser being reflected in my context ?
if i do this :
SelectedUser.Username = "test";
User updatedUser = _db.User.First(p => p.Id == SelectedUser.Id);
i was thinking that SelectedUser object has the "test" username and updatedUser has the old username, but not .. updatedUser already have "test"

Related

I need your suggestion regarding loop in Model to ViewModel MVVM

I'am new to WPF and MVVM and I was given the task to continue working on one of the unfinished project that is made using the said technology. I've written a sample code below that is similar to the structure of the project.
My concern is, the loop used in GetBookPages() to display the details on the grid might take some time to finish.
public class BookModel
{
public string BookTitle { get; set; }
public List<BookDetailModel> BookDetails { get; set; }
}
public class BookDetailModel
{
public int Pages { get; set; }
public string Others { get; set; }
// ....
}
public class BookViewModel : INotifyPropertyChanged
{
private BookModel _model;
private ObservableCollection<BookDetailViewModel> _bookDetailSource;
private BookService _service;
public BookViewModel()
{
_model = new BookModel();
_service = new BookService();
GetBookPages();
}
/// <summary>
/// This is the item source of datagrid that is located in view
/// </summary>
public ObservableCollection<BookDetailViewModel> BookDetailSource
{
get { return _bookDetailSource; }
set
{
if (value == _bookDetailSource)
return;
_bookDetailSource = value;
OnPropertyChanged();
}
}
private void GetBookPages()
{
BookModel bookModel = _service.GetBookData();
var listOf = new List<BookDetailViewModel>();
bookModel.BookDetails.ForEach(e =>
{
// This is were the system's bottle neck is.
// can someone please suggests me a good work around.
listOf.Add(
new BookDetailViewModel
{
Others = e.Others,
// ....
});
});
BookDetailSource = new ObservableCollection<BookDetailViewModel>(listOf);
}
public string BookTitle
{
get { return _model.BookTitle; }
set
{
if (value == _model.BookTitle)
return;
_model.BookTitle = value;
OnPropertyChanged();
}
}
#region Property Change
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class BookDetailViewModel : INotifyPropertyChanged
{
private BookDetailModel _model;
#region Constructor
public BookDetailViewModel()
{
_model = new BookDetailModel();
ViewPageDataCommand = new RelayCommand(x => ViewPageData());
RemovePageCommdand = new RelayCommand(x => RemovePage());
}
#endregion
#region Properties
public int Page
{
get { return _model.Pages; }
set
{
if (value == _model.Pages)
return;
_model.Pages = value;
OnPropertyChanged();
}
}
public string Others
{
get { return _model.Others; }
set
{
if (value == _model.Others)
return;
_model.Others = value;
OnPropertyChanged();
}
}
#endregion
// These are the button command inside the grid's row
public ICommand ViewPageDataCommand { get; private set; }
public ICommand RemovePageCommdand { get; private set; }
private void ViewPageData()
{
// view the page data by clicking the row button inside the grid
}
private void RemovePage()
{
// will remove the currently selected row inside the grid
}
#region Property Change
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class BookService
{
public BookModel GetBookData()
{
var data = GetBookData(99);
data.BookDetails = GetBookDetail(99);
return data;
}
private BookModel GetBookData(int bookId)
{
// return 1 row only
}
private List<BookDetailModel> GetBookDetail(int bookId)
{
// return List<BookDetailModel> that might consists of more than 100 index's
}
}
I hope you understand what I mean. Your suggestion will be much appreciated. Thanks in advance!

Caliburn CanExecute not working even though firing

Trying to implement simple validation with caliburn. All I want is to enable/disable save button based on certain conditions.
View:
`<xctk:MaskedTextBox x:Name="pm_personId" cal:Message.Attach="[Event LostFocus] = [Action CanSave()]" Mask="00-000-000?"/>
<Button Content="Save" x:Name="Save" />`
Model:
public class PersonModel
{
public String personId { get; set; }
public PersonModel() {}
public PersonModel(String id)
{
this.id = personId;
}
}
ViewModel:
[ImplementPropertyChanged]
public class PersonViewModel : Screen
{
public PersonModel pm { get; set; }
public PersonViewModel()
{
pm = new PersonModel();
}
public bool CanSave()
{
MessageBox.Show(pm.personId);
if (pm.personId != null)
return true;
else return false;
}
}
The MessageBox is fired with the right value but button is not enable. Am I missing anything. Either am missing something with caliburn or it's doing too much magic. Am beginning to suspect that the time it may save you initially will be lost in debugging, just my exeprience.
Thanks #CCamilo but your answer was incomplete. For other people who encounter a similar problem, below is my final working code:
[ImplementPropertyChanged]
public class PersonModel
{
public String personId { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public PersonModel() {}
public PersonModel(String id)
{
this.id = personId;
}
}
[ImplementPropertyChanged]
public class PersonViewModel : Screen
{
public PersonModel pm { get; set; }
public PersonViewModel()
{
pm = new PersonModel();
this.pm.PropertyChanged += pm_PropertyChanged;
}
void pm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyOfPropertyChange(() => CanSave);
}
public bool CanSave
{
get { return pm.personId != null; }
}
}
The error you have is with CanSave() method.. It should be a property instead:
public bool CanSave
{
get
{
if (pm.personId != null)
return true;
else return false;
}
}

How to add sublist in wpf using mvvm?

namespace colourchanges
{
public class Group
{
public string Name { get; set; }
//its a class for adding parent list using group class
}
public class EmployeeTree : INotifyPropertyChanged
{
public EmployeeTree()
{
this.GroupStaff = new List<Group>();
GroupStaff.Add(new Group { Name = "Designers" });
GroupStaff.Add(new Group { Name = "Developers" });
GroupStaff.Add(new Group { Name = "Managers" });
//here we are declaring list for adding parent list
}
private List<Group> _GroupStaff;
public List<Group> GroupStaff
{
get { return _GroupStaff; }
set
{
_GroupStaff = value;
RaisePropertyChanged("GroupStaff");
}
}
//creates a list for parentlist
private Group _selectedGroupStaff;
public Group selectedGroupStaff
{
get { return _selectedGroupStaff; }
set
{
_selectedGroupStaff = value;
if (selectedGroupStaff.Name == "Designers")
{
City = "Chennai";
Country = "India";
Email = "Designer#gmail.com";
MobileNo = 9094117917;
Address = "Annanagar";
}
else if (selectedGroupStaff.Name == "Developers")
{
City = "Trichy";
Country = "India";
Email = "Developer#gmail.com";
MobileNo = 9094667878;
Address = "Koyambedu";
}
else if (selectedGroupStaff.Name == "Managers")
{
City = "Salem";
Country = "India";
Email = "Manager#gmail.com";
MobileNo = 9094154678;
Address = "Arumbakkam";
}
RaisePropertyChanged("selectedGroupStaff");
}
}//for selecting parent list in order to bind to textbox
private string _City;
private string _Country;
private string _Email;
private long _MobileNo;
private string _Address;
//properties of parent list to bind to textbox
public string City
{
get { return _City; }
set
{
_City = value;
RaisePropertyChanged("City");
}
}
public string Country
{
get { return _Country; }
set
{
_Country = value;
RaisePropertyChanged("Country");
}
}
public string Email
{
get { return _Email; }
set
{
_Email = value;
RaisePropertyChanged("Email");
}
}
public long MobileNo
{
get { return _MobileNo; }
set
{
_MobileNo = value;
RaisePropertyChanged("MobileNo");
}
}
public string Address
{
get { return _Address; }
set
{
_Address = value;
RaisePropertyChanged("Address");
}
}
///raise property changed event handler code
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
//how to add sub list for designers developers and managers in the constructor
Let the following be your Model class
public class Group
{
private string _City;
private string _Country;
private string _Email;
private long _MobileNo;
private string _Address;
public Group()
{
Items = new ObservableCollection<Group>();
}
public ObservableCollection<Group> Items { get; set; }
}
and at the ViewModel's constructor, you can add the Items.
public EmployeeTree()
{
this.GroupStaff = new List<Group>();
Group rootGroup = new Group(){Name ="Manager"};
Group childGroup = new Group(){Name = "Developer"};
rootGroup.Items.Add(childGroup);
this.GroupStaff.Add(rootGroup);
}
This is for Hierarchical structure. Hope you are looking for this.
And your XAML should be like this
<TreeView Name="GroupTreeView">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>

Cannot bind to the property or column States on the DataSource

Why this work :
public Form1()
{
InitializeComponent();
exCheckedListBox1.DataSource = Profiles;
this.exCheckedListBox1.DataBindings.Add(new System.Windows.Forms.Binding("Tag", this, "States", true));
}
CheckedBindingList Profiles = new CheckedBindingList();
public int States
{
get
{
return Profiles.States;
}
set
{
Profiles.States = value;
}
}
}
public class CheckedBindingList : List<string>
{
public int States { get; set; }
}
but when change binding to
this.exCheckedListBox1.DataBindings.Add(new System.Windows.Forms.Binding("Tag", this.Profiles, "States", true));
throw the Exception ?
Thanks all very very very. I try to bind filed from my custom list class that inherit form List.
Exception - Cannot bind to the property or column States on the DataSource.
Parameter name: dataMember
Seems System.Windows.Forms.Binding parameter dataSource cannot be a class that inherits from List< T >
This solves the problem:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Load += new EventHandler(Form1_Load);
}
CheckedBindingList Profiles = new CheckedBindingList();
SomeClass some_class = new SomeClass();
public int States
{
get
{
return Profiles.States;
}
set
{
Profiles.States = value;
}
}
private void Form1_Load(object sender, EventArgs e)
{
exCheckedListBox1.DataSource = Profiles;
exCheckedListBox1.DataBindings.Add(new System.Windows.Forms.Binding("Tag", some_class, "States", true));
}
}
public class CheckedBindingList : List<string>
{
public int States { get; set; }
}
public class SomeClass
{
public int States { get; set; }
}

Again, ObservableCollection doesnt Update item

Thats my first project using MVVM , MVVM light.
I have a listbox, that gets refreshed from the PersonList Observable collection, adding and removing refresh it normal. the problem is when editing an item.
I looked for all the solutions for this problem, nothing worked, which make me think that I missed something.
so here is the code :
public class AdminViewModel : ApplicationPartBaseViewModel
{
private ObservableCollection<Person> personList;
public AdminViewModel()
{
this.context = new Entities();
this.SavePersonCommand = new RelayCommand(() => this.SavePerson ());
this.PersonList = new ObservableCollection<Peson>(context.Person.OrderBy(o => o.PersonName).ToList());
}
public ObservableCollection<Person> PersonList
{
get
{
return personList;
}
set
{
this.personList = value;
RaisePropertyChanged("PersonList");
}
}
private void SavePerson()
{
//Add and update code here
this.context.SaveChanges();
RaisePropertyChanged("PersonList");
}
}
Person Class is Autogenerated template from the DataModel edmx
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
public partial class Person
{
#region Primitive Properties
public virtual int PersonId
{
get;
set;
}
public virtual string PersonName
{
get;
set;
}
public virtual Nullable<int> PersonAge
{
get;
set;
}
#endregion
#region Navigation Properties
public virtual ICollection<Humans> Humans
{
get
{
if (_human == null)
{
var newCollection = new FixupCollection<Human>();
newCollection.CollectionChanged += FixupHuman;
_human = newCollection;
}
return _human;
}
set
{
if (!ReferenceEquals(_human, value))
{
var previousValue = _human as FixupCollection<Human>;
if (previousValue != null)
{
previousValue.CollectionChanged -= FixupHuman;
}
_human = value;
var newValue = value as FixupCollection<Human>;
if (newValue != null)
{
newValue.CollectionChanged += FixupAssets;
}
}
}
}
private ICollection<Human> _human;
#endregion
#region Association Fixup
private void FixupHuman(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Human item in e.NewItems)
{
if (!item.Person.Contains(this))
{
item.Person.Add(this);
}
}
}
if (e.OldItems != null)
{
foreach (Human item in e.OldItems)
{
if (item.Person.Contains(this))
{
item.Person.Remove(this);
}
}
}
}
#endregion
}
I thought that MVVM light update the item when I call RaisePropertyChanged.
I am so confused.
Thanks in advance.
First option is try to get your auto-generated class to implement INPC if you can. Have a look at Fody.PropertyChanged
If that's not possible, since it does have it's properties as "virtual", we can over-ride them in a derived class such as
public class ObservablePerson : Person, INotifyPropertyChanged {
public override int PersonId {
get {
return base.PersonId;
}
set {
base.PersonId = value;
OnPropertyChanged();
}
}
public override string PersonName {
get {
return base.PersonName;
}
set {
base.PersonName = value;
OnPropertyChanged();
}
}
public override int? PersonAge {
get {
return base.PersonAge;
}
set {
base.PersonAge = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in your AdminViewModel work with objects of type ObservablePerson than Person

Resources