You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Gtk# NodeView控件Tooltip显示错位至错误行问题咨询

Gtk# NodeView控件Tooltip行错位问题

问题描述

在Gtk#的NodeView控件使用过程中,Tooltip存在异常错位现象:具体原因为GetPathAtPos方法返回TreePath时未将表头行的高度纳入计算,最终导致Tooltip显示在错误的行位置。

复现代码

using Gtk;

[TreeNode(ListOnly = true)]
public class MyTreeNode : TreeNode
{
    [TreeNodeValue(Column = 0)]
    public string RowNumber { get; set; } = "";

    [TreeNodeValue(Column = 1)]
    public string Data { get; set; } = "";
}

public partial class MainWindow : Gtk.Window
{
    public MainWindow() : base(Gtk.WindowType.Toplevel)
    {
        Build();

        nodeview1.AppendColumn("Row Number", new CellRendererText(), "text", 0);
        nodeview1.AppendColumn("Data", new CellRendererText(), "text", 1);

        NodeStore nodeStore = new NodeStore(typeof(MyTreeNode));

        nodeStore.AddNode(new MyTreeNode { RowNumber = "One", Data = "foo" });
        nodeStore.AddNode(new MyTreeNode { RowNumber = "Two", Data = "bar" });
        nodeStore.AddNode(new MyTreeNode { RowNumber = "Three", Data = "baaz" });
        nodeStore.AddNode(new MyTreeNode { RowNumber = "Four", Data = "quux" });

        nodeview1.NodeStore = nodeStore;
        nodeview1.HasTooltip = true;
        nodeview1.QueryTooltip += Nodeview1_QueryTooltip;
    }

    private void Nodeview1_QueryTooltip(object o, QueryTooltipArgs args)
    {
        bool result = false;

        if (nodeview1.GetPathAtPos(args.X, args.Y, out TreePath path))
        {
            TreeModel model = nodeview1.Model;

            if (model.GetIter(out TreeIter iter, path))
            {
                string rowNumber = model.GetValue(iter, 0) as string;

                args.Tooltip.Text = "This is the tooltip for Row Number " + rowNumber;
                result = true;
            }
        }

        args.RetVal = result;
    }

    protected void OnDeleteEvent(object sender, DeleteEventArgs a)
    {
        Application.Quit();
        a.RetVal = true;
    }
}

复现表现

运行上述代码时会出现固定偏移:

  • 鼠标悬停在RowNumber属性为"Three"的行上,Tooltip显示内容为 "This is the tooltip for Row Number Four"
  • 鼠标悬停在表头区域时,Tooltip会显示RowNumber为"One"的第一行提示内容
  • 所有行的Tooltip均存在等距偏移问题

临时方案与诉求

目前临时规避方案为调用GetPathAtPos时传入Y轴参数为args.Y - 23,测试环境下表头行高度约为23像素,但硬编码固定像素值适配性差,不符合开发规范。
需要寻找该问题的正式修复方案,或是具备动态适配性的规避方法(例如运行时动态获取表头行高度的实现方式)。

测试环境

测试代码在Ubuntu 22.04 LTS环境下通过MonoDevelop 7.8.4 (build 2)编译,环境说明如下:

  • 当前MonoDevelop仅支持GTK 2版本,若有支持更高版本GTK的配置方案也可参考
  • 测试所用Ubuntu系统运行于VirtualBox虚拟机中,宿主操作系统为Windows 10

解决方案

这个偏移本质是坐标参考系不匹配导致的,不属于需要补丁修复的源码bug:

  • QueryTooltip事件回调中传入的args.X、args.Y是相对于整个NodeView控件左上角的坐标,包含表头区域
  • GetPathAtPos方法要求传入的坐标是相对于NodeView内容区域(BinWindow)左上角的坐标,不包含表头区域
    硬编码减去固定23像素仅能匹配当前测试环境的表头高度,更换系统主题、修改字体、调整DPI缩放后都会失效。

动态适配实现

直接调用ConvertWidgetToBinWindowCoords方法做坐标转换即可自动扣除表头高度,不需要手动获取表头像素值,适配所有场景。将原回调逻辑修改为如下代码即可:

private void Nodeview1_QueryTooltip(object o, QueryTooltipArgs args)
{
    bool result = false;
    // 把控件全局坐标转换为内容区域相对坐标,自动适配任意高度的表头
    nodeview1.ConvertWidgetToBinWindowCoords(args.X, args.Y, out int binX, out int binY);
    
    // 传入转换后的坐标匹配行路径
    if (nodeview1.GetPathAtPos(binX, binY, out TreePath path))
    {
        TreeModel model = nodeview1.Model;
        if (model.GetIter(out TreeIter iter, path))
        {
            string rowNumber = model.GetValue(iter, 0) as string;
            args.Tooltip.Text = "This is the tooltip for Row Number " + rowNumber;
            result = true;
        }
    }
    args.RetVal = result;
}

补充说明

  • 该写法在GTK2、GTK3版本的Gtk#下均可正常使用,无版本兼容问题
  • 即使后续设置HeadersVisible = false隐藏表头,转换逻辑也能自动适配,不需要加额外判断分支
  • 关于GTK版本支持:原生旧版MonoDevelop确实仅支持GTK2栈,若要使用GTK3开发,可直接引用Gtk# 3.x版本的绑定库,搭配支持GTK3的.NET开发环境即可,不需要依赖旧版MonoDevelop。

内容的提问来源于stack exchange,提问作者CSquared

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 10:15:30