WPF Caliburn.Micro - Best way to navigate in Single Window Application - c#

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.

Related

When and how to create presenters using windows forms with MVP and dependency injection

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

AvaloniaUI - What is the proper way to inject ViewModels into Views using composition-root based DI system?

I am new to Avalonia/ WPF, Xaml and desktop development in general so please forgive and clarify any related misunderstandings I demonstrate. I will continue to study available documentation but I am having a difficult time finding material which addresses the point I am getting stuck on.
I am trying to implement a composition-root, constructor-injection based dependency-injection system in my Avalonia application, using the recommended MVVM pattern and associated Avalonia project template. I have some familiarity with the Microsoft.Extensions.DependencyInjection package so have been trying to work with this system.
Between tutorials for WPF and Avalonia based on this DI framework as well as other frameworks, I have tried to piece together a working solution. I think I have things figured out conceptually as far as registering Services and ViewModels and setting up constructors for these classes appropriately such that the framework will inject dependencies into these classes on instantiation. However, where I am getting stuck is with how to implement constructor injection for View classes.
I attempted to register both MainWindow and MainWindowViewModel as services:
// App.axaml.cs
public partial class App : Application
{
private IServiceProvider _services;
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
ConfigureServiceProvider();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = _services.GetService<MainWindow>();
}
base.OnFrameworkInitializationCompleted();
}
private void ConfigureServiceProvider()
{
var services = ConfigureServices();
_services = services.BuildServiceProvider();
}
private static IServiceCollection ConfigureServices()
{
var services = new ServiceCollection();
services.AddTransient<MainWindow>();
services.AddTransient<MainWindowViewModel>();
return services;
}
}
The goal is then to be able to inject the MainWindowViewModel class into the MainWindow class via constructor and then assign that argument to the DataContext property of the MainWindow view-class:
// MainWindow.axaml.cs
public partial class MainWindow : Window
{
public MainWindow(MainWindowViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
However, this causes the following error to be raised:
MainWindow.axaml(1, 2): [XAMLIL] Unable to find public constructor for type MyApp.Client:MyApp.Client.Views.MainWindow() Line 1, position 2.
It seems the View cannot be instantiated without the existence of a parameter-less constructor, however, this would seem to prevent constructor injection.
It is very possible I have some fundamental misunderstanding about the intended relationship between ViewModels and Views. I have come across a number of examples where ViewModels are not registered with the service-container, and instead are instantiated directly in the View constructor and assigned to the DataContext property. I would prefer to avoid this approach.
Meanwhile, every tutorial I have come across which demonstrates injecting ViewModels into corresponding View classes, does so using the Service Locator pattern, where the DI service container is passed explicitly (or invoked as a global object) and the ViewModel is resolved explicitly from the container.
Can anybody direct me to any example source code or tutorial which demonstrates how to properly inject ViewModels into Views via constructor? Is this possible to achieve? Is there something I can modify in the MainWindow.axaml file to enable the desired behavior? Thank you for your time and again, I would greatly appreciate clarification of any misunderstandings I may have.
Just for reference, here is the MainWindow markup:
// MainWindow.axaml
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyApp.Client.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MyApp.Client.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
x:CompileBindings="True"
Icon="/Assets/avalonia-logo.ico"
Title="MyApp">
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Window>
View models are associated with views via DataContext instead of constructor injection. Note that a singe view can be reusable (especially if you are dealing with virtualized lists).
In general your DI should not know about most of the view part at all, it should be only concerned with ViewModel and lower layers.
Instead of being created via DI views are usually located via view locator by other views that bind particular properties to ContentControl, e. g.
<ContentControl Content="{Binding MySubViewModel} />
(you can find a simple view locator in avalonia.mvvm template, you can tune it for your needs).
When one needs to show a new top-level view from your view model code, they usually implement some kind of a window manager that manages top-level views and is accessible from the view model via DI, e. g.
public class ViewManager : IViewManager
{
private Window CreateWindowForModel(object model)
{
foreach (var template in Application.Current.DataTemplates)
{
if (template.Match(model))
{
var control = template.Build(model);
if (control is Window w)
return w;
return new Window { Content = control };
}
}
throw new KeyNotFoundException("Unable to find view for model: " + model);
}
public void ShowWindow(object model) => CreateWindowForModel(model).Show();
}
Then you add IViewManager implementation to your DI.
Note that this approach is reusable for all XAML frameworks and makes it possible to completely reuse the view model between various platforms (e. g. if you want to implement mobile UI with Xamarin and desktop with Avalonia) with only a few UI-toolkit specific services.

Best way to enable/disable controls on different Views depending on a state in the MainViewModel

I have a application that I am developing. It can be in two states (Connected and Disconnected). There is a boolean property in my MainViewModel that keeps track of the current state.
I have many other Views (and ViewModels) in my application. When the application goes into a Disconnected state I need to make several controls (not all) in each view disabled. When the applicaiton is in the connected state then obviously I need to enable those same controls.
I am wondering what would be a good way to accomplish this?
I guess you have only one instance of your MainViewModel.
So expose this unique instance through a static property, and even make it a singleton.
This way you can easily share your connection status between views.
using System.ComponentModel;
namespace WpfMagic
{
public class MainViewModel : INotifyPropertyChanged
{
private static readonly MainViewModel instance = new MainViewModel();
public static MainViewModel Instance { get { return instance; } }
private bool isConnected;
public bool IsConnected
{
get { return isConnected; }
set
{
if (value != isConnected)
{
isConnected = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsConnected"));
}
}
}
private MainViewModel()
{
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
The tricky part is the static binding, but otherwise it's simple:
Your first view:
<Button IsEnabled="{Binding Path=(local:MainViewModel.Instance).IsConnected}">Send Spams</Button>
Another one:
<Button IsEnabled="{Binding Path=(local:MainViewModel.Instance).IsConnected}">DDOS SO</Button>
And a last one:
<Button IsEnabled="{Binding Path=(local:MainViewModel.Instance).IsConnected}">Open Lol Cats Videos</Button>
To test it you can use a CheckBox in yet another view:
<CheckBox IsChecked="{Binding Path=(local:MainViewModel.Instance).IsConnected}">Is Connected?</CheckBox>
You could use ViewModelLocator and have a singleton MainViewModel within there. This will allow you to access the MainVM from any ViewModel. From there directly access the bool property needed.
Hope this helps.
ViewModelLocator IOC Containers, MVVM ViewModelLocator
Viewmodel locator pattern is one option, and not a bad one.
I would recommend installing mvvm light from nuget as that plugs in a template for you.
Another alternative is to use the messaging framework of mvvm light. In this scenario you're main view model issues a message when your state changes which your other view models, assumed to be bound to your views, can subscribe to.
This has the advantage that each view model can respond to that behaviour in whatever way. For those just requiring the bool for binding, create a base view model which registers to the message and updates a property. For other view models you might want to alter can execute command bindings or update labels etc.

How to implement dialog architecture in MVVM

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...

How can I use identical model data in two different view models?

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.

Categories