Why is my DashboardView not beeing displayed? - c#

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.

Related

WPF set DataContext to a ViewModel without default constructor

I have a ViewModel that accepts several constructor parameters. As I understand because of this the only way to set View's DataContext is using code behind.
This has its downsides:
Visual Studio will not show ViewModel inteliscence for the view that is being constructed
There is no way to see design time data that is defined in my ViewModel constructor in XAML designer because designer simply breaks
What are my options?
I would like to have a ViewModel that can accept constructor parameters, have design time data and that my Visual Studio inteliscence provide me suggestions about members in my ViewModel so I could have a good design experience.
PS. I'm using MVVM Toolkit / Windows Community Toolkit from Microsoft, but I would appreciate any answer on how to achieve my end goal. Thank you.
What are my options?
A common approach is to create a separate design time view model class with a parameterless constructor and set the design time data context to this type:
<Window ... d:DataContext="{d:DesignInstance Type=local:DesignTimeViewModel, IsDesignTimeCreatable=True}"
What you shouldn't do is to design your application and define your classes according to how the designer in Visual Studio works.
The cleanest way to assign a view-model with a paramterised constructor as the data context for a view is using the ViewModelLocator pattern.
public class ViewModelLocator
{
public ViewModelLocator()
{
// define view-model definitions in IoC container.
}
public MainViewModel MainViewModel
{
get
{
// use the appropriate construct for your choice of IoC container
var result = IoCContainer.GetInstance<MainViewModel>();
return result;
}
}
}
An instance of this class can be created in App.xaml
<Application.Resources>
<local:ViewModelLocator x:Key="ViewModelLocator">
</Application.Resources>
which becomes an application wide resource, that can be referenced in each view.
Now we can fetch an instance of a specific view-model at any time, including in the designer, and assign it as the DataContext of the view.
<Window
x:Class="MyApplication.MainView"
...
DataContext="{Binding Source={StaticResource ViewModelLocator}, Path=MainViewModel}" />
For more details on the usage of this ViewModelLocator pattern, including having design-time data in your ViewModel, check out my blog post.

Subscribing to an event of a child view - not working

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

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.

WPF Open a new View from the ViewModel

