【问题标题】:Pattern for binding commands on a child ViewModel from a parent menu用于从父菜单绑定子 ViewModel 上的命令的模式
【发布时间】:2013-03-06 23:19:13
【问题描述】:

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

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

【问题讨论】:

  • 将您的按钮绑定到视图模型中的中继命令,该命令设置可以根据当前场景执行。
  • 这会产生这种效果,但我希望有一些更有活力和更少蛮力的东西。也许 Screen 视图模型可以定义它支持的命令,因此可以添加额外的屏幕,而无需为 ScreenConductor 视图模型中的每个按钮和屏幕显式连接。

标签: wpf mvvm caliburn.micro eventaggregator


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

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

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

    在 shell 中定义你的按钮,确保它们有一个指向活动子虚拟机的目标

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

    然后照常在您的子虚拟机中实现该方法

    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 上找到具有匹配签名的方法时才会激活按钮 - 显然在使用之前对其进行很好的测试:)

    【讨论】:

    • 嗯,我觉得这不太对,我必须提供一个 CanXX 防护装置才能工作 - 再看看...
    • 好的,我进行了调整,只需要确保在找到目标但没有 CanXX 保护时按钮不会单独存在(而是假设您需要按钮没有警卫但有一个处理程序被启用)
    【解决方案3】:

    为 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(() =&gt; CanSave);。这样做会导致您的按钮被禁用或启用,具体取决于活动屏幕是否能够处理该命令。在此示例中,如果活动屏幕未实现 ISave,则“保存”按钮将被禁用。

    【讨论】:

      【解决方案4】:

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

      • 创建一个名为ScreenCapabilities 的类,其中包含一组布尔属性(例如CanSaveCanLoad 等)
      • 使用ScreenCapabilities 类型的属性创建一个名为ScreenActivatedMessage 的消息
      • 为订阅(处理)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 了解更多信息。

      【讨论】:

        【解决方案5】:

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

            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");
                }
            }
        

        【讨论】:

          猜你喜欢
          • 2012-02-11
          • 2016-06-04
          • 1970-01-01
          • 1970-01-01
          • 2018-09-20
          • 2010-11-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多