用于从父菜单绑定子ViewModel上的命令的模式

时间:2013-03-06 23:19:14

标签: wpf mvvm caliburn.micro eventaggregator

我正在使用Caliburn Micro创建一个WPF MVVM应用程序。我在菜单(功能区)中有一组按钮,它们位于我的shell视图模型的视图中,它是一个ScreenConductor。根据当前活动的屏幕视图模型,如果可以使用活动屏幕,并且在活动屏幕上调用操作或命令,我希望禁用/启用功能区按钮。

这似乎是一种常见的情况。是否有创建此行为的模式?

5 个答案:

答案 0 :(得分:1)

为什么不做相反的事情,而不是检查当前活动屏幕支持哪些命令,让活动屏幕使用它支持的所有控件填充菜单或功能区选项卡,(我会让它注入它自己的用户控件本身可能只是一个完整的菜单或功能区选项卡),这也将增强用户体验,因为它只会向用户显示他可以为当前活动屏幕使用的控件。

答案 1 :(得分:1)

编辑:再看看你的问题,我认为这比看起来简单得多

我能看到的唯一问题是,子VM上缺少处理程序(和防护)方法将意味着仍然会启用当前活动VM上没有实现的按钮。

CM的默认策略是尝试查找匹配的方法名称(解析操作文本后),如果找不到,则单独保留按钮。如果您要自定义该行为以便默认禁用按钮,则可以通过在shell中实现命令按钮轻松实现它,确保将命令目标设置为活动项:

在shell中定义按钮,确保它们具有指向活动子VM的目标

<Button cal:Message.Attach="Command1" cal:Action.TargetWithoutContext="{Binding ActiveItem}" />

然后按照常规

在子VM中实现该方法
public void Command1() { }

以及可选的CanXX后卫

public bool CanCommand1 
{ 
    get 
    { 
        if(someCondition) return false; 

        return true; 
    } 
}

假设你没有比这更复杂,它应该适合你

我将快速浏览一下CM来源,看看我是否能想出一些适用于此的内容

编辑:

好的,您可以自定义ActionMessage.ApplyAvailabilityEffect func以获得您想要的效果 - 在您的bootstrapper.Configure()方法(或启动时的某个地方)使用:

        ActionMessage.ApplyAvailabilityEffect = context =>
        {
            var source = context.Source;

            if (ConventionManager.HasBinding(source, UIElement.IsEnabledProperty))
            {
                return source.IsEnabled;
            }

            if (context.CanExecute != null)
            {
                source.IsEnabled = context.CanExecute();
            }
            // Added these 3 lines to get the effect you want
            else if (context.Target == null)
            {
                source.IsEnabled = false;
            }
            // EDIT: Bugfix - need this to ensure the button is activated if it has a target but no guard
            else 
            {
                source.IsEnabled = true;
            }

            return source.IsEnabled;
        };

这似乎对我有用 - 对于无法绑定到命令的方法没有目标,所以在这种情况下我只是将IsEnabled设置为false。只有在活动子VM上找到具有匹配签名的方法时才会激活按钮 - 显然在使用它之前给它一个很好的测试:)

答案 2 :(得分:0)

为shell视图模型上的每个命令创建方法和附带的布尔属性。 (请参阅下面的代码示例。)Caliburn.Micro的约定会自动将它们连接到按钮。然后,当您更改视图以重新评估它们时,只需为布尔属性引发属性更改事件。

例如,假设您有一个“保存”按钮。 xaml中该按钮的名称将为Save,在视图模型中,您将拥有Save方法以及CanSave布尔属性。见下文:

public void Save()
{
    var viewModelWithSave = ActiveItem as ISave;
    if (viewModelWithSave != null) viewModelWithSave.Save();
}

public bool CanSave { get { return ActivateItem is ISave; } }

然后,在你的指挥中,每当你改变你的活动屏幕时,你都会打电话给NotifyOfPropertyChange(() => CanSave);。执行此操作将导致您的按钮被禁用或启用,具体取决于活动屏幕是否能够处理该命令。在此示例中,如果活动屏幕未实现ISave,则将禁用“保存”按钮。

答案 3 :(得分:0)

我会在这种情况下使用Caliburn.Micro事件聚合,如下所示:

  • 创建一个名为ScreenCapabilities的类,其中包含一堆布尔属性(例如CanSaveCanLoad等。)
  • 使用类型为ScreenActivatedMessage
  • 的属性创建名为ScreenCapabilities的邮件
  • 为您的功能区创建一个订阅(处理)ScreenActivatedMessage
  • 的视图模型

在功能区视图模型的Handle方法中,根据提供的ScreenCapabilities设置本地CanXXX属性。

它看起来像这样(手工输入的代码,未经过测试):

public class ScreenCapabilities
{
  public bool CanSave { get; set; }
  // ...
}

public class ScreenActivatedMessage
{
  public ScreenCapabilities ScreenCapabilities { get; set; }
  // ...
}

public class RibbonViewModel : PropertyChangedBase, IHandle<ScreenActivatedMessage>
{
  private bool _canSave;
  public bool CanSave
  {
    get { return _canSave; }
    set { _canSave = value; NotifyPropertyChanged(() => CanSave); }
  }

  // ...

  public void Handle(ScreenActivatedMessage message)
  {
    CanSave = message.ScreenCapabilities.CanSave;
    // ...
  }
}

然后,在某个适当的地方,当屏幕发生变化时,发布消息。有关详细信息,请参阅see Caliburn.Micro wiki

答案 4 :(得分:0)

在shell视图模型中为活动屏幕定义属性(比如说ActiveScreen)。 我们假设您拥有每个按钮的属性,例如DeleteButton,AddButton。 屏幕是屏幕的视图模型。

    private Screen activeScreen;

    public Screen ActiveScreen
    {
        get
        {
            return activeScreen;
        }
        set
        {
            activeScreen= value;

            if (activeScreen.Name.equals("Screen1"))
            {
                 this.AddButton.IsEnabled = true; 
                 this.DeleteButton.IsEnabled = false; 
            }
            if else (activeScreen.Name.equals("Screen2"))
            {
                 this.AddButton.IsEnabled = true; 
                 this.DeleteButton.IsEnabled = true; 
            }

            NotifyPropertyChanged("ActiveScreen");
        }
    }