我正在使用 Caliburn Micro 创建一个 WPF MVVM 应用程序。我在一个菜单(功能区)中有一组按钮,它们位于我的 shell 视图模型的视图中,它是一个 ScreenConductor。基于当前活动的屏幕视图模型,如果功能区按钮可用于活动屏幕,我希望禁用/启用它们,并在活动屏幕上调用操作或命令。
这似乎是一个常见的场景。是否有创建这种行为的模式?
我正在使用 Caliburn Micro 创建一个 WPF MVVM 应用程序。我在一个菜单(功能区)中有一组按钮,它们位于我的 shell 视图模型的视图中,它是一个 ScreenConductor。基于当前活动的屏幕视图模型,如果功能区按钮可用于活动屏幕,我希望禁用/启用它们,并在活动屏幕上调用操作或命令。
这似乎是一个常见的场景。是否有创建这种行为的模式?
你为什么不做相反的事情,而不是检查当前活动屏幕支持哪些命令,让活动屏幕使用它支持的所有控件填充菜单或功能区选项卡,(我会让它注入自己的用户控件本身可能只是一个完整的菜单或功能区选项卡),这也将增强用户体验,因为它只会向用户显示他可以在当前活动屏幕上使用的控件。
编辑:再次查看您的问题,我认为这比看起来要简单得多
我可以看到您遇到的唯一问题是子 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 上找到具有匹配签名的方法时才会激活按钮 - 显然在使用它之前给它一个很好的测试:)
在 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");
}
}
在这种情况下,我会使用 Caliburn.Micro 事件聚合,如下所示:
ScreenCapabilities
一堆布尔属性命名的类(例如CanSave
,CanLoad
等)ScreenActivatedMessage
带有 type 属性的消息ScreenCapabilities
ScreenActivatedMessage
在功能区视图模型的Handle
方法中,根据提供的ScreenCapabilities
.
它看起来像这样(手动输入的代码,未经测试):
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;
// ...
}
}
然后,在适当的地方,当屏幕发生变化时,发布消息。有关更多信息,请参阅 Caliburn.Micro wiki。
为 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
,则保存按钮将被禁用。