为什么"在Setter.Value结构中设置事件处理程序"会出现编译错误?

ale*_*hro 8 wpf events styles

我完全按照在Setter.Value结构设置事件处理程序中所述的方式来解决问题.但我想理解为什么问题的作者提供的解决方案不起作用.看起来我错过了一些概念.

Cod*_*ked 15

这似乎是生成XAML代码隐藏的一个错误.除了XAML文件的用户代码隐藏之外,还有一个"编译器"生成的版本,它定义了命名元素的InitializeComponent和类字段(即x:Name).

举个简单的例子:

<Window.Resources>
    <Style TargetType="Button">
        <Setter Property="ContextMenu">
            <Setter.Value>
                <ContextMenu>
                    <MenuItem Header="Header" Click="MenuItem_Click"/>
                </ContextMenu>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

<Button />
Run Code Online (Sandbox Code Playgroud)

如果你运行它,你将得到以下异常:

System.Windows.Markup.XamlParseException occurred
  Message='Set connectionId threw an exception.' Line number '13' and line position '8'.
  Source=PresentationFramework
  LineNumber=13
  LinePosition=8
  StackTrace:
       at System.Windows.Markup.XamlReader.RewrapException(Exception e, IXamlLineInfo lineInfo, Uri baseUri)
  InnerException: System.InvalidCastException
       Message=Unable to cast object of type 'System.Windows.Controls.MenuItem' to type 'System.Windows.Controls.Button'.
       Source=Windows7Theme
       StackTrace:
            at Windows7Theme.MainWindow.System.Windows.Markup.IComponentConnector.Connect(Int32 connectionId, Object target) in c:\Users\TJoe\Documents\Visual Studio 10\Projects\Windows7Theme\Windows7Theme\MainWindow.xaml:line 13
            at MS.Internal.Xaml.Runtime.ClrObjectRuntime.SetConnectionId(Object root, Int32 connectionId, Object instance)
       InnerException: 
Run Code Online (Sandbox Code Playgroud)

生成的代码隐藏文件可以在obj文件夹中找到,所以如果我们检查一下,我们可以看到以下代码:

void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
    switch (connectionId)
    {
    case 1:

    #line 13 "..\..\..\MainWindow.xaml"
    ((System.Windows.Controls.Button)(target)).AddHandler(System.Windows.Controls.MenuItem.ClickEvent, new System.Windows.RoutedEventHandler(this.MenuItem_Click));

    #line default
    #line hidden
    return;
    }
    this._contentLoaded = true;
}
Run Code Online (Sandbox Code Playgroud)

这里的问题是生成的代码试图将其MenuItem转换为Button.如果我们像这样改变我们的样本:

<Window.Resources>
    <ContextMenu x:Key="ContextMenuKey">
        <MenuItem Header="Header" Click="MenuItem_Click"/>
    </ContextMenu>

    <Style TargetType="Button">
        <Setter Property="ContextMenu"
                Value="{StaticResource ContextMenuKey}" />
    </Style>
</Window.Resources>

<Button />
Run Code Online (Sandbox Code Playgroud)

然后生成的代码是:

void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
    switch (connectionId)
    {
    case 1:

    #line 10 "..\..\..\MainWindow.xaml"
    ((System.Windows.Controls.MenuItem)(target)).Click += new System.Windows.RoutedEventHandler(this.MenuItem_Click);

    #line default
    #line hidden
    return;
    }
    this._contentLoaded = true;
}
Run Code Online (Sandbox Code Playgroud)

根据我的测试,代码生成器似乎为每个控件分配一个ID,它需要"连接"或添加处理程序/支持字段.在ContextMenu被包含在内联的情况下(即第一个示例),它的事件处理程序被分配给窗口内的根元素,并且没有获得它自己的ID.

如果我们将Button更改为包含在Grid中,则上面的异常将指示它无法将MenuItem强制转换为Grid.因为现在Grid是根元素.这表明它与Style目标的类型无关.

当ContextMenu作为单独的资源包含时,代码生成器似乎正确地为其分配了ID,因此可以正确附加处理程序.

最终,这是XAML代码生成器中的一个错误.

  • 谢谢你的精彩解释.我想你可以把这个bug提交给wpf开发者. (2认同)