Subscribing to an event of a child view - not working - c#

Im working on a WPF app and I am trying to use the MainWindowViewModel that holds various views as a type of messenger to pass information between views:
The following is done in a child viewmodel:
public event Action<ModelObject> NameOfEvent= delegate {};
public void Open_Command()
{
ModelObject modelObject= RandomViewModel.ImportModelObject();
NameOfEvent(modelObject); //event is triggered while running the app
}
Then in the constructor of my MainWindowViewModel (the parent of the above view model) I am subscribing to the event. And Its not picking it up
private readonly RandomViewModel _randomViewModel = new RandomViewModel();
public MainWindowViewModel()
{
Random= _randomViewModel; // sets view model to a bindable
//property that lods the view in the main window
_randomViewModel.NameOfEvent+= DoSomething; //subscribes to childs event
}
private void DoSomething(ModelObject obj)
{
//It never reaches here
}
To summarise the issue. When the event is being triggered in the child view model, the parent is not executing DoSomething method, it doesnt seem to work, i cant figure out why

Ok so my issue as seen from the comments above was that I had a double instance of my childViewModel, this was done as I employed two techniques that initialize view models:
1.) Setting Data Context in xaml of my view initializes a viewmodel
<UserControl.DataContext>
<local:MyViewModel>
</UserControl.DataContext>
2.) Using a technique I found to initialize view-models in a MainWindowViewModel:
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:MyViewModel}">
<view:MyView/>
</Window.Resources>
After you initialize in your MainWindowViewModel you assign it to a Bindable Property in the constructor and add it to xaml in a Content Control
Everything I learned so far for MVVM is to use DataContext but the 2nd method is a new way I found that works very well if you are initialising your viewmodels in a MainWindowViewModel.
I got this technique off of Brian Noyes course "MVVM in depth" on pluralsight.
So after I removed the customary :
<UserControl.DataContext>
<local:MyViewModel>
</UserControl.DataContext>
The solution worked, now my MainWindowViewModel holds all my viewmodels and can act as a messenger service similar to stuff that can be found in MVVM light e.t.c

Related

Why is my DashboardView not beeing displayed?

