Pass data across viewmodels - c#

I just can't figure this out. So I will try describe my problem best I can.
I am building application using MVVM pattern. I have user control AAAView with viewmodel AAAViewModel that is used to fill data class CDataClass. I also have main window MainView and its viewmodel MainViewModel. Next I have window DialogView with DialogViewModel.
So now MainViewModel (that has its own user control) creates DialogViewModel (with another instance of user control). How can I transfer data in CDataClass between these two user controls? I tried to create property in AAAViewModel that would hold instance of MainViewModel or DialogViewModel so I can pass data to it but I get stuck because I couldn't make it as dependency property.
My goal is to make user control that can be used in different views which can have different data in underlaying CDataClass.
Just to clarify... I am using user control as <views:GeneralInfoView Grid.Row="0" /> and don't know how to share data between two different instances of the same user control in different views. Any point to some pattern or method would be much appreciate.
Thank you for help.

I don't think it's ideal that you've got your application architecture diagrammed as relationships among views; I think a better way to think about it is as a set of relationships among viewmodels, with the views hanging off that tree as needed. When you think about it that way, "how does data get passed" gets a lot simpler. A view is just a conduit between a viewmodel and the user. You don't design a house as a set of windows and telephones and then try to figure out the floor plan from that. You start with what the house does and how people will live in it.
So this is easy:
Some viewmodels have an AAViewModel property. There may be all kinds of simple or complicated views on those viewmodels; if a view wants to let the user edit the viewmodel's AAViewModel stuff, then it includes an AAView bound appropriately to the viewmodel's AAViewModel. Your MainViewModel and DialogViewModel are both big complicated interactive views that want to let somebody edit their vm's AAViewModel stuff.
If MainViewModel is DialogViewModel's parent, or created a temporary instance of DialogViewModel just to put in a modal dialog, then MainViewModel would show the dialog, and have a look at dialogVM.AAVM.CData.IsDirty to decide what to do with it. Or maybe it gives dialogVM.AAVM a new CDataClass instance before showing the dialog (maybe a clone of its own instance), and if ShowModel() returns true, then it does something with dialogVM.AAVM.CData.
The point is that once your viewmodels are driving everything, it becomes relatively simple for them to communicate with each other. Parent-child is easy: Parent gives stuff to the child and looks at what the child brings back. A viewmodel can subscribe to another viewmodel's PropertyChanged event; a parent viewmodel can monitor its children; when something happens on a child, the parent can decide whether to update a sibling. In general, children should not know anything at all about their parents; this makes it much easier to reuse child viewmodels in disparate contexts. It's up to parents to decide what to do with that information.
All AAViewModel knows is that somebody handed him a copy of CDataClass; he updates his public properties accordingly. Then somebody else (probably AAView, but he doesn't know) hands him some changes by setting his properties; he updates his CDataClass instance accordingly. After a while, unknown to him, one viewmodel or another comes and looks at that CDataClass.
And communication between views and viewmodels happens via bindings.
UPDATE
It turns out that you're creating viewmodels in your views, and as a result you have no idea how the parent can get to them. And now you know why it's not a good idea to create child view viewmodels that way.
Here's how you do child view/viewmodels in the viewmodel-centric design I described above:
First, get rid of whatever you're doing to create the child viewmodels inside the view.
Second, create a DataTemplate for the child viewmodel type. This should go in a resource dictionary which is merged into the resources in App.xaml, but it's so simple that it won't kill you if you get lazy and just paste it into the Resources of the two different views where it's used.
I don't know what your namespace declarations look like. I'm going to assume that views are in something called xmlns:view="...", and viewmodels are in something called xmlns:vm="...".
<DataTemplate DataType="{x:Type vm:AAAViewModel}">
<view:AAAView />
</DataTemplate>
Now, you can assign an AAAViewModel to the ContentProperty of any control that inherits from ContentControl (and that's most of them), and the template will be instantiated. That means that XAML will create an AAAView for you and assign that instance of AAAViewModel to the DataContext property of the AAAView that it just created.
So let's create a child AAAViewModel next, and then we'll show it in the UI.
public class DialogViewModel
{
// You can create this in DialogViewModel's constructor if you need to
// give it parameters that won't be known until then.
private AAAViewModel _aaavm = new AAAViewModel();
public AAAViewModel AAAVM
{
get { return _aaavm; }
protected set {
_aaavm = value;
OnPropertyChanged(nameof(AAAVM));
}
}
And now we can display AAAVM in DialogView:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentControl
Content="{Binding AAAVM}"
Grid.Row="0"
/>
<StackPanel Orientation="Vertical" Grid.Row="1">
<!-- Other stuff -->
</StackPanel>
</Grid>
Now how does MainViewModel get in touch with a DialogViewModel? In the case of dialogs, since they have a finite lifespan, it's not actually a big deal to let them create their own viewmodels. You can do it either way. I generally lean towards having it create its own as in the second example below.
Not quite the same, but close. First, once again, get rid of whatever you're doing where the dialog creates its own viewmodel.
MainViewModel.cs
public CDataClass CDC { /* you know the drill */ }
public void ShowDialog()
{
var dvm = new DialogViewModel();
// Maybe this isn't what you want; I don't know what CDataClass does.
// But I'm assuming it has a copy constructor.
dvm.AAAVM.CDC = new CDataClass(this.CDC);
if (DialogView.ShowDialog(dvm).GetValueOrDefault())
{
CDC = dvm.CDC;
}
}
Note that this next one is view codebehind, not viewmodel.
DialogView.xaml.cs
public static bool? ShowDialog(DialogViewModel dvm)
{
var vw = new DialogView() { DataContext = dvm };
return vw.ShowDialog();
}
Now, you could let the dialog continue creating its own viewmodel; in that case you would give it a public property like this:
public DialogViewModel ViewModel => (DialogViewModel)DataContext;
And a ShowDialog method like this:
DialogView.xaml.cs
public static bool? ShowDialog(CDataClass cdc)
{
var dlg = new DialogView();
dlg.ViewModel.AAAVVM.CDC = cdc;
return dlg.ShowDialog();
}
And then the parent could interact with it like this instead:
MainViewModel.cs
public void ShowDialog()
{
var cdcClone = new CDataClass(this.CDC);
if (DialogView.ShowDialog(cdcClone).GetValueOrDefault())
{
CDC = cdcClone;
}
}
Nice and tidy.
If that dialog isn't modal, make the dialog viewmodel a private member of MainViewModel, and have MainViewModel subscribe to events on the dialog viewmodel to keep abreast of what the dialog is doing. Whenever the user updates the dialog's copy of CDataClass, the dialog would raise DataClassUpdated, and MainViewModel would have a handler for that event that sniffs at _dialogViewModel.AAAVM.CDC, and decides what to do with it. We can get into example code for that if necessary.
So now you can see what I mean by building everything in terms of parent/child viewmodels, and stuffing them into views when and as appropriate.

Related

How to instantiate a dialog and record user input while MVVM pattern is used to create dialog in WPF

I am a C++ developer and new to WPF and MVVM. please bear with me if I choose any wrong word to ask my question
I have my Main application in MFC/C++ which is passing some data to C# library(CLI is used as middle layer).
In C# library, there is a section of code where a dialog is opened , data is filled and user selection is notified to the calling object in below way -
public classA()
{
MyDialog dlg = new MyDialog(param1, param2, param3)
if(dlg.ShowDialog().GetValueOrDefault())
{
var name = dlg.name;
var roll = dlg.roll;
}
else
{
var name = string.Empty;
var roll = string.Empty;
}
}
Now Dialog has been modified and implemented using MVVM pattern.
I have created below files as part of implementation-
1
MyDialogView.Xaml
MyDialogView.xaml.cs
MyDialogViewModel.cs
MyDialogModel.cs
My question is, how to instantiate the new dialog now from my classA so that data is filled using the parameters passed to dialog in same way as previously it was doing and record user selection without loosing any data and safely closing the view.
Standard MVVM approach works like this (at least when using MVVM Light):
You have a VM layer, a Class Library.
You have a View layer, a WPF Controls Library or WPF Application.
View layer adds reference to VM layer. VM layer doesn't know anything about View.
You create a normal public class for your dialog's VM. Call it DialogVM or whatever. Make sure this class inherits from MVVM Light's built-in ViewModelBase. This will get you access to change notification methods provided by MVVM Light. Might look like this in your case:
public partial class DialogVM : ViewModelBase
{
private string _Name;
public string Name
{
get { return _Name; }
set { Set(ref _Name, value); }
}
private string _Roll;
public string Roll
{
get { return _Roll; }
set { Set(ref _Roll, value); }
}
}
VM layer has a global static class called ViewModelLocator. This class performs IoC/DI and provides public static properties to expose different VMs. (In your case your dialog's VM goes to the VM project and the ViewModelLocator looks something like this:
using System;
namespace VMLayer
{
public class ViewModelLocator
{
static ViewModelLocator()
{
SimpleIoc.Default.Register<DialogVM>(true);
}
public static DialogVM MyDialog => SimpleIoc.Default.GetInstance<DialogVM>();
}
}
Your dialog box (a Window) goes to View layer and uses this exposed property MyDialog to provide a DataContext for the dialog:
<Window x:Class="GlasshouseTestingShell.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:vm="clr-namespace:VMLayer;assembly=VMLayer"
DataContext="{x:Static vm:ViewModelLocator.MyDialog}"
d:DataContext="{d:DesignInstance Type=vm:DialogVM}">
</Window>
Look how cleanly we have created View layer's DataContext without writing a line of C# code in the View layer. This is also elegant in the sense that you get all design-time Intellisense in Binding expressions and elsewhere.
You now bind all your UI stuff (textboxes, buttons etc.) to the public properties and commands exposed by your dialog's VM. Still no lines in the code-behind. Might look like this in your case:
<TextBox Text="{Binding Name}" />
Rest of the stuff is in C++:
You add reference to your View and VM DLLs in your C++ project.
Create an object of your dialog. It will automatically instantiate its VM and perform binding. You call ShowDialog() to bring it to screen.
Use takes actions in the dialog and finally presses OK or Cancel.
You capture dialog result and then access your dialog object's DataContext property, which is an object of DialogVM class. You can access user-supplied values from therein as Binding has updated those properties for you in the VM.
I'm not sure I follow all of your requirements but this is roughly how I'd approach such a task:
Instantiate the view and viewmodel in class A.
Set whatever parameters you want on your viewmodel. Either as properties or via constructor injection.
Set the datacontext of the view to the viewmodel.
Everything you need to bind should then bind between them.
showdialog the view.
The user edits in the view and changes persist to the viewmodel properties.
They finish editing and you then work with the viewmodel properties. Maybe one of them is the model you mention. Maybe the model is instantiated by the viewmodel to get data or by classA if that is more convenient. In the latter case you probably have to pass that model to the viewmodel.
Bearing in mind the above.
Some rough code:
public class ClassA
{
MyDialogViewModel vm = new MyDialogViewModel { Name = "X", Roll = "Y" };
MyDialog dlg = new MyDialog();
dlg.ShowDialog();
var name = vm.Name;
var roll = vm.roll;
// Do something to persist your data as necessary. Either here or in a model within the viewmodel
}
Name and Roll presumably bind to some textboxes Text properties in the view or some such.
If it's as simple as obtaining two string values then I see no advantage to actually having a model at all. On the other hand, if processing is more involved then of course the viewmodel might instantiate a model.
MyDialogViewModel should implement inotifypropertychanged and anything you need to bind should be a public property. Not sure if you'll need propertychanged notification but always implement it. Optionally raise propertychanged from property setters.

How to manage multiple windows in MVVM

I am aware there are a couple of questions similar to this one, however I have not quite been able to find a definitive answer. I'm trying to dive in with MVVM, and keep things as pure as possible, but not sure how exactly to go about launching/closing windows while sticking to the pattern.
My original thinking was data bound commands to the ViewModel triggering code to start a new View, with the View's DataContext then set to it's ViewModel via XAML. But this violates pure MVVM I think...
After some googling/reading answers I came across the concept of a WindowManager (like in CaliburnMicro), now if I was to implement one of these in a vanilla MVVM project, does this go in with my ViewModels? or just in the core of my application? I'm currently separating out my project into a Model assembly/project, ViewModel assembly/project and View assembly/project. Should this go into a different, "Core" assembly?
Which leads on a bit to my next question (relates somewhat to the above), how do I launch my application from an MVVM point of view? Initially I would launch my MainView.xaml from App.xaml, and the DataContext in the XAML would attach the assigned ViewModel. If I add a WindowManager, is this the first thing that is launched by my Application? Do I do this from the code behind of App.xaml.cs?
Well it mainly depends on how your application looks like (i.e. how many windows opened at the same time, modal windows or not...etc).
A general recommendation I would give is to not try to do "pure" MVVM ; I often read things like "there should be ZERO code-behind"...etc., I disagree.
I'm currently separating out my project into a Model assembly/project,
ViewModel assembly/project and View assembly/project. Should this go
into a different, "Core" assembly?
Separating views and ViewModels into different assemblies is the best thing you can do to ensure you won't ever reference something related to the views in your viewModel. You'll be fine with this strong separation.
Separating Model from ViewModel using two different assemblies could be a good idea too, but it depends on what your model looks like. I personally like 3-tier architectures, so generally my model is the WCF client proxies and are indeed stored in their own assembly.
A "Core" assembly is always a good idea anyway (IMHO), but only to expose basic utility methods that can be used in all the layers of your application (such as basic extension methods....etc.).
Now for your questions about views (how to show them...etc), I would say do simple. Personally I like instantiating my ViewModels in the code-behind of my Views. I also often use events in my ViewModels so the associated view is notified it should open another view for example.
For example, the scenario you have a MainWindow that should shows a child window when the user click on a button:
// Main viewModel
public MainViewModel : ViewModelBase
{
...
// EventArgs<T> inherits from EventArgs and contains a EventArgsData property containing the T instance
public event EventHandler<EventArgs<MyPopupViewModel>> ConfirmationRequested;
...
// Called when ICommand is executed thanks to RelayCommands
public void DoSomething()
{
if (this.ConfirmationRequested != null)
{
var vm = new MyPopupViewModel
{
// Initializes property of "child" viewmodel depending
// on the current viewModel state
};
this.ConfirmationRequested(this, new EventArgs<MyPopupViewModel>(vm));
}
}
}
...
// Main View
public partial class MainWindow : Window
{
public public MainWindow()
{
this.InitializeComponent();
// Instantiates the viewModel here
this.ViewModel = new MainViewModel();
// Attaches event handlers
this.ViewModel.ConfirmationRequested += (sender, e) =>
{
// Shows the child Window here
// Pass the viewModel in the constructor of the Window
var myPopup = new PopupWindow(e.EventArgsData);
myPopup.Show();
};
}
public MainViewModel ViewModel { get; private set; }
}
// App.xaml, starts MainWindow by setting the StartupUri
<Application x:Class="XXX.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
...
StartupUri="Views/MainWindow.xaml">

WPF & MVVM Light- Pass object into new window

I would like to learn the most proper way to go about this: I have a Listview in my GameView that is bound to an ObservableCollection<Adventurer>. Upon double-clicking on a cell, I need a new window (or something else if anything is more appropriate) to open and display data about the correct Adventurer according to the cell. So far I haven't been able to. This is what I have so far (it's not much, but nothing I've tried has worked).
The trigger/command in my ListView in GameView.xaml
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=ShowAdvCommand}"
CommandParameter="{Binding ElementName=AdvListView,
Path=SelectedItem}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
And the command in GameViewModel.cs
ShowAdvCommand = new RelayCommand<Adventurer>(p =>
{
System.Windows.MessageBox.Show(p.Name);
});
The MessageBox is just there to confirm that Eventtocommand was working.
I essentially need a container that will take in the correct Adventurer as a parameter after double-clicking a Listview cell and allow me to display data specific to that instance. I would also prefer to stick to something MVVM-friendly.
Any advice would be greatly appreciated.
Update: I may have made a little progress:
GameViewModel:
ShowAdvCommand = new RelayCommand<Adventurer>(p =>
{
AdventurerView adv = new AdventurerView(p);
adv.Show();
});
AdventurerView:
public partial class AdventurerView : Window
{
Adventurer adv;
public AdventurerView(Adventurer adv)
{
this.adv = adv;
InitializeComponent();
}
}
Now I need to figure out how to make this work in XAML, databinding and such.
Update: ...and then I realized that this completely goes against MVVM. Does anybody have any advice?
Update: Would MVVM Light's messenger help me here? I've been tinkering with it but haven't gotten it to work.
Update: This question is still up in the air. I tried the Prism approach but there was some conflict between Prism and MVVM Light that caused more trouble than it was worth. I'm open to any ideas that are compatible with MVVM Light and the MVVM pattern in general.
Update: Also, I would like to do this in a way where multiple popups can exist concurrently, if possible.
In a similar situation, I've used MvvmLight's Messenger, and it worked really well. On double click, send a message from your viewmodel containing the entity you want to pass. Somewhere you will need to register to receive the message, depending on how you have set up your views and viewmodels to be activated.
You could register to receive the message in your MainPage.xaml, and either pass the entity straight to the view's constructor, or access the view's DataContext via an interface to pass the entity, depending on whether you're using a viewmodel in you childwindow. E.g.
AdventurerView adv = new AdventurerView();
IEntityViewModel vm = adv.DataContext as IEntityViewModel;
vm.SetCurrentEntity(entity);
adv.Show();
The IEntityViewModel might look like the following:
public interface IEntityViewModel<T> where T : class
{
void SetCurrentEntity(T entity);
}
The viewmodel would implement this interface:
public class AdventurerViewModel : IEntityViewModel<Adventurer>
{
public void SetCurrentEntity(Adventurer entity)
{
// Do what you need to with the entity - depending on your needs,
// you might keep it intact in case editing is cancelled, and just
// work on a copy.
}
}
As you've pointed out, proper MVVM wouldn't instantiate the view and pass the view model in through the constructor. You'd be better off binding the ViewModel to the View and there are many different ways of doing it.
One pattern that has emerged is a concept known as a "screen conductor". This is a top level ViewModel or controller that handles which ViewModel represents the main window. Again, many different ways to do this. For example, the ViewModel could raise a standard .net event that the Screen Conductor handles. You could use an message passing system like Caliburn.Micro's EventAggregator or MVVM Light's Messenger. I think MEFedMVVM also has an event aggregator to accomplish this as well.
Caliburn.Micro also has a WindowManager that you can pass in your ViewModel and have it automatically find the corresponding View and manage the window lifetime.
Lots of options. Find the one that works the best for you.
This is a nice case for Prism's InteractionRequest. Essentially, you have an InteractionRequest object on your ViewModel that you raise when you double click (inside your double click command). Your view has an Action on it that handles the Raised event and shows the new view. You pass a new ViewModel to that interaction and that's the DataContext for the window that'll display. Here's some good information to get you started. This is how I display all child windows in my application.

MVVM MEF WindowFormHost

I am currently trying to design an application that loads viewmodels through MEF imports.
So far so good, I navigate from viewmodel to viewmodel, having loaded each vm datatemplate through dictionaries.
Each time I navigate, I modify the content of the main contentPresenter in my Shell (MainWindow).
One of the viewmodel allows me to display a WindowFormHost for an activeX control (such as acrobat reader for example). Since WindowFormHost does not allow binding, I created the windowFormHost in the viewmodel and binded it to a ContentPresenter in the view.
And here is where it fails : when coming back to the same viewmodel, the view is created again... throwing a “Element is already the child of another element.” error.
How can I prevent that ? Should I unload WindowFormHost when view is reloaded ? Or Can I keep view instances so that I keep only one instance for each view and let data binding update controls ? (It looks better for memory consumption).
Thanks for your help !
[EDIT]
Loaded dictionary :
<DataTemplate x:Shared="False" DataType="{x:Type vm:DAVPDC3DVIAControlViewModel}">
<vw:MyUserControl />
</DataTemplate>
View :
<DockPanel>
<ContentControl Name="WFH3DVia" Content="{Binding Path=Control3DVIA, Mode=OneWay} </ContentControl>"
<!--<WindowsFormsHost Name="WFH3DVia"></WindowsFormsHost>-->
</DockPanel>
VM (singleton, mef module) :
[Export(typeof(IDAVPDC3DVIAControl))]
public partial class DAVPDC3DVIAControlViewModel : ViewModelBase, IViewModel, IPartImportsSatisfiedNotification
VM (main window)
[Export]
public class MainWindowViewModel : ViewModelBase, IPartImportsSatisfiedNotification
// CurrentUC binds main widow view to controller active viewmodel
public IViewModel CurrentUC
{
get
{
return myAddinManager.CurrentVM;
}
}
Main view :
Controler (displays module on event) :
private void ModuleReadyEventAction(string iModuleName)
{
if (null != this.Modules && this.Modules.Count() > 0)
{
foreach (var item in Modules)
{
IBaseModule ibasemodule = item as IBaseModule;
if (null != ibasemodule)
{
Type tp = ibasemodule.GetType();
if (0 == tp.Name.CompareTo(iModuleName))
{
CurrentVM = ibasemodule.GetViewModel();
break;
}
}
}
}
}
I'm also working on a project in WPF using Prism v4 and MVVM (except I'm using Unity). I also have at least two controls that I need to use which are Windows Forms controls that must be hosted in a WindowsFormsHost. Let me explain my thoughts on the process..
It seems to me, that you are trying to avoid any code in your View's code behind. That's the only reason I can think of that you are moving your WindowsFormsHost into your ViewModel. I think that this is fundamentally the wrong approach. The WindowsFormsHost exists for the reason of displaying a graphical Windows Forms control. Therefore, it belongs in the view!
Now, I understand the appeal of DataBindings. Trust me, I've wanted to able to DataBind many parts of my WindowForms control. Of course, to accept a WPF data binding the property must be a dependency property on a dependency object. The easiest solution, which is not unreasonable, is to simply add the code to configure your windows forms control in the code behind for your view. Adding your UI logic into your ViewModel is an actual violation of the MVVM design pattern, while adding code behind is not. (And in some cases is the best approach)
I've seen possible hacks to try to get around this limitation. Including using "proxies" which inject a databinding, or perhaps extending WindowsFormsHost and adding DependencyProperties which wrap a specific hosted control's properties, or writing classes using reflection and trying to throw in windows forms bindings. However, nothing I've seen can solve the problem completely. For example, my windows forms control can contain other graphical components, and those components would need to support binding as well.
The simplest approach is to simply synchronize your view with your viewmodel in your view's code behind. Your view model can keep the file or document that is open, filename, title, etc., but leave the display and display related controls up to the View.
Last, let me comment more directly on your question. I would need to see how you are registering your View and ViewModel with the MEF Container and how you are navigating to understand why you are receiving that error. It would seem to me that either your view or view model is getting created more than once, while the other is not. Are these registered as singleton types? Regardless, I stand by what I said about not including the WindowsFormsHost in your ViewModel.

WPF MVVM - Simple login to an application

I'm continuing to learn WPF, and focusing on MVVM at the moment and using Karl Shifflett’s "MVVM In a Box" tutorial. But have a question about sharing data between views/viewmodels and how it updates the view on the screen. p.s. I haven't covered IOC's yet.
Below is a screenshot of my MainWindow in a test application. Its split into 3 sections (views), a header, a slide panel with buttons, and the remainder as the main view of the application. The purpose of the application is simple, login to the application. On a successful login, the login view should disappear by it being replaced by a new view (i.e. OverviewScreenView), and relevant buttons on the slide of the application should become visible.
I see the application as having 2 ViewModels. One for the MainWindowView and one for the LoginView, given the MainWindow doesn't need to have commands for Login so i kept it separate.
As i haven't covered IOC's yet, I created a LoginModel class which is a singleton. It only contains one property which is "public bool LoggedIn", and an event called UserLoggedIn.
The MainWindowViewModel constructor registers to the event UserLoggedIn. Now in the LoginView , when a user clicks Login on the LoginView, it raises a command on the LoginViewModel, which in turn if a username and password is correctly entered will call the LoginModel and set LoggedIn to true. This causes the UserLoggedIn event to fire, which is handled in the MainWindowViewModel to cause the view to hide the LoginView and replace it with a different view i.e. an overview screen.
Questions
Q1. Obvious question, is logging in like this a correct use of MVVM. i.e. Flow of control is as follows. LoginView --> LoginViewViewModel --> LoginModel --> MainWindowViewModel --> MainWindowView.
Q2. Assuming the user has logged in, and the MainWindowViewModel has handled the event. How would you go about creating a new View and putting it where the LoginView was, equally how do you go about disposing of the LoginView once it is not needed. Would there be a property in the MainWindowViewModel like "UserControl currentControl", which gets set to LoginView or a OverviewScreenView.
Q3. Should the MainWindow have a LoginView set in the visual studio designer. Or should it be left blank, and programatically it realises that no one is logged in, so once the MainWindow is loaded, then it creates a LoginView and shows it on the screen.
Some code samples below if it helps with answering questions
XAML for the MainWindow
<Window x:Class="WpfApplication1.MainWindow"
xmlns:local="clr-namespace:WpfApplication1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="372" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<local:HeaderView Grid.ColumnSpan="2" />
<local:ButtonsView Grid.Row="1" Margin="6,6,3,6" />
<local:LoginView Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Window>
MainWindowViewModel
using System;
using System.Windows.Controls;
using WpfApplication1.Infrastructure;
namespace WpfApplication1
{
public class MainWindowViewModel : ObservableObject
{
LoginModel _loginModel = LoginModel.GetInstance();
private UserControl _currentControl;
public MainWindowViewModel()
{
_loginModel.UserLoggedIn += _loginModel_UserLoggedIn;
_loginModel.UserLoggedOut += _loginModel_UserLoggedOut;
}
void _loginModel_UserLoggedOut(object sender, EventArgs e)
{
throw new NotImplementedException();
}
void _loginModel_UserLoggedIn(object sender, EventArgs e)
{
throw new NotImplementedException();
}
}
}
LoginViewViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Input;
using WpfApplication1.Infrastructure;
namespace WpfApplication1
{
public class LoginViewViewModel : ObservableObject
{
#region Properties
private string _username;
public string Username
{
get { return _username; }
set
{
_username = value;
RaisePropertyChanged("Username");
}
}
#endregion
#region Commands
public ICommand LoginCommand
{
get { return new RelayCommand<PasswordBox>(LoginExecute, pb => CanLoginExecute()); }
}
#endregion //Commands
#region Command Methods
Boolean CanLoginExecute()
{
return !string.IsNullOrEmpty(_username);
}
void LoginExecute(PasswordBox passwordBox)
{
string value = passwordBox.Password;
if (!CanLoginExecute()) return;
if (_username == "username" && value == "password")
{
LoginModel.GetInstance().LoggedIn = true;
}
}
#endregion
}
}
Holy long question, Batman!
Q1:
The process would work, I don't know about using the LoginModel to talk to the MainWindowViewModel however.
You could try something like LoginView -> LoginViewModel -> [SecurityContextSingleton || LoginManagerSingleton] -> MainWindowView
I know that singleton's are considered anti-patterns by some, but I find this to be easiest for situations like these. This way, the singleton class can implement the INotifyPropertyChanged interface and raise events whenever a login\out event is detected.
Implement the LoginCommand on either the LoginViewModel or the Singleton (Personally, I would probably implement this on the ViewModel to add a degree of separation between the ViewModel's and the "back-end" utility classes). This login command would call a method on the singleton to perform the login.
Q2:
In these cases, I typically have (yet another) singleton class to act as the PageManager or ViewModelManager. This class is responsible for creating, disposing and holding references to the Top-level pages or the CurrentPage (in a single-page only situation).
My ViewModelBase class also has a property to hold the current instance of the UserControl that is displaying my class, this is so I can hook the Loaded and Unloaded events. This provides me the ability to have virtual OnLoaded(), OnDisplayed() and OnClosed() methods that can be defined in the ViewModel so the page can perform loading and unloading actions.
As the MainWindowView is displaying the ViewModelManager.CurrentPage instance, once this instance changes, the Unloaded event fires, my page's Dispose method is called, and eventually GC comes in and tidy's up the rest.
Q3:
I'm not sure if I understand this one, but hopefully you just mean "Display login page when user not logged in", if this is the case, you could instruct your ViewModelToViewConverter to ignore any instructions when the user is not logged in (by checking the SecurityContext singleton) and instead only show the LoginView template, this is also helpful in cases where you want pages that only certain users have rights to see or use where you can check the security requirements before constructing the View, and replacing it with a security prompt.
Sorry for the long answer, hope this helps :)
Edit:
Also, you have misspelled "Management"
Edit for questions in comments
How would the LoginManagerSingleton talk directly to the
MainWindowView. Shouldn't everything go through the
MainWindowViewModel so that there is no code behind on the
MainWindowView
Sorry, to clarify - I don't mean the LoginManager interacts directly with the MainWindowView (as this should be just-a-view), but rather that the LoginManager just sets a CurrentUser property in response to the call that the LoginCommand makes, which in turn raises the PropertyChanged event and the MainWindowView (which is listening for changes) reacts accordingly.
The LoginManager could then call PageManager.Open(new OverviewScreen()) (or PageManager.Open("overview.screen") when you have IOC implemented) for example to redirect the user to the default screen users see once logged in.
The LoginManager is essentially the last step of the actual login process and the View just reflects this as appropriate.
Also, in typing this, it has occurred to me that rather than having a LoginManager singleton, all this could be housed in the PageManager class. Just have a Login(string, string) method, which sets the CurrentUser on successful log in.
I understand the idea of a PageManagerView, basically through a PageManagerViewModel
I wouldn't design PageManager to be of View-ViewModel design, just an ordinary house-hold singleton that implements INotifyPropertyChanged should do the trick, this way the MainWindowView can react to the changing of the CurrentPage property.
Is ViewModelBase an abstract class you created?
Yes. I use this class as the base class of all my ViewModel's.
This class contains
Properties that are used on all pages such as Title, PageKey and
OverriddenUserContext.
Common virtual methods such as PageLoaded, PageDisplayed, PageSaved and PageClosed
Implements INPC and exposes a protected OnPropertyChanged method to use to raise the PropertyChanged event
And provides skeleton commands to interact with the page such as ClosePageCommand, SavePageCommand etc.
When a logged in detected, CurrentControl is set to a new View
Personally, I would only hold the instance of the ViewModelBase that is currently being displayed. This is then referenced by the MainWindowView in a ContentControl like so: Content="{Binding Source={x:Static vm:PageManager.Current}, Path=CurrentPage}".
I also then use a converter to transform the ViewModelBase instance in to a UserControl, but this is purely optional; You could just rely on ResourceDictionary entries, but this method also allows the developer to intercept the call and display a SecurityPage or ErrorPage if required.
Then when the application starts it detects no one is logged in, and
thus creates a LoginView and sets that to be the CurrentControl.
Rather than harding it that the LoginView is displayed by default
You could design the application so that the first page that is displayed to the user is an instance of the OverviewScreen. Which, since the PageManager currently has a null CurrentUser property, the ViewModelToViewConverter would intercept this and the rather than display the OverviewScreenView UserControl, it would instead show the LoginView UserControl.
If and when the user successfully logs in, the LoginViewModel would instruct the PageManager to redirect to the original OverviewScreen instance, this time displaying correctly as the CurrentUser property is non-null.
How do people get around this limitation as you mention as do others, singletons are bad
I'm with you on this one, I like me a good singleton. However, the use of these should be limited to be used only where necessary. But they do have perfectly valid uses in my opinion, not sure if any one else wants to chime in on this matter though?
Edit 2:
Do you use a publicly available framework/set of classes for MVVM
No, I'm using a framework that I have created and refined over the last twelve months or so. The framework still follows most the MVVM guidelines, but includes some personal touches that reduces the amount of overall code required to be written.
For example, some MVVM examples out there set up their views much the same as you have; Whereas the View creates a new instance of the ViewModel inside of its ViewObject.DataContext property. This may work well for some, but doesn't allow the developer to hook certain Windows events from the ViewModel such as OnPageLoad().
OnPageLoad() in my case is called after all controls on the page have been created and have come in to view on screen, which may be instantly, within a few minutes after the constructor is called, or never at all. This is where I do most of my data loading to speed up the page loading process if that page has multiple child pages inside tabs that are not currently selected, for example.
But not only that, by creating the ViewModel in this manner increases the amount of code in each View by a minimum of three lines. This may not sound like much, but not only are these lines of code essentially the same for all views creating duplicate code, but the extra line count can add up quite quickly if you have an application that requires many Views. That, and I'm really lazy.. I didn't become a developer to type code.
What I will do in a future revision through your idea of a page
manager would be to have several views open at once like a tabcontrol,
where a page manager controls pagetabs instead of just a single
userControl. Then tabs can be selected by a separate view binded to
the page manager
In this case, the PageManager won't need to hold a direct reference to each of the open ViewModelBase classes, only those at the top-level. All other pages will be children of their parent to give you more control over the hierarchy and to allow you to trickle down Save and Close events.
If you put these in an ObservableCollection<ViewModelBase> property in the PageManager, you will only then need to create the MainWindow's TabControl so that it's ItemsSource property points to the Children property on the PageManager and have the WPF engine do the rest.
Can you expand a bit more on the ViewModelConverter
Sure, to give you an outline it would be easier to show some code.
public override object Convert(object value, SimpleConverterArguments args)
{
if (value == null)
return null;
ViewModelBase vm = value as ViewModelBase;
if (vm != null && vm.PageTemplate != null)
return vm.PageTemplate;
System.Windows.Controls.UserControl template = GetTemplateFromObject(value);
if (vm != null)
vm.PageTemplate = template;
if (template != null)
template.DataContext = value;
return template;
}
Reading through this code in sections it reads:
If value is null, return. Simple null reference check.
If the value is a ViewModelBase, and that page has already been loaded, just return that View. If you don't do this, you will be creating a new View each time the page is displayed and will cause some unexpected behaviour.
Get the page template UserControl (shown below)
Set the PageTemplate property so this instance can be hooked, and so we don't load a new instance on each pass.
Set the View DataContext to the ViewModel instance, these two lines completely replace those three lines I was talking about earlier from every view from this point on.
return the template. This will then be displayed in a ContentPresenter for the user to see.
public static System.Windows.Controls.UserControl GetTemplateFromObject(object o)
{
System.Windows.Controls.UserControl template = null;
try
{
ViewModelBase vm = o as ViewModelBase;
if (vm != null && !vm.CanUserLoad())
return new View.Core.SystemPages.SecurityPrompt(o);
Type t = convertViewModelTypeToViewType(o.GetType());
if (t != null)
template = Activator.CreateInstance(t) as System.Windows.Controls.UserControl;
if (template == null)
{
if (o is SearchablePage)
template = new View.Core.Pages.Generated.ViewList();
else if (o is MaintenancePage)
template = new View.Core.Pages.Generated.MaintenancePage(((MaintenancePage)o).EditingObject);
}
if (template == null)
throw new InvalidOperationException(string.Format("Could not generate PageTemplate object for '{0}'", vm != null && !string.IsNullOrEmpty(vm.PageKey) ? vm.PageKey : o.GetType().FullName));
}
catch (Exception ex)
{
BugReporter.ReportBug(ex);
template = new View.Core.SystemPages.ErrorPage(ex);
}
return template;
}
This is the code in the converter that does most of the grunt work, reading through the sections you can see:
Main try..catch block used to catch any class construction errors including,
Page does not exist,
Run-time error in constructor code,
And fatal errors in XAML.
convertViewModelTypeToViewType() just tries to find the View that corresponds to the ViewModel and returns the type code that it thinks it should be (this may be null).
If this is not null, create a new instance of the type.
If we fail to find a View to use, try to create the default page for that ViewModel type. I have a few additional ViewModel base classes that inherit from ViewModelBase that provide a separation of duties between the types of page's they are.
For example, a SearchablePage class will simply display a list of all objects in the system of a certain type and provide the Add, Edit, Refresh and Filter commands.
A MaintenancePage will retrieve the full object from the database, dynamically generate and position the controls for the fields that the object exposes, creates children pages based on any collection the object has and provides the Save and Delete commands to use.
If we still don't have a template to use, throw an error so the developer knows something went wrong.
In the catch block, any run-time error that occurs are displayed to the user in a friendly ErrorPage.
This all allows me to focus on only creating ViewModel classes as the application will simple display the default pages unless the View pages have been explicitly overridden by the developer for that ViewModel.

Categories