How to Form.ShowDialog() using MVP and Passive Views? - c#

Summary
I'm experimenting with the MVP pattern in a Windows Forms application.
I'd like to make both my Presenters and Views platform agnostic, so if I wish to port my application to another platform, say the Web or mobile, I simply need to implement the views with platform dependant GUI, and my presenters can still be platform independent.
And now I wonder, how to ShowDialog() using MVP and Passive Views?
To my understanding so far, passive views shouldn't know/care about any presenter. They don't even know it exists. So, the solution presented in this question's answer is not suitable, according to me:
Refactoring Form.ShowDialog() code to MVP
Some code samples to help on the understanding:
ApplicationView
public partial class ApplicationForm : Form, IApplicationView {
// I ensure that all the IApplicationView events are raised
// upon button clicks text changed, etc.
// The presenter, which this view ignores the existence,
// is subscribed to the events this view raises.
}
ApplicationPresenter
public class ApplicationPresenter
: Presenter<IApplicationView>
, IApplicationPresenter {
public ApplicationPresenter(IApplicationView view) : base(view) {
View.OnViewShown += OnViewShown;
}
public void OnViewShown() {
IAuthenticaitonView authView = new AuthenticationForm();
IAuthenticationPresenter authPresenter = new AuthenticationPresenter(authView);
authPresenter.ShowDialog(); // 1.
}
}
This is where I'm struggling. The ApplicationPresenter is like the master in the universer and may be aware of the user authentication through both the IAuthenticationView and IAuthenticationPresenter.
IAuthenticationView
public interface IAuthenticationView : IDialogView {
string ErrorMessage { get; set; }
string Instance { get; set; }
IEnumerable<string> Instances { get; set; }
string Login { get; set; }
string Password {get; set; }
void EnableConnectButton(bool enabled);
event VoidEventHandler OnConnect;
event SelectionChangedEventHandler OnDatabaseInstanceChanged;
event VoidEventHandler OnLoginChanged;
event VoidEventHandler OnPasswordChanged;
}
IDialogView
public interface IDialogView : IView {
void ShowDialog();
}
IView
public interface IView {
void Show();
event VoidEventHandler OnViewInitialize;
event VoidEventHandler OnViewLoad;
event VoidEventHandler OnViewShown;
}
IAuthenticationPresenter
public interface IAuthenticationPresenter : IPresenter<IAuthenticationView> {
void OnConnect();
void OnViewDatabaseInstanceChanged(SelectionChangedEventArgs e);
void OnViewLoginChanged();
void OnViewPasswordChanged();
}
IPresenter<V>
public interface IPresenter<V> where V : IView {
V View { get; }
OnViewInitialize();
OnViewLoad();
ShowView();
}

Based on these premisses:
The presenter shall be platform agnostic
Only the view knows how to show/display itself (WPF, Mobile, Silverlight, Web, WinForms...)
The view MUST provide a way to show itself
The view doesn't have to be platform agnostic, since the display will differ from a platform to another
But the presenter shall order the view when to show itself
I came to this:
IView
public interface IView {
void OnShowView();
}
IPresenter<V>
public interface IPresenter<V>where V : IView {
void ShowView();
event VoidEventHandler OnShowView;
}
Presenter<V>
public abstract class Presenter<V> : IPresenter<V> {
public Presenter(V view) {
View = view;
OnShowView += View.OnShowView;
}
public void ShowView() { raiseShowViewEvent(); }
public event VoidEventHandler OnShowView;
private void raiseShowViewEvent() { if (OnShowView != null) OnShowView(); }
}
So, following the logic of where I struggled so far, I solved it by doing this:
ApplicationForm
public partial class ApplicationForm : Form, IApplicationView {
private void ApplicationForm_Shown(object sender, EventArgs e) { raiseOnViewShown(); }
private void raiseOnViewShownEvent() { if (OnViewShown != null) OnViewShown(); }
}
ApplicationPresenter
public void OnViewShown() {
// This method is the subscriber of the IView.OnViewShown event
// The event is raised with the ApplicationForm_Shown event.
IAuthenticationView authView = new AuthenticationForm();
IAuthenticationPresenter authPresenter = new AuthenticationPresenter(authView);
authPresenter.ShowView(); // 1.
}
This raises the OnShowView event which the IAuthenticationView has subscribed. Then, back in the form, the view's response to the event is:
AuthenticationForm
public partial class AuthenticationForm : Form, IAuthenticationView {
public void OnShowView() { ShowDialog(); }
}
Then, the view shows itself as a dialog/modal window.

