Use RESX file in WPF application with Prism - c#

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));
}

Related

MVVM-way to notify neighbour usercontrol about changes

all!
In my main window I have a Grid with 2 columns. In column 0 is a usercontrol with settings, in column 1 is usercontrol with content.
The goal is to reset usercontrol with content when settings are changed. What is the right "MVVM"-way to do it?
Both usercontrols are implemented in MVVM-way, having all business logic in ViewModels.
Say I have a CheckBox bound to a Property in the settings-usercontrol:
Settings.xaml
...
<CheckBox IsChecked="{Binding Path=MySettingNr1}">
...
In Settings_ViewModel.cs
...
public bool MySettingNr1
{
get
{
return _model.SttNr1;
}
set
{
if(_model.SttNr1 == value) return;
_model.SttNr1 = value;
OnPropertyChanged(nameof(MySettingNr1));
}
}
...
How can I notify my content usercontrol if user clicks this checkbox?
Routed event would possibly not do, because both usercontrols are neighbours in the main window grid.
The only way I thought about was to fire an event in the usercontrol with settings, catch it in main windows and call a function of the usercontrol with content. Is there a way to make this call chain shorter?
Thanks in advance.
You could use a single shared view model for the window that both user controls bind to, i.e. they both inherit the DataContext of the parent window. This way they could communicate through the shared view model directly.
If you prefer to have two different view models, one for each user control, you could use an event aggregator or a messenger to send an event/message from one view model to antoher in a loosely coupled way.
Most MVVM libraries have implemented solutions for this. Prism uses an event aggregator and MvvmLight uses a messenger, for example.
Please refer to this blog post for more information about the concept.

XAML x:bind oneTime on initially null property still works. Why?

x:Bind defaults to OneTime, which updates the target UI with the data when the Page's Loading event triggers the generated code's Initialize function.
I have a Page with a ViewModel property. This ViewModel class implements INPC for its properties. The data for the viewModel is loaded asynchronously, only after the page is loaded. So on Page initialization, and subsequently the generated code initialization, the UI target using x:Bind will have null data.
Since it is OneTime, it shouldn't change unless I manually call Update(which I don't).
So why does my x:Bind UI work?
The following is some simplified code snippets.
<Page x:Name="MyPage" x:Class="MyProject.Pages.MyPage">
<Button Command="{x:Bind ViewModel.GoToAnotherPageCommand}">
public sealed partial class MyPage : Page
{
public MyPageViewModel ViewModel { get; set; }
public MyPage()
{
this.InitializeComponent();
}
// called by an event bound to a Frame's Navigated, which all pages use
public void OnNavigatedTo()
{
this.ViewModel = new MyPageViewModel();
}
}
public class MyPageViewModel : INotifyPropertyChanged, INotifyPropertyChanging
{
// GoToAnotherPageCommand is an INPC property and its set in the constructor
The reason that your command works fine is because OnNavigatedTo will be called before the command instantiation. This means by the time the code tries to set the command, the ViewModel has already been instantiated and is no longer null.
To prove my point, first go open the file under the following path(could be ARM or *x64 depending on which platform you are running on) -
obj/x86/Debug/MyPage.g.cs
This is basically the code-generated file that hooks up all the x:Bind stuff for your page.
Now put a breakpoint at where the command is set. In my case, it's a method called Set_Windows_UI_Xaml_Controls_Primitives_ButtonBase_Command. Then put another breakpoint at OnNavigatedTo.
Now run the app, you will see that the OnNavigatedTo method gets called first.
If your page's NavigationCacheMode is set to Disabled, this behavior makes OnNavigatedTo the ideal place to instantiate x:Bind bindings so the page only uses memory to create these new objects when the user actually navigates to it, instead of doing everything inside the page constructor.
Don't do this inside the Loaded event of the Page though. Because it will get called after the command instantiation. You can try the following code to instantiate the ViewModel, and the result is very different(your command will not work).
public MyPage()
{
InitializeComponent();
Loaded += (s, e) => ViewModel = new MyPageViewModel();
}
The compiled binding system (x:Bind) is smart enough to check for initial null values and not consider them the actual value you wish to bind. It will wait for the first non-null value and bind that value.
This is by design, as binding to an initial null value is (almost) never the intention of the binding.
I didn't find the source of this information, but I believe it was in the Build talk detailing the x:Bind system in 2015.
Updated:
As Justin mentions in the comments below and in his own answer, the binding will not work if the view model is set after the binding operation happens.
I believe this is because the binding terminates when it encounter a null reference in the property chain, but I haven't tested this, so I might be incorrect.

MVVM: Bring control into view

I have a Grid with a ScrollViewer around it. At the top of my ScrollViewer is a Button. On a Click on the Button, I want the ScrollViewer to scroll to a Control at the bottom of the ScrollViewer.
With the following XAML I can bring the Control into view:
<Button Grid.Row="2" Content="Some Button" Command="{Binding DoJumpCommand}" CommandParameter="{Binding ElementName=window}"/>
The Command in the ViewModel is:
if (parameter is MainWindowView)
{
var mainWindowView = parameter as MainWindowView;
mainWindowView.myJumpTarget.BringIntoView();
}
This works fine. But I'm not sure if this is clean MVVM because I pass the complete View into the ViewModel.
Is there a better way to do this?
When I first saw your question, I thought that the general solution to handling events with MVVM is to handle them in an Attached Property. However, looking again, it occurred to me that you're not actually handling any events... you just want to call a method from a UI control. So really, all you need is a way to pass a message from the view model to the view. There are many ways to do this, but my favourite way is to define a custom delegate.
First, let's create the actual delegate in the view model:
public delegate void TypeOfDelegate();
It doesn't need any input parameters, because you don't need to pass anything from the view model to the view, except a signal... your intention to scroll the ScrollViewer.
Now let's add a getter and setter:
public TypeOfDelegate DelegateProperty { get; set; }
Now let's create a method in the code behind that matches the in and out parameters of the delegate (none in your case):
public void CanBeCalledAnythingButMustMatchTheDelegateSignature()
{
if (window is MainWindowView) // Set whatever conditions you want here
{
window.myJumpTarget.BringIntoView();
}
}
Now we can set this method as one (of many) handlers for this delegate in a Loaded event handler in the view code behind:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Assumes your DataContext is correctly set to an instance of YourViewModel
YourViewModel viewModel = (YourViewModel)DataContext;
viewModel.DelegateProperty += CanBeCalledAnythingButMustMatchTheDelegateSignature;
}
Finally, let's call our delegate from the view model... this is equivalent to raising the event:
if (DelegateProperty != null) DelegateProperty(dataInstanceOfTypeYourDataType);
Note the important check for null. If the DelegateProperty is not null, then all of the attached handler methods will be called one by one. So that's it! If you want more or less parameters, just add or remove them from the delegate declaration and the handling method... simple.
So this is an MVVM way to call methods on a UI control from a view model. However, in your case it could well be argued that implementing this method would be overkill, because you could just put the BringIntoView code into a basic Click handler attached to your Button. I have supplied this answer more as a resource for future users searching for a way to actually call a UI method from a view model, but if you also chose to use it, then great!

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 to get notifications from nested properties in C#/WPF?

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.

Categories