MVVM Modal Dialog using Service Locator - wpf

I am developing a WPF application that follows MVVM pattern. To display modal dialogs, I am trying to follow the way the following articles suggests.
http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern?fid=1541292&fr=26#xx0xx
But in these kind of articles, What I have observed, that ShowDialog method of DialogService interface is called from MainWindowViewModel.
Situation in my application is slightly different.
MainWindow.xaml contains a user control say ChildView that contains a button Add.
MainWindowViewModel contains another ViewModel say ChildVM that is bind with ChildView.
ChildVM contains AddCommand, and I need to display Modal Dialog when AddExecute method
corresponding to AddCommand is called.
How can I accomplish that?
Edited Code
private Window FindOwnerWindow(object viewModel)
{
FrameworkElement view = null;
// Windows and UserControls are registered as view.
// So all the active windows and userControls are contained in views
foreach (FrameworkElement viewIterator in views)
{
// Check whether the view is an Window
// If the view is an window and dataContext of the window, matches
// with the viewModel, then set view = viewIterator
Window viewWindow = viewIterator as Window;
if (null != viewWindow)
{
if (true == ReferenceEquals(viewWindow.DataContext, viewModel))
{
view = viewWindow;
break;
}
}
else
{
// Check whether the view is an UserControl
// If the view is an UserControl and Content of the userControl, matches
// with the viewModel, then set view = userControl
// In case the view is an user control, then find the Window that contains the
// user control and set it as owner
System.Windows.Controls.UserControl userControl = viewIterator as System.Windows.Controls.UserControl;
if (null != userControl)
{
if (true == ReferenceEquals(userControl.Content, viewModel))
{
view = userControl;
break;
}
}
}
}
if (view == null)
{
throw new ArgumentException("Viewmodel is not referenced by any registered View.");
}
// Get owner window
Window owner = view as Window;
if (owner == null)
{
owner = Window.GetWindow(view);
}
// Make sure owner window was found
if (owner == null)
{
throw new InvalidOperationException("View is not contained within a Window.");
}
return owner;
}

Ok, if I get you right, you want to open the modal dialog not from the MainWindowViewModel, but from a different ChildViewModel?
Have a look at the constructors of the MainWindowViewModel of the CodeProject article that you have linked:
The ViewModel has a constructor with the following signature:
public MainWindowViewModel(
IDialogService dialogService,
IPersonService personService,
Func<IOpenFileDialog> openFileDialogFactory)
This means that for construction you need the service which shows the modal dialogs, another service (personService), which doesn't matter here and a factory for the specific dialog to open files, openFileDialogFactory.
In order to use the service, which is the core part of the article, a simple ServiceLocator is implemented and a default constructor is defined, which uses the ServiceLocator to get instances of the services that the ViewModel needs:
public MainWindowViewModel()
: this(
ServiceLocator.Resolve<IDialogService>(),
ServiceLocator.Resolve<IPersonService>(),
() => ServiceLocator.Resolve<IOpenFileDialog>())
{}
This is possible, because ServiceLocator is static. Alternatively, you could set the local field for the services in the constructor, using the ServiceLocator. The above approach is better, because it allows you to set the services yourself, if you don't want to use the ServiceLocator.
You can do exactly the same in your own ChildViewModel.
public ChildViewModel(IDialogService dialogService)
{
_dialogService = dialogService;
}
Create an default constructor, which calls the above constructor with the service instance resolved from the ServiceLocator:
public ChildViewModel() : this(ServiceLocator.Resolve<IDialogService>()) {}
And now you can use the service from anywhere in your ChildViewModel like this:
_dialogService.ShowDialog<WhateverDialog>(this, vmForDialog);
In order to find the owner window of your view, which is not a view itself, you need to modify the FindOwnerWindow method of the DialogService to find the parent window of the view, rather than expecting a Window as the view itself. You can use the VisualTreeHelper to do so:
private Window FindOwnerWindow(object viewModel)
{
var view = views.SingleOrDefault(v => ReferenceEquals(v.DataContext, viewModel));
if (view == null)
{
throw new ArgumentException("Viewmodel is not referenced by any registered View.");
}
DependencyObject owner = view;
// Iterate through parents until a window is found,
// if the view is not a window itself
while (!(owner is Window))
{
owner = VisualTreeHelper.GetParent(owner);
if (owner == null)
throw new Exception("No window found owning the view.");
}
// Make sure owner window was found
if (owner == null)
{
throw new InvalidOperationException("View is not contained within a Window.");
}
return (Window) owner;
}
You still need to register the UserControl though, setting the attached property on the UserControl:
<UserControl x:Class="ChildView"
...
Service:DialogService.IsRegisteredView="True">
...
</UserControl>
As far as I can tell, this works.
Additional information:
To accomplish the same thing, I use the PRISM framework, which comes with a lot of functionality for exactly this kind of decoupling, Inversion of Control (IoC) and dependency injection (DI). Maybe it is worth it to have a look at it for you, too.
Hope this helps!
Edited to consider the comment.

