Share data between ViewModels and Update View - c#

i just need i tiny help because my second view is not updating itself.
Is just have two views and the corresponding viewmodels. in ViewA i press a butten that sets the CurrentUser Property in my UserController (Or CurrentContext)
the class implements the prism BindableBase (witch includes INotiefyPropertyChanged)
Also Fody and PropertyChanged/Fody is installed and setup correctly.
public class UserController : BindableBase
{
private static UserController instance;
public User CurrentUser { get; set; }
public static UserController Instance
{
get
{
if (instance == null)
instance = new UserController();
return instance;
}
}
private UserController()
{
}
}
Set the CurrentUser in ViewModel A:
private void ShowDetails(User user)
{
UserController.Instance.CurrentUser = user;
}
Try to Display the User in ViewModel B:
public User CurrentUser => UserController.Instance.CurrentUser;
ViewB:
<Page.DataContext>
<viewModels:UserInfoPageViewModel />
</Page.DataContext>
<Grid Background="Black">
<StackPanel>
<TextBox Text="{Binding CurrentUser.UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Grid>
what am i not seeing why the ViewB is not updating?
Basic:
MainViewModel --> UserController --> UserDetailViewModel

The short answer to "why" is because when UserController.CurrentUser is changed it's not telling anyone. Normally if you want to notify other elements of a property change you need to use the INotifyPropertyChanged interface and invoke the PropertyChanged event in the setter of the property.
Your pattern is a little more complex though because you're not even binding to the property that's being changed, instead you're binding to a view model property which fetches its value from UserController. So not only does UserController not tell anyone when CurrentUser has changed, but your view model likewise doesn't know when this has happened and can't notify its binding targets either.
There are always multiple ways to do something like this, but I'd approach it this way:
UserController needs to implement INotifyPropertyChanged if it doesn't already via BindableBase
For UserController.CurrentUser, instead of a simple get; set;, you will need to rewrite it as follows:
private User _currentUser;
public User CurrentUser
{
get => _currentUser;
set
{
_currentUser = value;
NotifyPropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentUser));
}
}
In your view model, instead of a CurrentUser property, expose a UserController property:
public UserController UserController => UserController.Instance;
In XAML:
<StackPanel>
<TextBox Text="{Binding UserController.CurrentUser.UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
What this does is treat UserController as the source of truth for CurrentUser, which it is, rather than your view model. It works because UserController (apparently) will never change during the life of the app so you don't have to worry about that segment of the binding path, while UserController.CurrentUser's setter will take care of notifying WPF to update the binding when that property changes.
By contrast your view model's CurrentUser property is what I would call a derived property; these are tricky to bind to because even if UserController properly gave a property change notification for CurrentUser, you would still need some way of notifying every view model when UserController.CurrentUser has changed, and then they would have to in turn notify everyone that their own, derived CurrentUser property had changed. Basically you would need two layers of binding, which is not easy to do properly (and can lead to memory leaks if you're not careful).

Where you have this
public User CurrentUser =>
That's a one time binding as it stands.
You would have to raise property change event in code with "CurrentUser"
Edit:
I think you could reduce your problems by using a different approach with your User object.
Rather than implementing a singleton pattern using a static, it would be more usual to have dependency injection in any substantial sized project. Pretty much a given in commercial apps where automated testing such as TDD is kind of expected.
The usual pattern for an app would be you can initially do nothing at all until you log in. You input name an password and are authenticated. Your user is looked up in the database. Authorisation is somehow obtained for what you can do in the app.
Then you're on to using the app. The ui is built out and you can do stuff.
If you switch user then you tear everything down and start again. It is quite common just to have to log out and close the app. Start it up again. Because with most PCs when User 1 stops using it he logs out of windows. User B comes along and logs in. The app is closed down inbetween.
With that in mind I'm assuming you would want to dispose existing viewmodels if a user could switch user.
I put some code together which, just uses mainviewmodel to instantiate two viewmodels passing in user. We can imagine resolving our viewmodels out a dependency injection container rather than directly out that viewmodel.
I also used the mvvm community toolkit for this. Partly because I prefer it but also because I think it's clearer to any reader that some code generation is going on and where it is. The attributes drive property code generation with inpc implementation in a partial class.
I could have added another control to take focus but as it is this only has one textbox so I have UpdateSourceTrigger=PropertyChanged. The entire purpose is to demonstrate the notification working with an instance class so the textblocks changing as I type is a plus.
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<TextBlock Text="Input:"/>
<TextBox Text="{Binding TheUser.UserName, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="First:"/>
<ContentPresenter Content="{Binding FirstViewModel}"/>
<TextBlock Text="Second:"/>
<ContentPresenter Content="{Binding SecondViewModel}"/>
</StackPanel>
</Grid>
</Window>
User
public partial class User : ObservableObject
{
[ObservableProperty]
private string userName = string.Empty;
}
As I mentioned, making User a single instance of an instance class is implemented in a quick and dirty way here. A singleton lifespan declaration in a DI container would be the proper way to do this.
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private User theUser = new User();
[ObservableProperty]
public object firstViewModel;
[ObservableProperty]
public object secondViewModel;
public MainWindowViewModel()
{
firstViewModel = new AViewModel(theUser);
secondViewModel = new BViewModel(theUser);
}
}
All three viewmodels therefore have the one instance of User.
Both usercontrols only have a textblock in them
<TextBlock Text="{Binding TheUser.UserName}"/>
Both viewmodels are very similar but there is no raising of property changed going on in them. No attribute.
public partial class AViewModel : ObservableObject
{
public User TheUser { get; set; }
public AViewModel(User user)
{
TheUser = user;
}
}
Bviewmodel is almost exactly the same.
When I spin this up and type in the textbox, the change is seen in the textblocks immediately.
Not sure how far you'll want to go with changes but maybe this is something to consider or will help someone else comes across this question later.

