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.
如何在不违反MVVM模式规则的情况下在WPF中打开/关闭新窗口?我只是想模仿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.
我目前正在使用棱镜5.0。
2 个解决方案
#1
4
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
更新是什么让我得出另一个答案是无法在我使用Prism 6的项目上应用已接受的答案,但在提出原始答案(见下文)并在评论中讨论之后,我发现核心问题是: Prism 6更改了某些类的名称空间,所接受的答案中使用的所有类仍然存在于Prism 6中,但是在另一个dll和名称空间中所以如果你使用Prism 6,你可以将这些修改应用于接受的答案
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
第二次更新XAML如下
<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.
注意1确保您使用的视图(在上面的示例中
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.
注2:只有在使用Prism 6时才需要进行这些修改。因为(正如我在下面的原始答案中所说),Prism 6中不推荐接受答案使用的dll。
NOTE 3
Make sure you are referencing the Prism.Wpf
dll, which could be downloaded from Nuget.
注3:确保您引用的是Prism.Wpf dll,可以从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
andPrism.Wpf
dlls (at least) to make this code work. -
ShowWindow
method, will trigger theInvoke
method of theShowWindowAction
, 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 theShowWindowAction
class, and we called it from there when the the window really closed.
你需要Prism.Core和Prism.Wpf dll(至少)使这段代码有效。
ShowWindow方法,将触发ShowWindowAction的Invoke方法,这将真正显示窗口。
你可以在OnWindowClosed中处理窗口的关闭,我们将它作为回调传递给ShowWindowAction类,当窗口真正关闭时我们从那里调用它。
#2
12
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.
幸运的是,Prism 5.0(我也假设6.0,还没有使用它),有一个名为InteractionRequest
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
在XAML中你会做类似的事情
<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.
我使用了自己的PopupWindowAction实现(参见github项目页面的实现),名为LazyPopupWindowAction,它将在点击时实例化嵌入式ConnectionSettingsView视图。如果您不关心您的视图只被实例化一次,请随意使用PopupWindowAction,然后它将与包含该操作的View同时实例化。
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.
它基本上是复制和粘贴,从我的一个项目中删除了一些无用的线条。我使用IConfirmation和INotification接口而不是具体的实现。
XAML with PopupWindowAction
XAML与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.
更新:由于人们不断询问LazyPopupWindowAction,我已将源代码放入GitHub Gist中。基本上它是基于Prims 5的PopupWindowAction(对于Prism,尚未用Prism 6进行测试,可能无法进行无需调整)并完全相同,但也添加了区域和导航支持打开的窗口,我在我的应用程序中需要的东西。
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).
我不喜欢默认实现的一件事是,视图和它的viewmodel将在Shell被实例化的同时实例化,并且ViewModel保持在它的状态,当你关闭它时(它实际上只是隐藏)。
#1
4
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
更新是什么让我得出另一个答案是无法在我使用Prism 6的项目上应用已接受的答案,但在提出原始答案(见下文)并在评论中讨论之后,我发现核心问题是: Prism 6更改了某些类的名称空间,所接受的答案中使用的所有类仍然存在于Prism 6中,但是在另一个dll和名称空间中所以如果你使用Prism 6,你可以将这些修改应用于接受的答案
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
第二次更新XAML如下
<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.
注意1确保您使用的视图(在上面的示例中
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.
注2:只有在使用Prism 6时才需要进行这些修改。因为(正如我在下面的原始答案中所说),Prism 6中不推荐接受答案使用的dll。
NOTE 3
Make sure you are referencing the Prism.Wpf
dll, which could be downloaded from Nuget.
注3:确保您引用的是Prism.Wpf dll,可以从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
andPrism.Wpf
dlls (at least) to make this code work. -
ShowWindow
method, will trigger theInvoke
method of theShowWindowAction
, 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 theShowWindowAction
class, and we called it from there when the the window really closed.
你需要Prism.Core和Prism.Wpf dll(至少)使这段代码有效。
ShowWindow方法,将触发ShowWindowAction的Invoke方法,这将真正显示窗口。
你可以在OnWindowClosed中处理窗口的关闭,我们将它作为回调传递给ShowWindowAction类,当窗口真正关闭时我们从那里调用它。
#2
12
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.
幸运的是,Prism 5.0(我也假设6.0,还没有使用它),有一个名为InteractionRequest
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
在XAML中你会做类似的事情
<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.
我使用了自己的PopupWindowAction实现(参见github项目页面的实现),名为LazyPopupWindowAction,它将在点击时实例化嵌入式ConnectionSettingsView视图。如果您不关心您的视图只被实例化一次,请随意使用PopupWindowAction,然后它将与包含该操作的View同时实例化。
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.
它基本上是复制和粘贴,从我的一个项目中删除了一些无用的线条。我使用IConfirmation和INotification接口而不是具体的实现。
XAML with PopupWindowAction
XAML与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.
更新:由于人们不断询问LazyPopupWindowAction,我已将源代码放入GitHub Gist中。基本上它是基于Prims 5的PopupWindowAction(对于Prism,尚未用Prism 6进行测试,可能无法进行无需调整)并完全相同,但也添加了区域和导航支持打开的窗口,我在我的应用程序中需要的东西。
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).
我不喜欢默认实现的一件事是,视图和它的viewmodel将在Shell被实例化的同时实例化,并且ViewModel保持在它的状态,当你关闭它时(它实际上只是隐藏)。