如何从ViewModel中取消windowManager.ShowDialog()

时间:2013-04-21 12:55:03

标签: wpf caliburn.micro

我有一个ShellViewModel,可以加载模态对话框。 Dialog的ViewModel具有OnActivate()覆盖,它收集要在Dialog上显示的数据。我想知道如何根据支持对话框的ViewModel的OnActivate中的条件要求WindowManager取消其ShowDialog。

例如,假设我在ShellViewModel中有以下代码,它试图加载基于StationOpenViewModel的模式对话框

public class ShellViewModel : Conductor<object>, IShell, IHandle<ConnectionChangedEvent> {
    public void ShowOpenStationPage() {
        StationOpenViewModel viewModel = container.GetExportedValue<StationOpenViewModel>();
        windowManager.ShowDialog(viewModel);
    }
    ...
}

以下是OnOctivate覆盖StationOpenViewModel

的代码
public class StationOpenViewModel : Screen {
    ...
    protected override void OnActivate() {
        try {
            using (StationRepository stationRepository = new StationRepository()) {
            //code to get Station Data
        }
        catch (Exception ex) {
            //Here I have no data, so there is no point in showing the window. 
            //How to cancel showDialog() for this viewModel
        }
    ...
}

所以在上面的代码中,如果我在OnActivate覆盖中得到Exception,我没有要显示的任何Station数据,我想取消StationOpenViewModel的showDialog()。我尝试使用TryClose(),但如果我这样做,WindowManager.ShowDialog()会抛出异常,说操作无效。

总之,如果我为一些ViewModel支持的对话框调用WindowManager.ShowDialog(),那么在该ViewModel中如何取消ShowDialog()操作。

1 个答案:

答案 0 :(得分:1)

CM源中的ShowDialog()实现是:

public virtual void ShowDialog(object rootModel, object context = null, IDictionary<string, object> settings = null) 
{
    var view = EnsureWindow(rootModel, ViewLocator.LocateForModel(rootModel, null, context));
    ViewModelBinder.Bind(rootModel, view, context);

    var haveDisplayName = rootModel as IHaveDisplayName;
    if(haveDisplayName != null && !ConventionManager.HasBinding(view, ChildWindow.TitleProperty)) {
        var binding = new Binding("DisplayName") { Mode = BindingMode.TwoWay };
        view.SetBinding(ChildWindow.TitleProperty, binding);
    }

    ApplySettings(view, settings);

    new WindowConductor(rootModel, view);

    view.Show();
}

完整来源:

http://caliburnmicro.codeplex.com/SourceControl/changeset/view/ae25b519bf1e46a506c85395f04aaffb654c0a08#src/Caliburn.Micro.Silverlight/WindowManager.cs

使用默认实现看起来不是很好的方法。您应该实现自己的WindowManager并继承原始实现

上面代码文件中的WindowConductor负责窗口的生命周期,因此VM可以实现的其他接口也能正常运行:

public interface ICancelActivate
{
    public bool ActivationCancelled { get };
}

然后将您的MyWindowConductor实施更改为:

    public MyWindowConductor(object model, ChildWindow view) 
    {
            // Added this field so the window manager can query the state of activation (or use a prop if you like)
            public bool ActivationCancelled;

            this.model = model;
            this.view = view;

            var activatable = model as IActivate;
            if (activatable != null) 
            {
                activatable.Activate();
            }

            // Added code here, check to see if the activation was cancelled:
            var cancelActivate = model as ICancelActivate;
            if(cancelActivate != null)
            {
                ActivationCancelled = cancelActivate.ActivationCancelled;                   
                if(ActivationCancelled) return; // Don't bother handling the rest of activation logic if cancelled
            }

            var deactivatable = model as IDeactivate;
            if (deactivatable != null) {
                view.Closed += Closed;
                deactivatable.Deactivated += Deactivated;
            }

            var guard = model as IGuardClose;
            if (guard != null) {
                view.Closing += Closing;
            }
        }

然后停止显示视图:

    // This is in 'ShowDialog' - you can override the default impl. as the method is marked virtual        
    ApplySettings(view, settings);

    // Get a ref to the conductor so you can check if activation was cancelled
    var conductor = new MyWindowConductor(rootModel, view);

    // Check and don't show if we don't need to
    if(!conductor.ActivationCancelled)
        view.Show();

显然我只是把它扔在一起所以它可能不是最好的方式,我会仔细看看这会留下你的申请状态

您的虚拟机只是实现了这个:

public class StationOpenViewModel : Screen, ICancelActivation {

    private bool _activationCancelled;
    public bool ActivationCancelled { get { return _activationCancelled; } }

    ...
    protected override void OnActivate() {
        try {
            using (StationRepository stationRepository = new StationRepository()) {
            //code to get Station Data
        }
        catch (Exception ex) {
            _activationCancelled = true;
        }
        ... 
 }

...当然,你可能有更好的方法来检查你是否需要首先打开一个虚拟机 - 我不确定它们会是什么,但仍然值得考虑

修改:

我之所以不在WindowManager ...

中这样做的原因
    new WindowConductor(rootModel, view);

    var cancel = rootModel as ICancelActivation;

    if(cancel == null || !cancel.ActivationCancelled) // fixed the bug here!
        view.Show();

是双重的 - 1:您仍然允许WindowConductor添加Deactivate和GuardClose挂钩,即使它们永远不会被使用,可能会导致某些不良行为(不确定参考持有其中任何一个 - 这可能是好的,因为没有任何东西对导体/ VM有任何反对意见

2:似乎激活VM的WindowConductor应该负责处理激活的取消 - 好吧它确实意味着WindowManager需要知道是否显示VM,但它似乎是更自然适合我

编辑2:

一个想法可能是将view.Show()移动到指挥中 - 这样您就可以取消激活而无需向管理器公开详细信息。两者都相互依赖,但这对我来说都是一样的