I have started learning MVVM for a project I'm writing, and I'm sketching out some of the more complicated parts of the project beforehand to help me get a better handle on how MVVM works. One of the biggest things I'm having trouble with though is dialogs, specifically custom dialogs and message boxes. Right now, I have a list of objects, and to add a new one, a button is pressed. This button calls a command in my ViewModel which invokes a Func that returns the object I want (Pile), then adds that to the list.
Here's that function
private void OnAdd()
{
Pile? pile = GetPileToAdd?.Invoke();
if (pile is null) return;
Piles.Add(pile);
}
This function is set in the view when the data context gets set (I'm implementing a Model-First architecture)
private void PileScreenView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is PileScreenViewModel psvm)
{
psvm.GetPileToAdd = () =>
{
MessageBox.Show("getting pile");
return new Pile() { Name = "Name", Length = 0 };
};
}
}
The Messagebox.Show call will eventually get replaced with a custom dialog that will provide the data needed. My question then is:
Is this MVVM compliant? It feels a bit gross having to wait until the DataContext is changed to add the method to it, but I'm 99% sure that having the messagebox call in the ViewModel is a big no-no. Also not sure if I'm allowed to interact with the Model like this from the View.
Thanks for the help and for helping me with my MVVM journey :)
Your gut feeling is absolutely right: dialogs are components of the View as they interact with the user as part of the UI. Therefore, dialogs of any kind must be handled in the View.
Your current problem is that your View class PileScreenView depends on a particular View Model instance in order to register the callback.
Note that the callback itself violates MVVM as it delegates dialog handling to the View Model. The View Model must never execute or actively participate in any UI logic.
You can improve your design by making the dependency on the DataContext anonymous and independent (of the particular instance). This will also eliminate the need to observe DataContext changes.
Then move the dialog trigger to the View. Simply let the PileScreenView expose an "Add New Pile" button to the user.
This button will trigger the PileScreenView to show the dialog. Ideally, you would create a dedicated dialog view model class, that will hold the input data of the dialog.
Then use the dialog result (e.g., the dialog view model) and call e.g. CreatePile method on the PileScreenViewModel in order to pass the dialog result to the View Model.
The View Model can then create the actual Pile from the dialog view model class.
It's best if the View only knows View Model types by their interface:
IPileScreenViewModel.cs
interface IPileScreenViewModel : INotifyPropertyChanged
{
// Create the Pile in the View Model.
// View Model should never wait for anything.
// It is invoked by the View after the required data is collected.
void CreatePile(CreatePileViewDialogModel newPileInfo);
}
Then in the View you can show the dialog e.g. on click of a corresponding button. Dialogs an their logic should be generally designed to be triggered by the UI i.e. the user:
PileScreenView.xaml.cs
partial class PileScreenView : UserControl
{
private void OnCreatePileButtonClicked(object sender, RoutedEventArgs e)
{
var dialogViewModel = new CreatePileViewDialogModel();
var createPileDialog = new CreatePileDialog() { DataContext = dialogViewModel };
createPileDialog.ShowDialog();
(this.DataContext as IPileScreenViewModel)?.CreatePile(dialogViewModel);
}
}
The above example is a very simple to demonstrate how the interaction and data flow could look like. There are the usual options to send the data to the View Model, like data binding.
For example, PileScreenView could expose a dependency property e.g., CreatedPileScreenInfo. The PileScreenView would then assign the dialog result to this property, to which the PileScreenViewModel can bind to. This way the DataContext is completely unimportant for the PileScreenView.
Very important note: your current event handler introduces a potential memory leak. Since you are registering a lambda expression as event callback, you will not be able to unregister the handler. There is a chance that the garbage collector won't be able to collect the PileScreenView instance. The old PileScreenViewModel can keep it alive. You must therefore always take care to unregister event handlers. This should be your general practice to help you to guard against accidental leaks of this kind:
private void PileScreenView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// Unregister handler from old view model instance
if (e.oldValue is PileScreenViewModel oldPsvm)
{
oldPsvm.GetPileToAdd -= OnGetPileToAdd;
}
if (e.NewValue is PileScreenViewModel newPsvm)
{
newPsvm.GetPileToAdd += OnGetPileToAdd;
}
}
private void OnGetPileToAdd(object sender, EventArgs e)
{
// TODO::Handle view model event
}
I suggest implementing a dialog service and injecting it into the view-model.
interface IDialogService
{
Pile? ShowPileDialog(/* any arguments that will be needed to show the dialog */);
}
class MessageBoxDialogService : IDialogService
{
public Pile? ShowPileDialog(/* arguments */)
{
MessageBox.Show("getting pile");
return new Pile() { Name = "Name", Length = 0 };
}
}
You need to register MessageBoxDialogService as the implementation of IDialogService in your DI container.
In the view-model:
class PileScreenViewModel
{
private IDialogService _dialogService;
public PileScriptViewModel(IDialogService dialogService)
{
_dialogService = dialogService;
}
private void OnAdd()
{
Pile? pile = _dialogService.GetPile(/* pass parameters from the view model */);
if (pile is null) return;
Piles.Add(pile);
}
}
It doesn't go against the principles of MVVM. As the view-model is decoupled from the view. You can inject a fake dialog service to test the view-model in isolation. By doing so, your code will also respect the open-closed principle, i.e., if the day comes and you decide to implement your custom dialog you only need to create a new dialog service class without needing to change existing classes.
Related
This is a question regarding dependency injection (DI) using Lamar, with windows forms (C#) using the model-view-presenter (MVP) pattern and how (and when) to initialize presenter objects.
I have a solution split up into three projects:
Infrastructure
Domain
Presentation
In the presentation project, I have my forms and user controls, which are separated using the MVP-pattern.
In the Program.cs file for the presentation project, I define my container using Lamar and create my main view as:
var container = new Container(x =>
{
x.AddSingleton<IInterfaceFromDomainProject, ClassFromDomainProject>();
x.AddSingleton<IMainView, MainView>();
x.AddSingleton<IMainPresenter, MainPresenter>();
x.AddSingleton<ISubView, SubView>();
x.AddSingleton<ISubPresenter, SubPresenter>();
});
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var mainView = new MainView();
Application.Run(mainView);
This will resolve any dependencies my presenters and views may have.
For the MVP-pattern I am using the observing presenter style, which is explained here. I like this specific style because it completely decouples knowledge of the presenters from the views. An example of this can be found here.
This means that the constructors for my views (forms and user controls) doesn't take any paramaters. This allows me to drag and drop user controls into my forms when designing it.
For example, if my MainView (which is a form) has a tab control in it, I can drag and drop the SubView (which is an user control) into a tab page of the tab control.
All the logic (what data to present etc.) are handled in the presenters. This means that my presenters constructors takes interfaces from the domain project as parameters, as well as the interface of the concrete view.
My main view:
public interface IMainView
{
event EventHandler MyCustomEvent;
void ShowMessage();
}
public partial class MainView : Form, IMainView
{
public event EventHandler MyCustomEvent;
public MainView()
{
InitializeComponent();
}
private void button_Click(object sender, EventArgs e)
{
MyCustomEvent.Invoke(sender, EventArgs.Empty);
}
public void ShowMessage()
{
MessageBox.Show("Hello!");
}
}
My main presenter:
public interface IMainPresenter
{
void ShowMessageHandler(object sender, EventArgs e);
void ShowData();
}
public class MainPresenter: IMainPresenter
{
private readonly IMainView _view;
private readonly IInterfaceFromDomainProject _foo;
public MainPresenter(IMainView view, IInterfaceFromDomainProject foo)
{
_view = view;
_foo = foo;
_view.MyCustomEvent += ShowMessageHandler;
}
public void ShowMessageHandler(object sender, EventArgs e)
{
_view.ShowMessage();
}
public void ShowData()
{
// Do something with _foo. Get data and display it in its view.
}
}
From the previous link:
The presenter doesn't have any methods that the view can call, but the view has events that the presenter can subscribe to.
The presenter knows its view. This is accomplished using constructor injection.
The view has no idea what presenter is controlling it.
Question
Based on this implementation of MVP and DI, how and when do I create my presenters? They are dependent on interfaces from the domain project, which is why they are used in the lamar-container. Am I supposed to call var mainPresenter = new MainPresenter(container.GetRequiredService<IMainView>(), /* get service for all required interfaces*/); for all my presenters in Program.cs?
Am I misunderstanding something regarding DI or the MVP pattern?
Edit
The var mainPresenter = new MainPresenter(container.GetRequiredService<IMainView>() /* get service for all required interfaces*/); doesn't work and i get a NullReferenceException: 'Object reference not set to an instance of an object.' on the MyCustomEvent.Invoke(sender, EventArgs.Empty); (I know I should use ?.).
The only way to do it is to call:
var mainView = new MainView();
var mainPresenter = new MainPresenter(mainView);
I have seen other implementations of MVP, where the presenter is created in the constructor for the concrete view, but how do i pass the necessary interfaces to the presenter?
E.g.:
public partial class MainView : Form, IMainView
{
public MainView()
{
InitializeComponent();
var presenter = new MainPresenter(this, /* How to pass interfaces here? */)
}
}
Your question is very broad and not easy to answer in such a post. You are asking about overall UI architecture, probably because you want to modularize your user interface. This is generally difficult to achieve with Winforms as, by its core design, everything is attached to the main window, and views can only hardly be decoupled from components such as presenters or view models.
If you want to achieve modularity of your UI, I would recommend you to switch technology to Windows Presentation Framework (WPF), which has a much better, modular, architecture and supports MVP, MVVM, event tunneling and much more.
If you want to study how to build modular applications, a good starting point might be the prism library, which highlights most of the concepts. You will also find answers on how to use dependency injection, how to create presenters, etc. Please have a look here: https://prismlibrary.com/docs/wpf/view-composition.html
My history:
I'm developing a WPF application, which will run in full screen on the touch screen. Navigation in my application can be done only by clicking a button on each page ("back" or "logout").
This is not a Universal App, but it looks like.
Assumptions of the project:
Application will run on full screen mode in Windows 7 on the touch screen.
I'm using Caliburn.Micro MVVM framework.
Problem and question:
I've got 1 window and 3 UserControl (and ViewModels) Concept art
Window ShellView
UserControl LoginView
UserControl OrdersView
UserControl OrderDetailView
When application starting, i'm set LoginView as default and load it by using CM Conductor ActivateItem method, but i don't know how to set another View from UserControl like LoginView
I have read: this question but this doesn't cover my case
and this answer but it's to hard to understand for me.
My ideas:
make static method in ShellViewModel like:
ShellViewModel
public static void setOrdersView() {
ActivateItem(new OrdersViewModel());
// Error : An object reference is required for the non-static field, method, or property 'Caliburn.Micro.ConductorBase<object>.ActivateItem(object)
}
ShellViewModel.setOrdersView();
make listener in ShellViewModel and send event from child ViewModel ( but now i don't know how to achieve it)
Question: What is the best way to handle navigation in this case?
Application architecture:
ShellView
<Window>
<ContentControl x:Name="ActiveItem" />
</Window>
ShellViewModel
public class ShellViewModel : Conductor<object>, IShell
{
public ShellViewModel()
{
LoadDefault();
}
public void LoadDefault()
{
ActivateItem(new LoginViewModel());
}
}
LoginView
<UserControl>
<Button x:Name="Login" />
</UserControl>
LoginViewModel
public class LoginViewModel : PropertyChangedBase
{
public void Login() {
if (LoginManager.Login("User", "Password")) {
// How to redirect user to OrdersView?
}
}
}
I have similar applications with one shell window and many activated views inside and some dialog windows.
You should use EventAggregator pattern for these needs and Caliburn already has implementation.
How to Achieve:
Minimum Shell signature
public class ShellViewModel : Conductor<object>,
IHandle<ChangePageMessage>,
IHandle<OpenWindowMessage>
You need two fields inside (second one is for dialogs):
public IEventAggregator EventAggregator { get; private set; }
public IWindowManager WindowManager { get; private set; }
I've set single instances of that objects through IoC. You can also define them as Singletons.
EventAggregator needs subscription on object where IHandles are implemented.
EventAggregator.Subscribe(this); //You should Unsubscribe when message handling is no longer needed
Handlers implementation:
public void Handle(ChangePageMessage message) {
var instance = IoC.GetInstance(message.ViewModelType, null);//Or just create viewModel by type
ActivateItem(instance);
}
public void Handle(OpenWindowMessage message) {
var instance = IoC.GetInstance(message.ViewModelType, null);//Or just create viewModel by type
WindowManager.ShowWindow(instance);
}
Messages for event aggregator can be only marker classes but sometimes it is useful to pass more parameters like ours OpenWindowMessage and ChangePageMessage classes - they are absolutely similar by content, so for example:
public class OpenWindowMessage {
public readonly Type ViewModelType;
public OpenWindowMessage(Type viewModelType) {
ViewModelType = viewModelType;
}
}
All your viewModels can also subscribe to EventAggregator instance and Handle some messages for communication, or even for initial parameters. I have something like MyViewModelInitMessage classes almost for every viewModel, and just use two publish methods together.
EventAggregator.Publish(new ChangePageMessage(typeof(MyViewModel)));
EventAggregator.Publish(new MyViewModelInitMessage("...all needed parameters"));
So when I publish those two - mine ViewModel will be activated, then it subscribes to EventAggregator(don't forget to do that or second message handling never occurs), and will handle its InitMessage right after.
Now with EventAggregator you can send messages between all ViewModels that are currently subscribed to it.
That seems pretty common solution.
While watching a video about MVVM on Pluralsight there was a situation where the MVVM pattern got violated but no correct way of doing it was shown:
The view had a button that uses ICommand to trigger a handler in the ViewModel.
The handler correctly relayed the execution to a repository implementation.
The concrete implementation of the repository called a web service method.
However: if the webservice call failed, the ViewModel would bring up a message box that informs the user about the error.
As the ViewModel is an abstraction of the View, it should not directly create UI, but what is the 100% clean way to get that message box presented to the user?
Create a service:
interface IDialogService
{
void ShowMessageBox(string message);
}
Implement it:
class DialogService : IDialogService
{
public void ShowMessageBox(string message)
{
MessageBox.Show(); // ...
}
}
Use dependency injection:
class ViewModel
{
[Import] // This is MEF-specific sample
private readonly IDialogService dialogService;
}
or service location:
class ViewModel
{
private AnyCommandExecute()
{
// This is MEF-specific sample
var dialogService = container.GetExportedValue<IDialogService>();
}
}
to obtain a concrete IDialogService in your view model, then call the obtained implementation from ViewModel.
The same approach is applicable for any other similar cases: show open/save dialog, show your custom view model in dialog.
There are several ways to do this that adhere to the MVVM pattern, such as Interaction Service and Interaction Request.
Interaction Service
... a service that can be used by the view model to initiate interaction
with the user, thereby preserving its independence on the view's
implementation
Interaction Request
... uses events raised by the view model to express the intent to
interact with the user, along with components in the view that are
bound to these events and that manage the visual aspects of the
interaction.
Source
Both quotes above are from this source (which also contains more detailed information about the patterns): http://msdn.microsoft.com/en-us/library/gg405494(v=pandp.40).aspx#sec10
I am developing a WPF 4.0 - MVVM application based on PRISM framework (Unity Container).
I was wondering what is the best way to implement dialogs in the mvvm pattern.
I am planning to use quite a few in my application so I want something reusable.
Since you are using Prism/Unity implement the mediator pattern for your View Models.
Add a DialogService (IDialogService) module to your project.
Modules containing dialogs register them with the IDialogService.
Don't forget to declare DialogServiceModule as a ModuleDependency.
ViewModels now use the IDialogService to show the required dialog.
public interface IDialogService
{
void RegisterDialog (string dialogID, Type type);
bool? ShowDialog (string dialogID);
}
public class DialogService : IDialogService
{
private IUnityContainer m_unityContainer;
private DialogServiceRegistry m_dialogServiceRegistry;
public DialogService(IUnityContainer unityContainer)
{
m_unityContainer = unityContainer;
m_dialogServiceRegistry = new DialogServiceRegistry();
}
public void RegisterDialog(string dialogID, Type type)
{
m_dialogServiceRegistry.RegisterDialog(dialogID, type);
}
public bool? ShowDialog(string dialogID)
{
Type type = m_dialogServiceRegistry[dialogID];
Window window = m_unityContainer.Resolve(type) as Window;
bool? dialogResult = window.ShowDialog();
return dialogResult;
}
}
If you use ViewModel events & handlers in the View, use the WeakEventHandler pattern to eliminate a potential resource leak.
Also, it is possible for multiple Views to be attached to the same ViewModel.
I've worked on projects with one ViewModel -> one View. But also one ViewModel -> multiple Views.
Just something to consider when making your design decisions.
This article about dialogs with MVVM you might find useful: http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern
I let the ViewModel raise events when it needs to get user information. It is then up to the View how to supply it. This does mean that the code behind file will get Event handlers though, something real MVVM adepts will shudder at...
I'm prototyping a WPF application making use of the MVVM pattern. The application shall have two windows: MainWindow and LoginWindow.
The Model contains two properties: Username and Password.
The LoginWindow is responsible for handling the username and password entered by the user, so the corresponding view model updates these properties. However, the MainWindow also needs access to the username and password for later usage with a client object.
How should I handle this?
Passing the instance of the Model created in the LoginViewModel to the MainWindowViewModel's constructor?
What you need is a Messenger/Event Aggregator. An event aggregator is a broker object that you can take a reference to and specify what type of events you want to receive, without having to take a reference or even be aware of the objects generating the events.
Prism's EventAggregator is the most common one. See: Event Aggregator
So:
ViewModel 1:
public ViewModel1(IEventAggregator eventAggregator)
{
_eventAggregator=eventAggregator;
}
private void SendMessage()
{
_eventAggregator.GetEvent<UserLogin>().Publish(new UserLogin(_userName,_password);
}
ViewModel 2:
public ViewModel2(IEventAggregator eventAggregator)
{
_eventAggregator=eventAggregator;
_eventAggregator.GetEvent<UserLogin>().Subscribe(UserLoginReceived,ThreadOption.BackgroundThread,true);
}
private void UserLoginReceived(UserLogin login)
{
//do what you like here
}
What's happening is that the eventaggregator is passed to both the viewmodels. ViewModel1 publishes a message but doesn't know who (if anyone) is listening to it. ViewModel2 has subscribed to the event and is listening for a publisher to send it a message.
Using this approach you can have your viewmodels communicate without them taking references out on each other.