MVP Navigation in WinForms - c#

Have been learning about MVP and have tried writing a test app using it in WinForms. I'm struggling to find a well explained example on how to navigate between my forms/views. As an example, the program starts and I want to show a login dialog then go into my main view if the login was successful. At the moment, my Main method looks something like this:
static void Main()
{
var loginView = Injector.Resolve<ILoginView>();
if (loginView.DoLogin() != LoginResult.OK) return;
var mainView = Injector.Resolve<IMainView>();
Application.Run(mainView); // won't work as mainView isn't a form
}
The Injector object is just a wrapper around an IoC tool (currently StructureMap). The thing is, I've read that I shouldn't really be manually creating instances via the Injector as they should really be done via constructor injection.
I've managed to do this up to a point but not when it comes to navigation. I can't think of an elegant way of moving through my views and was wondering if anyone here might shed some light on this? I've read a little on application controllers but have not found an example to show it clearly.

In regards to the navigation question:
I've managed to do this up to a point but not when it comes to
navigation. I can't think of an elegant way of moving through my views
and was wondering if anyone here might shed some light on this? I've
read a little on application controllers but have not found an example
to show it clearly.
Below is a simplified version of a construct I've used. Note that the setup and tear down hooks are called automatically when the NavigateTo method is called. Also, +1 to #AlexBurtsev, as his answer hints at this very same approach.
// Presenter can and should offer common services for the
// subclasses
abstract class Presenter
{
public void Display()
{
OnDisplay();
}
public void Dismiss()
{
OnDismiss();
}
protected virtual OnDisplay() // hook for subclass
{
}
protected virtual OnDismiss() // hook for subclass
{
}
private NavigationManager _navMgr;
internal NavigationMgr NavigationManager
{
get
{
return _navMgr;
}
set
{
_navMgr = value;
}
}
}
// NavigationManager is used to transition (or navigate)
// between views
class NavigationManager
{
Presenter _current;
// use this override if your Presenter are non-persistent (transient)
public void NavigateTo(Type nextPresenterType, object args)
{
Presenter nextPresenter = Activator.CreateInstance(nextPresenterType);
NavigateTo(nextPresenter);
}
// use this override if your Presenter are persistent (long-lived)
public void NavigateTo(Presenter nextPresenter, object args)
{
if (_current != null)
{
_current.Dismiss()
_current.NavigationMgr = null;
_current = null;
}
if (nextPresenter != null)
{
_current = nextPresenter;
_current.NavigationMgr = this;
_current.Display(args);
}
}
}
class MainMenuPresenter : Presenter
{
private IMainMenuView _mainMenuView = null;
// OnDisplay is your startup hook
protected override void OnDisplay()
{
// get your view from where ever (injection, etc)
_mainMenuView = GetView();
// configure your view
_mainMenuView.Title = GetMainTitleInCurrentLanguage();
// etc
// etc
// listen for relevent events from the view
_mainMenuView.NewWorkOrderSelected += new EventHandler(MainMenuView_NewWorkOrderSelected);
// display to the user
_mainMenuView.Show();
}
protected override void OnDismiss()
{
// cleanup
_mainMenuView.NewWorkOrderSelected -= new EventHandler(MainMenuView_NewWorkOrderSelected);
_mainMenuView.Close();
_mainMenuView = null;
}
// respond to the various view events
private void MainMenuView_NewWorkOrderSelected(object src, EventArgs e)
{
// example of transitioning to a new view here...
NavigationMgr.NavigateTo(NewWorkOrderPresenter, null);
}
}
class NewWorkOrderPresenter : Presenter
{
protected override void OnDisplay()
{
// get the view, configure it, listen for its events, and show it
}
protected override void OnDismiss()
{
// unlisten for events and release the view
}
}

I haven't used WinForms for a long time, but I'll try to answer this. I would use the same strategy as WPF Prism do.
About MainView and Application.Run:
Create a main Region (root Form), with empty container inside which can hold UserControl (I forgot exact class names), then when you need to switch root view, you do RootView.SetView(UserControl view) which will do something like Form.Clear(), Form.AddChild(view).
About the navigation and using container:
You could create a service for navigation: INavigationService which you inject in constructors with method like INavigationService.NavigateView(String(or Type) viewName, params object[] additionalData)

