How can I open/close a new window in WPF without violating rules of the MVVM pattern?
I just want to mimic the login module of ms office outlook.
I've already read this article, but there are an error in passing a parameter confirmation
I'm currently using prism 5.0.
Update
What lead me to put another answer was the inability to apply the accepted answer on my project which using the Prism 6,
but after putting the original answer (see it below) and discussing it in comments, I discovered that the core problem was: The Prism 6 changed the namespaces of some classes, all the classes which used in the accepted answer is still exists in Prism 6, but in another dlls and namespaces
So if you are using Prism 6, you can apply the accepted answer with those modifications
first replace those namesapces
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:pi="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
xmlns:pit="clr-namespace:Microsoft.Practices.Prism.Interactivity.InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"
with the following namespaces
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:prism="http://prismlibrary.com/"
second update the XAML as the following
<Button Content="Options" Command="{Binding OpenConnectionOptionsCommand}">
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding OptionSettingConfirmationRequest, Mode=OneWay}" >
<prism:PopupWindowAction>
<prism:PopupWindowAction.WindowContent>
<views:CustomPopupView />
</prism:PopupWindowAction.WindowContent>
</prism:PopupWindowAction>
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
</Button>
NOTE 1
Make sure that the view you are using (in the example above <views:CustomPopupWindow>
) is NOT a window, or you will receive an exception.
NOTE 2
These modifications are required ONLY in case you are using Prism 6. because (As I said in the Original Answer below) the dlls which used by the accepted answer is deprecated in Prism 6.
NOTE 3
Make sure you are referencing the Prism.Wpf
dll, which could be downloaded from Nuget.
Original Answer
The
accepted answer is directed to the
Prism 5, it uses this library which is
deprecated in the
Prism 6.
Actually the article which you reference in your question was very helpful (at least for me), and it does not crash.
I will try to summary that article.
ViewModel
public class ViewModel : BindableBase
{
public ViewModel()
{
_showWindowCommand = new DelegateCommand(ShowWindow);
_interactionRequest = new InteractionRequest<Confirmation>();
}
private readonly DelegateCommand _showWindowCommand;
private InteractionRequest<Confirmation> _interactionRequest;
public ICommand ShowWindowCommand
{
get { return _showWindowCommand; }
}
public IInteractionRequest InteractionRequest
{
get { return _interactionRequest; }
}
private void ShowWindow()
{
_interactionRequest.Raise(
new Confirmation(),
OnWindowClosed);
}
private void OnWindowClosed(Confirmation confirmation)
{
if (confirmation.Confirmed)
{
//perform the confirmed action...
}
else
{
}
}
}
XAML
<Button Command="{Binding ShowWindowCommand}" Content="Show Window" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Raised" SourceObject="{Binding InteractionRequest}">
<i:EventTrigger.Actions>
<local:ShowWindowAction></local:ShowWindowAction>
</i:EventTrigger.Actions>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
and you will need to use those namespaces
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:The namespace which contains the ShowWindowAction">
ActionTrigger
using System;
using Prism.Interactivity.InteractionRequest;
using System.Windows.Interactivity;
using System.Windows;
public class ShowWindowAction : TriggerAction<FrameworkElement>
{
protected override void Invoke(object parameter)
{
InteractionRequestedEventArgs args = parameter as InteractionRequestedEventArgs;
if (args != null)
{
Confirmation confirmation = args.Context as Confirmation;
if (confirmation != null)
{
// Replace ParametersWindow with your own window.
ParametersWindow window = new ParametersWindow();
EventHandler closeHandler = null;
closeHandler = (sender, e) =>
{
window.Closed -= closeHandler;
args.Callback();
};
window.Closed += closeHandler;
window.Show();
}
}
}
}
Explanation
- You need
Prism.Core
and Prism.Wpf
dlls (at least) to make this code work.
ShowWindow
method, will trigger the Invoke
method of the ShowWindowAction
, which will really show the window.
- you can handle the closing of the window in the
OnWindowClosed
, which we passed it as a callback to the ShowWindowAction
class, and we called it from there when the the window really closed.
Luckily, Prism 5.0 (and I assume 6.0 too, haven't worked with it yet), has a class called InteractionRequest<T>
which you can use from code to raise interaction requests.
An interaction request is basically a window, that asks the user for a certain action and calls a callback (if necessary or desired) with the users decisions/actions.
public class ShellViewModel : BindableBase
{
private readonly IRegionManager regionManager;
public ShellViewModel(IRegionManager regionManager)
{
if (regionManager == null)
throw new ArgumentNullException("regionManager");
this.regionManager = regionManager;
this.OptionSettingConfirmationRequest = new InteractionRequest<IConfirmation>();
openConnectionOptionsCommand = new DelegateCommand(RaiseConnectionOptionsRequest);
}
public InteractionRequest<IConfirmation> OptionSettingConfirmationRequest { get; private set; }
private readonly ICommand openConnectionOptionsCommand;
public ICommand OpenConnectionOptionsCommand { get { return openConnectionOptionsCommand; } }
private void RaiseConnectionOptionsRequest()
{
this.OptionSettingConfirmationRequest.Raise(new Confirmation { Title = "Options not saved. Do you wish to save?" }, OnConnectionOptionsResponse);
}
protected virtual void OnConnectionOptionsResponse(IConfirmation context)
{
if(context.Confirmed)
{
// save it
}
// otherwise do nothing
}
}
In XAML you would do something like
<Button Content="Options" Command="{Binding OpenConnectionOptionsCommand}">
<i:Interaction.Triggers>
<pit:InteractionRequestTrigger SourceObject="{Binding OptionSettingConfirmationRequest, Mode=OneWay}" >
<pie:LazyPopupWindowAction RegionName="ConnectionSettings"
NavigationUri="ConnectionSettingsView" IsModal="True" />
</pit:InteractionRequestTrigger>
</i:Interaction.Triggers>
</Button>
I used my own implemetation of PopupWindowAction
(see github project page for it's implementation) called LazyPopupWindowAction
, which will instantiate the embedded ConnectionSettingsView
View on click. If you don't care that your view is instantiated only once, feel free to use PopupWindowAction
, then it will be instantiated at the same time as the View containing the action.
It's basically copy & paste with cutting some useless lines from one of my projects. I used IConfirmation
and INotification
interfaces instead of the concrete implementations.
XAML with PopupWindowAction
<Button Content="Options" Command="{Binding OpenConnectionOptionsCommand}">
<i:Interaction.Triggers>
<pit:InteractionRequestTrigger SourceObject="{Binding OptionSettingConfirmationRequest, Mode=OneWay}" >
<pi:PopupWindowAction>
<pi:PopupWindowAction.WindowContent>
<views:CustomPopupView />
</pi:PopupWindowAction.WindowContent>
</pi:PopupWindowAction>
</pit:InteractionRequestTrigger>
</i:Interaction.Triggers>
</Button>
Namespace declarations
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:pi="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
xmlns:pit="clr-namespace:Microsoft.Practices.Prism.Interactivity.InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"
xmlns:pie="clr-namespace:MyProject.UI.Prism.Interactivity;assembly=MyProject.UI"
Update:
Since people keep asking about the LazyPopupWindowAction
, I've put the source in a GitHub Gist. Basically it's based on the PopupWindowAction
from Prims 5 (and for Prism, haven't test it with Prism 6 yet, probably won't work w/o adjustments) and does the exact same thing, but also adds Region and Navigation support with the opened window, something that I needed in my application.
One thing I disliked about the default implementation was, that the view and it's viewmodel will be instantiated at the same time the Shell gets instantiated and the ViewModel remains in it's state, when you close it (it was actually just hidden).