How to get notifications from nested properties in C#/WPF? - c#

I have the following property in my view model and the view is binding to this property.
All works fine except for a special case where the ActiveCategory (within the _catManager) can change from other events (outside of this view).
I don't want to expose the entire Category Manager in the view model so I'm only exposing what properties I need. What is the best way to do this so that the view gets notified of all changes, even those changes not triggered within this view model?
public ICategory SelectedCategory
{
get
{
return _catManager.ActiveCategory;
}
set
{
_catManager.ActiveCategory = value;
OnPropertyChanged("SelectedCategory");
}
}

Have your viewmodel hook into the _catManager's INotifyPropertyChanged event and have it relay the property change events through the viewmodel. When you see "ActiveCategory" come through, that means you need to raise an INPC for "SelectedCategory".

You need to delegate notification to whatever class _catManager is as well.
So a change to it's ActiveCategory property raises a notification.
One way would be to add a handler in the the class that has it as a property and then raise a notification that it's _catManager has changed somehow.

Related

Use RESX file in WPF application with Prism

I'm working on a project with WPF/Prism. The application has different modules and one of those modules can change the culture of the resx object. This works fine and each module loaded will display the translated text.
In case any module has already been loaded, the text will not be updated. First, I tried an approach with x:Static but quickly realized that a static value doesn't help my issue. Now I've implemented a get-only property in my view model that returns the localized string.
public string UserDetailsLabel => Messages.UserDetailsLabel;
<TextBlock Text="{Binding UserDetailsLabel, Mode=OneWay, NotifyOnSourceUpdated=True}"/>
However, I have no idea where to tell WPF (or Prism) that the culture has changed and to update the displayed value. The module that changes the culture sets the Culture property on Messages (generated in Messages.Designer.cs).
Is there a way to notify that the value changed?
Is there a way to notify that the value changed?
Implement INotifyPropertyChanged and raise the PropertyChanged event for the data-bound UserDetailsLabel property.
This is the way to notify WPF that the source value has changed.
In the context of a multi-module Prism application, you could for example use the event aggregator to raise an event that each view model handles by raising the PropertyChanged event for all data-bound properties that need to be refreshed in the view.
I solved my problem by attaching the Loaded event in the UserControl xaml definition. The view then asks my view model to update the UI.
The view model then calls RaisePropertyChanged(nameof(UserDetailsLabel)) and updates the view.
// UserListView.xaml
<UserControl ... Loaded="UserListView_OnLoaded">...</UserControl>
// UserListView.xaml.cs
private void UserListView_OnLoaded(object sender, RoutedEventArgs e) {
var dataContext = (IUserListViewModel) DataContext;
dataContext.RefreshUI();
}
// UserListViewModel.cs
public void RefreshUI() {
RaisePropertyChanged(nameof(IUserListViewModel));
}

wpf mvvmlight passing data to viewmodel

I have the following views and viewModels View: Staff, VM: StaffViewModel and View: Notes, VM: NotesViewModel.
My StaffViewModel has a SelectedStaffMember property of type SelectedEmployee.
The Staff view has a button that launches another view (Notes).
When the user enters a note I need to save it against the SelectedEmployee, which means the NotesViewModel needs to know the currently selected employee ID.
Right now Im doing this via my ViewModelLocator but this seems wrong, what is the corret way to pass data to a VM???
I'm using MVVM Light.
Relevant code - StaffViewModel
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set
{
if (value == _selectedEmployee) return;
_selectedEmployee = value;
HolidayAllowance = _staffDataService.GetEmployeeHolidayAllowance(_selectedEmployee.Id);
RaisePropertyChanged();
RaisePropertyChanged(nameof(HolidayAllowance));
}
}
NoteViewModel
public RelayCommand SaveNoteCommand { get; private set; }
private void SaveNote()
{
var note = new Note
{
NoteContent = NoteContent,
EmployeeId = ViewModelLocator.Staff.SelectedEmployee.Id,
NoteDate = NoteDate
};
_dataService.SaveNote(note);
}
I'm using MahApps Flyouts to show the view for add note:
This is where the view is shown, it is launched from MainView.xaml NOT Staff.xaml, which I think is going to be another issue of getting SelectedEmployee ID:
MainView.xaml
<controls:Flyout Name="AddNoteFlyout"
Header="Add Note"
IsModal="True"
IsOpen="{Binding IsAddNoteOpen}"
Opacity="85"
Position="Right"
Width="450">
<views:AddNote VerticalAlignment="Top" Margin="0,30,0,0"/>
</controls:Flyout>
Im considering firing a message on the button click that launches the View, which my staff view would register against. The message would contain the selectedEmployeeId. Would that be a better way?
The simple way
The simple way is what you are doing, but maybe a bit better solution is to create a static or singleton class like a NavigationParameterContainer and store the selected StaffMember in a public property. Then you can retrieve it in your NotesViewModel
The best practice
The better solution for passing data between ViewModels is using a custom navigation service, and navigation aware ViewModels.
MVVMLight don't support this, so either you use a different framework like Prism or write yourself an architecture that you can use for making parameterized navigationt.
The base idea is that you create an INavigationAware interface that support navigation lifecycle callbacks like OnNavigatedTo, which receives an object representing the NavigationParamter (the selected StaffMember).
Then you create some kind of NavigationService with a Navigate method, that accepts some parameter to determine the Page you want to navigate to, and an object wich is the NavigationParamter.
When you navigate you call the Navigate method on your Service and pass the selected item as parameter. Then you need to make the actual navigation inside your service, and after the navigation is finished, you call the OnNavigatedTo callback on your ViewModel if it is implementing the INavigationAware interface. (You can retreive the VM from the Page.DataContext and cast it to INavigationAware if it is not null you can call the OnNavigatedTo).
Finally in your NotesViewModel you just need to implement the INavigationAware interface, and handle the parameter you received in the OnNavigatedTo method.
This is just the basic idea but I strongly recommend you to see some MVVM framework that already implements this. (like PrismLibrary).