Related

ContentControl Content Property not changing with hosted content

I am trying to learn MVVM and have come across a weird snag. I have a main menu with a drawer control that comes out and shows a menu:
In the main window where this drawer is, I have a ContentControl where I set its content with a Binding.
<ContentControl x:Name="MainWindowContentControl" Content="{Binding Path=WindowContent}"/>
This window's binding is set to a view model.
<Window.DataContext>
<viewmodels:MainWindowViewModel/>
</Window.DataContext>
and here is the ViewModel:
MainWindowViewModel.cs
public class MainWindowViewModel: ViewModelBase
{
private object _content;
public object WindowContent
{
get { return _content; }
set
{
_content = value;
RaisePropertyChanged(nameof(WindowContent));
}
}
public ICommand SetWindowContent { get; set; }
public MainWindowViewModel()
{
SetWindowContent = new ChangeWindowContentCommand(this);
}
}
So far up to this point, everything works fine. So for example, if I click "Recovery Operations", I get this:
RecoveryOperationsView.xaml
In "RecoveryOperationsView.xaml" (which is a UserControl) I also reference the view model from above like so..
<UserControl.DataContext>
<viewmodels:MainWindowViewModel/>
</UserControl.DataContext>
and have a button to call the command to change the Content property of the ContentControl from the main window..
<Button Grid.Row="2" Content="Restore Database" Width="150" Style="{StaticResource MaterialDesignFlatButton}" Command="{Binding SetWindowContent}" CommandParameter="DatabaseRecovery" >
In my class to process the commands, I change the content based off of the passed parameter using a switch statement like so
ChangeWindowContentCommand.cs
public class ChangeWindowContentCommand : ICommand
{
private MainWindowViewModel viewModel;
public ChangeWindowContentCommand(MainWindowViewModel vm)
{
this.viewModel = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
switch (parameter)
{
case "Home":
viewModel.WindowContent = new HomeView();
break;
case "RecoveryOps":
viewModel.WindowContent = new RecoveryOperationsView();
break;
case "DatabaseRecovery":
viewModel.WindowContent = new DatabaseRestoreView();
break;
}
}
}
However, this is where I get lost... If I click something within this new window, say "Restore Database" and inspect it with a breakpoint, I can see the property being changed but the actual ContentControl Content property doesnt change to the new UserControl I made... I can change the content with anything in the drawer, but if I try to click a button in the hosted Content of the ContentControl nothing changes. What am I missing?
It's hard to be 100% sure without having your project to test with, but I am fairly confident that at least one of the issues is that your UserControl and your MainWindow use different instances of the MainWindowViewModel. You do not need to instantiate the VM for the user control, as it will inherit the DataContext from the MainWindow. The way it works in WPF is that if any given UIElement does not have theDataContext assigned explicitly, it will inherit it from the first element up the logical tree that does has one assigned.
So, just delete this code, and it should solve at least that issue.
<UserControl.DataContext>
<viewmodels:MainWindowViewModel/>
</UserControl.DataContext>
And since you're learning WPF, I feel obligated to provide a couple other tips. Even though you're using a ViewModel, you are still mixing UI and logic by creating a very specific implementation of ICommand and assigning a UI element through your ViewModel. This breaks the MVVM pattern. I know MVVM takes a little time to understand, but once you do, it is very easy to use and maintain.
To solve your problem, I would suggest creating View Models for each of your user controls. Please see this answer, where I go into quite a bit of detail on the implementation.
For switching the different views, you have a couple of options. You can either use a TabControl, or if you want to use a command, you can have a single ContentControl bound to a property of MainWindowViewModel that is of type ViewModelBase. Let's call it CurrentViewModel. Then when the command fires, you assign the view model of the desired user control to that bound property. You will also need to utilize implicit data templates. The basic idea is that you create a template for each of the user control VM types, which would just contains an instance of the Views. When you assign the user control VM to the CurrentViewModel property, the binding will find those data templates and render the user control. For example:
<Window.Resources>
<DataTemplate DataType = "{x:Type viewmodels:RecoveryOperationsViewModel}">
<views:RecoveryOperationsView/>
</DataTemplate>
<!-- Now add a template for each of the views-->
</Window.Resources>
<ContentControl x:Name="MainWindowContentControl" Content="{Binding CurrentViewModel}"/>
See how this approach keeps UI and logic at an arm's length?
And lastly, consider creating a very generic implementation of ICommand to use in all your ViewModels rather than many specific implementations. I think most WPF programmers have more or less this exact RelayCommand implementation in their arsenal.

