How to use ViewModelCloser to close the View of the ViewModel? - c#

In the MvvmCross v3, CustomerManagement example, the method void RequestClose(IMvxViewModel viewModel) closes the top View. How do you close the View of a ViewModel instead?

I wouldn't use that ViewModelCloser method - although it could be extended if you want to.
MvvmCross v3 removed the previous CloseViewModel method - because it didn't really work across all platforms and across all presentation styles - across all of navigation controllers, splitviews, tabs, flyouts, popups, dialogs, etc.
To replace it, v3 introduces a new ViewModel call:
protected bool ChangePresentation(MvxPresentationHint hint)
This is matched in the UIs with an IMvxViewPresenter method:
void ChangePresentation(MvxPresentationHint hint);
To use this, you will need to:
Create a new Hint class - e.g. public class CustomPresentationHint : MvxPresentationHint { /* ... */ }
In each UI project, provide a custom presenter (normally by overriding CreateViewPresenter() in your Setup.cs class) - and in that custom presenter handle the ChangePresentationHint call:
public void ChangePresentation(MvxPresentationHint hint)
{
if (hint is CustomPresentationHint)
{
// your custom actions here
// - which may involve interacting with the RootFrame, with a NavigationController, with the AndroidFragment manager, etc
}
}
In your viewmodel, you can send a CustomPresentationHint when you want to.
I realise this is 'more work' than was required in vNext, but hopefully it's a more flexible, powerful approach.

Related

(WPF/MVVM) What's the different between an IService and ViewModel?

I wanted to use SaveFileDialog in my ViewModel, But since it's not correct that bind to a View from ViewModel, I searched for ways to do that. But I found a few answers that doesn't completely separate View form ViewModel, Like this:
public interface IOService
{
void IMessageBox(string Message);
string ISaveFileDialog(string DefaultPath);
}
public class IDialog : IOService
{
public void IMessageBox(string Message)
{
System.Windows.MessageBox.Show(Message);
}
public string ISaveFileDialog(string DefaultPath)
{
System.Windows.Forms.SaveFileDialog dg = new SaveFileDialog
{
InitialDirectory = DefaultPath,
Filter = "PDF files (*.pdf) | *.pdf"
};
dg.ShowDialog();
if (dg.FileName == null)
dg.FileName = string.Empty;
return dg.FileName;
}
}
They said that, this is a Service and using it will separate View from ViewModel. But we have make an Instance from this in ViewModel:
IDialog iDialog = new IDialog();
So I wanna know, What's the diffrence between this method and calling MessageBox or SaveFileDialog from ViewModel directly?
Note: Also I find something that said I could use a Service like the above, But implement it like this:
public class ExportViewModel : BaseViewModel
{
IOService _IOService;
public ExportViewModel(IOService ioservice)
{
_IOService = ioservice;
.
.
}
}
But I don't know how send IOService as a parameter to ExportViewModel (Because we can't create Instances from an Interface!)
You shouldn't pop up dialogs directly from your VM for automated testability.
If you call MessageBox.Show(), your test will get stuck until a person closes the dialog.
If, instead, you use "IMessageBox", for unit tests, you can inject an implementation that doesn't actually show the dialog, but rather returns a specific value (the result).
It's an abstraction used to separate UI concerns from the view model, and to allow you a means to intercept these calls within unit tests. Intercepting these calls allows you to both prevent showing a dialog during a test (which will block execution of your tests, not good) and verify the view model is acting as expected.
There is nothing special about calling it a "Service" or calling the abstraction "IService". It's just an allusion to a common pattern
I need to do something
I can't do it myself
I'll find a service I can hire to do it
I found a service that can do this for me
This service will do it for me
You could just as well call it "IDontTouchUIStuffInMuhViewModel", but that's not as elegant.
Your second problem is solved trivially at runtime by providing an implementation of the interface to your view model. Your view model shouldn't care how it's implemented (polymorphism), so you can pass in a legit implementation at runtime and a fake one during testing.
You can also accomplish this by using Dependency Injection or Inversion of Control. By using a library such as Unity to instantiate your view models, all dependencies are automatically provided, based on how you configured the container.

