MVVM Solution structure - c#

I'd like to understand the best practice for structuring an MVVM solution.
Currently I've created a separate project for my View and my ViewModel. My View project references my ViewModel project. So far so good. When it comes to implementing navigation my ViewModel classes need access to the RootFrame in order to navigate around, but the RootFrame resides in App.xaml which is in the View project. So I have a circular dependency issue here.
Is there a recommended structure I should be using? I could just lump this all into one big project but in order to decouple the View and ViewModel I feel like having separate projects is a better approach.

When it comes to implementing navigation my ViewModel classes need access to the RootFrame
This is a false assumption.
You could use a message broker (a single object) that is responsible for distributing messages between publishers (ViewModels) and subscribers (some object that is responsible for opening Views).
Most MVVM frameworks have such a broker.
About the dependencies
The single responsibility of the Broker is to raise events. So, in general, it exposes a couple of methods that can be called by publishers and a couple of events that can be registered to by subscribers.
In MVVM you could use this mechanism to have a ViewModel raise the event that signals that a View should be opened and a View Manager that subscribes to this event. The View Manager should be able to instantiate a View and attach the correct ViewModel.
To prevent the ViewManager from needing references to all Views and ViewModels you could pass to the event logical names (just a string) and have the View Manager look up the matching View(Model) type by using reflection or a static list in a configuration file.
This way you do NOT need any circular references references. In fact, when you find you need references that go against the proper dependencies in MVVM, you should first doubt the setup and after that consider using a base class for the View and/or ViewModels.

There is no best practice for MVVM since it is a design pattern that everybody implements differently according to their preference. I've seen quite a few different implementations but have never seen a views and view models in separate projects. I would recommend just keeping them in different folders in the same project and put them in different namespaces.
e.g. Your View Models can go in a ViewModels folder and be in the namespace MyProject.ViewModels
Your Views can go in a Views folder and be in the namespace MyProject.Views
And the same for Models if you are using them

