Caliburn Micro的“Enter”键事件

20

我正在尝试使用Caliburn Micro绑定事件,但是在将正确的消息传递给方法方面遇到了一些问题。我希望能够在更改文本框中的值后按下“回车”键并执行与旁边按钮绑定的相同方法。然而,无论按哪个键,都会出现以下异常:

发生了类型为'System.InvalidCastException'的第一次机会异常 MyApp.exe

发生了类型为'System.Reflection.TargetInvocationException'的第一次机会异常mscorlib.dll

发生了类型为'System.Reflection.TargetInvocationException'的第一次机会异常WindowsBase.dll

根据另一个类似的问题Binding KeyDown Event Silverlight的建议,我尝试使用ActionExecutionContext,但是没有成功。

这是xaml代码:

<TextBox Name="Threshold"                     
              Margin="5"
              Grid.Column="1"
              >
     <i:Interaction.Triggers>
         <i:EventTrigger EventName="KeyDown">
             <cal:ActionMessage MethodName="ExecuteFilterView">
                 <cal:Parameter Value="$executionContext"/>
             </cal:ActionMessage>
         </i:EventTrigger>
     </i:Interaction.Triggers>
</TextBox>

并且这个方法:

 public void ExecuteFilterView(ActionExecutionContext context)
    {
        //Do stuff...
    }

我知道我可能可以通过在代码后台中创建一个标准的事件处理程序来避免一些麻烦,但这个应用程序是MVVM和学习使用Caliburn.Micro的练习,所以我希望坚持使这种特定方法起作用。

我是不是仅仅试图从事件中发送错误的信息?我的XAML是否编码不当以达到我想要的效果?或者我完全错过了其他什么重要的东西?

3个回答

55
刚刚随便写了个测试,这两种方法对我都可以:
使用完整语法:
    <TextBox Name="Threshold"                     
          Margin="5"
          Grid.Column="1">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="KeyDown">
                <cal:ActionMessage MethodName="ExecuteFilterView">
                    <cal:Parameter Value="$executionContext"/>
                </cal:ActionMessage>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </TextBox>

使用 CM 语法(我更喜欢它,因为它更易读)
    <TextBox Name="Threshold"                     
          Margin="5"
          Grid.Column="1"
          cal:Message.Attach="[Event KeyDown] = [Action ExecuteFilterView($executionContext)]" />            

这是测试虚拟机:
public class MainWindowViewModel
{
    public void ExecuteFilterView(ActionExecutionContext context)
    { 
        // This method is hit and the context is present and correct
    }
}

你能发布你的完整代码吗?你确定你已经正确设置了框架吗?(你是否按照入门示例进行了操作?)

http://caliburnmicro.codeplex.com/wikipage?title=Basic%20Configuration%2c%20Actions%20and%20Conventions&referringTitle=Documentation

编辑:

好的,在您澄清之后,我可以给您一些如何实现此操作的示例-我会告诉您我的个人偏好和原因,但请选择最适合您的方法

  1. 使用ActionExecutionContext并转换事件参数:
    cal:Message.Attach="[Event KeyDown] = [Action ExecuteFilterView($executionContext)]"

    public void ExecuteFilterView(ActionExecutionContext context)
    {
        var keyArgs = context.EventArgs as KeyEventArgs;

        if (keyArgs != null && keyArgs.Key == Key.Enter)
        {
            // Do Stuff
        }
    }

使用 EventArgs 直接。
    cal:Message.Attach="[Event KeyDown] = [Action ExecuteFilterView($eventArgs)]"

    public void ExecuteFilterView(KeyEventArgs keyArgs)
    {
        if (keyArgs.Key == Key.Enter)
        {
            // Do Stuff
        }
    }

我的个人最爱,是创建自己的 SpecialValues 字典条目:
在你的 Bootstrapper.Configure 方法中...
MessageBinder.SpecialValues.Add("$pressedkey", (context) =>
{
    // NOTE: IMPORTANT - you MUST add the dictionary key as lowercase as CM
    // does a ToLower on the param string you add in the action message, in fact ideally
    // all your param messages should be lowercase just in case. I don't really like this
    // behaviour but that's how it is!
    var keyArgs = context.EventArgs as KeyEventArgs;

    if (keyArgs != null)
        return keyArgs.Key;

    return null;
});