You can insert a method in mainView that returns the actual form.Then you can call
Mainview:IMainView
{
Form GetView()
{
//return new Form();
}
}
In Main you can call ,
Application.Run(mainView.GetView())

Related

IoC.BuildUp doesn´t build object

I Am very new to Caliburn and have to expand an existing program, using caliburn.micro
I need my DataContext in a View to be set to a ViewModel which has some propertyinjected parameters. All this should work, so i only need to build it up.
however it does not! here is the code where i try to bulid:
public ProductionMainView()
{
InitializeComponent();
var gg = IoC.Get<ProductionMainViewModel>();
IoC.BuildUp(gg);
this.DataContext = gg;
}
As far as i can understand I need to get the object and then build it.
Now I should get a view "Containing" my ViewModel, but the View is blank,
I hope i have explained myself well enough.
Best regards
First off you are using a really old version, present version is 2.0.2, and you still need to have overrides for GetInstance, GetAllInstances, BuildUp methods in your BootStrapper or a variant for use with Ninject
http://putridparrot.com/blog/caliburn-micro-and-inversion-of-control-using-ninject/
Link above has some source for this.
Remember there is nearly no code in the Codebehind of the View... It will all go into the ViewModels (for the most part). ShellView => ShellViewModel with in ShellViewModel var gg = IoC.Get<ProductViewModel>() depending on you are intent is with that you can bind it to a <ContentControl x:Name="ProductViewModel" /> with a property in yoru viewmodel called exactly that. CM will go out and fine that ViewModel and associated view and mash them together automagically. Expected results on the screen should be your ProductViewModel in that contentcontrol location.
public class ShellViewModel : Screen
{
private ProductViewModel _productViewModel;
public ShellViewModel (){
}
protected override void OnActivate(){
ProductViewModel = IoC.Get<ProductViewModel>();
}
public ProductViewModel ProductViewModel {
get{ return _productViewModel;}
set{
_productViewModel = value;
NotifyOfPropertyChanged (); // 2.0.2 feature...
}
}
}
Now, if you decide to move to the latest version there are subtle changes one Bootstrapper<T> does not exist anymore it was refactored into BootstrapperBase. Next Initialize() will be placed into the ctor of your Bootstrapper(). From there everything will be based on your starting screen is after Initialize() is called in the CTOR you will call DisplayRootViewFor<ShellViewModel>() in the OnStartup override.
Some have said and I tend to agree over using IoC.Get<T>() can be anti-pattern, caution when using it. Sometimes things change unexpectedly. Those tend to DI the items via the CTOR and use from there accordingly.
/*version 1.52*/
public class Bootstrapper : BootStrapper<LoginViewModel>{
public Bootstrapper(){
Start();
}
protected override Configure(){
/* -- your Ninject configurations -- */
}
protected override object GetInstance(Type service, string Key){
if(service == null)
throw new ArgumentNullException("Service");
return _kernel.Get(service);
}
protected override IEnumerable<object> GetAllInstances(Type service){
return _kernel.GetAll(service);
}
protected override void BuildUp(object instance){
_kernel.Inject(instance);
}
protected override void OnExit(object sender, EventArgs e)
{
kernel.Dispose();
base.OnExit(sender, e);
}
}
To clarify do you have to login to get to the ProductionMainViewModel? What determines your logic for getting to ProductionMainViewModel to even display?

ASP.NET C# [createnew] property attribute

while reading this tutorial at
http://www.codeproject.com/Articles/380215/Understanding-MVP-WCSF-Over-ASP-NET-Web-Forms?msg=4970179#xx4970179xx
I find the attribute [CreateNew] used with property Presenter
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
this._presenter.OnViewInitialized();
}
this._presenter.OnViewLoaded();
}
[CreateNew]
public ProductPresenter Presenter
{
get
{
return this._presenter;
}
set
{
if (value == null)
throw new ArgumentNullException("value");
this._presenter = value;
this._presenter.View = this;
}
}
and here it is used before method parameter
private IProductsController _controller;
public ProductPresenter([CreateNew] IProductsController controller)
{
//// Method on controller class invoked here
_controller = controller;
}
What does the attribute [CreateNew] meand before property and what does it mean when used with the method parameter? Thanks.
I got the answer. Here it is. [CreateNew] is used to create new object for dependency
injection
Dependency Injection and the Composite Application Block (Introduction to CAB/SCSF Part 5)
It is part of the Composite Application Block and here is a good in fact a very good tutorial introducing the Composite Application Block and Smart Client Software Factory - Introduction to CAB/SCSF.
And here is the link for tutorial table of contents for all tutorial parts
Table of Contents: Introduction to CAB/SCSF