Duplicate Model instantiated from InitializeComponent()

I'm working on a MVVM WPF application and have run into a dead end trying to solve this. %)
I have a model class DeviceModel instantiated from App.xaml.cs. This model implements INotifyPropertyChanged interface.
public partial class App : Application
{
public DeviceModel DeviceModelInstance { get; set; }
public App()
{
DeviceModelInstance = new DeviceModel();
}
}
In App.xaml:
<Application.Resources>
<vm:ViewModelBase x:Key="ViewModelBaseApp"/>
<m:DeviceModel x:Key="DeviceModelApp"/>
</Application.Resources>
Then from MainWindow.xaml I instantiate the ViewModelBase:
<Window.DataContext>
<Binding Source="{StaticResource ViewModelBaseApp}"/>
</Window.DataContext>
Then I bind buttons in the UI (MainWindow.xaml) to commands in ViewModelBase, which implement ICommand interface:
Command="{Binding InputPhantomCommand, Converter={StaticResource InputPhantomConverter}, Source={StaticResource ViewModelBaseApp}}"
This works great! I push a button, a command in the ViewModelBase gets called and changes a property of the DeviceModelInstance (instantiated in App.xaml.cs), which in turn triggers a PropertyChanged event, which propagates to other classes listening to that event. All the ICommand bound buttons in the UI work this way.
However, when I bind sliders to properties in the DeviceModel class, i.e.:
Value="{Binding InputChannel2.Gain, Converter={StaticResource InputGainConverter}, Source={StaticResource DeviceModelApp}}"
All of the sliders get bound to a duplicate DeviceModel class, which gets instantiated at InitializeComponent() method in the constructor of MainWindow.xaml.cs. When I move any of the sliders, the duplicate model reacts fine to changes. All of the labels that are bound to the same properties as sliders get dynamically updated. Yet, all of the events are listened to on the original DeviceModelInstance object (from App.xaml.cs) and no events occur, obviously.
I cannot figure out why all of the buttons are interacting with the DeviceModelInstance that I created in App.xaml.cs, while all of the sliders and their corresponding labels are interacting with a duplicate DeviceModel() that gets created at InitializeComponent() of MainWindow...
I suspect it's an issue of referencing the original DeviceModelInstance correctly in XAML, but I don't know how to do this. Thank you! )
So, in addition to clues given in comments by Will and Clemens, somehow this answer cleared it up for me even more - Multiple Instances of ViewModel
Specifically, the line:
To get access to it in Code-Behind, grab your AdminViewModel with (AdminViewModel)this.DataContext.
In my case, after relocating DeviceModelInstance from App class to ViewModelBase class, setting it as a public property, then instantiating ViewModelBase instance via XAML:
<Window.DataContext>
<vm:ViewModelBase/>
</Window.DataContext>
And finally referencing this specific ViewModelBase instance:
public partial class MainWindow : Window
{
public ViewModelBase vm;
public MainWindow()
{
vm = (ViewModelBase)this.DataContext;
InitializeComponent();
}
This works for me as intended because only one instance of ViewModelBase (and subsequently only one instance of DeviceModel) gets instantiated via XAML and gets referenced in code-behind.
However, I still don't understand how to do the same the other way around, i.e. instantiating Model objects in code and ONLY referencing them in XAML, not creating their new instances.
Thanks!

How to make a navigation service with Caliburn.Micro?

I am using Caliburn.Micro 3 in my MVVM WPF Application. I succesfully managed to implement navigation in my app by following the documentation and the samples provided.
However, I want to follow the SOLID principles and I think that using a ShellViewModel as a Conductor is a violation of the single responsibility principle.
In order to resolve this problem I created a "service" which control my navigation but I can't manage to display the ActiveItem. When I navigate, I have the ViewModel name as a string instead of the View associated with it.
public class NavigationService : Conductor<IScreen>, INavigationService
{
public void GoTo<T>() where T : IScreen
{
var viewModel = IoC.Get<T>();
ActivateItem(viewModel);
}
}
And I use it from my "ShellViewModel".
public class ShellViewModel : PropertyChangedBase
{
private readonly INavigationService _navigationService;
public HomeViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
}
public INavigationService NavigationService => _navigationService;
public void ShowChartPage() => _navigationService.GoTo<TimeSeriesViewModel>();
}
The ContentControl from my ShellView :
<ContentControl Content="{Binding NavigationService.ActiveItem}" />
Am I missing something ?
The problem which causes you issue regards your XAML snippet: you are binding the Content property directly to your ViewModel (TimeSeriesViewModel), then your application cannot work as you wish. In this situation you will see just a string which represents the type of the object that you are binding to the ContentControl.
To make you application work correctly you have to use:
Caliburn's naming convention i.e. you name your ContentControl in a proper way, so Caliburn can create automatically a binding for you.
a Caliburn attached property called View.Model.
Both these approaches retrieve the right View for the ViewModel that your infer in the ActiveItem property of your Conductor.
In the first case your can use <ContentControl x:Name="ActiveItem" /> (but you need to create the corresponding property in the ShellViewModel class); with the second approach you can use <ContentControl cal:View.Model="{Binding NavigationService.ActiveItem}" />.
I hope that my hint and my quick explanation can help you.