你的行动:
cal:Message.Attach="[Event KeyDown] = [Action ExecuteFilterView($pressedKey)]"

而且这段代码:

public void ExecuteFilterView(Key key)
{
    if (key == Key.Enter)
    {
        // Do Stuff
    }
}

这是我喜欢的原因吗?这意味着您的虚拟机只接收您想要的值(大多数情况下,您不关心许多其他参数),您不需要知道如何或烦扰地转换事件参数 - 您可以直接操作该值。显然,使用最适合您的方法。 还值得注意的是,如果您有其他类型的控件子类,它们会继承 KeyEventArgs ,则它们也将适用于此。 如果它们不是 KeyEventArgs 的子类,但仍返回 Key 类型的值,则这仍将起作用,因为如果第一个失败,您可以在委托中添加另一个强制转换:
MessageBinder.SpecialValues.Add("$pressedkey", (context) =>
{
    var keyArgs = context.EventArgs as KeyEventArgs;

    if (keyArgs != null)
        return keyArgs.Key;

    // Ok so it wasn't KeyEventArgs... check for some other type - maybe a 3rd party implementation
    var thirdPartyKeyArgs = context.EventArgs as ThirdPartyKeyArgs;

    if (thirdPartyKeyArgs != null)
        return thirdPartyKeyArgs.KeyProperty;

    return null;
});

1
所以我想我应该澄清一下,我没有任何关于方法被调用的问题。问题是,一旦方法触发,我就无法在不出现异常的情况下处理信息。基本上,我正在尝试弄清楚如何使用ActionExecutionContext来确定按下的键是否为“Enter”键,并在这种情况下对其进行处理。 - spugm1r3
1
我可能会使用 MessageBinder.SpecialValues,因为它抽象了实际的事件参数转换,并提供了一种漂亮可重用的方法来处理此问题。我将在帖子中更新所有选项。 - Charleh
1
太好了!它是可重用的事实更好,因为我将在4个不同的视图中执行相同的操作。在实施您的建议时,我发现了另一个我错过的错误。我正在使用System.Windows.Forms来处理KeyEventArgs,这不允许我尝试的转换。当我切换到System.Windows.Input时,一切都顺利了。感谢您深思熟虑的答案。 - spugm1r3
SpecialValues方法对我有用,但其他方法传入了null而不是键。 - samuelesque
其他方法不会传递键,而是会传递某些类型的事件参数(例如 KeyEventArgs),您需要在 VM 中进行强制转换或检查。这就是使用特殊值的原因,以抽象掉那一层间接性。 - Charleh

3
如果使用keyeventargs作为参数类型,并且返回null,则请注意以下内容: 您可能正在使用System.Windows.Forms.KeyEventArgsKeyEventArgs,因为参数类型是指向此类的,请尝试使用System.Windows.Input.KeyEventArgs(它对我有效)。
public void ExecuteFilterView(System.Windows.Input.KeyEventArgs context)
{
     if(context.Key==System.Windows.Input.Key.Return)
     {
         //action here
     }      
}

0

请查看Caliburn.Micro示例,Scenario.Keybinding。

您可以这样做:

<TextBox Name="Threshold"                     
          Margin="5"
          Grid.Column="1"
          cal:Message.Attach="[Key Enter] = [ExecuteFilterView($this.Text)]"/>

这将把文本框的文本发送到ExecuteFilterView方法。

为了使其工作,您需要将以下代码(我从示例中复制的)添加到您的引导程序中。

protected override void Configure()
{
    var defaultCreateTrigger = Parser.CreateTrigger;

    Parser.CreateTrigger = (target, triggerText) => 
    {
        if (triggerText == null)
        {
            return defaultCreateTrigger(target, null);
        }

        var triggerDetail = triggerText
            .Replace("[", string.Empty)
            .Replace("]", string.Empty);

        var splits = triggerDetail.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);

        switch (splits[0])
        {
            case "Key":
                var key = (Key)Enum.Parse(typeof(Key), splits[1], true);
                return new KeyTrigger { Key = key };

            case "Gesture":
                var mkg = (MultiKeyGesture)(new MultiKeyGestureConverter()).ConvertFrom(splits[1]);
                return new KeyTrigger { Modifiers = mkg.KeySequences[0].Modifiers, Key = mkg.KeySequences[0].Keys[0] };
        }

        return defaultCreateTrigger(target, triggerText);
    };
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接