WPF TreeViewItem上下文菜单取消使用HierarchicalDataTemplate的项目

时间:2012-09-14 16:02:26

标签: c# wpf treeview

我遇到与this question相同的问题,我希望TreeViewItem在显示其上下文菜单时仍然主动选择。但是,在我的树中,每个级别都有不同类型的对象,因此我希望每个级别都有不同的ContextMenu。我正在使用HierachicalDataTemplate完成此任务。所以我有以下XAML:

<Window x:Class="Project.MainWindow">
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Project" ContentRendered="Window_ContentRendered">
    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="VolumeTemplate">
                <StackPanel Orientation="Horizontal">
                    <Image Source="{StaticResource VolumeIcon}" Margin="3,3,3,3" />
                    <TextBlock Text="{Binding Path=Name}" Margin="3,3,3,3">
                        <TextBlock.ContextMenu>
                            <ContextMenu>
                                <MenuItem Command="{Binding VolumeTestCommand}"
                                          Header="VolumeTest" />
                            </ContextMenu>
                        </TextBlock.ContextMenu>
                    </TextBlock>
                </StackPanel>
            </DataTemplate>
            <HierachicalDataTemplate x:Key="ServerTemplate"
                                     ItemsSource="{Binding Volumes}"
                                     ItemTemplate="{StaticResource VolumeTemplate}">
                <StackPanel Orientation="Horizontal">
                    <Image Source="{StaticResource ServerIcon}" Margin="3,3,3,3" />
                    <TextBlock Text="{Binding Name}" Margin="3,3,3,3" >
                        <TextBlock.ContextMenu>
                            <ContextMenu>
                                <MenuItem Command="{Binding ServerTestCommand}"
                                          Header="ServerTest" />
                            </ContextMenu>
                        </TextBlock.ContextMenu>
                    </TextBlock>
                </StackPanel>
            </HierarchicalDataTemplate>
        </Grid.Resources>
        <TreeView HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                  ItemsSource="{Binding Servers}" Name="tvMain"
                  ItemTemplate="{StaticResource ServerTemplate}"
                  PreviewMouseRightButtonDown="tvMain_PreviewMouseRightButtonDown" />
    </Grid>
</Window>

代码背后:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_ContentRendered(object sender, EventArgs e)
    {
        //set DataContext here, based on a login dialog
    }

    static T VisualUpwardSearch<T>(DependencyObject source) where T : DependencyObject
    {
        DependencyObject returnVal = source;

        while (returnVal != null && !(returnVal is T))
        {
            if (returnVal is Visual || returnVal is Visual3D)
            {
                returnVal = VisualTreeHelper.GetParent(returnVal);
            }
            else
            {
                returnVal = LogicalTreeHelper.GetParent(returnVal);
            }
        }

        return returnVal as T;
    }

    private void tvMain_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
        TreeViewItem treeViewItem = VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject);

        if(treeViewItem != null)
        {
            treeViewItem.IsSelected = true;
            e.Handled = true;
        }
    }
}

我尝试了引用问题的答案,但我认为它不起作用,因为上下文菜单在TextBlock而不是TreeViewItem上。有没有办法将ContextMenu附加到DataTemplate中的TreeViewItem,或者解决此问题的另一种方法?

1 个答案:

答案 0 :(得分:0)

我最终必须创建一个附加属性,它将上下文菜单从TextBlock移动到TreeViewItem:

public static readonly DependencyProperty StealContextMenuProperty = 
    DependencyProperty.RegisterAttached(
        "StealContextMenu", 
        typeof(bool), 
        typeof(ParentClass),
        new UIPropertyMetadata(false, new PropertyChangedCallback(SCMChanged))
    );

public static bool GetStealContextMenu(FrameworkElement obj)
{
    return (bool)obj.GetValue(StealContextMenuProperty);
}

public static void SetStealContextMenu(FrameworkElement obj, bool value)
{
    obj.SetValue(StealContextMenuProperty, value);
}

public static void SCMChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    FrameworkElement fe = sender as FrameworkElement;
    if (fe == null) return;

    bool value = (bool)e.NewValue;
    if (!value) return;

    fe.Loaded += new RoutedEventHandler(fe_Loaded);
}

public static void fe_Loaded(object sender, RoutedEventArgs e)
{
    FrameworkElement fe = (FrameworkElement)sender;
    FrameworkElement child;
    child = VisualDownwardSearch<FrameworkElement>(fe, x => x.ContextMenu != null);
    if (child != null)
    {
        fe.ContextMenu = child.ContextMenu;
        child.ContextMenu = null;
    }
}

public static T VisualDownwardSearch<T>(T source, Predicate<T> match)
    where T : DependencyObject
{
    Queue<DependencyObject> queue = new Queue<DependencyObject>();
    queue.Enqueue(source);
    while(queue.Count > 0)
    {
        DependencyObject dp = queue.Dequeue();

        if (dp is Visual || dp is Visual3D)
        {
            int childrenCount = VisualTreeHelper.GetChildrenCount(dp);
            for (int i = 0; i < childrenCount; i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(dp, i);
                if (child is T)
                {
                    T tChild = (T)child;
                    if (match(tChild)) return tChild;
                }
                queue.Enqueue(child);
            }
        }
        else
        {
            foreach (DependencyObject child in LogicalTreeHelper.GetChildren(dp))
            {
                if (child is T)
                {
                    T tChild = (T)child;
                    if (match(tChild)) return tChild;
                }
                queue.Enqueue(child);                    
            }
        }
    }
    return null;      
}