C# MEF 上下文中的 WPF Window 关闭确认
WPF Window closing confirmation in c# MEF context
我的任务是实现一个简单的 确认对话框 以尝试 关闭我的应用程序的主要 window,但是只有,而特定进程是运行。关闭应用程序意味着中止该过程。如果该过程不是 运行,则不需要确认。
起初我只是创建了一个接口来获取Application.Current.MainWindow并使用了Closing事件,但是我的老师想到了别的东西,我就是找不到正确的方法来完成它。
我使用的代码库中已经有一个CanClose方法,默认为空。它位于主窗口打开的 AppInit class 中。我在 构造函数 中所做的是这样的:
[ImportingConstructor]
public AppInit(MainWindow mainWindow, [ImportMany] IEnumerable<IClosingValidator> closingValidator)
{
this.mainWindow = mainWindow;
this.closingValidator = closingValidator;
}
那是我老师的主意。现在,在 CanClose 方法中,我可以遍历这些关闭验证和 return true 或 false,如下所示:
public Boolean CanClose()
{
foreach (var validation in this.closingValidator)
{
var result = validation.CanClose();
if (result == false)
{
return false;
}
}
return true;
}
ClosingValidator 目前看起来像下面这样,我认为这是错误的,但我需要在某处构建对话框。整个工作正常,但指示相关进程是否 运行 的标志位于另一个项目的 ViewModel 中,这意味着始终显示此确认对话框。
[Export(typeof(IClosingValidator))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ClosingValidator : IClosingValidator
{
private readonly IMessageDialog messageDialog;
private readonly IOverlayEffect overlayEffect;
[ImportingConstructor]
public ClosingValidator(IMessageDialog messageDialog, IOverlayEffect overlayEffect)
{
this.messageDialog = messageDialog;
this.overlayEffect = overlayEffect;
}
public Boolean CanClose()
{
using (this.overlayEffect.Apply())
{
var result = this.messageDialog.ShowDialog(
new MessageDialogArgs(Resources.Resources.ConfirmClosingApplicationTitle,
Resources.Resources.ConfirmClosingApplicationMessage,
MessageBoxButton.YesNo,
MessageBoxImage.SystemQuestion));
if (result == MessageBoxResult.Yes)
{
return true;
}
}
return false;
}
我想我的问题归结为:
我在哪里构建我的对话框以及如何使用中的布尔标志 ]ViewModel首先判断是否显示dialog?我对 MEF 比较陌生,很抱歉有任何不清楚的地方。
编辑:我认为我的想法是我可以在将来的某个时候使用该接口来实现进一步的关闭验证。
编辑 3:
简化的ViewModel实现(实际的viewModel参数太多):
[Export]
public class TocViewModel
{
[ImportingConstructor]
public TocViewModel(MeasurementSequenceExecution measurementSequenceExecution)
{
this.measurementSequenceExecution = measurementSequenceExecution;
}
public Boolean CanApplicationClose
{
get { return !this.measurementSequenceExecution.IsMeasurementSequenceRunning; }
set
{
this.measurementSequenceExecution.IsMeasurementSequenceRunning = !value;
}
}
IClosingValidator 实现:
[Export(typeof(IClosingValidator))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ClosingValidator : IClosingValidator
{
[ImportingConstructor]
public ClosingValidator()
{
}
public Boolean CanApplicationClose { get; }
IClosingValidator 接口:
public interface IClosingValidator
{
Boolean CanApplicationClose { get; }
}
class处理关闭:
[Export(typeof(IApp))]
public class AppInit : IApp
{
[ImportingConstructor]
public AppInit(MainWindow mainWindow,
[ImportMany(typeof(IClosingValidator))] IEnumerable<IClosingValidator> closingValidatorClients,
IOverlayEffect overlayEffect,
IMessageDialog messageDialog)
{
this.mainWindow = mainWindow;
this.closingValidatorClients = closingValidatorClients;
this.overlayEffect = overlayEffect;
this.messageDialog = messageDialog;
}
public Boolean CanClose()
{
if (this.closingValidatorClients != null)
{
foreach (var client in this.closingValidatorClients)
{
if (!client.CanApplicationClose)
{
using (this.overlayEffect.Apply())
{
var result = this.messageDialog.ShowDialog(
new MessageDialogArgs(Resources.Resources.ConfirmClosingApplicationTitle,
Resources.Resources.ConfirmClosingApplicationMessage,
MessageBoxButton.YesNo,
MessageBoxImage.SystemQuestion));
if (result == MessageBoxResult.Yes)
{
return true;
}
return false;
}
}
}
}
return true;
}
private readonly IEnumerable<IClosingValidator> closingValidatorClients;
private readonly MainWindow mainWindow;
private readonly IMessageDialog messageDialog;
private readonly IOverlayEffect overlayEffect;
更新
我让它工作了,使用 typeof(IClosingValidator) 导出 ViewModel 的建议是正确的,我只需要添加第二个导出,而不是替换默认的,不知道你可以有 2 个。所以现在使用 2 个 ExportAttributes 就可以了!
我认为您对 IClosingValidator 的实现在错误的地方。
我有一个类似的项目,我所做的是:
我有一个像你一样的接口 IClosingValidator:
public interface IConfirmShellClosing
{
/// <summary>
/// Gets a value that indicates whether the shell window can be closed.
/// </summary>
bool CanShellClose { get; }
}
如果shell可以关闭,应该询问所有ViewModels实现的这个接口。在您的情况下,进程可以是 运行 的所有 ViewModel 都应该实现此接口。因此,每个 ViewModel 自身都将实现 CanShellClose 属性 并决定进程在此上下文中是否为 运行。只有当所有 ViewModels return 为真时,Window 才能关闭。
然后,在您的 window 实例中,您可以订阅 WindowClosing 事件并询问所有已注册的 ViewModel,是否可以关闭 Window。此实现转到 Window(或代码隐藏文件)的 ViewModel:
[ImportMany(typeof(Lazy<IConfirmShellClosing>))]
private IEnumerable<Lazy<IConfirmShellClosing>> _confirmShellClosingClients;
private void ExecuteWindowClosing(CancelEventArgs args)
{
if (_confirmShellClosingClients != null)
{
foreach (var client in _confirmShellClosingClients)
{
if (!client.Value.CanShellClose)
{
// Show your Dialog here and handle the answer
}
}
}
}
希望对您有所帮助。
编辑:
好的,您在 ViewModel 和 Validator 的实现中仍然存在一些错误。
首先,Class ClosingValidator 不再需要了。您希望将责任交给 ViewModel,而不是中央验证器 class.
之后,我们需要像这样更改 ViewModel:
[Export]
public class TocViewModel : IClosingValidator
{
[ImportingConstructor]
public TocViewModel(MeasurementSequenceExecution measurementSequenceExecution)
{
this.measurementSequenceExecution = measurementSequenceExecution;
}
public Boolean CanApplicationClose
{
get { return !this.measurementSequenceExecution.IsMeasurementSequenceRunning; }
set
{
this.measurementSequenceExecution.IsMeasurementSequenceRunning = !value;
}
}
现在发生了什么? ViewModel 实现了 IClosingValidator 接口,因此它必须实现 CanShellClose属性。在这个属性中,你可以定义逻辑,这个ViewModel可以决定Shell是否可以关闭。如果您添加其他 ViewModel,它也可以实现该接口并且具有不同的关闭逻辑。
在应用程序本身的导入中,所有实现 IClosingValidator 接口的 classes 都被导入,然后询问属性是真还是假。
编辑 2:
我有 3 个 .dll(HelpingProject、InterfaceProject 和 ViewModelProject)所有 3 个都应该在 compositionContainer
正在搜索的目录中。在我的例子中,我构建了这样的容器:
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(@"C:\Projects\HelpingSolution\HelpingWpfProject\bin\Debug"));
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
因此将扫描 Debug 文件夹以查找匹配的导出。我建议您在您的代码库中搜索此代码,并查看您的容器在哪里寻找导出。这不一定只是一个文件夹,也可以是一个插件文件夹或不同的位置。你可以找到一个相当不错的介绍here.
我的任务是实现一个简单的 确认对话框 以尝试 关闭我的应用程序的主要 window,但是只有,而特定进程是运行。关闭应用程序意味着中止该过程。如果该过程不是 运行,则不需要确认。
起初我只是创建了一个接口来获取Application.Current.MainWindow并使用了Closing事件,但是我的老师想到了别的东西,我就是找不到正确的方法来完成它。
我使用的代码库中已经有一个CanClose方法,默认为空。它位于主窗口打开的 AppInit class 中。我在 构造函数 中所做的是这样的:
[ImportingConstructor]
public AppInit(MainWindow mainWindow, [ImportMany] IEnumerable<IClosingValidator> closingValidator)
{
this.mainWindow = mainWindow;
this.closingValidator = closingValidator;
}
那是我老师的主意。现在,在 CanClose 方法中,我可以遍历这些关闭验证和 return true 或 false,如下所示:
public Boolean CanClose()
{
foreach (var validation in this.closingValidator)
{
var result = validation.CanClose();
if (result == false)
{
return false;
}
}
return true;
}
ClosingValidator 目前看起来像下面这样,我认为这是错误的,但我需要在某处构建对话框。整个工作正常,但指示相关进程是否 运行 的标志位于另一个项目的 ViewModel 中,这意味着始终显示此确认对话框。
[Export(typeof(IClosingValidator))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ClosingValidator : IClosingValidator
{
private readonly IMessageDialog messageDialog;
private readonly IOverlayEffect overlayEffect;
[ImportingConstructor]
public ClosingValidator(IMessageDialog messageDialog, IOverlayEffect overlayEffect)
{
this.messageDialog = messageDialog;
this.overlayEffect = overlayEffect;
}
public Boolean CanClose()
{
using (this.overlayEffect.Apply())
{
var result = this.messageDialog.ShowDialog(
new MessageDialogArgs(Resources.Resources.ConfirmClosingApplicationTitle,
Resources.Resources.ConfirmClosingApplicationMessage,
MessageBoxButton.YesNo,
MessageBoxImage.SystemQuestion));
if (result == MessageBoxResult.Yes)
{
return true;
}
}
return false;
}
我想我的问题归结为: 我在哪里构建我的对话框以及如何使用中的布尔标志 ]ViewModel首先判断是否显示dialog?我对 MEF 比较陌生,很抱歉有任何不清楚的地方。
编辑:我认为我的想法是我可以在将来的某个时候使用该接口来实现进一步的关闭验证。
编辑 3:
简化的ViewModel实现(实际的viewModel参数太多):
[Export]
public class TocViewModel
{
[ImportingConstructor]
public TocViewModel(MeasurementSequenceExecution measurementSequenceExecution)
{
this.measurementSequenceExecution = measurementSequenceExecution;
}
public Boolean CanApplicationClose
{
get { return !this.measurementSequenceExecution.IsMeasurementSequenceRunning; }
set
{
this.measurementSequenceExecution.IsMeasurementSequenceRunning = !value;
}
}
IClosingValidator 实现:
[Export(typeof(IClosingValidator))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ClosingValidator : IClosingValidator
{
[ImportingConstructor]
public ClosingValidator()
{
}
public Boolean CanApplicationClose { get; }
IClosingValidator 接口:
public interface IClosingValidator
{
Boolean CanApplicationClose { get; }
}
class处理关闭:
[Export(typeof(IApp))]
public class AppInit : IApp
{
[ImportingConstructor]
public AppInit(MainWindow mainWindow,
[ImportMany(typeof(IClosingValidator))] IEnumerable<IClosingValidator> closingValidatorClients,
IOverlayEffect overlayEffect,
IMessageDialog messageDialog)
{
this.mainWindow = mainWindow;
this.closingValidatorClients = closingValidatorClients;
this.overlayEffect = overlayEffect;
this.messageDialog = messageDialog;
}
public Boolean CanClose()
{
if (this.closingValidatorClients != null)
{
foreach (var client in this.closingValidatorClients)
{
if (!client.CanApplicationClose)
{
using (this.overlayEffect.Apply())
{
var result = this.messageDialog.ShowDialog(
new MessageDialogArgs(Resources.Resources.ConfirmClosingApplicationTitle,
Resources.Resources.ConfirmClosingApplicationMessage,
MessageBoxButton.YesNo,
MessageBoxImage.SystemQuestion));
if (result == MessageBoxResult.Yes)
{
return true;
}
return false;
}
}
}
}
return true;
}
private readonly IEnumerable<IClosingValidator> closingValidatorClients;
private readonly MainWindow mainWindow;
private readonly IMessageDialog messageDialog;
private readonly IOverlayEffect overlayEffect;
更新 我让它工作了,使用 typeof(IClosingValidator) 导出 ViewModel 的建议是正确的,我只需要添加第二个导出,而不是替换默认的,不知道你可以有 2 个。所以现在使用 2 个 ExportAttributes 就可以了!
我认为您对 IClosingValidator 的实现在错误的地方。 我有一个类似的项目,我所做的是:
我有一个像你一样的接口 IClosingValidator:
public interface IConfirmShellClosing
{
/// <summary>
/// Gets a value that indicates whether the shell window can be closed.
/// </summary>
bool CanShellClose { get; }
}
如果shell可以关闭,应该询问所有ViewModels实现的这个接口。在您的情况下,进程可以是 运行 的所有 ViewModel 都应该实现此接口。因此,每个 ViewModel 自身都将实现 CanShellClose 属性 并决定进程在此上下文中是否为 运行。只有当所有 ViewModels return 为真时,Window 才能关闭。
然后,在您的 window 实例中,您可以订阅 WindowClosing 事件并询问所有已注册的 ViewModel,是否可以关闭 Window。此实现转到 Window(或代码隐藏文件)的 ViewModel:
[ImportMany(typeof(Lazy<IConfirmShellClosing>))]
private IEnumerable<Lazy<IConfirmShellClosing>> _confirmShellClosingClients;
private void ExecuteWindowClosing(CancelEventArgs args)
{
if (_confirmShellClosingClients != null)
{
foreach (var client in _confirmShellClosingClients)
{
if (!client.Value.CanShellClose)
{
// Show your Dialog here and handle the answer
}
}
}
}
希望对您有所帮助。
编辑:
好的,您在 ViewModel 和 Validator 的实现中仍然存在一些错误。
首先,Class ClosingValidator 不再需要了。您希望将责任交给 ViewModel,而不是中央验证器 class.
之后,我们需要像这样更改 ViewModel:
[Export]
public class TocViewModel : IClosingValidator
{
[ImportingConstructor]
public TocViewModel(MeasurementSequenceExecution measurementSequenceExecution)
{
this.measurementSequenceExecution = measurementSequenceExecution;
}
public Boolean CanApplicationClose
{
get { return !this.measurementSequenceExecution.IsMeasurementSequenceRunning; }
set
{
this.measurementSequenceExecution.IsMeasurementSequenceRunning = !value;
}
}
现在发生了什么? ViewModel 实现了 IClosingValidator 接口,因此它必须实现 CanShellClose属性。在这个属性中,你可以定义逻辑,这个ViewModel可以决定Shell是否可以关闭。如果您添加其他 ViewModel,它也可以实现该接口并且具有不同的关闭逻辑。
在应用程序本身的导入中,所有实现 IClosingValidator 接口的 classes 都被导入,然后询问属性是真还是假。
编辑 2:
我有 3 个 .dll(HelpingProject、InterfaceProject 和 ViewModelProject)所有 3 个都应该在 compositionContainer
正在搜索的目录中。在我的例子中,我构建了这样的容器:
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(@"C:\Projects\HelpingSolution\HelpingWpfProject\bin\Debug"));
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
因此将扫描 Debug 文件夹以查找匹配的导出。我建议您在您的代码库中搜索此代码,并查看您的容器在哪里寻找导出。这不一定只是一个文件夹,也可以是一个插件文件夹或不同的位置。你可以找到一个相当不错的介绍here.