WPF Windows Controller

I am having some problems with WPFs.
I have a project that has multiple windows, so to control this windows, I have created a controller class. This controller will have a instance of each windows:
this.mainWindow = new MainWindow();
this.loginWindow = new LoginWindow();
this.registerWindow = new RegisterWindow();
The problem comes when I callback from any of the windows to the controller class and from this controller I want to update the information of the window (for example update the value of a property), the information is not being updated
// In controller
public void login(String email, String pass)
{
....
this.loginWindow.showErrorInPassword();
}
// In LoginWindow
public void showErrorInPassword()
{
this.emailErrorImage.Visibility = System.Windows.Visibility.Visible;
}
... but if I send from the LoginWindow a reference of itself to the login function on the controller, the emailErrorImage will be shown
public void login(String email, String pass, LoginWindow lw)
{
....
lw.showErrorInPassword();
}
Seems that the instance that I have in the controller is not the same as the one that is being displayed when I do this.loginWindow.show()
Can someone tell me what am I doing wrong?
You are going to need to bind the UI objects to a MVVM class to update each window.
Use events to call back to the controller.
Here is a brief example. First create a class to contain event args. Doesn't really have to contain anything. It just differentiates between different delegates. Make it its own class in the namespace so everything has access to it.
public class SomeEventArgs: EventArgs
{
}
Inside the window class:
public event EventHandler<SomeEventArgs> CallBackToController;
protected virtual void OnCallBackEvent(object sender, SomeEventArgse)
{
EventHandler<SomeEventArgs> handle = CallBackToController;
if (handle != null)
{
handle(this, e);
}
}
In the controller class, after instantiating the window assign the event to a method.
this.loginWindow = new LoginWindow();
this.loginWindow.CallBackToController += new EventHandler<SomeEventArgs>(MethodToHandleEvent);
Then the Method must have the same form as expected:
private void MethodToHandleEvent(object sender, SomeEventArgs e)
{
// Do something in response.
}
Now anytime you call OnCallBackEvent(this, new SomeEventArgs()) from the window class, the controller class will catch the event and execute MethodToHandleEvent
For instance:
private void LoginWindowBtn_Click(object sender, RoutedEventArgs e)
{
// Logged in ok, let the controller know.
OnCallBackEvent(this, new SomeEventArgs ());
}
There are a ton of tutorials on this, I think this is a better approach to passing references of windows from window to window.

MVVM light, send message via Messenger from ViewModel to new child window, which is not initialized yet

