0

我和这个问题有同样的问题,我希望 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,或者解决这个问题的另一种方法?

4

1 回答 1

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;      
}
于 2012-10-18T14:03:59.187 回答