Raise CanExecuteChanged when the model changes

In my ViewModel I have an ObservableCollection of Person objects (that implement INotifyPropertyChanged) and a SelectedPerson property. These are bound to a ListBox in my view.
There is also the following Prism DelegateCommand in my ViewModel:
Private DelegateCommand _myCommand = New DelegateCommand(CanExecute)
Public DelegateCommand MyCommand {get {return _myCommand;}}
Private Bool CanExecute()
{
Return (SelectedPerson.Age > 40);
}
What is the most elegant way of calling MyCommand.RaiseCanExecuteChanged whenever the SelectedPerson changes and whenever the SelectedPerson's age changes?
Adding and removing property changed handlers in the SelectedPerson's setter seems a bit messy to me.
Adding and removing property changed handlers in the SelectedPerson's setter seems a bit messy to me.
That's how I do it, and I'm not sure what a cleaner alternative would be. If the command state depends on a sub-property, you need to observe the changes somehow. Be careful about unsubscribing, though, or you risk a memory leak if your Person outlives your view model. PropertyChangedEventManager and weak event handlers can help if you can't guarantee that you unsubscribe.
To keep things clean, I usually just have one handler that listens for any sub-property changes, which calls a RequeryCommands method (also called directly by view model methods), which in turn invokes RaiseCanExecuteChanged for all the commands in my view.

If a model implements INotifyPropertyChanged, how should ViewModel register/deregister for PropertyChanged event?

I have a Model which implements INotifyPropertyChanged and it may get updated by a background business thread. Its related ViewModel also implements INotifyPropertyChanged. And their View obviously binds to ViewModel. This View may be shown on multiple places, and my goal is that all of them get updated when the model changes.
I know that ViewModel should register for PropertyChanged event of Model. But I don't know when and where is the best place for this registering and deregistering. Specially about the deregistering, since I'm scared of having hundreds of VM event handlers on the Model for the VM/views that are not shown anymore.
Thanks in advance.
Is it an absolute necessity for you to limit the View not directly bind to the Model?
You can expose the Model as a property on the VM and then have your View directly bind to it thereby not having the VM subscribe to INPC from Model
something like:
public class MyViewModel: INotifyPropertyChanged {
...
private MyModel _model;
public MyModel Model {
get {
return _model;
}
set {
if (value == _model)
return;
value = _model;
RaisePropertyChanged(() => Model);
}
}
...
}
and in xaml (when MyViewModel is the DataContext):
<TextBlock Text="{Binding Model.ModelProperty}" />
Update:
Maybe this is of some help for tapping into the PropertyChanged events of Models in a "weak" fashion
IWeakEventListener
Using the central event dispatching of a WeakEventManager enables the handlers for listeners to be garbage collected (or manually purged) even if the source object lifetime extends beyond the listeners.
which is used in
Josh Smith's PropertyObserver
This should hopefully solve your issue of needing to explicitly un-register?
I've gotten around this issue by hooking in to model events on load and removing them on unloaded, the problem here is that the view model can miss events if it's off the screen. I usually just refresh the data quickly on load.
OnLoad - Refresh the VM data from the model and hook events.
OnUnLoad - remove any hooks that you've put in place.

How can I catch a bound data updated event?

I am writing an app for windows 8 and I have a UI class called GroupedItemsPage which inherits from LayoutAwarePage that contains the data :
this.DefaultViewModel["Groups"] = sampleDataGroups;
Each item in sampleDataGroups is binded to a tile in the UI and sampleDataGroups is a class which inherits from BindableBase and each property is set using
set{this.SetProperty(ref this._property, value); }
What I would like to do is to be able to catch a general event in my UI class GroupedItemsPage each time a property in sampleDataGroups is changed (so that I can rewrite sampleDataGroups to a file). I've done some research and I've only really found how to do get the notify event caught for sampleDataGroups, but not if I want sampleDataGroups to notify GroupedItemsPage?
Your page should not be writing data to a file. It's more of a duty of the view model or model rather. If you really have to though then you will need to subscribe to the PropertyChanged event declared by the type of your sampleDataGroups variable and handle the event in your page object.

Categories