I've following architecture:
desktop application, .Net 4.5, C#, WPF, MVVM Light, Messenger, IoC - ViewModel locator, so ViewModels doen't know anyhing about Views.
I have main view with data grid of some elements, and I want to display details of each individual element in new/child windows after double click on data grid.
I've bind event double click on main view to main view model. From this event handler in main view model, message is sent via Messanger.
New view (new/child window) is created in main view via delegate of also double click.
New/child window is a view which locate his view model and this view model register to the specific message in his constructor.
The problem is that new/child window (new view, and view model so on) is created too late, because message is already sent when new view model register for it.
Do you know maybe some patterns for such architecture. Any ideas will be appreciated.
It would help to know exactly what you try to do.
If your problem is just to display a detailed Window when double click on a row, I would say: create only one childWindow at start, and play with its visbility when required.
If you really need a new window each time, you could create it from your viewModel with an injected service for example.
In any case, you never has to create your window from main view! Either you create one window at start, either you dynamically create it from view model.
You cannot hope to create it from view and send the message in your view model.
Edit about the injected service, you could use something like that:
public interface IWindowService
{
void Open<TWindow>(ViewModelBase viewModel)
where TWindow : Window;
}
public class WindowService : IWindowService
{
private readonly IUIDispatcher _dispatcher;
public WindowService(IUIDispatcher dispatcher)
{
_dispatcher = dispatcher;
}
public void Open<TWindow>(ViewModelBase viewModel)
where TWindow : Window
{
_dispatcher.Run(() => OpenThreadSafe<TWindow>(viewModel));
}
private static void OpenThreadSafe<TWindow>(ViewModelBase viewModel) where TWindow : Window
{
var view = (TWindow) Activator.CreateInstance(typeof(TWindow), viewModel);
view.Show();
}
}
public class UIDispatcher : IUIDispatcher
{
public void Run(Action action)
{
var dispatcher = DispatcherHelper.UIDispatcher;
if (dispatcher == null)
{
action();
return;
}
DispatcherHelper.CheckBeginInvokeOnUI(action);
}
Note this DispatcherHelper come from MVVMlight, but you could erplace it easily.
Hope it helps.
The problem is that the ViewModel Locator creates the viewmodel instance only when it is needed (lazy loading).
just configure the ViewModelLocator to instantiate the viewmodel eager instead of lazy. This is done by passing the parameter "true" to the IoC Container.
Sample:
namespace Administration.ViewModel
{
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
//Eager Loading
SimpleIoc.Default.Register<UserManagementViewModel>(true);
//Lazy Loading
SimpleIoc.Default.Register<InformationManagementViewModel>();
}
public UserManagementViewModel UserManagementViewModel
{
get
{
return ServiceLocator.Current.GetInstance<UserManagementViewModel>();
}
}
public InformationManagementViewModel InformationManagementViewModel
{
get
{
return ServiceLocator.Current.GetInstance<InformationManagementViewModel>();
}
}
public static void Cleanup()
{
SimpleIoc.Default.Unregister<UserManagementViewModel>();
SimpleIoc.Default.Unregister<InformationManagementViewModel>();
}
}
}

Dependency Injection with MVVM and Child Windows

I am using MVVM Light and I'm currently using SimpleIoC that comes with the package. I'm getting a bit stuck with the dependency injection. I have a bunch of services that I want to use in my view models, however most windows are a List-Edit paradigm, i.e. one screen lists all of type Person and then you can Add or Edit a Person via a new screen.
When I was doing all code in the code behind my code for adding and editing a record was as follows:
View
private void btnEdit_Click(object sender, RoutedEventArgs e)
{
_viewModel.Edit();
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
_viewModel.Add();
}
View Model
public void Add()
{
var f = new TypeDetails();
f.Show();
}
public void Edit()
{
if (SelectedItem == null)
return;
var f = new TypeDetails(SelectedItem.Id);
f.Show();
}
The constructor of TypeDetails is as follows:
public TypeDetails(int id = 0)
{
InitializeComponent();
_viewModel = new TypeDetailsViewModel(id);
DataContext = _viewModel;
}
What would the best be to implement this type functionality with MVVM Light? I have been using the ViewModelLocator class for the List screens, however I cannot see a way to do this using the SimpleIoC. My way round so far has been to keep the constructor the same, which works fine until I need to inject dependencies into the TypeDetailsViewModel such as a service. With a service the constructor of TypeDetailsViewModel would be:
public TypeDetailsViewModel(ISomeService someService, int id = 0)
{
...
}
But that means in my view constructor I have to build these dependencies one at a time and manually inject them...
public TypeDetails(int id = 0)
{
InitializeComponent();
_viewModel = new TypeDetailsViewModel(SimpleIoC.Current.GetInstance<ISomeService>(),id);
DataContext = _viewModel;
}
Is there a better way to do this?
First off I would look into the "RelayCommand" class which is part of MVVM Light. It will remove the need for events in your code behind. Start with that.
You should always favor "Constructor Injection" instead of the ServiceLocator (ex: SimpleIoC.Current.GetInstance())
Your ViewModel constructor should only be injecting services and not primitive types like "int". In your example "int id" should be the parameter of a method and not injected.
Ex: Instead, your TypeDetailsViewModel should look more like:
public TypeDetailsViewModel(ISomeService someService)
{
TypeDetail GetDetailsCommand(int id)
{
...
}
}
Lastly, your Models should never have any reference to your ViewModels.
For your DataContext, you can use a ViewModelLocator (ViewModels in ViewModelLocator MVVM Light)
To hook up your View and ViewModel to use the GetDetailsCommand, you can use the EventToCommand behavior (http://msdn.microsoft.com/en-us/magazine/dn237302.aspx). Ex: The OnLoaded event on the View calls the GetDetailsCommand on your ViewModel.

Categories

Resources