Avalonia TreeDataGrid 为每一列自定义右键菜单

Avalonia 的 TreeDataGrid

在 Avalonia 的文档中其实没有特别详细,你可能有一些功能想要做,为此特别贴出来。

目标:自定义右键菜单/自定义菜单
你需要有 https://docs.avaloniaui.net/zh-Hans/docs/reference/controls/treedatagrid/ 的简单使用经验!

因为你自己可能希望每一列不同列应该会有一些不同的右键菜单功能,这篇文章就是关于这个需求的介绍。

一、界面展示




二、XAML 界面

<Window
    x:Class="AvaloniaPlayground2.Views.MainWindow"
    xmlns="https://github.com/avaloniaui"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:views="using:AvaloniaPlayground2.Views"
    xmlns:vm="using:AvaloniaPlayground2.ViewModels"
    Title="AvaloniaPlayground2"
    d:DesignHeight="450"
    d:DesignWidth="800"
    x:DataType="vm:MainWindowViewModel"
    mc:Ignorable="d">

    <Panel>
        <TreeDataGrid Name="PART_DataGrid" ContextRequested="TreeDataGrid_ContextRequested">
            <TreeDataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem
                        Name="PART_CopyNameItem"
                        Header="复制名称"
                        IsVisible="false" />
                    <MenuItem
                        Name="PART_CopyNameProMaxItem"
                        Header="复制名称ProMax"
                        IsVisible="false" />
                    <MenuItem
                        Name="PART_CopyDescItem"
                        Header="复制描述"
                        IsVisible="false" />
                    <MenuItem
                        Name="PART_CopyNameDescItem"
                        Header="复制名称+描述"
                        IsVisible="false" />
                    <MenuItem
                        Name="PART_LabelItem"
                        Header="复制标签"
                        IsVisible="false" />
                </ContextMenu>
            </TreeDataGrid.ContextMenu>
        </TreeDataGrid>
        <Button
            HorizontalAlignment="Right"
            VerticalAlignment="Bottom"
            Click="Button_Click"
            Content="加载数据" />
    </Panel>


</Window>

三、C# 代码

using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Models.TreeDataGrid;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using System.Collections.Generic;
namespace AvaloniaPlayground2.Views
{
    public class TestItemViewModel
    {
        public string Name { get; set; }
        public string Desciption { get; set; }
        public string Label { get; set; }

        public TestItemViewModel(string name, string description, string label)
        {
            Name = name;
            Desciption = description;
            Label = label;
        }
    }

    public partial class MainWindow : Window
    {

        private FlatTreeDataGridSource<TestItemViewModel> _source;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object? sender, RoutedEventArgs e)
        {
            var list = new List<TestItemViewModel>()
            {
                new TestItemViewModel("测试1", "描述描述描述1", "LB1"),
                new TestItemViewModel("测试2", null, "LB2"),
                new TestItemViewModel("测试3", "描述描述描述3", null),
                new TestItemViewModel("测试4", "描述描述描述4", "LB4"),
            };
            _source = new FlatTreeDataGridSource<TestItemViewModel>(list)
            {
                Columns =
                {
                    new TextColumn<TestItemViewModel, string>("名称", i=>i.Name),
                    new TextColumn<TestItemViewModel, string>("描述", i=>i.Desciption),
                    new TextColumn<TestItemViewModel, string>("标签", i=>i.Label),
                }
            };

            PART_DataGrid.Source = _source;
        }

        private int GetIndex(IEnumerable<Visual> visuals, Visual visual)
        {
            var index = 0;
            foreach (var child in visuals)
            {
                if (child == visual) return index;
                index++;
            }

            return -1;
        }

        private int GetColumnIndex(Control? control)
        {
            if (control is TextBlock tb) return GetColumnIndex(tb);
            if (control is Border bd) return GetColumnIndex(bd);
            return -1;
        }
        private int GetColumnIndex(TextBlock? control)
        {
            var p1 = control.Parent;
            var p2 = p1.Parent as Control;
            var p3 = p2.Parent as Control;
            var childrens = p3.GetVisualChildren();
            var index = GetIndex(childrens, p2);
            return index;
        }

        private int GetColumnIndex(Border? control)
        {
            var parent = control.Parent as Control;
            var grand = parent.Parent as Control;
            var childrens = grand.GetVisualChildren();
            var index = GetIndex(childrens, parent);
            return index;
        }

        private void TreeDataGrid_ContextRequested(object? sender, ContextRequestedEventArgs e)
        {
            var textBlock = e.Source as Control;
            var columnIndex = GetColumnIndex(textBlock);

            if (columnIndex < 0) return;

            var header = (string)_source.Columns[columnIndex].Header;

            if (header == "名称")
            {
                PART_CopyNameItem.IsVisible = true;
                PART_CopyNameProMaxItem.IsVisible = true;
                PART_CopyDescItem.IsVisible = false;
                PART_CopyNameDescItem.IsVisible = true;
                PART_LabelItem.IsVisible = false;
            }
            else if (header == "描述")
            {
                PART_CopyNameItem.IsVisible = false;
                PART_CopyNameProMaxItem.IsVisible = false;
                PART_CopyDescItem.IsVisible = true;
                PART_CopyNameDescItem.IsVisible = true;
                PART_LabelItem.IsVisible = false;
            }
            else if (header == "标签")
            {
                PART_CopyNameItem.IsVisible = false;
                PART_CopyNameProMaxItem.IsVisible = false;
                PART_CopyDescItem.IsVisible = false;
                PART_CopyNameDescItem.IsVisible = false;
                PART_LabelItem.IsVisible = true;
            }
            else
            {
                PART_CopyNameItem.IsVisible = false;
                PART_CopyNameProMaxItem.IsVisible = false;
                PART_CopyDescItem.IsVisible = false;
                PART_CopyNameDescItem.IsVisible = false;
                PART_LabelItem.IsVisible = false;
            }
        }
    }
}

四、总结

  1. ContextRequested 可以在右键菜单创建前触发,我的思路是在右键菜单打开前,读取当前格子的信息并且塞到复制按钮的Tag中,其实你也完全可以放到别的地方。
  2. TreeDataGrid_ContextRequested 的 e.Source 会直接来自当前控件,多亏了路由事件的机制,我们可以得到当前右键聚焦的控件是谁,e.Source 一般而言会是当前的 TextBlock ,如果没有文本块,按照我的经验会拿到 Border。
  3. 可以通过一些技巧拿到当前选中格所在列,可以参考代码中的 GetColumnIndex()。
  4. 在右键菜单显示前,通过判断当前列的信息,从而对本来的全部右键菜单进行进行可见性控制(可以优化的更好看一些,但是这样可能会需要更多的理解)。
  5. 示例代码右键菜单的按钮按下是没有反应的,因为我没有订阅它的事件。
posted @ 2025-06-16 11:39  fanbal  阅读(160)  评论(0)    收藏  举报