INotifyPropertyChanged and ObservableCollection WPF - c#

Right now I have a calender that only displays one month(what ever month I pass in). I'm trying to let the user pick what month and year from a comboBox and have the calender update. I'm binding using observablecollection which I'm sort of familiar with. I have no clue how INotifyPropertyChanged works though. I've never used it before. Any help or advice is greatly appreciated. This is what I have so far:
public class Schedule : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void Update(int propertyName)
{
if (propertyName != null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler.Invoke(this, new PropertyChangedEventArgs(propertyName.ToString()));
}
}
// public void UpdateCal(PropertyChangedEventArgs e)
// {
// if (PropertyChanged != null)
// PropertyChanged(this, e);
// }
public string MonthWeek { get; set; }
public string Year { get; set; }
public string Month { get; set; }
public string day { get; set; }
public string WeekOfYear { get; set; }
public string dayofweek { get; set; }
// public string month {
// get {return Month; }
// set
// {
// UpdateCal(new PropertyChangedEventArgs("month"));
// }
// }
public int WeekNo { get; set; }
public int WeekDay { get; set; }
public DateTime Date { get; set; }
}
---This is another class that figures out where to place each date on the grid----
public SchedulePage(MainWindow parentForm)
{
InitializeComponent();
pick = Convert.ToInt32(comboMonth.SelectedItem) + 1;
_parentForm = parentForm;
// DateTime date = new DateTime(year, month, day);
var t = new List<Schedule>();
DateTime curr = DateTime.Now;
// comboMonth.Items.Add(curr.Month);
DateTime newcurr = new DateTime(2011, pick, 1);
// pickdate = datePickercal.SelectedDate;
// DateTime newcurr = new DateTime(curr.Year, curr.Month, 1);
var cal = System.Globalization.DateTimeFormatInfo.CurrentInfo.Calendar;
var ms = cal.GetWeekOfYear(new DateTime(newcurr.Year, newcurr.Month, 1), System.Globalization.CalendarWeekRule.FirstDay, System.DayOfWeek.Sunday);
for (var i = 1; newcurr.Month == pick; newcurr = newcurr.AddDays(1))
{
var sched = new Schedule();
var month_week = (newcurr.Day / 7) ;
sched.MonthWeek = newcurr.GetWeekOfMonth().ToString();
sched.Month = newcurr.Month.ToString();
sched.Year = newcurr.Year.ToString();
sched.day = newcurr.Day.ToString();
sched.WeekOfYear = cal.GetWeekOfYear(newcurr, System.Globalization.CalendarWeekRule.FirstDay, DayOfWeek.Sunday).ToString();
sched.dayofweek = newcurr.DayOfWeek.ToString();
t.Add(sched);
_parentForm.bindings.schedule.Add(new Schedule { WeekNo = newcurr.GetWeekOfMonth()-1, WeekDay = (int)newcurr.DayOfWeek, day = newcurr.Day.ToString() });
}
lblDate.Content = (newcurr.Month -1) + "/" + newcurr.Year;
DataContext = _parentForm.Bindings;
---And this class makes the observablecollections-----
public partial class BindingCamper
{ // This class assist in binding campers from listview to the textboxes on the camperspage
public ObservableCollection<Camper> Campers { get; set; }
public ObservableCollection<Staff> StaffMembers { get; set; }
public ObservableCollection<Schedule> schedule { get; set; }
public BindingCamper()
{
Campers = new ObservableCollection<Camper>();
StaffMembers = new ObservableCollection<Staff>();
schedule = new ObservableCollection<Schedule>();
}

This is how you typically implement INotifyPropertyChanged:
public class Schedule : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private string _monthWeek;
public string MonthWeek
{
get { return _monthWeek; }
set
{
if (value != _monthWeek)
{
_monthWeek = value;
OnPropertyChanged("MonthWeek");
}
}
}
// And so on for other properties...
}
Basically, you just need to trigger the PropertyChanged event every time a property is updated, so every setter must call OnPropertyChanged. Note that you can't do it with auto-implemented properties, since you need to add custom logic in the setter.

When you bind to a property (even if that property is an ObservableCollection), any changes to the PROPERTY (not the contents of the property) should raise the PropertyChanged event.
An ObservableCollection is self-contained when it comes to raising the CollectionChanged event, so don't worry about firing off an event for the ItemsSource items themselves.
XAML:
<!-- This says that ItemsSource is bound to the Campers property... -->
<ItemsControl ItemsSource="{Binding Path=Campers, Mode=OneWay}" />
CLASS:
public class TheViewModel()
{
private ObservableCollection<Camper> _campers;
public ObservableCollection<Camper> Campers
{
get { return _campers; }
set
{
if (Equals(_campers, value)) return;
_campers = value;
RaisePropertyChanged("Campers"); //Or however you implement it
}
}
private void SomeFunc()
{
var bindingCamper = new BindingCamper();
Campers = bindingCamper.Campers; //This will fire the event
//etc.
}
}
Alternatively, if your BindingCamper is your ViewModel then you do the same thing in there instead.

When you change a property from code behind and you wanna update your UI then you use INotifyPropertyChanged inteface. As i see you implemented the interface and even set up a helper to use it just you used an int as parameter you should use a string instead. If you set the property then just call your helper with the right PropertyName and you are good to go.
Like this:
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And to trigger the event to notify the UI:
NotifyPropertyChanged("YourPropertyName");
Maybe you will need to set the TwoWay binding too but that's only true if you wanna change the property from the UI too.

Related

Unable to bind my DatePicker to my ViewModel

I'm trying to implement a datepicker and bind it to a model I've created.
I've made a global tournament object in App.xaml.cs:
private Tournament _tournament;
public Tournament tournament {
get { return _tournament; }
set { _tournament = value; OnPropertyChanged("tournament"); }
}
And I've made an OnStartup override to launch my windows:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
//setup score
score = new Score();
// Tournament setup
tournament = new Tournament();
tournament.GamesToWin = 1;
tournament.Games = new List<Game>(1);
tournament.Players = new List<Player>(2) { new Player(), new Player() };
//tournament.TimeAndDate = new DateTime(2021, 11, 22);
tournament.Winner = null;
// Initializing the UserInput
UserInput userInput = new UserInput();
UserInputWindowViewModel userinputViewModel = new UserInputWindowViewModel();
userInput.DataContext = userinputViewModel;
// Opening the UserInput Window
bool? res = userInput.ShowDialog();
// If the UserInput Window is closed, open the next Window
if (res == true)
{
// Opening the MainWindow
MainWindow main = new MainWindow();
main.Show();
}
else
{
Shutdown();
}
}
Model.cs (Tournament.cs):
public class Tournament : INotifyPropertyChanged
{
private Player _winner;
private DateTime? _timeAndDate;
private List<Player> _players;
public List<Player> Players
{
get { return _players; }
set { _players = value; OnPropertyChanged("Players"); }
}
private List<Game> _games;
public List<Game> Games
{
get { return _games; }
set { _games = value; OnPropertyChanged("Games"); }
}
private int _gamesToWin;
public int GamesToWin
{
get { return _gamesToWin; }
set { _gamesToWin = value; OnPropertyChanged("GamesToWin"); }
}
public Player Winner
{
get { return _winner; }
set { _winner = value; OnPropertyChanged("Winner"); }
}
public DateTime? TimeAndDate
{
get { return _timeAndDate; }
set { _timeAndDate = value; OnPropertyChanged("TimeAndDate"); }
}
#region PropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
ViewModel.cs (UserInputWindowViewModel.cs):
public class UserInputWindowViewModel
{
// Calling the current app to access the tournament object globally
public App currentApp = Application.Current as App;
#region The players object
private List<Player> _players;
public List<Player> Players
{
get { return _players; }
set { _players = value; }
}
#endregion
#region The DateTime object
private DateTime? _dateTime;
public DateTime? TournamentDateTime
{
get { return _dateTime; }
set { _dateTime = value; }
}
#endregion
public UserInputWindowViewModel()
{
Tournament tournament = currentApp.tournament;
Players = tournament.Players;
TournamentDateTime = new DateTime(2021, 11, 22);
tournament.TimeAndDate = TournamentDateTime;
//TournamentDateTime = tournament.TimeAndDate;
}
#region ICommand Members
private ICommand mUpdater;
public ICommand UpdateCommand
{
get
{
if (mUpdater == null)
mUpdater = new Updater();
return mUpdater;
}
set
{
mUpdater = value;
}
}
private class Updater : ICommand
{
public bool CanExecute(object parameter) => true;
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
}
}
#endregion
}
Inside my window (UserInput.xaml) I have the following Datepicker:
<DatePicker SelectedDate="{Binding TournamentDateTime, Mode=TwoWay}"/>
I've set the date inside my viewmodel to 22-11-2021 as test, but when I change the date, it's not changing. What am I doing wrong?
EDIT: I also tried the OnPropertyChanged in the ViewModel, which didn't work
You forgot to implement INotifyPropertyChanged under UserInputWindowViewModel.cs.
public class UserInputWindowViewModel : INotifyPropertyChanged
{
}
Also make sure you are triggering PropertyChanged event in property setter, it's important for two-way binding
public DateTime? TournamentDateTime
{
get { return _dateTime; }
set
{
_dateTime = value;
OnPropertyChanged(nameof(TournamentDateTime);
}
}
I'd also would suggest not to access Application directly from a view model, better pass tournament object as a dependency to a view model as soon it would be better separation of concerns
I managed to fix my problem:
I made a small function to update my global model:
public void updateModel(DateTime? s)
{
currentApp.tournament.TimeAndDate = s;
}
I've added this small bit of code to my getter/setter inside my viewmodel (notice I used DateTime.Now to set it to the current date):
private DateTime? _dateTime = DateTime.Now;
public DateTime? TournamentDateTime
{
get { return _dateTime; }
set { _dateTime = value; updateModel(value); }
}
This fixed all my problems with the datepicker. And I didn't have to use INotifyPropertychanged inside my viewmodel because my model already had it implemented.

observable collection not changing combobox

I have an observable collection which is the item source to a combo box. When I add a new item to the observable collection, it appears in the combobox. However, when I update an existing item, it does not update the combo box. What am I doing wrong?
xaml:
<ComboBox ItemsSource="{Binding ChildGroupOC}" DisplayMemberPath="childGroupName" />
observable collection property:
public ObservableCollection<ChildGroupBO> ChildGroupOC
{
get
{ return childGroupOC;}
set
{
childGroupOC = value;
}
}
public class ChildGroupBO: INotifyPropertyChanged
{
public int parentGroupId { get; set; }
public int childGroupId { get; set; }
public string childGroupName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
Your implementation of ChildGroupComboBoxBO not only has to implement INotifyPropertyChanged but also call the event on changes:
OnPropertyChanged("parentGroupId");
Example from MSDN:
public class Person : INotifyPropertyChanged
{
private string name;
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public Person()
{
}
public Person(string value)
{
this.name = value;
}
public string PersonName
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("PersonName");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}

Inheritance with INotifyPropertyChanged

I was wondering if anyone knows how to solve the following problem... I have a base class that needs to update it's modifiedDateTime property when a derived class property is changed.
BaseObject.cs
public class BaseObject
{
private DateTime? _modifiedDateTime;
public DateTime? modifiedDateTime
{
get { return _modifiedDateTime ; }
set { _modifiedDateTime = value; }
}
public BaseObject
{
_modifiedDateTime = DateTime.Now.ToUniversalTime();
}
public void Update(object sender, PropertyChangedEventArgs e)
{
_modifiedDateTime = DateTime.Now.ToUniversalTime();
}
}
ExampleClass1.cs
public class ExampleClass1: BaseObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _data;
public int data
{
get { return _data; }
set
{
_data = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("data"));
}
}
public ExampleClass1()
{
PropertyChanged += base.Update;
}
}
The previous example is working as expected.
However if the derived class contains an object of another class. For example:
ExampleClass2.cs
public class ExampleClass2: BaseObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _data;
private ExampleClass1 _objClass1;
public int data
{
get { return _data; }
set
{
_data = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("data"));
}
}
public ExampleClass1 objClass1
{
get { return _objClass1; }
set
{
_objClass1 = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("objClass1"));
}
}
public ExampleClass2()
{
PropertyChanged += base.Update;
}
}
When I change the data property of the objClass1, the modifiedDateTime property of the ExampleClass2 inherited by the base class BaseObject is not updated.
How can I solve this problem?
When you set the value of objClass1 subscribe to its property changed event as well
public ExampleClass1 objClass1 {
get { return _objClass1; }
set {
//in case one already existed. unsubscribe from event
if(_objClass1 != null) _objClass1.PropertyChanged -= base.Update
_objClass1 = value;
//subscribe to event
if(_objClass1 != null) _objClass1.PropertyChanged += base.Update
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("objClass1"));
}
}

