0

I have a ShellViewModel which loads a Modal Dialog. The Dialog's ViewModel has its OnActivate() override, where it gathers the data to be displayed on the Dialog. I would like to know how can we ask the WindowManager to cancel its ShowDialog based on a condition in OnActivate of the ViewModel backing the dialog.

For example, lets say that I have following code in ShellViewModel which tries to load a modal dialog based on StationOpenViewModel

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

and here is to code of OnActivate override of the 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
        }
    ...
}

So in the above code, if I get Exception in OnActivate override, I don't have any Station data to show and I would like to cancel the showDialog() for the StationOpenViewModel. I tried using TryClose(), but if I do so, the WindowManager.ShowDialog() throws exception saying that the operation is invalid.

In summary, if I call WindowManager.ShowDialog() for a dialog backed by some ViewModel, then in that ViewModel how do I cancel the ShowDialog() operation.

4

1 回答 1

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代码文件负责窗口的生命周期,因此您的虚拟机可以实现的附加接口可以很好地工作:

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:你仍然让WindowConductoradd Deactivate 和 GuardClose 钩子,即使它们永远不应该被使用,这可能会导致一些不良行为(也不确定引用是否持有——这一次可能没问题,因为没有任何东西持有对导体/VM)

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

编辑2:

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

于 2013-04-21T15:48:49.147 回答