Winforms - MVP Pattern: Using static ApplicationController to coordinate application?

Background
I'm building a two-tiered C# .net application:
Tier 1: Winforms client application using the MVP (Model-View-Presenter) design pattern.
Tier 2: WebAPI RESTful service sitting on top of Entity Framework and SQL Server.
Currently, I have questions relating to the overall architecture of the Winforms client application. I'm new to programming (about a year) but I've made good progress with this application. I want to step back and re-evaluate my current approach to check that I'm generally heading in the right direction.
Application Domain
The Winforms application is a fairly simple security personnel tracking application. The main view (Form) is the focus of the application, and has different sections which group content into functional areas (e.g. a section for tracking personnel schedules, a section for tracking who is assigned where, etc.). A menu on the side of the application launches secondary views (e.g. history, statistics, contacts, etc.). The idea is that the app could be used by a security office to organize daily operations and then keep a detailed history of everything in a database for reporting on in the future.
Technical Details
As mentioned, the Winforms client is built using the MVP pattern (passive view), focusing on using dependency injection as much as possible (via SimpleInjector IoC container). Each view (form) is paired up with a single presenter. The views implement interfaces, allowing the presenter to control the view (regardless of the concrete implementation). The view raises events for the presenter to subscribe to. Currently, presenters are not allowed to directly communicate to another presenter.
An application controller is used to coordinate the application. This is the area of my application architecture where I'm the most shakey (hence the post title). The application controller is currently used to:
Open new views (forms) and manage open forms.
Facilitate communication between application components via an event aggregator. One presenter publishes an event and any number of presenter can subscribe to that event.
Host session information (i.e. security context/logon, config data, etc.)
The IoC container is registered into the application controller at application start-up. This allows the application controller, for example, to create a presenter from the container, and then have all subsequent dependencies (view, services, etc.) to be automatically handled by the container.
Question
In order to make the Application Controller accessible to all presenters, I have created the controller as a static class.
public static class ApplicationController
{
private static Session _session;
private static INavigationWorkflow _workflow;
private static EventAggregator _aggregator;
#region Registrations
public static void RegisterSession(Session session) {}
public static void RegisterWorkflow(INavigationWorkflow workflow) {}
public static void RegisterAggregator(EventAggregator aggregator) {}
#endregion
#region Properties
public static Session Session
{
get { return _session; }
}
#endregion
#region Navigation
public static void NavigateToView(Constants.View view) {}
#endregion
#region Events
public static Subscription<TMessageType> Subscribe<TMessageType>(Action<TMessageType> action) {}
public static void Publish<TMessageType>(TMessageType message) {}
public static void Unsubscribe<TMessageType>(Subscription<TMessageType> subscription) {}
#endregion
}
Is this considered an acceptable practice to make a static class like this? I mean, it certainly works. It just feels... off? Are there any other holes that you can see in my architecture based on what I have described?
-
** EDIT **
This edit is made in response to Ric .Net’s answer posted below.
I have read through all of your suggestions. As I am committed to utilizing dependency injection to the fullest extent I can, I’m onboard with all of your suggestions. That was my plan from the beginning, but when I ran into things I didn’t understand how to accomplish via injection, I turned to the global static controller class to solve my problems (A god class it is becoming, indeed. Yikes!). Some of those questions still exist:
Event Aggregator
The defining line here is what should be considered optional, I think. I’ll provide a bit more context about my app before outlining my problem. Using web terminology, my main form generally acts like a layout view, hosting navigation controls and a notification section in the left menu, and partial views being hosted in the center. Coming back to winforms terminology, the partial views are just custom made UserControls that I treat like views, and each of them are paired up with their own presenter. I have 6 of these partial views hosted on my main form, and they serve as the meat and potatoes of the application.
As an example, one partial view lists available security guards and another lists potential patrol areas. In a typical use case, a user would drag an available security guard from the available list to one of the potential patrol areas, effectively becoming assigned to that area. The patrol area view would then update to show the assigned security guard and the guard would be removed from the available list view. Utilizing drag-and-drop events, I can handle this interaction.
My questions come when I need to handle other types of interactivity between the various partial views. For example, double clicking on guard that is assigned to a location (as seen in one partial view) could highlight that guard’s name on another partial view showing all personnel schedules, or bring up employee details/history on another partial view. I could see the graph/matrix of what partial views are interested in events occurring in other partial views as becoming quite complex, and I’m not sure how to handle that via injection. With 6 partial views, I wouldn’t want to inject the other 5 partial views/presenters into each one. I was planning on accomplishing this via the event aggregator. Another example I could think of is needing to update data on a separate view (its own form) based off an event that occurs on one of the partial views on the main form.
Session & Form Opener
I really like your thoughts here. I’m going to take these ideas and run with them, and see where I end up!
Security
What are your thoughts on controlling user access to certain functionality based on what type of account they have? The recommendations I’ve been reading online say that security could be implemented by modifying the views based on their account type. The thought being, if a user can’t interact with a UI element to kick off a certain task, then the presenter will never be asked to perform that task. I’m curious if you inject the WindowsUserContext into each presenter and do additional checks, especially for http service bound requests?
I haven’t done too much development on the service side of things yet, but for http service bound requests, I imagine you need to send security information along with each request so that the service can authenticate the request. My plan was to inject the WindowsUserContext directly into the winforms service agents that end up making the service requests (i.e. the security validation would not be coming from the presenter). In that case, the service agents could potentially do a last minute security check before sending off a request.
A static class is of course handy in some cases but there are a lot of downsides to this approach.
The tend to grow into something like a God class. You already see this happening. So this class violates SRP
A static class cannot have dependencies and therefore it needs to use the Service Locator anti pattern to get it's dependencies. This is not a problem perse if you consider this class to be part of the composition root, but nevertheless, this often heads the wrong way.
In the supplied code I see three responsibilities of this class.
EventAggregator
What you call Session information
A service to open other views
Some feedback on this three parts:
EventAggregator
Although this is a widely used pattern and sometimes it can be very powerful I myself am not fond of this pattern. I see this pattern as something that provides optional runtime data where in most cases this runtime data is not optional at all. In other words, only use this pattern for truly optional data. For everything that is not really optional, use hard dependencies, using constructor injection.
The ones that need the information in that case depend upon IEventListener<TMessage>. The one that publish the event, depend upon IEventPublisher<TMessage>.
public interface IEventListener<TMessage>
{
event Action<TMessage> MessageReceived;
}
public interface IEventPublisher<TMessage>
{
void Publish(TMessage message);
}
public class EventPublisher<TMessage> : IEventPublisher<TMessage>
{
private readonly EventOrchestrator<TMessage> orchestrator;
public EventPublisher(EventOrchestrator<TMessage> orchestrator)
{
this.orchestrator = orchestrator;
}
public void Publish(TMessage message) => this.orchestrator.Publish(message);
}
public class EventListener<TMessage> : IEventListener<TMessage>
{
private readonly EventOrchestrator<TMessage> orchestrator;
public EventListener(EventOrchestrator<TMessage> orchestrator)
{
this.orchestrator = orchestrator;
}
public event Action<TMessage> MessageReceived
{
add { orchestrator.MessageReceived += value; }
remove { orchestrator.MessageReceived -= value; }
}
}
public class EventOrchestrator<TMessage>
{
public void Publish(TMessage message) => this.MessageReceived(message);
public event Action<TMessage> MessageReceived = (e) => { };
}
To be able to guarantee events are stored in one single location, we extract that storage (the event) into its own class, the EventOrchestrator.
The registration is as follows:
container.RegisterSingleton(typeof(IEventListener<>), typeof(EventListener<>));
container.RegisterSingleton(typeof(IEventPublisher<>), typeof(EventPublisher<>));
container.RegisterSingleton(typeof(EventOrchestrator<>), typeof(EventOrchestrator<>));
Usage is trivial:
public class SomeView
{
private readonly IEventPublisher<GuardChanged> eventPublisher;
public SomeView(IEventPublisher<GuardChanged> eventPublisher)
{
this.eventPublisher = eventPublisher;
}
public void GuardSelectionClick(Guard guard)
{
this.eventPublisher.Publish(new GuardChanged(guard));
}
// other code..
}
public class SomeOtherView
{
public SomeOtherView(IEventListener<GuardChanged> eventListener)
{
eventListener.MessageReceived += this.GuardChanged;
}
private void GuardChanged(GuardChanged changedGuard)
{
this.CurrentGuard = changedGuard.SelectedGuard;
}
// other code..
}
If another view will receive a lot of events you could always wrap all IEventListeners of that View in a specific EventHandlerForViewX class which get all important IEventListener<> injected.
Session
In the question you define several ambient context variables as Session information. Exposing this kind of information through a static class promotes tight coupling to this static class and thus makes it more difficult to unit test parts of your application. IMO all information provided by Session is static (in the sense that it doesn't change throughout the lifetime of the application) data that could just as easily be injected into those parts that actually need this data. So Session should completely be removed from the static class. Some examples how to solve this in a SOLID manner:
Configuration values
The composition root is in charge of reading all information from the configuration source (e.g. your app.config file). This information can there be stored in a POCO class crafted for its usage.
public interface IMailSettings
{
string MailAddress { get; }
string DefaultMailSubject { get; }
}
public interface IFtpInformation
{
int FtpPort { get; }
}
public interface IFlowerServiceInformation
{
string FlowerShopAddress { get; }
}
public class ConfigValues :
IMailSettings, IFtpInformation, IFlowerServiceInformation
{
public string MailAddress { get; set; }
public string DefaultMailSubject { get; set; }
public int FtpPort { get; set; }
public string FlowerShopAddress { get; set; }
}
// Register as
public static void RegisterConfig(this Container container)
{
var config = new ConfigValues
{
MailAddress = ConfigurationManager.AppSettings["MailAddress"],
DefaultMailSubject = ConfigurationManager.AppSettings["DefaultMailSubject"],
FtpPort = Convert.ToInt32(ConfigurationManager.AppSettings["FtpPort"]),
FlowerShopAddress = ConfigurationManager.AppSettings["FlowerShopAddress"],
};
var registration = Lifestyle.Singleton.CreateRegistration<ConfigValues>(() =>
config, container);
container.AddRegistration(typeof(IMailSettings),registration);
container.AddRegistration(typeof(IFtpInformation),registration);
container.AddRegistration(typeof(IFlowerServiceInformation),registration);
}
And where you need some specific information, e.g. information to send an email you can just put IMailSettings in the constructor of the type needing the information.
This will also give you the possibility to test a component using different config values, which would be harder to do if all config information had to come from the static ApplicationController.
For security information, e.g. the logged on User the same pattern can be used. Define an IUserContext abstraction, create a WindowsUserContext implementation and fill this with the logged on user in the composition root. Because the component now depends on IUserContext instead of getting the user at runtime from the static class, the same component could also be used in an MVC application, where you would replace the WindowsUserContext with an HttpUserContext implementation.
Opening other forms
This is actually the hard part. I normally also use some big static class with all kinds of methods to open other forms. I don't expose the IFormOpener from this answer to my other forms, because they only need to know, what to do, not which form does that task for them. So my static class exposes this kinds of methods:
public SomeReturnValue OpenCustomerForEdit(Customer customer)
{
var form = MyStaticClass.FormOpener.GetForm<EditCustomerForm>();
form.SetCustomer(customer);
var result = MyStaticClass.FormOpener.ShowModalForm(form);
return (SomeReturnValue) result;
}
However....
I'm not at all happy with this approach, because over time this class grows and grows. With WPF I use another mechanism, which I think could also be used with WinForms. This approach is based on a message based architecture described in this and this awesome blogposts. Although at first the information looks as it is not at all related, it is the message based concept that let these patterns rock!
All my WPF windows implement an open generic interface, e.g. IEditView. And if some view needs to edit a customer, it just get's this IEditView injected. A decorator is used to actually show the view in pretty much the same way as the forementioned FormOpener does it. In this case I make use of a specific Simple Injector feature, called decorate factory decorator, which you can use to create forms whenever it is needed, just as the FormOpener used the container directly to create forms whenever it needs to.
So I did not really test this, so there could be some pitfalls with WinForms, but this code seems to work on a first and single run..
public class EditViewShowerDecorator<TEntity> : IEditView<TEntity>
{
private readonly Func<IEditView<TEntity>> viewCreator;
public EditViewShowerDecorator(Func<IEditView<TEntity>> viewCreator)
{
this.viewCreator = viewCreator;
}
public void EditEntity(TEntity entity)
{
// get view from container
var view = this.viewCreator.Invoke();
// initview with information
view.EditEntity(entity);
using (var form = (Form)view)
{
// show the view
form.ShowDialog();
}
}
}
The forms and decorator should be registered as:
container.Register(typeof(IEditView<>), new[] { Assembly.GetExecutingAssembly() });
container.RegisterDecorator(typeof(IEditView<>), typeof(EditViewShowerDecorator<>),
Lifestyle.Singleton);
Security
The IUserContext must the base for all security.
For the userinterface I normally hide all controls/buttons that a certain userrole doesn't have access to. The best place is to perform this in the Load event.
Because I use the command/handler pattern as described here for my all actions external of my forms/views I use a decorator to check if a user has permission to perform this certain command (or query).
I would advise you to read this post a few times until you really get the hang of it. Once you get familiar with this pattern you won't do anything else!
If you have any questions about these patterns and how to apply a (permission)decorator, add a comment!

mvvmcross IOS: How to callback from a ViewModel to a View

I have a MvxViewController and in the ViewDidLoad i bind the button click to the viewmodel. When the button is clicked I open another view in which I will need to return a string back to my first view
public override void ViewDidLoad ()
{
var set = this.CreateBindingSet<MyView1, MyView1ViewModel>();
set.Bind(myButton).To(vm => vm.MyButtonCommand);
set.Apply();
}
public ICommand MyButtonCommand
{
get
{
_myButtonCommand = _myButtonCommand ?? new MvxCommand(MyButtonCommandClick);
return _myButtonCommand;
}
}
private void MyButtonCommandClick()
{
ShowViewModel<ViewModelNumber2>();
}
After some logic is ran in my second view I want to return the string
private void SomeMethodInViewModelNumber2()
{
//Raise event that will get pickup up in MyView
//Or somehow get "SomeString"
if (OnMyResult != null)
OnMyResult ("SomeString");
}
The problem is that I don't want to send the string back using the messenger. I have my reasons but basically because ViewModelNumber2 can be opened from many different places and works slightly different and managing the different messages that would need to be sent back and where to subscribe to these messages would be a mess
Is there any way that I can do something like the below?
public override void ViewDidLoad ()
{
var set = this.CreateBindingSet<MyView1, MyView1ViewModel>();
set.Bind(myButton).To(vm => vm.MyButtonCommand).OnMyResult((myString) => {Process(myString)});
set.Apply();
}
Or perhaps when I create ViewModelNumber2 I should pass a callBack into the constructor and use that to send the string back from ViewModelNumber2 to MyView1ViewModel
ShowViewModel<ViewModelNumber2>(OnMyResult);
What is the best way to do this?
In short: I don't know what "the best way to do this" is.
The area of ChildViewModel-ParentViewModel messages is complicated - especially because on platforms like Android using Activities and WindowsPhone using Pages you have no guarantee that the ParentViewModel will be in memory when the Child is shown. (Note: this isn't a problem on iOS as its "app suspension" model is simpler)
When I do need one ViewModel returning data to another, then:
Often I try to implement the data collection views as "popup dialogs" rather than as "whole pages" - this makes the parent-child ViewModel relationship more correct - and ensures the parent ViewModel will be in memory when the child closes.
Often I recommend people use a Messenger-based technique like Greg describes in: http://www.gregshackles.com/2012/11/returning-results-from-view-models-in-mvvmcross/
often I've done this messaging via background services rather than via ViewModel-ViewModel messaging (a bit like the way screens are updated in https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/tree/master/N-17-CollectABull-Part6)
Another solution I've used is to:
implement a IDropBoxService singleton - with an API like void Deposit(key, value) and bool TryCollect(key, out value)
allow the closing "child" ViewModels to leave "values" when they close
implement IVisible functionality in my "parent" ViewModel - like in https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/blob/master/N-42-Lifecycles/Lifecycle.Core/ViewModels/FirstViewModel.cs#L10
use the IVisible method to check for messages
To implement anything perfectly, you really should add serialisation code to make sure this all works during "tombstoning" on all platforms... but often this is overkill - for a simple data collection dialog users often don't need "perfect" tombstoning support.

Caliburn ShowDialog and MessageBox

I'm making a small demo application for MVVM with caliburn.
Now I want to show a MessageBox, but the MVVM way.
For dialogs I created an event, that is handled in the ShellView (the root view)
and just calls WindowManager.ShowDialog with a Dialogs ViewModel type.
Seems to stick to MVVM for me.
But what is the way to show a messagebox and get its result (Okay or cancel)?
I already saw this question, but it contains no answer either.
Mr Eisenberg hisself answers with
"Caliburn has services built-in for calling custom message boxes."
Can anyone tell what he means with that? I don't see it in the samples.
As you mentioned, you just prepare the view model (e.g. ConfirmationBoxViewModel) and an appropriate view. You'll have to create two actions (after inheriting the view model from Screen, which is necessary to use TryClose. You can always implement IScreen instead, but that would be more work):
public void OK()
{
TryClose(true);
}
public void Cancel()
{
TryClose(false);
}
and then in your other view model:
var box = new ConfirmationBoxViewModel()
var result = WindowManager.ShowDialog(box);
if(result == true)
{
// OK was clicked
}
Notice that after the dialog closes, you can access the view model properties if you need to pull additional data from the dialog (e.g. Selected item, display name etc).
In the article A Billy Hollis Hybrid Shell (written by the framework coordinator) the author showed a nice way to handle both dialog and message boxes, but he used dependency injection (you can go without DI of course but it makes things simpler). The main idea is that you can let your main window, the one used as the application shell implement an interface that looks something like this:
public interface IDialogManager
{
void ShowDialog(IScreen dialogModel);
void ShowMessageBox(string message, string title = null, MessageBoxOptions options = MessageBoxOptions.Ok, Action<IMessageBox> callback = null);
}
and then he registers this interface with the IoC container, I guess you can use your imagination from there on and if you don't have time then you can look at the source code that accompanies the article.
When the root/main/shell view-model implements a kind of DialogService interface, every other view-model needing to show dialogs will end up with a dependency on the root view-model. Sometimes this might not be desiderable, e.g. if it could cause a dependency loop:
DialogService (aka RootViewModel) -> SomeViewModel -> RootViewModel.
A more involved approach to break this dependency chain (and actually invert it) is the following:
Implement a behavior that detects Window.OnSourceInitialized event and attach it to main view Window component. That is the event fired when the window handle is available. Upon event, behavior will notify some handler passed in via attached property:
<my:WindowSourceBehavior InitListener="{Binding WindowListener}" />
public class WindowSourceBehavior : Behavior<Window>
{
// ...
// boilerplate code for IWindowListener InitListener dependency property
// ...
attachedWindow.SourceInitialized += (sender, evt) =>
{
// ...
InitListener.SourceInitialized(sender as Window);
}
}
DialogService exposes a handler - or interface - as requested by behavior:
public class DialogService : IWindowListener
{
// ...
public void SourceInitialized(Window rootWindow) { /* ... */ }
}
In root view-model, (indirectly) get the DialogService injected as a dependency. During construction, sets view-model bound property, WindowListener, to the DialogService handler/interface:
public MainViewModel(IWindowListener dialogServiceInDisguise)
{
WindowListener = dialogServiceInDisguise;
}
public IWindowListener WindowListener { get; private set; }
Doing so, the DialogService is able to get a hold of root Window, and whichever view-model needs to show a dialog does not create a(n indirect) dependency on main view-model.

Caliburn Micro WinRT Navigation and Conductors

I have a WinRT app which is up and running nicely using the caliburn.micro INavigationService (FrameAdapter). The problem is all of the pages in my app are full pages and they repeat a lot of view xaml markup. What I would like to do would be to have more of a "MasterPages" type of architecture where I have a shell view which defines the main layout and contains a ContentControl and uses the Conductor pattern to switch out the contents of the ContentControl. I have had success with this sort of architecture in the past with WPF and Silverlight so I assume it is possible in WinRT.
The problem is there seems to be a disconnect between the navigation infrastructure (using the FrameAdapter) and the Conductor infrastructure using a ContentControl bound to the ActiveItem of the conductor (like the SimpleNavigation sample).
In the conductor scenario I use ActivateItem:
ActivateItem(new MyViewModel());
but with the INavigationService I use NavigateToViewModel:
navigationService.NavigteToViewModel<MyViewModel>();
and the two don't seem to be connected as far as I can tell.
One idea I had was to create a ConductorNavigationService which implements INavigationService and basically handles the creation and activation of the child screens. While it seems possible it doesn't seem very straight forward so I figured I would check to see if there is an already supported way to do this in caliburn.micro.
Ok my understanding might be a little strained as I've not used WinRT or the INavigationService but I assume Frame is part of RT and INavigationService provides viewmodel resolution and navigation for the frames.
My other assumption is that your frame is a bit like your conductor already, and when you call 'Navigate()' on the frame, it just replaces the content of the frame with the newly specified content. If this is the case then CM is doing view first resolution for viewmodels.
Since you want to go the conductor route, it sounds like you want to ditch the CM implementation of INavigationService and just roll your own to handle the INavigationService navigation methods (e.g. skip the Frames Navigate() method).
A quick look at the CM source reveals that all NavigationService is doing is handling the Navigate events on the frame and then doing VM resolution and setting up the view (something that conductor probably already does). All you would need to do is ensure that your INavigationService implementation just loads the specified view into the shell instead of navigating the frame
You could just need to steal the constructor code for NavigationService and change the implementation of Navigate(), then just call ActivateItem(x) on your shell where x is the instance of the VM. CM will take care of the rest (I think CM boostrapper will already setup your root 'Frame' too so you shouldn't need to worry about that).
e.g.
An implementation may look more like this (bear in mind this is just something I thrown together and may be barefaced lies!):
public class NewFrameAdapter : INavigationService
{
private readonly Frame frame;
private readonly IConductActiveItem shell;
private event NavigatingCancelEventHandler ExternalNavigatingHandler = delegate { };
public NewFrameAdapter(Frame frame)
{
this.frame = frame;
// Might want to tighten this up as it makes assumptions :)
this.shell = (frame as FrameworkElement).DataContext as IConductActiveItem;
}
public bool Navigate(Type pageType)
{
// Do guardclose and deactivate stuff here by looking at shell.ActiveItem
// e.g.
var guard = shell.ActiveItem as IGuardClose;
if (guard != null)
{
var shouldCancel = false;
guard.CanClose(result => { shouldCancel = !result; });
if (shouldCancel)
{
e.Cancel = true;
return;
}
}
// etc
// Obviously since the guard is probably async (assume it is, if not you are ok to continue!) you'd have to not call this code right
// here but I've just stuck it in here as an example
// edit: looking at the code above (the guard code) it looks like this is all sync so the below code should be fine
// You might get away with calling shell.ActivateItem(pageType) as I'm not sure
// if the viewmodel binder in RT would resolve this all for you, but if it doesnt...
// Init the view and then resolve the VM type
ViewLocator.InitializeComponent(pageType);
var viewModel = ViewModelLocator.LocateForView(pageType);
// Activate the VM in the shell)
shell.ActivateItem(viewModel);
}
It shouldn't be too difficult to roll this your own way. Does this help you at all?
Then your XAML would be pretty simple:
<Frame blah blah>
<SomeStaticContent />
<ContentControl x:Name="ActiveItem" /> <!-- The dynamic bit... -->
<SomeMoreStaticContent />
</Frame>
I'm thinking that this will probably be a hybrid of view-first and viewmodel-first since your root Frame will be using view-first, and your conductor will be using ActivateItem() which takes a viewmodel and then resolves the view when the binder kicks in, but if my assumptions are ok, it should work

Categories

Resources