Wpf binded property not updating in 2-way-mode

I have simple gallery with notes and text box to add new one by title.
I use mvvm, other bindings work correcty.
When I set title of note and click add property NewNote.Title is updated and object saved in database. After that I clear NewNote.Title but UI is not updated.
here is short video https://youtu.be/l3vFwI-a4TQ
xaml
<TextBox Text="{Binding NewNote.Title}" />
page view model
class NotesPageViewModel : INotifyPropertyChanged
{
public ObservableCollection<NoteViewModel> Notes { get; set; }
public NoteViewModel NewNote { get; set; }
public NotesPageViewModel()
{
NewNote = new NoteViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
internal void LoadNotes()
{
using (var uow = new UnitOfWork())
{
Notes.Clear();
uow.NotesRepository.OrderBy(n => n.Position).ToList()
.ForEach(note => Notes.Add((NoteViewModel)note));
}
}
internal void AddNote()
{
if (string.IsNullOrWhiteSpace(NewNote.Title))
return;
using (var uow = new UnitOfWork())
{
uow.NotesRepository.Add((Note)NewNote);
uow.Complete();
}
NewNote.Title = "";
LoadNotes();
}
}
object view model
class NoteViewModel : INotifyPropertyChanged
{
public int Id { get; set; }
public string Title { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
You're not calling the OnPropertyChanged on the viewmodel when you're setting the property
class NoteViewModel : INotifyPropertyChanged
{
private int _id;
public int Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
OnPropertyChanged("Title");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Also, the LoadNotes and AddNotes should be called via a command, and the new title should be sent as a parameter.
https://msdn.microsoft.com/en-us/library/ms752308(v=vs.110).aspx

Force Binding isn't what I want

My oneway binding isn't working as I'm expecting.
When I click a button to add a new "person" it doesn't add the newly entered person to the listview until I shut it down and restart the application (so the value gets added to the DB just not to the UI)
What am I doing incorrectly? I have the INotifyPropertyChanged, I have the ObservableCollection... What am I missing?
I have my Model:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _personName;
private string _personEmail;
private string _personPhone;
private DateTime _personDOB;
[PrimaryKey, AutoIncrement]
public int personId { get; set; }
[MaxLength(25)]
public string personName {
get { return _personName; }
set
{
_personName = value;
OnPropertyChanged("personName");
}
}
[MaxLength(50)]
public string personEmail {
get { return _personEmail; }
set
{
_personEmail = value;
OnPropertyChanged("personEmail");
}
}
[MaxLength(13)]
public string personPhone {
get { return _personPhone; }
set
{
_personPhone = value;
OnPropertyChanged("personPhone");
}
}
public DateTime personDOB {
get { return _personDOB;}
set
{
_personDOB = value;
OnPropertyChanged("personDOB");
}
}
public Boolean isPersonActive { get; set; }
public string Summary
{
get { return string.Format("{0} - {1} : {2} -- {3}", personName, personEmail, personPhone, personDOB); }
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
In my ViewModel I have this:
public class ChoresVM
{
private ObservableCollection<win8Chores.Model.databaseTables.Person> _personList;
public ObservableCollection<win8Chores.Model.databaseTables.Person> personList
{
get { return _personList; }
set { _personList = value; }
}
...
public ObservableCollection<win8Chores.Model.databaseTables.Person> selectAllPerson()
{
using (var db = new SQLiteConnection(dbPath))
{
ObservableCollection<win8Chores.Model.databaseTables.Person> pList = new ObservableCollection<win8Chores.Model.databaseTables.Person>(db.Query<win8Chores.Model.databaseTables.Person>("select personId,personName,personEmail,personDOB from Person"));
_personList = new ObservableCollection<Model.databaseTables.Person>(db.Query<win8Chores.Model.databaseTables.Person>("select personId,personName,personEmail,personDOB from Person"));
return _personList;
}
}
public void insertPerson(string name, string email, string phone, DateTime dob, Boolean isActive = true)
{
dbPath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "myDB");
db = new SQLiteConnection(dbPath);
using (db)
{
var p = db.Insert(new win8Chores.Model.databaseTables.Person()
{
personName = name,
personEmail = email,
personPhone = phone,
personDOB = dob,
isPersonActive = isActive
});
}
selectAllPerson();
}
Then in my View:
public MainPage()
{
this.InitializeComponent();
VM = new ViewModel.ChoresVM();
DataContext = VM;
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
DateTime myDOB = new DateTime(1955, 02, 28);
VM.insertPerson("test","test#live.com","123-456-7890", myDOB);
}
With my XAML like this:
<ListView HorizontalAlignment="Left" Height="224" Margin="287,344,0,0" VerticalAlignment="Top" Width="740" x:Name="test" DisplayMemberPath="Summary" ItemsSource="{Binding personList, Mode=OneWay}" />
Try to implement INotifyPropertyChanged on ChoresVM and raise PropertyChanged("personList") when you initialize the collection (in selectAllPerson() or personList setter). This way itemssource binding will be notified, that collection property was changed and will pickup new collection. Also you have a typo. In Binding you have "PersonList" (Pascal case) but you property is in camel case (personList)

Categories

Resources