WPF from ICommand back to MainView

I'm probably missing the point here, I've been digging around for some time now, looked at the different approaches to bind views and view models and how to navigate between them.
Setup:
1 MainWindow (ViewModel: MainWindowViewModel)
MainWindow contains some visuals and a ContentControl that is binded to a ViewModelBase. So in MainWindowViewModel I can set any other view(model) to display.
Got two user controls, one is a login form, the other one is for now a loading indicator.
Inside App.xaml
<DataTemplate DataType="{x:Type vms:LoginViewModel}">
<Views:LoginView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:LoadingViewModel}">
<Views:LoadingView />
</DataTemplate>
Goal:
From the ICommand inside LoginViewModel go back to the MainWindowViewModel, a long with the form data. MainWindowViewModel will then switch to LoadingViewModel, do async call to a service. Next time on startup when a refresh token is saved, I will show the LoadingViewModel instead of the login form. On completion a new window will open (or something else, don't know yet).
Problem: Most examples show how to do it when a button is outside of both User Controls, so when the ICommand is inside the MainWindow, then it would be easy, yet the event is called from one of the subviews. Using PropertyChange seems a bit off as well.
Let's dig into some code, shell we?
MainWindow.xaml, only one important line
<ContentControl Content="{Binding CurrentView}" Grid.Column="1" Grid.Row="1" />
MainWindow.xaml.cs, in constructor
this.DataContext = new MainWindowViewModel();
MainWindowViewModel
public class MainWindowViewModel
{
public ViewModelBase CurrentView { get; set; }
public MainWindowViewModel()
{
CurrentView = new LoginViewModel();
}
}
LoginViewModel
public class LoginViewModel : ViewModelBase
{
public DelegateCommand loginCommand { get; set; }
public LoginViewModel()
{
loginCommand = new DelegateCommand(Execute, CanExecute);
}
private bool CanExecute()
{
return true;
}
private void Execute()
{
//I need to go to MainWindowViewModel
throw new NotImplementedException();
}
//more properties below heere.
ViewModelBase inherits from BindableBase (from Prism), so that handles the PropertyChanged events. The properties inside my view models use the correct SetProperty methods. I prefer not to use Prism's region, IEventAggregator or Unitiy.
Solutions
What comes to mind is sending an interface a long with the constructor, and use the interface to do 'callbacks' to the MainWindowViewModel, but I guess this will give errors, since I will change the view, and thus setting MainWindowViewModel.CurrentView to something else, leaving LoginViewModel null. Since the request is coming from that object I can imagine it is not really good.
A good way to communicate is the Messanger (MVVM Light) or EventAggregator (Prism) concept.
It's basically an in memory pub/sub system.
Here is an example from an article on MSDN
MVVM - Messenger and View Services in MVVM
Using the event aggregator pattern to communicate between view models
I am not sure which framework/library are you using but most MVVM framework have a similar concept for lousily coupled communication.
This is a very powerful concept for handling communication. But with great power comes responsibility =)...
HTH

Binding a ContentControl to UserControl, and reuse same instance

I'm trying to bind a ContentControl's Content to a UserControl I have instantiated in my ViewModel. I cannot use the method with binding to a ViewModel and then have the UserControl be the DataTemplate of the ViewModel, as I need the Content of the ContentControl to be able to change frequently, using the same instance of the UserControls/Views, and not instantiate the views each time i re-bind.
However, when setting the UserControl-property to a UserControl-instance, and then when the view is rendered/data-bound I get: Must disconnect specified child from current parent Visual before attaching to new parent Visual. Even though I have not previously added this UserControl to anywhere, I just created this instance earlier and kept it in memory.
Are there a better way to achieve what I am doing?
In the ViewModel
public class MyViewModel : INotifyPropertyChanged
{
//...
private void LoadApps()
{
var instances = new List<UserControl>
{
new Instance1View(),
new Instance2View(),
new Instance3View(),
};
SwitchInstances(instances);
}
private void SwitchInstances(List<UserControl> instances)
{
CenterApp = instances[0];
}
//...
private UserControl _centerApp;
public UserControl CenterApp
{
get { return _centerApp; }
set
{
if (_centerApp == value)
{
return;
}
_centerApp = value;
OnPropertyChanged("CenterApp");
}
}
//...
}
In the View.xaml
<ContentControl Content="{Binding CenterApp}"></ContentControl>
Too long for a comment.
Building up on what #Kent stated in your comment, The whole point of MVVM is to disconnect the view-model from view related stuff(controls) which blocks the testing capability of GUI applications. Thus you having a UserControl / Button / whatever graphical view-related item negates the entire principle of MVVM.
You should if using MVVM comply with its standards and then re-address your problem.
With MVVM you normally have 1 view <-> 1 view-model
View knows about its View Model(Normally through DataContext). Reverse should not be coded into.
You try to put logic controlling the view in the view-model to allow testing logic(Commands and INPC properties)
... and quite a few more. It's pretty specific in the extents of view-model not having view related stuff for eg not even having properties in view-model like Visibility. You normally hold a bool and then in the view use a converter to switch it to the Visibility object.
Reading up a bit more into MVVM would certainly help you,
Now for something to address your current issue:
Following a MVVM structure,
your going to have ViewModels such as
Main: MyViewModel
Derive all instance ViewModels from a base to allow them being kept in a list.
List or hold individually Instance1ViewModel, Instance2ViewModel, Instance3ViewModel in MyViewModel (Either create it yourself or if your using an IOC container let it inject it)
Have MyViewModel expose a property just like your posted example:
Example:
// ViewModelBase is the base class for all instance View Models
private ViewModelBase _currentFrame;
public ViewModelBase CurrentFrame {
get {
return _currentFrame;
}
private set {
if (value == _currentFrame)
return;
_currentFrame = value;
OnPropertyChanged(() => CurrentFrame);
}
}
Now in your MyView.xaml View file you should(does'nt have to be top level) set the top-level DataContext to your MyViewModel
Your View's xaml can then be declared like:
Example:
...
<Window.Resources>
<DataTemplate DataType="{x:Type local:Instance1ViewModel}">
<local:Instance1View />
</DataTemplate>
<DataTemplate DataType="{x:Type local:Instance2ViewModel}">
<local:Instance2View />
</DataTemplate>
<DataTemplate DataType="{x:Type local:Instance3ViewModel}">
<local:Instance3View />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding Path=CurrentFrame}" />
</Grid>
...
Thats it!. Now you just switch the CurrentFrame property in your view-model and make it point to any of three instance view-models and the view will be correspondingly updated.
This gets you an MVVM compliant application, for your other issue of working around not having to recreate views dynamically based on DataTemplate you could follow the approaches suggested here and expand it for your own usage.

Categories

Resources