Related

How to pass value to constructor of ViewModel - MVVM using Caliburn.Micro

I'm getting started with MVVM using Caliburn.Micro and the following tutorial: https://www.youtube.com/watch?v=laPFq3Fhs8k
The tutorial shows how to set up a ShellView and open one 'child' View at a time in a <ContentControl>. I have all of this working fine, but now I want to pass some parameters from one child view to the constructor of a new child view. My first child view is called FindQuotaView and the second is called QuotaView. Basically I want the FindQuotaView to take input and display the result on QuotaView. As the <ContentControl> is in the ShellView, I figure I need FindQuotaView to "ask" the ShellView to load QuotaView.
After some searching I discovered the EventAggregator and I'm using that to notify the ShellView when I want to change the loaded view. At first I thought of raising two events, the first telling ShellView to load the QuotaView and the second to pass the parameters to the QuotaView. But my understanding from the video is that once the new view model is instantiated the existing view model is destroyed, so the second event wouldn't be raised. Instead I figured I would need to pass the parameters to the ShellView first. Here is what I have at the moment:
Models
public class LaunchRequest
{
public LaunchRequest(Type viewModel, List<object> parameters)
{
ViewModel = (IScreen)viewModel;
Parameters = parameters;
}
public IScreen ViewModel { get; set; }
public List<object> Parameters { get; set; }
}
ViewModels
ShellViewModel
public class ShellViewModel : Conductor<object>, IHandle<object>
{
private readonly IEventAggregator _eventAggregator;
public ShellViewModel()
{
_eventAggregator = new EventAggregator();
_eventAggregator.Subscribe(this);
LoadViewModel(new LaunchRequest(typeof(FindQuotaViewModel), new List<object> { _eventAggregator }));
}
public void LoadViewModel(LaunchRequest launchRequest)
{
object[] args = { _eventAggregator, launchRequest.Parameters };
ActivateItem(Activator.CreateInstance(launchRequest.ViewModel.GetType(), args));
//Previous Method
//switch (viewModel)
//{
// case "FindQuota":
// ActivateItem(new FindQuotaViewModel(_eventAggregator));
// break;
// case "Quota":
// ActivateItem(new QuotaViewModel(_eventAggregator));
// break;
//}
}
//Event Aggregator message handler
public void Handle(object message)
{
LaunchRequest launchRequest = message as LaunchRequest;
if (launchRequest != null)
{
LoadViewModel(launchRequest);
}
}
}
FindQuotaViewModel
class FindQuotaViewModel : Screen
{
private IEventAggregator _eventAggregator;
public FindQuotaViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
private void FindUsersHomeShare(string username)
{
using (PowerShell cs = PowerShell.Create())
{
//Unrelated code removed to shorten
//string path = result from PowerShell
//TODO: Pass the path to the QuotaViewModel
_eventAggregator.PublishOnCurrentThread(new LaunchRequest(typeof(QuotaViewModel), new List<object> {path}));
}
}
}
QuotaViewModel
class QuotaViewModel : Screen
{
private IEventAggregator _eventAggregator;
public QuotaViewModel(IEventAggregator eventAggregator, object[] args)
{
_eventAggregator = eventAggregator;
}
}
I'm currently getting an error casting QuotaViewModel to IScreen in the LaunchRequest model. I'm not quite sure why as the QuotaViewModel is derived from Screen which implements IScreen. I've also tried passing the parameter as Screen and IScreen but I can't get it working.
Could you please help me understand how to fix this, but also let me know if I'm going about this the right way? Am I over complicating things? Is there a simpler or better method to do this?

How does a child ViewModel prompt the parent ViewModel to navigate away in Caliburn.Micro?

