如何将ContextMenu绑定到每个TreeView项?

时间:2011-07-29 14:10:03

标签: .net silverlight binding custom-contextmenu

我有一个带有TreeView的UserControl,以及一个ContextMenu DependencyProperty:

    public ObservableCollection<Control> ContextMenu {
        get {
            return ( ObservableCollection<Control> )GetValue( ContextMenuProperty );
        }
        set {
            SetValue( ContextMenuProperty, value );
        }
    }

    public static readonly DependencyProperty ContextMenuProperty =
        DependencyProperty.Register( "ContextMenu", typeof( ObservableCollection<Control> ), typeof( FilterableTreeViewControl ),
        new PropertyMetadata( new ObservableCollection<Control>(), new PropertyChangedCallback( FilterableTreeViewControl.OnContextMenuPropertyChange ) ) );

    private static void OnContextMenuPropertyChange( DependencyObject d, DependencyPropertyChangedEventArgs e ) {
        FilterableTreeViewControl ctrl = d as FilterableTreeViewControl;
        ctrl.OnContextMenuChange( ( Object )e.NewValue );
    }

    protected virtual void OnContextMenuChange( Object NewItemsSource ) {
    }

XAML:

        <controlsToolkit:TreeViewDragDropTarget AllowDrop="True" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" Drop="TreeViewDragDropTarget_Drop" AllowedSourceEffects="All">
            <controlsToolkit:TreeViewDragDropTarget.Resources>
                <Data:HierarchicalDataTemplate x:Key="TreeViewTemplate" ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal" Height="Auto" Width="Auto">
                        <Image Source="{Binding Type,Converter={StaticResource TreeIconConverter}}" />
                        <TextBlock x:Name="NameTextBlock" Text="{Binding Name}">
                            <controlsInputToolkit:ContextMenuService.ContextMenu>
                                <controlsInputToolkit:ContextMenu ItemsSource="{Binding ElementName=MyTreeViewControl, Path=ContextMenu}" />
                            </controlsInputToolkit:ContextMenuService.ContextMenu>
                        </TextBlock>
                    </StackPanel>
                </Data:HierarchicalDataTemplate>
            </controlsToolkit:TreeViewDragDropTarget.Resources>
            <Controls:TreeView Name="treeView" ItemTemplate="{StaticResource TreeViewTemplate}">
            </Controls:TreeView>
        </controlsToolkit:TreeViewDragDropTarget>

用法:

        <my:MyControl
                DragEnabled="False"
                ItemsSource="{Binding TreeRootNodes}" 
                FilterCaption="Filter:" 
                SelectionChangedCommand="{Binding SelectedMachineGroupChangedCommand_L}"
                DropCommand="{Binding DropCommand}">
            <my:FilterableTreeViewControl.ContextMenu>
                <controlsInputToolkit:MenuItem Header="Menu 1" />
                <controlsInputToolkit:MenuItem Header="Menu 2" />
                <controlsInputToolkit:MenuItem Header="Menu 3" />
            </my:MyControl.ContextMenu>
        </my:MyControl>

首先做的一切都很好,但是在第二次之后我明显得到了“元素已经是另一个元素的孩子”。异常。

是否可以通过绑定解决这个问题,而不需要任何代码隐藏?

1 个答案:

答案 0 :(得分:1)

你得到的是“元素已经是另一个元素的孩子”。异常,因为TreeView中的所有项都将其ContextMenus绑定到同一个对象(您定义的ContextMenu)。

不是将ContextMenu公开为MyControl中的属性,而是公开其HeirarchicalDataTemplate:

public HeirarchicalDataTemplate TreeViewItemTemplate {
    get {
        return (HeirarchicalDataTemplate)this.treeView.ItemTemplate; 
    }
    set {
        this.treeView.ItemTemplate = value;
    }
}

如果选择这种方式,则必须在原始用户控件之外定义TreeView ItemTemplate。在使用UserControl的外部客户端中,您可以执行以下操作:

    <my:MyControl>
        <my:MyControl.TreeViewItemTemplate>
            <Data:HierarchicalDataTemplate>
                   <!-- Rest of the template -->
                    <TextBlock x:Name="NameTextBlock" Text="{Binding Name}">
                        <controlsInputToolkit:ContextMenuService.ContextMenu>
                            <!-- ContextMenu -->
                        </controlsInputToolkit:ContextMenuService.ContextMenu>
                    <!-- Rest of the template -->
            </Data:HierarchicalDataTemplate>
        </my:MyControl.TreeViewItemTemplate>
    </my:MyControl>

这样做也会增加UserControl作为副作用的灵活性,因为您现在可以从外部自定义UserControl中的TreeView的ItemTemplate。如果要始终如一地重用它,可以将HierarchicalDataTemplate放在ResourceDictionary中。


如果您愿意使用代码隐藏,第二种解决方案是只为整个UserControl使用一个ContextMenu,然后以编程方式确定在客户端的代码中选择了哪个项目。

    <my:MyControl>
        <my:MyControl.TreeViewItemTemplate>
            <controlsInputToolkit:ContextMenuService.ContextMenu>
                <!-- ContextMenu -->
            </controlsInputToolkit:ContextMenuService.ContextMenu>
        </my:MyControl.TreeViewItemTemplate>
    </my:MyControl>