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.
Related
I am developing an Application using Prism's Module.
Modules are loaded from the directory at runtime.
An application has multiple views.
Each view can call the same module, and has region to display modules view.
Each module subscribes event using EventAggregator when the ctor is called, and it is used when the application calls the module from the view.
And only one view is displayed at a time.
The current problem is that multiple Views each create an instance of the Module and subscribe to the same Event in the ctor.
As a result, when the module is called, one event is processed multiple times.
I want the event to be processed only once.
I thought of the two methods below, but I don't know what to do because the module is dynamically loaded in prism.
Using one module instance in multiple views.
Identifying the module by passing an integer id as a parameter when creating each module instance.
Thanks
I'm new of prism.
Please let me know if there is a better way or it is bad way.
The problem is each call of regionManager.RegisterViewWithRegion make ViewModel.
But I need multiple regions and only one ViewModel.
So, I coded to create only one ViewModel in the module class inheriting from IModule.
containerRegistry.RegisterInstance<MyModuleViewViewModel>(_viewModel) this will block make ViewModel from module's view
And make module's ctor because ViewModel need EventAggregator.
Here is my code.
public class MyModule : IModule
{
private MyModuleViewViewModel _viewModel;
public MyModule(IEventAggregator eventAggregator)
{
_viewModel = new MyModuleViewViewModel(eventAggregator);
}
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("ContentRegion1", typeof(MyModuleView));
regionManager.RegisterViewWithRegion("ContentRegion2", typeof(MyModuleView));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterInstance<MyModuleViewViewModel>(_viewModel);
containerRegistry.RegisterForNavigation<MyModuleView, MyModuleViewViewModel>();
}
}
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.
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...