Overview:
I have one MainWindow and two Views. One representing a Login Screen and the other one is representing the Dashboard. The LoginView is shown on StartUp. When the User presses the LoginButton the DashboardView should be shown.
Here's my Problem:
The underlying Command is beeing executed. The Constructor of DashboardViewModel is beeing called. And the View and ViewModel for Dashboard are connected via a DataTemplate. But the View or the InitializeComponent Method are not beeing called.
The LoginCommand looks like this:
LoginMainCommands is a RelayCommand Class derived from ICommand.
public class ButtonViewModel : LoginMainViewModel
{
public ButtonViewModel()
{
_loginCommand = new LoginMainCommands(Login);
}
private LoginMainCommands _loginCommand;
public LoginMainCommands LoginCommand
{
get { return _loginCommand; }
}
public void Login()
{
ViewModelLocator ObjViewModelLocator = new ViewModelLocator();
ObjViewModelLocator.MWInstance.SwitchViewModel();
}
}
The connection between the Views and the ViewModels is in ManWindow.xaml as follows:
<Window.Resources>
<local:ViewModelLocator x:Key="ViewModelLocator"/>
<DataTemplate DataType="{x:Type loginViewModel:LoginMainViewModel}">
<loginView:LoginMainViewUserControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type dashboardViewModel:DashboardMainViewModel}">
<dashboardViews:DashboardMainViewUserControl/>
</DataTemplate>
</Window.Resources>
To switch between the Views I added this Method in MainWindowViewModel:
public void SwitchViewModel()
{
if (!isLoginView)
{
isLoginView = true;
ViewModel = new LoginMainViewModel();
}
else
{
isLoginView = false;
ViewModel = new DashboardMainViewModel();
}
}
What I've tried so far:
I did almost everything this answer suggests. But I can't connect the Views and ViewModels in the App.xaml, cause then I can't use my ResourceDictionaries for Icons and Logos. But they are connected in MainWindow.xaml.
Later on I recognized in order for this to work only one Instance of MainWindowViewModel could exist because otherwise ViewModel would be null everytime a new Object is created. That's why I created ViewModelLocator like this answer suggests.
The weird part for me is when I change the if bracket in the SwitchViewModel Method to this:
if (!isLoginView)
{
isLoginView = true;
ViewModel = new DashboardMainViewModel();
}
Now DashboardMainViewModel is the default View to show and it does excatcly that it shows up.
I have no clue why the Dashboard Screen is not beeing shown.
I want to thank everybody in advance for your help and patience!
The problem is this line in the Login method in ButtonViewModel.
ViewModelLocator ObjViewModelLocator = new ViewModelLocator();
In your view model you create a new instance of the ViewModelLocator, which in turn creates a new instance of MainWindowViewModel on which you then call SwitchViewModel. That is the wrong instance.
You could solve this problem in many different ways, e.g.:
Make ViewModelLocator a singleton.
You would set the DataContext like this, without creating an instance in the Window.Resources:
DataContext="{Binding MainWindowViewModel, Source={x:Static local:ViewModelLocator.Instance}}"
Then in ButtonViewModel you can directly call the SwitchViewModel through ViewModelLocator:
ViewModelLocator.Instance.MWInstance.SwitchViewModel();
Pass a reference of the MainWindowViewModel to the other view models
Passing the instance of MainWindowViewModel to the other view models in order to call SwitchViewModel works, but makes them tightly coupled and exposes more of the view model than necessary.
Alternatively, you could create an interface ISwitchViewModels with a method SwitchViewModel that is implemented by MainWindowViewModel and pass it via the interface instead to hide the actual implementation.
Use events to communicate between view models
Many WPF MVVM frameworks like Prism or Caliburn.Micro feature something called an Event Aggregator to communicate via events between view models and enable loose coupling. In this case you would not have to depend on a reference from ButtonViewModel to MainWindowViewModel, instead you would send a message to request changing the current view.
Use dependency injection to register the ViewModelLocator as singleton and pass it to view models
I guess you are not familiar with dependency injection and it might be beyond the scope of this question, so this is just for further reading.
Note that in this case the ViewModelLocator is not implemented as singleton, but only a single instance of it is created by a container and passed to all other types that specify this dependency.
Understanding dependency injection
What is dependency injection?
The weird part for me is when I change the if bracket in the SwitchViewModel Method to this: [...]
Now DashboardMainViewModel is the default View to show and it does excatcly that it shows up.
That is not weird at all, since you directly assign the ViewModel property on the right instance of MainWindowViewModel without even using the ViewModelLocator.

How to raise an event in a ViewModel from within a subview's ViewModel

I have an application with a main view that contains several subviews. The views have a corresponding ViewModel that display some data and monitor events contained in models.
Let's say the main view looks like this:
<UserControl x:Class="MyView" Name="myView">
<StackPanel>
<local:MySubView Name="mySubView" someProperty="{Binding DataContext.someField, ElementName=myView}"/>
[...]
</StackPanel>
</UserControl>
and the sub view looks like this:
<UserControl x:Class="MySubView"
prism:ViewModelLocator.AutoWireViewModel="True">
[...]
</UserControl>
Then in MySubViewModel.cs, some event happens and a function is called with parameter:
public Event EventHandler<SomeClass> MySubViewEvent;
[...]
void foo() {
SomeClass o = new SomeClass(...);
MySubViewEvent.Invoke(this, o);
}
Now, in MyViewModel.cs, I do not have a direct reference to the subview and thus cannot say something like
subViewModel.MySubViewEvent += OnSubViewEvent;
void OnSubViewEvent(object sender, SomeClass param) { ... }
I can add some property to MySubViewModel and make it dependent on some field in MyViewModel with a dependency property, but 1. how can I do it the other way round such that some callback or event handler will be called in MyViewModel when the event is raised in MySubViewModel, and 2. can I somehow pass event handlers as dependency properties?
Or should I use something else than DependencyProperties for this?
What is the most effective way to achieve this?
Textbook example for using the EventAggregator, though I should add that child view models are also a textbook example for going view model-first. The ViewModelLocator is most useful for independent top-level views...
That being said, in sub view model:
_eventAggregator.GetEvent<MySubViewEvent>().Publish();
Somewhere else:
public MainViewModel( IEventAggregator eventAggregator )
{
eventAggregator.GetEvent<MySubViewEvent>().Subscribe( OnSubViewEvent );
}

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!

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

Categories

Resources