See if you like this idea... I'm using Castle Windsor and Prism, so your mileage may vary, but the concepts should be the same with another MVVM and IoC.
You start with your MainViewModel.cs that wants to open a modal dialog
var view = ServiceLocator.Current.GetInstance<SomeDialogView>();
view.ShowDialog();
but of course it's not honoring what you set in MainView.xaml
WindowStartupLocation="CenterOwner"
Drat!
But wait, couldn't ServiceLocator just give me the MainView?
var view = ServiceLocator.Current.GetInstance<SomeDialogView>();
view.Owner = ServiceLocator.Current.GetInstance<MainView>(); // sure, why not?
view.ShowDialog();
This line throws an exception with my IoC configuration, since my IoC registers views to have a "transient lifetime". In Castle Windsor, this means each request is provided a brand new instance, and I need the MainView instance itself, not a new one that hasn't been shown.
But by simply changing the registration from every view being "transient"
container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn")
.InNamespace("WhateverNamespaceTheyreIn")
.LifestyleTransient());
to be slightly more discriminating by using the fluent Unless() and If()
container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn")
.InNamespace("WhateverNamespaceTheyreIn")
.Unless(type => type == typeof(MainView))
.LifestyleTransient()); // all as before but MainView.
container.Register(Classes.FromAssemblyNamed("WhateverAssemblyTheyreIn")
.InNamespace("WhateverNamespaceTheyreIn")
.If(type => type == typeof(MainView))
.LifestyleSingleton()); // set MainView to singleton!
the MainView provided is the one we wanted!
HTH

Related

Prism MVVM - Showing Pop up window from viewmodel on a button click command in WPF

