在Caliburn.Micro中如何将动作绑定到嵌套的ViewModel方法?

sup*_*jos 4 c# wpf xaml caliburn.micro

我有一个由Caliburn.Micro支持的WPF应用程序,它是视图模型优先方法。有一个命令栏类型的控件,其CommandBarView.xaml和绑定的CommandBarViewModel。命令行VM包含许多嵌套的VM,每个按钮控件一个VM,它们都显示一个公共界面并具有相同的行为。命令行VM公开它们,以便可以从视图中对其进行绑定:

public interface IWarningButtonViewModel
{
    bool IsVisible { get; }
    bool CanShowWarning { get; }
    void ShowWarning();
}

public class CommandBarViewModel : PropertyChangedBase
{
    public IWarningButtonViewModel UserNotFoundWarning { get; private set; }
    public IWarningButtonViewModel NetworkProblemWarning { get; private set; }
    // ... initialization omitted for simplicity
}
Run Code Online (Sandbox Code Playgroud)

这是一些CommandBarView的暂定XAML:

<Button x:Name="UserNotFoundWarning_ShowWarning"
        IsEnabled="{Binding UserNotFoundWarning.CanShowWarning}">
  ... 
  <DataTrigger Binding="{Binding UserNotFoundWarning.IsVisible}" Value="True">
  ...
</Button>
Run Code Online (Sandbox Code Playgroud)

这样,我就可以成功绑定两个属性(CanShowWarningIsVisible),但无法将按钮命令/操作绑定到ShowWarning方法。

我尝试使用深层属性绑定,但对于属性,它又可以工作,但对于动作而言,它却不能工作。
我也试过用的混合cal:Model.Bindcal:Message.Attach

<Button cal:Model.Bind="{Binding UserNotFoundWarning}" 
        cal:Message.Attach="[Event Click] = [Action ShowWarning]"
        IsEnabled="{Binding CanShowWarning}">
  ... 
  <DataTrigger Binding="{Binding IsVisible}" Value="True">
  ...
</Button>
Run Code Online (Sandbox Code Playgroud)

这似乎在运行时有效,但是cal:Model.Bind使VS设计器完全不可用,UI控件未显示。

我已经搜索了很多东西,但是找不到实际的解决方案让我也可以与设计师一起工作。对于我而言,我只能找到针对属性而非操作进行深度绑定的示例,这对我来说似乎很奇怪。

任何想法如何解决这个问题?

tor*_*vin 6

这是我的解决方法:

private static void EnableNestedViewModelActionBinding()
{
    var baseGetTargetMethod = ActionMessage.GetTargetMethod;
    ActionMessage.GetTargetMethod = (message, target) =>
    {
        var methodName = GetRealMethodName(message.MethodName, ref target);
        if (methodName == null)
            return null;

        var fakeMessage = new ActionMessage { MethodName = methodName };
        foreach (var p in message.Parameters)
            fakeMessage.Parameters.Add(p);
        return baseGetTargetMethod(fakeMessage, target);
    };

    var baseSetMethodBinding = ActionMessage.SetMethodBinding;
    ActionMessage.SetMethodBinding = context =>
    {
        baseSetMethodBinding(context);
        var target = context.Target;
        if (target != null)
        {
            GetRealMethodName(context.Message.MethodName, ref target);
            context.Target = target;
        }
    };
}

private static string GetRealMethodName(string methodName, ref object target)
{
    var parts = methodName.Split('.');
    var model = target;
    foreach (var propName in parts.Take(parts.Length - 1))
    {
        if (model == null)
            return null;

        var prop = model.GetType().GetPropertyCaseInsensitive(propName);
        if (prop == null || !prop.CanRead)
            return null;

        model = prop.GetValue(model);
    }
    target = model;
    return parts.Last();
}
Run Code Online (Sandbox Code Playgroud)

EnableNestedViewModelActionBinding()从引导程序中调用一次,它将使您能够使用通常的点分符号将动作绑定到嵌套模型的方法。例如

cal:Message.Attach="[Event Click] = [Action UserNotFoundWarning.ShowWarning]"
Run Code Online (Sandbox Code Playgroud)

编辑:请注意,如果您在运行时更改嵌套的ViewModel实例,则此方法将无效。例如,如果您UserNotFoundWarning在绑定发生后将您分配给新的东西-Caliburn仍会在先前实例上调用操作。