Check out Rachel's good answer in this post. I also like to separate my Views and ViewModels, because then I know when I screwed up MVVM ground rules.
Your ViewModel should not have any references to the View, but the View must have a reference to the ViewModel. Consider, for example, my custom SplashScreen factory (the two important lines are "var viewModel..." and "var splashScreen..."):
namespace MyCompany.Factories
{
using System.Threading;
using MyCompany.Views;
using MyCompany.ViewModels;
public static class SplashScreenFactory
{
public static SplashScreenViewModel CreateSplashScreen(
string header, string title, string initialLoadingMessage, int minimumMessageDuration)
{
var viewModel = new SplashScreenViewModel(initialLoadingMessage, minimumMessageDuration)
{
Header = header,
Title = title
};
Thread thread = new Thread(() =>
{
var splashScreen = new SplashScreenView(viewModel);
splashScreen.Topmost = true;
splashScreen.Show();
splashScreen.Closed += (x, y) => splashScreen.Dispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
return viewModel;
}
}
}
The Factories project has references to both MyCompany.ViewModels and MyCompany.Views. The Views project only has a reference to MyCompany.ViewModels.
We first initiate the ViewModel from our caller (in this case, from SplashScreenFactory; or maybe from App.xaml.cs if you want; I prefer using my own Bootstrapper class for reasons beyond this discussion).
Then we initiate the View by passing the ViewModel reference into the View's constructor. This is called Dependency Injection. You may also need to write a Behaviour for the Window class in order to Close the window from your ViewModel. So now I can do the following from my Bootstrapper class:
/// <summary>
/// Called from this project's App.xaml.cs file, or from the Deals Main Menu
/// </summary>
public class Bootstrapper
{
private SplashScreenViewModel _splashScreenVM;
public Bootstrapper()
{
// Display SplashScreen
_splashScreenVM = SplashScreenFactory.CreateSplashScreen(
"MyCompany Deals", "Planning Grid", "Creating Repositories...", 200);
// Overwrite GlobalInfo parameters and prepare an AuditLog to catch and log errors
ApplicationFactory.AuditedDisplay(
Assembly.GetExecutingAssembly().GetName(),
() =>
{
// Show overwritten version numbers from GlobalInfo on SplashScreen
_splashScreenVM.VersionString = string.Format("v{0}.{1}.{2}",
GlobalInfo.Version_Major, GlobalInfo.Version_Minor, GlobalInfo.Version_Build);
// Initiate ViewModel with new repositories
var viewModel = new PlanningGridViewModel(new MyCompany.Repositories.PlanningGridHeadersRepository(),
new MyCompany.Repositories.PlanningGridLinesRepository(),
_splashScreenVM);
// Initiate View with ViewModel as the DataContext
var view = new PlanningGridView(viewModel);
// Subscribe to View's Activated event
view.Activated += new EventHandler(Window_Activated);
// Display View
view.ShowDialog();
});
}
/// <summary>
/// Closes SplashScreen when the Window's Activated event is raised.
/// </summary>
/// <param name="sender">The Window that has activated.</param>
/// <param name="e">Arguments for the Activated event.</param>
private void Window_Activated(object sender, EventArgs e)
{
_splashScreenVM.ClosingView = true;
}
Note that I have control over the SplashScreen's View by subscribing to the View's Activated event, and then closing the view with the "ClosingView" boolean INotifyProperty that sets the View's "Close" DependencyProperty in turn (sounds complicated, but once you get to know DependencyProperties, it becomes simple).
The point is, don't try to drive your navigation from RootFrame. Just view.Show() the RootFrame, and control the rest from RootFrameViewModel.
Hope this helps :-)

Related

Updating MainWindow Property from separate MVVM Module

I am looking to implement a "JQuery" style spinner in a WPF application. As this spinner will be triggered from some separate PRISM modules and I want the spinner to cover the whole application window I need to get access to a property in the MainWindow.
I have a property in the MainWindow but I am not able to see it from the other modules.
I have tried Application.Current.MainWindow but no luck.
I have also tried using the Application.Current.Properties[] but I do not know how to trigger a OnPropertyChanged event.
Can someone point me in the right direction please.
Update:
Here are some screenshots and a better description of what I want to do.
Ok, here is an example of my issue. I have an application that contains the following:
WPFApp
WPFApp.Module1
WPFApp.Module2
WPFApp.Module3
WPFApp.Module4
The WPFApp MainWindow contains 2 regions, a Menu region (on the left hand side) and a Content region.
Each module contains 2 views:
A menu view, which is loaded into the MainWindow Menu region
A content view, which is loaded into the MainWindow Content region
In each of the Module content views I want to perform a task that will take a few seconds and while the task is being performed I want to show a “Ajax Style” spinner that will cover the whole application window.
To have used the spinner class detailed here: WPF Spinner via MVVM & Attached Properties
I have been able to use this by adding the AsyncNotifier.Trigger (detailed in the link above) to each of the modules content view, see below.
My problem is:
If I want the spinner to cover the whole application window then I need to add the AsyncNotifier.Trigger to the MainWindow. I also need to expose a property from the MainWindow responsible for showing the spinner and be able to access this from each of the modules.
Any ideas on how to do this?
Update:
Ok, I think I might be getting a bit further but still a little bit stuck on how everything fits together.
I have created my Interface and put it into my Infrastructure module so every other module can access it.
I am using the following piece of code to load my modules from my AggregateModuleCatalog class.
/// <summary>
/// this.Catalogs is a readonly collection of IModuleCatalog
/// Initializes the catalog, which may load and validate the modules.
/// </summary>
public void Initialize()
{
foreach (var catalog in this.Catalogs)
{
catalog.Initialize();
}
}
My problem is that I am unsure of where I should put the SpinnerViewModel? Should it be in my Main project.
Also, where should I pass in the singleton using Constructor injection?
I would look at implementing the mediator pattern here. This is already present in Prism in the form of the EventAggregator
Basically, it sounds like you want to publish control messages of a certain type to any subscribers - in this case you have:
Subscribers
MainWindow
Publishers
Module 1
Module 2
Module 3
The modules should publish a message to signal that they want to 'busy' the MainWindow whilst work is being done/module is being loaded etc
The key thing is that they should not be aware of the MainWindow in any way. This keeps the solution decoupled, MVVM friendly, reusable and easily maintained. This method uses dependency injection, which is usually a good idea :)
In order for this to work, the modules need to take a dependency on the EventAggregator service (there is an IEventAggregator interface in PRISM which is your service interface) and should then publish messages to it
First you need an event which will serve as the message class. Obviously this needs to be visible from both the publisher and subscriber, so may need to sit in an external reference. This needs to inherit from CompositeWpfEvent and you should provide a generic argument for the message payload.
public class BusyUserInterfaceEvent : CompositeWpfEvent<bool>
{
}
(I suppose you could just directly use CompositeWpfEvent<bool> for this which may work equally as well but is not specific enough if you decide to include more event types which use bool as payloads)
Then you need to publish events of the above type from your module
public class Module1
{
private IEventAggregator _eventAggregator;
public Module1(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
public DoSomeWork()
{
// Busy the UI by publishing the above event
_eventAggregator.GetEvent<BusyUserInterfaceEvent>().Publish(true);
}
public FinishDoingSomeWork()
{
// Unbusy the UI by publishing the above event with 'false'
_eventAggregator.GetEvent<BusyUserInterfaceEvent>().Publish(false);
}
}
The MainWindow should also take a dependency on the EventAggregator service and should subscribe to any messages of a certain type. If work is to be done on the UI thread (as it is in this case) you should subscribe using ThreadOption.UIThread
public class MainWindow
{
private IEventAggregator _eventAggregator;
public MainWindow(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
// Subscribe to any messages of the defined type on the UI thread
// The BusyUserInterface method will handle the event
_eventAggregator.GetEvent<BusyUserInterfaceEvent>().Subscribe(BusyUserInterface, ThreadOption.UIThread);
}
public BusyUserInterface(bool busy)
{
// Toggle the UI - pseudocode here!
TickerActive = busy;
}
}
Whilst I have used this pattern in a couple of frameworks successfully before, I admit that I haven't used PRISM so the above code might not be quite right, however, the docs seem concise:
http://msdn.microsoft.com/en-us/library/ff921122.aspx
Using this pattern decouples any components, so it's easy to stop the MainWindow from subscribing to the events and to move the subscriber to a parent component if needs be without touching the implementation of the publishers.
This means you could swap the MainWindow for a completely different window and load your modules in during some part of your application lifecycle, and as long as it was subscribing to these event types it would still busy/unbusy based on the message publishing from the modules. It's a very flexible solution.
PRISMs implementation also provides subscription filtering so you can selectively listen for events.
Additionally, since you are aggregating events there's a lot you can do with this to allow inter-module communication. The only challenge you have is making sure everyone is aware of the message types, which is why an external dependency may be needed to hold the message types
Am I missing something? Should this be as easy as this?
App.xaml.cs
public partial class App
{
public static Window MainWindow {get;set;}
}
MainWindow.xaml.cs
public MainWindow()
{
App.MainWindow = this;
InitializeComponent();
}
public void Spinner(bool show)
{
// Your code here
}
Now in MVVM, the idea would be the same, just don't do it in code behind. You could create a singleton (Singleton has a static instance) for the spinner to bind to and then any module can mess with the singleton property.
If you like Dependency Injection, create an interface, create an object that implements that interface, have the Spinner bind to that object. Then inject that object as the interface into the modules when they are loaded.
Here is a singleton ViewModel that implements an interface. Your Prism projects need to know only about the interface. But your code that loads your Prism modules should know about both the interface and the singleton. Then when the Prism Modules are loaded, the singleton would be passed in (feel free to use whichever injection method is easiest: constructor injection, method injection, or property injection).
using System.ComponentModel;
namespace ExampleCode
{
/// <summary>
/// Interface for managing a spinner
/// </summary>
public interface IManageASpinner
{
bool IsVisible {get;set;}
// Add any other properties or methods you might need.
}
/// <summary>
/// A singleton spinner ViewModel for the main window spiner
/// </summary>
public class SpinnerViewModel : INotifyPropertyChanged, IManageASpinner
{ #region Singleton and Constructor
/// <summary>
/// Singleton instance
/// </summary>
public static SpinnerViewModel Instance
{
get { return _Instance ?? (_Instance = new SpinnerViewModel()); }
} private static SpinnerViewModel _Instance;
/// <summary>
/// Private constructor to prevent multiple instances
/// </summary>
private SpinnerViewModel()
{
}
#endregion
#region Properties
/// <summary>
/// Is the spinner visible or not?
/// Xaml Binding: {Binding Source={x:Static svm:SpinnerViewModel.Instance}, Path=IsVisible}
/// </summary>
public bool IsVisible
{
get { return _IsVisible; }
set
{
_IsVisible = value;
OnPropertyChanged("IsVisible");
}
} private bool _IsVisible;
// Add any other properties you might want to include
// such as IsSpinning, etc..
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
}
Hope you can connect the dots from here.
Added Feb 12
Maybe use Method injection? I am not sure you can though.Can you edit the IModuleCatalog interface or the Catalog class? If not, skip this and go to the next idea.
foreach (var catalog in this.Catalogs)
{
catalog.Initialize(SpinnerViewModel.Instance);
}
Maybe your Catalogs should implement a second interface and use property injection instead of constructor injection.
public interface IHaveASpinner
{
public IManageASpinner Spinner {get;set;}
}
Now, have all your catalogs that implement IModuleCatalog also implement IHaveASpinner.
Then have your code do this:
foreach (var catalog in this.Catalogs)
{
catalog.Initialize();
var needSpinner = catalog as IHaveASpinner;
if (needSpinner != null)
{
needSpinner.Spinner = SpinnerViewModel.Instance;
}
}
Where I should put the SpinnerViewModel?
I would suggest your main project. It depends on your design. Do all the modules already reference your main project? If so, then put it in your main project. If not, then perhaps there is a project the main project and all the modules already reference? You could even create a separate project/dll specific for your spinner, but only if you have to as that might be overboard unless you have a strict design with rules for what can reference what.
You shouldn't need a VM just for the spinner. What I do is have the spinner itself (animation, icon, whatever) in my main window's view, and have the main window VM implement the following interface:
public interface IApplicationBusyIndicator
{
bool IsBusy { get; set; }
}
Using your favourite IoC container (I use Castle), I just inject IApplicationBusyIndicator into other VMs. Whenever one of these starts some long-running task it just sets IsBusy to true (then back to false when it's finished). The busy indicator in the main window view has its Visibility property bound to the main window VMs IsBusy property (using a boolean-to-visibility converter of course).
If, in your scenario, there is some architectural constraint preventing VMs in other modules from being injected with an instance of the main window VM, then you could use Prism's event aggregator to publish a message to say "application busy", and have the main window VM subscribe to the event, and show the busy indicator.
If you are looking to create more of some "visual" style / interface of a spinner, I suggest looking into creating your own subclass spinner (if you need special functionality behind it), and then create your own customized style for its look / behavior. Then when you put the spinner on the forms, just use a spinner of the class/style you designate and they should function for you well.
Here is one of my own learning about styles, but simple label
Another link where I helped someone walking through creating their own custom class/style which MIGHT be what you are trying to globally implement.
And a link to template / style defaults to learn from

How should I communicate between ViewModels?

I am using MVVM Light and have used the packaged messenger system to communicate between view models, however I have hit a bit of a dilemma! Basically when a user clicks on a customer record the corresponding view is opened and with it the CustomerViewModel is instantiated. At this point the CustomerViewModel requires the selected customers ID from the previous view model (ViewAllCustomersViewModel) so that it can get selected customers info which the view binds to (still following?). So initially my thought was too send that ID in a message from the ViewAllCustomersViewModel (where the customer to be viewed is selected) to the CustomerViewModel... HOWEVER, the CustomerViewModel is not instantiated to be able to receive the message until the view is loaded (at which point the message has already been broadcast)!
So, what would be the best way to solve this issue? So far I have considered the CustomerViewModel sending a request to the ViewAllCustomersViewModel once it has been instantiated (basically saying "I am ready to receive the message"), and then the ViewAllCustomersViewModel sending the ID back to the CustomerViewModel... but is this a necessary approach to solve this? It seems a bit ugly to me!
Otherwise, I was thinking is there another way to communicate which can account for the issue I am having? But then isn't this the whole point of the messaging system... to be able to communicate between view models? OR can I force the view model to be instantiated on start up? If so, how would that affect the ViewModelLocator?
I hope I have outlined the issue clearly, I have used fictional view model names for the purpose of the explanation... and please feel free to edit or suggest any additional information that you would like me to add!
Did you try to communicate via your model? I was not able to read your topic until the end but this is how I communicate between ViewModels.
Both View Models have the instance of session.
public ViewModel1(ISession session)
{
_session = session;
}
public ViewModel2(ISession session)
{
_session = session;
}
This way, when you test your application in BDD (behavior driven development), you can test your application without the view. The glue is the model.
As you can see on this picture, you should be able to test your application without the view.
I came across the same situation where two view model is communicating each other. I have used Microsoft PRISM framework to publish and Subscribe.
In your case CustomerViewModel is parent View and ViewAllCustomersViewModel is child view.
Download prism framework "Microsoft.Practices.Prism.PubSubEvents.dll" from https://www.nuget.org/packages/Prism.PubSubEvents/
Add prism reference to your project "Microsoft.Practices.Prism.PubSubEvents.dll"
Create some custom class which is used for communication modem.
class Notifications : PubSubEvent<string>
{
}
Create IEventAggregator eventAggregator singleton instance for your project and initialize it.
public sealed class SessionInfo
{
public IEventAggregator eventHanlder;
private SessionInfo (){
}
private static SessionInfo _instance = null;
public static SessionInfo Instance{
get{
lock (lockObj){
if (_instance == null) {
_instance = new SessionInfo ();
_instance.eventHanlder= new EventAggregator();
}
}
return _instance;
}
}
}
Go to Popover model (ViewAllCustomersViwModel) button events handling and below codes in it.Now it has been published.
In ViewAllCustomersViwModel.cs:
public void OnSelectedItem(Item item)
{
SessionInfo.Instance.eventHanlder.GetEvent<Notification>().Publish(item.id);
}
These event aggregator has to be subscribe where it is needed. So add below code on your Parent View model (CustomerViewModel)
CustomerViewModel.cs
public class CustomerViewModel
{
public CustomerViewModel()
{
SessionInfo.Instance.eventHanlder.GetEvent<Notifications>().Subscribe(OnReceivedNotification);
}
//Handling the notification
public void OnReceivedNotification(string itemId)
{
Debug.WriteLine("Item Id is :" + itemId);
}
}
For more information:
https://sites.google.com/site/greateindiaclub/mobil-apps/windows8/communicationbetweenviewmodelsinwindows8mvvmpattern
I believe that standard way is to pass it through View.
Depending on how you instantiate your views, it could be DependencyProperty to bind in XAML, constructor parameter, or anything else.
Then View passes it to it's ViewModel (pushes it to VM, not the way around: ViewModel should not be aware of View). This way you get a standalone closed component (your View), and external code does not know about it's internal implementation (which is ViewModel).
In XAML it can be something like
<ListBox x:Name="customers" />
<CustomerView Customer="{Binding SelectedItem, ElementName=customers}" />
And then in CustomerPropertyChanged handler you push value to the ViewModel.
Personally, I used to use the MVVM-Light Messenger, but found I had way to many messages flying around, and I didn't like the feeling of using a "magical" messenger. What I did is outlined as the answer to the following link
Best Way to Pass Data to new ViewModel when it is initiated.
Now I warn you, I answered my own question, and nobody verfied it as good or bad practice, however it works for my situation and has elimnated the need for MVVM-Light Messenger. Because my program uses multiple threads in my implementation I changed all the entries in the repository to Dictionarys with the CurrentThread.ManagedThreadId as the Key.
So far I have considered the CustomerViewModel sending a request to
the ViewAllCustomersViewModel once it has been instantiated (basically
saying "I am ready to receive the message"), and then the
ViewAllCustomersViewModel sending the ID back to the
CustomerViewModel...
I would continue with this idea. It keeps the Views, ViewModels and Models all separate and unknowing of the others unlike other answers. Not to say other answers are wrong, or even bad, your option can be defined one or any of: personal preference, team convention, long-term MVVM goal of replacing components/modules, and complexity/ease of coding.
A side-effect to your idea I quoted above, which I prefer, is that you can request at any time as you've already set it up. So if you change when to perform that request very easily, or if you need to request updates, you use the same communication architecture in place.
Lastly, I prefer it because if you change your models or views or viewmodels - you keep the same core concept of communicating information between components.

Open/Close View from ViewModel

I have an AddClientViewModel which is referenced by 2 views (AddClientView and SuggestedAddressesView). The AddClientView is a form which has a field for an address. The form has a validate button which validates the entered address using Geocoding. If more than one address is returned, the SuggestedAddressesView opens.
Here is how I am currently doing it:
AddClientViewModel:
private void ValidateExecute(object obj)
{
SuggestedAddresses = new ObservableCollection<DBHelper.GeocodeService.GeocodeResult>(GeoCodeTest.SuggestedAddress(FormattedAddress));
....
if (SuggestedAddresses.Count > 0)
{
var window = new SuggestedAddressesView(this);
window.DataContext = this;
window.Show();
}
}
Here is the SuggestedAddressesView constructor where AddClientViewModel inherits from ViewModelBase
public SuggestedAddressesView(ViewModelBase viewModel)
{
InitializeComponent();
viewModel.ClosingRequest += (sender, e) => this.Close();
}
The other problem I am having is when I call OnClosingRequest() from the AddClientViewModel...both the AddClientView and SuggestedAddressesView closes. I know this happens because both views reference the same ViewModel. This is not the behaviour I want. I would like to be able to independently close either Window.
Is opening a View from the ViewModel proper MVVM structure and how would I go about being able to close windows independently?
As soon as you refer to UI elements(In this case the View) from the VM, you're going against suggested MVVM Guidelines. With just that we can know creating the Window object in the VM is wrong.
So now onto rectifying this:
Firstly try to keep a 1 View <-> 1 VM in your application. It's cleaner and allows you to switch out View implementations with the same logic very easily. Adding multiple View's to the same VM even if not "ground-breaking" just makes it clumsy.
So now you got AddClientView and SuggestedAddressesView with their own VM. Great!
Implementing a View Open/Close from the VM:
Since we cannot access the View directly from our VM(to comply with standards), we can use approaches such as using a Messenger(MVVM Light), EventAggregator(PRISM) and so on to send a "message" from the VM to the View when you need to open/close a View and do the actual operation in the View.
This way the VM just initiates the message and can be unit-tested fine for the same operation and does not reference any UI elements.
Using a "Messenger" approach to handle View open:
As per your Logic, it is the AddClientViewModel which would have to ask for the SuggestedAddressesView to be opened.
Thus when you detect SuggestedAddresses.Count > 0, you would send a message to the AddClientView asking it to open up the SuggestedAddressesView.
In AddClientView.xaml.cs upon receiving this message, you would do what you're currently doing in the VM. Create an object of SuggestedAddressesView and call .Show() on it.
One extra step you would add in the above step's process is to assign the DataContext of SuggestedAddressesView as SuggestedAddressesViewModel.
That's it. Now what you have is, when AddClientViewModel wants SuggestedAddressesView shown, it sends a message to it's own View and the View in-turn creates and shows the SuggestedAddressesView. This way the VM does not reference any View's and we keep to holding MVVM standards.
Using a "Messenger" approach to handle View close:
Closing a View is pretty simple. Again when you need to close the View from the VM, you send a message to it's own View asking for it to be closed.
On receiving this message, the View pretty much closes itself via .Hide() / .Close() or however else you want to get rid of it.
In this each VM handles it's own View's closing and you don't have any inter-connected dependencies.
You can use this as a start point to guide you in handling "messages" for this approach. it has an attached download you can get and see how the Messenger works. This is with MVVM Light, if you do not use it or use something else/ your own MVVM implementation, use it as a guide to help get to what you need.
you can use RelayCommand so that you send the parameter as follows:
Command="{Binding CloseWindowCommand, Mode=OneWay}"
CommandParameter="{Binding ElementName=TestWindow}"
By using this you can close the individual views.
Example:
public ICommand CloseCommand
{
get
{
return new RelayCommand(OnClose, IsEnable);
}
}
public void OnClose(object param)
{
AddClientView/SuggestedAddressesView Obj = param as AddClientView/SuggestedAddressesView;
obj.Close();
}
To open window from ViewModel:
Create NavigationService.cs class for opening window:
Let NavigationService.cs
Now put following code in that class file.
public void ShowWindow1Screen(Window1ViewModel window1ViewModel)
{
Window1= new Window1();
Window1.DataContext = window1ViewModel;
Window1.Owner = Window1View;
Window1.ShowDialog();
}
then.
Create instance of NavigationService.cs class MainWindowViewModel file.
Then
Window1ViewModel window1ViewModel = new Vindow1ViewModel();
window1ViewModel.Name = MainWindowTextValue;
NavigationService navigationService = new NavigationService();
navigationService.ShowWindow1Screen(window1ViewModel);

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