.netsilverlightbindingcustom-contextmenu

How to bind a ContextMenu to each TreeView item?


And I have a UserControl with a TreeView, and with a 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 ) {
    }

The 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>

The usage:

        <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>

First work everything fine, but after the second I obviously get the "Element is already the child of another element." exception.

Is it possible to solve that just with binding, without any code-behind?


Solution

  • You're getting the "Element is already the child of another element." exception because all the items in your TreeView has their ContextMenus bound the the same object (the ContextMenu you defined in ).

    Instead of exposing the ContextMenu as a property in MyControl you can expose its HeirarchicalDataTemplate instead:

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

    If you choose to go this way you will have to define the TreeView ItemTemplate outside of your original user control. In the outer client using the UserControl you can do this:

        <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>
    

    Doing it like this also increases the flexibility of your UserControl as a side effect because you can now customize the TreeView's ItemTemplate in the UserControl from the outside. You can put the HierarchicalDataTemplate in a ResourceDictionary if you want to consistently reuse it.


    A second solution, if you're willing to use the code-behind, is to just use one ContextMenu for the entire UserControl and then programatically determine which item was selected in the code behind for the client.

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