This is my first WPF-MVVM application, and this is my structure:
One project with my app.xaml to open the application and override the OnStartup to resolve the MainWindow. (I did that due to the references);
One project for my Views;
One project for my ViewModels;
One project for my Model.
And I have de following problem: I'm on the MainWindowView and I click on a button to show another view. How am I supposed to do to open this another view from my MainWindowViewModel whereas my View Project has reference with the ViewModel Project, and I can't reference the ViewModel Project with the View Project?
By the way, I'm using Unity for the dependency injection.
So, could you help me?
There are several approaches to that.
You can define a dialog/navigation/window service interface, defined in the ViewModels project. You will need to decide how the ViewModels will express which window they want to open. I generally use an IDialogViewModel interface, which some of my ViewModels implement, and pass an instance of the ViewModel to the service, but you can use an enum, string, whatever you want, so your implementation can map to the real window which will be opened.
For example:
public interface IDialogService
{
bool? ShowDialog(object dialogViewModel);
}
ViewModels that want to open new Windows would receive an instance of that service and use it to express the intention of opening a Window. In your Views project, you would define a type which implements your service interface, with the real logic behind opening the Window.
Following the example:
public class DialogService : IDialogService
{
private Stack<Window> windowStack = new Stack<Window>();
public DialogService(Window root)
{
this.windowStack.Push(root);
}
public bool? ShowDialog(object dialogViewModel)
{
Window dialog = MapWindow(dialogViewModel);
dialog.DataContext = dialogViewModel;
dialog.Owner = this.windowStack.Peek();
this.windowStack.Push(dialog);
bool? result;
try
{
result = dialog.ShowDialog();
}
finally
{
this.windowStack.Pop();
}
return result;
}
}
Your main project will be responsible for creating and injecting the dialog service in the ViewModels who need it. In the example, the App would create a new dialog service instance passing the MainWindow to it.
A similar approach to do it would be using some form of the messaging pattern (link1 link2 ).
In addition, if you want something simple you can also make your ViewModels raise events when they want to open Windows and let the Views subscribe to them.
EDIT
The complete solution that I use in my apps a generally a bit more complex, but the idea is basically that. I have a base DialogWindow, which expects a ViewModel which implements an IDialogViewModel interface as DataContext. This interface abstracts some functionalities you expect in dialog, like accept/cancel commands as well as a closed event so you can also close the window from the ViewModel. The DialogWindow consists basically in a ContentPresenter which Content property is bound to the DataContext and hooks the close event when the DataContext is changed (and a few other things).
Each "dialog" consists in an IDialogViewModel and an associated View (UserControl). To map them, I just declare implicit DataTemplates in the resources of the App. In the code I've shown, the only difference would be there wouldn't be a method MapWindow, the window instance would always be a DialogWindow.
I use an additional trick to reuse layout elements between dialogs. On approach is to include them in the DialogWindow (accept/cancel buttons, etc). I like to keep the DialogWindow clean (so I can use it event to "non-dialog" dialogs). I declare a template for a ContentControl with the common interface elements, and when I declare a View-ViewModel mapping template, I wrap the View with a ContentControl with my "dialog template" applied. You can then have as much "Master templates" for your DialogWindow as you want (like a "wizard like" one, for example).
Straight forward approach
If I understand you correctly MainWindowView is resolved through Unity when the app starts, which resolves its dependency on MainWindowViewModel?
If that is the flow you are using I would suggest continuing on the same path and letting the MainWindowView handle the opening of the new view via a simple click handler for the button. In this handler you can then resolve the new view, which would resolve that view's view model and then you're back in MVVM land for the new view as well.
That solution is straight forward and would work perfectly fine for most smaller applications.
Heavier approach for more complex apps
If you don't want that kind of view-first flow I would suggest introducing some kind of controller/presenter that coordinates the views and view models. The presenter is responsible for making decisions about if/when to actually open/close views and so on.
This is a quite heavy abstraction though, which is more suitable for more complex applications, so make sure you actually get enough benefit out of it to justify the added abstraction/complexity.
Here is a code sample of what this approach might look like:
public partial class App
{
protected override void OnStartup(StartupEventArgs e)
{
var container = new UnityContainer();
container.RegisterType<IMainView, MainWindow>();
container.RegisterType<ISecondView, SecondWindow>();
container.RegisterType<IMainPresenter, MainPresenter>();
container.RegisterType<ISecondPresenter, SecondPresenter>();
var presenter = container.Resolve<IMainPresenter>();
presenter.ShowView();
}
}
public interface IMainPresenter
{
void ShowView();
void OpenSecondView();
}
public interface ISecondPresenter
{
void ShowView();
}
public interface ISecondView
{
void Show();
SecondViewModel ViewModel { get; set; }
}
public interface IMainView
{
void Show();
MainViewModel ViewModel { get; set; }
}
public class MainPresenter : IMainPresenter
{
private readonly IMainView _mainView;
private readonly ISecondPresenter _secondPresenter;
public MainPresenter(IMainView mainView, ISecondPresenter secondPresenter)
{
_mainView = mainView;
_secondPresenter = secondPresenter;
}
public void ShowView()
{
// Could be resolved through Unity just as well
_mainView.ViewModel = new MainViewModel(this);
_mainView.Show();
}
public void OpenSecondView()
{
_secondPresenter.ShowView();
}
}
public class SecondPresenter : ISecondPresenter
{
private readonly ISecondView _secondView;
public SecondPresenter(ISecondView secondView)
{
_secondView = secondView;
}
public void ShowView()
{
// Could be resolved through Unity just as well
_secondView.ViewModel = new SecondViewModel();
_secondView.Show();
}
}
public class MainViewModel
{
public MainViewModel(MainPresenter mainPresenter)
{
OpenSecondViewCommand = new DelegateCommand(mainPresenter.OpenSecondView);
}
public DelegateCommand OpenSecondViewCommand { get; set; }
}
public class SecondViewModel
{
}
<!-- MainWindow.xaml -->
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Command="{Binding OpenSecondViewCommand}" Content="Open second view" />
</Grid>
</Window>
<!-- SecondWindow.xaml -->
<Window x:Class="WpfApplication1.SecondWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SecondWindow" Height="350" Width="525">
<Grid>
<TextBlock>Second view</TextBlock>
</Grid>
</Window>
This article presents a similar solution to what I've used in production before.
To open new window from MainWindowView, you need to pass reference of Frame component or whole window to MainWindowViewModel object(You can do this when binding command to transition button or something, pass them as object), there you can navigate to new page, however, if there is nothing of special that you need to do in ViewModel when transitioning, you can just use classic ButtonClick event or w/e in MainWindowView.cs that will do navigation for you, that is ok for basic transitions.
P.S. I am not sure why you use different projects for ViewModels/Views/Models.

Categories

Resources