In Caliburn.Micro I have a Shell ViewModel that has 3 IShell properties corresponding to 3 content controls in the associated View. They are 'Full', 'List' and 'Detail'. 'Full' sits above the other two and is as wide as the host Form. 'List' is on the left hand 1 row down and 'Detail' is in the same row as 'List' 1 column to the right.
When the app starts, a Login ViewModel is bound to 'Full' and nothing is bound to the other two. The screen shows only the Login screen. The user should login, and when complete the 'Full' content control should switch from displaying the Login ViewModel, to an AccountViewModel.
For that to work I need the LoginViewModel to tell the ShellViewModel (its parent) to navigate to AccountViewModel.
How do I do that?
public class ShellViewModel : Screen
{
#region Fields
private string _title = "License Manager";
private Conductor<IScreen> _fullFrameConductor;
private Conductor<IScreen> _listFrameConductor;
private Conductor<IScreen> _detailFrameConductor;
#endregion
public ShellViewModel()
{
_fullFrameConductor = new Conductor<IScreen>();
_listFrameConductor = new Conductor<IScreen>();
_detailFrameConductor = new Conductor<IScreen>();
FullFrame = Framework.GetContainer().Resolve<LoginViewModel>();
}
#region Properties
public string Title { get => _title; set => _title = value; }
public IScreen FullFrame
{
get { return _fullFrameConductor.ActiveItem; }
set {
_fullFrameConductor.ActivateItem(value);
NotifyOfPropertyChange(nameof(FullFrame));
}
}
public IScreen ListFrame
{
get { return _listFrameConductor.ActiveItem; }
set {
_listFrameConductor.ActivateItem(value);
NotifyOfPropertyChange(nameof(ListFrame));
}
}
public IScreen DetailFrame
{
get { return _detailFrameConductor.ActiveItem; }
set {
_detailFrameConductor.ActivateItem(value);
NotifyOfPropertyChange(nameof(DetailFrame));
}
}
#endregion
#region Commands
public void ShowProducts()
{
ListFrame = Framework.GetContainer().Resolve<ProductListViewModel>();
DetailFrame = Framework.GetContainer().Resolve<ProductViewModel>();
}
public void ShowLicenses()
{
ListFrame = Framework.GetContainer().Resolve<LicenseListViewModel>();
DetailFrame = Framework.GetContainer().Resolve<LicenseViewModel>();
}
#endregion
}
public class LicenseViewModel : Screen
{
public void Login()
{
// This should process the login and then tell the Shell it is done
// then the shell should navigate to the Account ViewModel sharing
// the user info with the AccountViewModel via a memory cache
// How do I alert the screen ViewModel causing it to close this viewmodel
// without causing a threading problem?
}
}
You can make use of Event Aggregator to communicate between LoginViewModel and ShellViewModel. You can read more on Event Aggregator here.
First, you need to create a Message Class
public class AuthenticationSuccessMessage
{
public bool IsValidLogin{get;set;}
}
Then next step is to use EventAggregator to notify the ShellViewModel from the LicenseViewModel .
private IEventAggregator _eventAggregator;
public LicenseViewModel (IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
public void Login()
{
_eventAggregator.PublishOnUIThread(new AuthenticationSuccessMessage{IsValidLogin=true});
}
The final step is to subscribe to the Events in ShellViewModel.
public class ShellViewModel:Screen, IHandle<AuthenticationSuccessMessage>
{
private readonly IEventAggregator _eventAggregator;
public ShellViewModel:Screen(IEventAggregator eventAggregator) {
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
void Handle<AuthenticationSuccessMessage>(AuthenticationSuccessMessage message)
{
if(message.IsValidLogin)
{
// Do Task
}
}
}
You can read more on Event Aggregators here.
Update : Do not forget to subscribe to Event Aggregator in ShellViewModel.

Set a Property in a ViewModel from Another ViewModel

I am trying to pass a value to a view model from another view model before navigating to the page attached to that view model.
I was previously passing it to the view, then passing it to the view model. This seems like a clumsy way of doing things.
I am not using any kind of framework so that is not an option.
At the moment the property is set as static and this works but im not sure if this is good practice.
The code:
View model 1:
This command opens the new page:
public void OpenRouteDetails()
{
RouteStopPopOverViewModel.RouteName = "TestRoute";
App.Page.Navigation.PushAsync(new RouteStopPopOverView());
}
View model 2: (RouteStopPopOverViewModel)
public static string RouteName { get; set; }
This does work but I would prefer not to use static as a way to achieve this.
Is there some way to set the RouteName property without using static or passing it through view-> view model.
I have seen some answers about this but they don't seem to answer to question clearly.
Share a controller class between view models.
The same instance has to be supplied to the constructor in both view models.
So you can set values, and listen for events in both view models.
The controller class becomes the intermediary.
public class SharedController : IControlSomething
{
private string _sharedValue;
public string SharedValue
{
get => _sharedValue;
set
{
if (_sharedValue == value)
return;
_sharedValue = value;
OnSharedValueUpdated();
}
}
public event EventHandler SharedValueUpdated;
protected virtual void OnSharedValueUpdated()
{
SharedValueUpdated?.Invoke(this, EventArgs.Empty);
}
}
public class ViewModel1
{
private readonly IControlSomething _controller;
public ViewModel1(IControlSomething controller)
{
// Save to access controller values in commands
_controller = controller;
_controller.SharedValueUpdated += (sender, args) =>
{
// Handle value update event
};
}
}
public class ViewModel2
{
private readonly IControlSomething _controller;
public ViewModel2(IControlSomething controller)
{
// Save to access controller values in commands
_controller = controller;
_controller.SharedValueUpdated += (sender, args) =>
{
// Handle value update event
};
}
}
here the sample you can achieve your requirement easily with navigation
public class ViewModelFrom : BaseViewModel
{
async Task ExecuteCommand()
{
string routeName="value to trasfer";
Navigation.PushAsync(new View(routeName));
}
}
public partial class View : ContentPage
{
public View(string routeName)
{
InitializeComponent();
BindingContext = new ViewModelTo(routeName);
}
}
public class ViewModelTo : BaseViewModel
{
public string RouteName { get; set; }
public ViewModelTo(string routeName)
{
RouteName=routeName;
}
}
If there is a hierarchy you could express that in a parent to both of them.
public class Route
{
private string Name;
}
public class RouteSelectedArgs : EventArgs
{
public Route Selected { get; set; }
}
public interface IRouteSelection
{
event EventHandler<RouteSelectedArgs> RouteSelected;
}
public interface IRouteDetails { }
public class RouteWizard
{
public UserControl view { get; set; }
private IRouteSelection _selection;
private IRouteDetails _details;
public RouteWizard(IRouteSelection selection, IRouteDetails details)
{
_selection = selection;
_details = details;
_selection.RouteSelected += Selection_RouteSelected;
view = MakeView(_selection);
}
private void Selection_RouteSelected(object sender, RouteSelectedArgs e)
{
_selection.RouteSelected -= Selection_RouteSelected;
view = MakeView(_details, e.Selected);
}
private UserControl MakeView(params object[] args)
{
////magic
throw new NotImplementedException();
}
}
As you are using the MVVM pattern, you can use one of the many MVVM Frameworks to achieve this.
I use FreshMvvm and it allow me to pass parameters between view models like this
await CoreMethods.PushPageModel<SecondPageModel>(myParameter, false);
Then in SecondPageModel I can see access the parameters in the Init method
private MyParamType _myParameter;
public override void Init(object initData)
{
base.Init(initData);
var param = initData as MyParamType;
if (param != null)
{
_myParameter = param;
}
}
You can find more details about FreshMvvm here although most MVVM frameworks have similar functionality.

Handling events of WPF User Control

I have a user control with several buttons, which need to take different actions depending on the class using it.
The problem is that I don't know how to implement those handlers because when using my user control from the final app I don't have direct access to the buttons to specify which handler handles which events.
How would you do that?
Another way to do this is to expose the events through events in your UserControl :
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public event RoutedEventHandler Button1Click;
private void button1_Click(object sender, RoutedEventArgs e)
{
if (Button1Click != null) Button1Click(sender, e);
}
}
This gives your usercontrol a Button1Click event that hooks up to that button within your control.
I would create a command for each button and delegate for each "handler". Than you can expose delegates to the user (final app) and internally call them on Execute() method on the commands. Something like this:
public class MyControl : UserControl {
public ICommand FirstButtonCommand {
get;
set;
}
public ICommand SecondButtonCommand {
get;
set;
}
public Action OnExecuteFirst {
get;
set;
}
public Action OnExecuteSecond {
get;
set;
}
public MyControl() {
FirstButtonCommand = new MyCommand(OnExecuteFirst);
FirstButtonCommand = new MyCommand(OnExecuteSecond);
}
}
Of cource, "MyCommand" needs to implement ICommand. You also need to bind your commands to coresponding buttons. Hope this helps.

MVP Framework for winforms

i'm working in a new project and i want to implement MVP pattern. There is a framework for winforms that use this pattern? I checked CAB but my project isn't complex to implement it, i search for something more simple to implement and use.
Thanks!
If you are looking for something simple... then you really don't need a framework. You can roll your own MVP pattern.
Writing the base classes takes only a few minutes.
//Base Presenter Class
public class Presenter<TView> where TView : class, IView {
public TView View { get; private set; }
public Presenter(TView view) {
if (view == null)
throw new ArgumentNullException("view");
View = view;
View.Initialize += OnViewInitialize;
View.Load += OnViewLoad;
}
protected virtual void OnViewInitialize(object sender, EventArgs e) { }
protected virtual void OnViewLoad(object sender, EventArgs e) { }
}
//Base View
public interface IView {
event EventHandler Initialize;
event EventHandler Load;
}
That is all you need to get started. You can then define a new view to suit your needs.
public interface IPersonView : IView {
String PersonName { get; set; }
DateTime? DOB { get; set; }
event EventHandler SavePerson;
}
Create a presenter that uses the view.
public class PersonPresenter : Presenter<IPersonView> {
private IPersonDb PersonDB { get; set; }
public PersonPresenter(IPersonView view, IPersonDb personDB)
: base(view) {
if (personDB == null)
throw new ArgumentNullException("personDB");
PersonDB = personDB;
}
protected override void OnViewInitialize(object sender, EventArgs e) {
base.OnViewInitialize(sender, e);
View.PersonName = "Enter Name";
View.DOB = null;
View.SavePerson += View_SavePerson;
}
void View_SavePerson(object sender, EventArgs e) {
PersonDB.SavePerson(View.PersonName, View.DOB);
}
}
And finally put it into use in a new form.
public partial class Form1 : Form, IPersonView {
private PersonPresenter Presenter { get; set; }
public Form1() {
Presenter = new PersonPresenter(this, new PersonDb());
InitializeComponent();
InvokeInitialize(new EventArgs());
}
public string PersonName {
get { return tbName.Text; }
set { tbName.Text = value; }
}
public DateTime? DOB {
get {
return String.IsNullOrWhiteSpace(tbDOB.Text) ?
(DateTime?) null :
DateTime.Parse(tbDOB.Text);
}
set {
tbDOB.Text = String.Format("{0}", value);
}
}
public event EventHandler Initialize;
public void InvokeInitialize(EventArgs e) {
EventHandler handler = Initialize;
if (handler != null) {
handler(this, e);
}
}
public event EventHandler SavePerson;
public void InvokeSavePerson(EventArgs e) {
EventHandler handler = SavePerson;
if (handler != null) {
handler(this, e);
}
}
}
I like Jeremy Miller's stuff a lot. And I have used the Smart Client Software Factory... but those are about solving very large complicated problems. There are so many other patterns mixed in that it overshadows the simplicity of the MVP pattern to begin with.
Start simple and as you start to run into rough spots, then you can begin to add in things like Service Locators and Event Aggregators.
The MVP pattern is really very trivial to implement. I hope this can help to get you off to a running start more quickly.
Cheers,
Josh
This is not a framework, but I would read Jeremy Miller's Build Your Own Cab series before you settle on your design. He covers the various presentation patterns in WinForms.

Categories

Resources