I have a datagrid in my view(UserControl) in which I have a button, The requirement is to show a window on click of that button. I achieved it by creating an object for the window and calling it's show method and yes it's a wrong way, So, I want a standard way of achieving it.
I'm using Prism framework, So I mapped the window view with Window viewmodel using ViewModelLocationProvider in the windowviewmodel.
var table = new Window();
table.Content = new windowViewModel();
table.Show();
This is the code I implemented in the main viewmodel on click of the button.
It is actually opening a window, but, view is not getting loaded on the window.
Use the IDialogService Introduced in Prism 7
first the dialog view or ViewModel needs to implement the IDialogAware interface like this:
class PopupViewModel : BindableBase, IDialogAware
{
public string Title => "Popup";
public event Action<IDialogResult> RequestClose;
public bool CanCloseDialog()
{
return true;
}
public void OnDialogClosed()
{
}
public DelegateCommand OkCommand => new DelegateCommand(() =>
{
//you can also pass any parameters you want to back pass to the caller
RequestClose.Invoke(new DialogResult(ButtonResult.OK));
});
public void OnDialogOpened(IDialogParameters parameters)
{
//here you get the parameters you passed;
}
}
then you need to register your view like this
containerRegistry.RegisterDialog<Views.Popup>();
to use you inject into your ViewModel the IDialogService and call it
this.dialogService.ShowDialog(nameof(Views.Popup), dialogParmaters, (result)=>
{
if(result.Result == ButtonResult.OK)
{
}
});
you can also register your custom window if you want the window needs to implement IDialogWindow
containerRegistry.RegisterDialogWindow<MyDialogWindow>();
First, you want the view model to be the DataContext (the Content is in fact the view). I'd advise againt the ViewModelLocator here, because it makes it impossible to pass parameters to the new window. Probably, you want to show different content depending on which row you click the button in.
new Window { DataContext = new WindowViewModel( myRow ) }.Show();
Second, there's nothing wrong with this approach but for one minor remark: put the code that shows the window in a service. You want to unittest your view models and that doesn't go well with opening real windows.
public interface IWindowService
{
void ShowWindow( SomeParameter parameter );
}
If you inject that into your view model, you can still test it (verify that ShowWindow is called).
Third, remove calls to new and replace them with calls to injected factories.
var viewModel = _viewModelFactory.Create( "some parameter" );
is a lot nicer than
var viewModel = new ViewModel( "some parameter", new ADependency( new OtherDependency(), new SomeService() );
not to speak of the difficulties if singletons are involved.

Why is Caliburn.Micro's (used with modern-ui) OnActivate not being called after ActivateItem?

I'm using Caliburn.Micro and Modern-UI in a WPF application. On a "page" inside the modern-ui framework (which is a UserControl), I am trying to use a Conductor to switch the current view. Here is what I've got so far:
NOTE: Namespaces removed from source for brevity
XAML of "page" inside modern-ui window
<UserControl x:Class="ShellView">
<ContentControl x:Name="ActiveItem" />
</UserControl>
Source for ShellViewModel (the conductor)
[Export]
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
private readonly Test1ViewModel m_TestView1;
private readonly Test2ViewModel m_TestView2;
public ShellViewModel()
{
this.m_TestView1 = new Test1ViewModel();
this.m_TestView2 = new Test2ViewModel();
this.ActivateItem(this.m_TestView1);
}
}
The XAML for Test1View doesn't have anything in it right now, just normal UserControl stuff.
Source for Test1ViewModel
public class Test1ViewModel : Screen
{
protected override void OnActivate()
{
//This DOES NOT show or fire, I even put a breakpoint to double check
Debug.Print("This should show in output");
}
}
when ActivateItem is called, OnActivate does not fire at all. I even tried calling ConductWith(this) on the view model Test1ViewModel in the conductor but that didn't work. I am using Modern-UI which might be important because this same thing works in a different project that is not using Modern-UI. Oh and when ActivateItem is called, the appropriate view does show on the screen (I added some buttons for verification that the view does change).
Any ideas as to why the UserControl will show in the ContentControl after calling ActivateItem but OnActivate does not fire at all?
One more thing... This might also have something to do with it, but if it does I don't know why or how to fix it. I'm using this class to make the view first Modern-UI work well with Caliburn.Micro's model first approach.
internal class ModernContentLoader : DefaultContentLoader
{
protected override object LoadContent(Uri uri)
{
object content = base.LoadContent(uri);
if (content == null)
return null;
// Locate the right viewmodel for this view
object vm = ViewModelLocator.LocateForView(content);
if (vm == null)
return content;
// Bind it up with CM magic
if (content is DependencyObject)
ViewModelBinder.Bind(vm, content as DependencyObject, null);
return content;
}
}
I went and downloaded the source for Caliburn.Micro and debugged the whole thing like I should have done from the start.
Turns out, because of the way Modern-UI handles navigation the Conductor (unless it's the main shell view attached to the main window) doesn't get activated. In other words, it never knows that it's being shown and the source for Caliburn checks to make sure the Conductor is active before it will allow activating a new view. For some reason the view is displayed just fine but the View Model (Screen) never gets activated or instantiated. In my case it is instantiated because of the Modern-UI+Caliburn.Micro view binding hack.
I did get it to finally work, so if anyone is interested, this is how to get ActivateItem with a Conductor to work inside Modern-UI.
Add the following line of code to your Conductor's constructor or the Modern-UI method OnNavigatedTo
ScreenExtensions.TryActivate(this);
This is part of Caliburn-Micro and will allow your Conductor to activate items properly. If you're using it inside the OnNavigatedTo you might want to add this line to your OnNavigatedFrom method:
ScreenExtensions.TryDeactivate(this, true);

Prism, Unity, and Multiple Views ala MDI

I'm trying to create an application similar to Visual Studio in that we have a main content area (i.e. where documents are displayed in a TabControl, not a true MDI interface), with a menu on the side.
So far, I have everything working, except the content. My goal is that when a user double clicks on an item in the navigation menu on the side, it opens the document in the Content region. This works, but every time I double click it spawns a new instance of that same view. There's a chance that I could have multiple views of the same type (but different "names") in the TabControl content container.
Right now, my code looks something like this...
IRegion contentRegion = IRegionManager.Regions[RegionNames.ContentRegion];
object view = IUnityContainer.Resolve(viewModel.ViewType, viewModel.UniqueName);
if (!IUnityContainer.IsRegistered(viewModel.ViewType, viewModel.UniqueName))
{
IUnityContainer.RegisterInstance(viewModel.UniqueName, view);
contentRegion.Add(view);
}
contentRegion.Activate(view);
However, it appears that the view is never registered, even though I register it... I imagine I'm probably doing this wrong -- is there another way to do this? (re: the right way)
So, the problem was trying to do it this entire way. The smart method (for anyone else trying to do this) is to make use of Prism the correct way.
What I ended up doing was instead Navigating by:
1. In the Navigation Menu, constructing a UriQuery (included in Prism) with the UniqueID of the view I want to display (which is guaranteed to be unique) and adding that to the View I wanted to navigate to, i.e.:
IRegionManager.RequestNavigate(RegionNames.ContentRegion, new Uri(ViewNames.MyViewName + query.ToString(), UriKind.Relative));
where query is the UriQuery object.
2. Register the View and ViewName in the Module via:
IUnityContainer container = ServiceLocator.Current.GetInstance<IUnityContainer>();
container.RegisterType<object, MyView>(Infrastructure.ViewNames.MyViewName);
3. In the View, make sure the ViewModel is a parameter on the constructor. Let Prism inject this manually for us. Inside the constructor, make sure you set the DataContext to the incoming ViewModel.
4. Finally, make sure your ViewModel implements INavigationAware interface... This is a very simple implementation of it (UniqueID is a property on the ViewModel):
public virtual bool IsNavigationTarget(NavigationContext navigationContext)
{
if (navigationContext.Parameters != null)
return (navigationContext.Parameters["UniqueID"] == UniqueID);
return false;
}
public virtual void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public virtual void OnNavigatedTo(NavigationContext navigationContext)
{
if (navigationContext.Parameters != null)
UniqueID = navigationContext.Parameters["UniqueID"];
}
From here, Prism will ensure that only one view of your "UniqueID" will exists, while allowing for others of the same view, but different ViewModel (or data for that ViewModel, i.e. viewing two users in different tabs, but both use the same templated view).

wpf mvvm passing parameters between viewmodels using commands

This is my first attempt at MVVM. My application's core is loosely based Josh Smith's msdn article. And I am also using the mvvm light framework.
I have a main window containing a command list area and a workspace area which shows usercontrols/views as tabitems, each usercontrol has a corresponding viewmodel. The mainWindow also has a viewmodel containing my command list, and the workspace viewmodels have a base workspace viewmodel.
My default view has a master datagrid, of MappingSets, that can have one selected item. The commands launch new tabitems with views that handle MappingSet detail based on that selected item. I have a View/ViewModel that, depending on the command used should return either a tabitem for creating a new MappingSet with no existing data, or a tabitem containing the detail of the selected item for editing, or a tabitem containing detail the selected item as the base for a new MappingSet.
Having Set the scene, what I have not managed to work out is command dependent way to pass parameters, such as the identifier of the selected MappingSet object, to instantiate my viewmodel in one of the three states mentioned above? For instance would the mvvmlight messenger be appropriate for this task?
This is a perfect scenario for the messenger/eventaggregator. However, your message chain might be a bit convoluted. From what I'm understanding, your Main window holds a list of commands (like a menu or a ribbon). Here is how I see the chain of events.
You select a MappingSet from the datagrid, this causes a MappingSetSelected message to be fired (with a payload of the selected MappingSet)
The main window listens for that message and stores the currently selected MappingSet
When the user clicks the button a "EditMappingSet" or "CreateNewMappingSet" message is fired (or if the Window is responsible for creating the new views, it creates them itself).
If there are only three options, you could have them binding to three different commands and within the commands do the passing of your self-defined variable.
private RelayCommand _openMappingSetCommand;
//Command that one of your options is bound to
public ICommand ViewMappingSetOption1
{
get
{
if (_openMappingSetCommand == null)
{
_openMappingSetCommand = new RelayCommand(param => this.DoTabRequest("your parameter");
}
return _openMappingSetCommand ;
}
}
// Method that creates your viewmodel
private void DoTabRequest(parameterType parameter)
{
WorkspaceViewModel viewModel = null;
if (viewModel == null)
{
viewModel = (WorkspaceViewModel)Activator.CreateInstance(typeof (viewModelType), parameter);
this.Workspaces.Add(viewModel);
}
this.ActiveWorkspace = viewModel;
}
Then allow for that parameter on the constructor of your viewmodel and do whatever else you need based on that.

How to open a new window using MVVM Light Toolkit

I am using MVVM Light toolkit in my WPF application. I would like to know what is the best approach for opening a new window from an existing window. I have got this MainViewModel, which is responsible for MainWindow of my application. Now in the MainView, on a button click, I would like to open a second window on top of it. I have got RelayCommmand binded to the Button's Command. In the RelayCommand's method, I can create a new window object and simply call Show(), something like this:
var view2 = new view2()
view2.Show()
but I don't think the ViewModel should be responsible for creating the new view2 object. I have read this post WPF MVVM Get Parent from VIEW MODEL where Bugnion has suggested to pass message to the view1 from the viewmodel1 and then view1 should create the new view2. But I am not sure what does he actually mean by passing the message to the view1? How should the view1 handle the message? In it's code behind or what?
Regards,
Nabeel
Passing a message from ViewModel1 to View1 means to use the messaging capabilities in the MVVM Light Toolkit.
For example, your ViewModel1 could have a command called ShowView2Command, then it would send a message to display the view.
public class ViewModel1 : ViewModelBase
{
public RelayCommand ShowView2Command { private set; get; }
public ViewModel1() : base()
{
ShowView2Command = new RelayCommand(ShowView2CommandExecute);
}
public void ShowView2CommandExecute()
{
Messenger.Default.Send(new NotificationMessage("ShowView2"));
}
}
View1 would register to receive messages in its code behind and display View2 when it receives the correct message.
public partial class View1 : UserControl
{
public View1()
{
InitializeComponent();
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
}
private void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == "ShowView2")
{
var view2 = new view2();
view2.Show();
}
}
}
Why do you go this route? Its simple. If you replace your button with a toggleButton, or a hyperlink, or any other number of button-like controls, you don't need to update your "code behind" - its a basic principle of the MVVM pattern. In your new toggleButton (or whatever), you still end up binding to the same exact Command.
For example, I'm creating a project for a client who wants to have 2 UI's - one is going to be fundamentally different in every way, in terms of presentation. Horizontal tabs vs Vertical RadPanelBar (think Accordion) for navigation. Both of these views can point to the same viewModel - when a user clicks the Work Order tab in View 1, it fires the same "WorkOrderCommand" that's fired in the Work Order Header in the panel bar.
In a code-behind model, you'd have to code two separate events. Here you only have to code one.
Furthermore, it allows a designer using Blend to create any layout they want. As long as they have the hooks (EventToCommand control) in place, myself (as a developer) couldn't care less what the final product looks like.
Loose coupling is incredibly powerful.
You can do in this way like you need to create some events and register those in view and call these in view model.and open that pop up window.
Like This example
public class Mainclass : MainView
{
public delegate abc RegisterPopUp(abc A);
public RegisterPopUp POpUpEvent ;
public RelayCommand ShowCommand { private set; get; }
public void ShowCommand()
{
ShowCommand("Your parameter");
}
}
inside the view MainView mn=new MainView();
Register the event here like thake mn.POpUpEvent += than click on tab button double time
and in registers popup method right the code for opening the pop up window.
Unless I am missing the point here - if I were to use the code behind, then why not directly implement button_click event and open the second view?
What Bugnion seems to be suggesting is view1 -> button click -> relay command -> viewmodel1 -> message -> view1 -> view1.cs -> open view 2.
You are going to sacrifice testability anyhow by writing code-behind, so why take such a long route?
You can abstract the view specific features into services using generic interface. In the view layer you can provide concrete instances of these services and build view models using the IoC container and Dependency Injection technique.
In your case you can build an interface IWindowManager or something similar which has the required method. This can be implmented in your view layer. I wrote a small blog post recently demonstrating how to abstract the dialog behaviour out of view model. Similar apporach can be used for any user interface related service like Navigation, MessageBoxes etc.
This link might be helpful for you http://nileshgule.blogspot.com/2011/05/silverlight-use-dialogservice-to.html
Many people also use the approach of firing events from view models which are subscribed on the view.cs file and from there the MessageBox or any other UI related action is performed. I personally like the approach of injecting services because then you can provide multiple implementations of the same service. A simple example would be how navigation is handled in Silverlight and Windows Phone 7 applications. You can use the same view model but inject different implementations of the Navigation service based on the application type.
I find the best way to approach this, is opening and closing the window from the ViewModel. As this link suggests,
Create a DialogCloser class
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty = DependencyProperty.RegisterAttached("DialogResult", typeof(bool?), typeof(DialogCloser), new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null) window.Close();
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
Create a Base ViewModel inheriting from GalaSoft.MvvmLight.ViewModelBase with there additional members. Once done, use this viewmodel as base for other viewmodels.
bool? _closeWindowFlag;
public bool? CloseWindowFlag
{
get { return _closeWindowFlag; }
set
{
_closeWindowFlag = value;
RaisePropertyChanged("CloseWindowFlag");
}
}
public virtual void CloseWindow(bool? result = true)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() =>
{
CloseWindowFlag = CloseWindowFlag == null ? true : !CloseWindowFlag;
}));
}
In the view, Bind the DialogCloser.DialogResult dependency property with the CloseWindowFlag property in the base viewmodel.
Then you can open/close/hide the window from the viewmodel.

Resources