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

.NET Framework 4.8 WinForms TreeView自定义绘制文本间距不一致

解决WinForms TreeView OwnerDrawText与默认绘制文本间距不一致问题

问题根源

默认TreeView使用GDI(通过Win32 DrawText函数)绘制文本,而OwnerDrawText模式下,即使设置DrawDefault = true,控件内部仍会切换为GDI+(TextRenderer)进行文本渲染。GDI和GDI+的文本度量逻辑不同,尤其是字符间距、字形对齐的细节处理,导致相同字体下显示效果出现差异。

解决方案:强制使用GDI绘制文本

要让OwnerDraw的TreeView文本渲染和默认TreeView完全一致,需要在DrawNode事件中手动调用GDI的DrawText函数绘制文本,同时保留自定义高亮框的逻辑。

步骤1:声明Win32 API及常量

首先需要P/Invoke声明DrawText函数和相关常量,以调用原生GDI绘制:

using System.Runtime.InteropServices;

public static class Win32Api
{
    public const int DT_LEFT = 0x00000000;
    public const int DT_VCENTER = 0x00000004;
    public const int DT_SINGLELINE = 0x00000020;
    public const int DT_NOPREFIX = 0x00000800; // 忽略&前缀(TreeView默认行为)

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int DrawText(IntPtr hdc, string lpchText, int cchText, ref RECT lprc, uint uFormat);

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left;
        public int Top;
        public int Bottom;
        public int Right;
    }
}

步骤2:实现DrawNode事件处理

在DrawNode事件中,先绘制自定义高亮框,再用GDI绘制文本,完全匹配默认TreeView的渲染逻辑:

private void CustomTreeView_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
    var treeView = sender as TreeView;
    if (treeView == null) return;

    // 1. 绘制自定义选中高亮框(按需调整)
    if ((e.State & TreeNodeStates.Selected) != 0)
    {
        // 替换为你的自定义高亮绘制逻辑,比如用指定颜色填充背景
        using (var highlightBrush = new SolidBrush(Color.FromArgb(51, 153, 255)))
        {
            e.Graphics.FillRectangle(highlightBrush, e.Bounds);
        }
        e.Graphics.SetClip(e.Node.Bounds);
    }
    else
    {
        // 非选中状态绘制默认背景
        e.DrawBackground();
    }

    // 2. 使用GDI绘制文本,匹配默认TreeView效果
    using (var hdc = e.Graphics.GetHdc())
    {
        var textRect = new Win32Api.RECT();
        textRect.Left = e.Bounds.Left + treeView.Indent; // 匹配TreeView的节点缩进
        textRect.Top = e.Bounds.Top;
        textRect.Right = e.Bounds.Right;
        textRect.Bottom = e.Bounds.Bottom;

        // 设置文本颜色
        var textColor = (e.State & TreeNodeStates.Selected) != 0 
            ? SystemColors.HighlightText 
            : treeView.ForeColor;
        // 将GDI+颜色转换为GDI可用的COLORREF
        var colorRef = (textColor.R | (textColor.G << 8) | (textColor.B << 16));
        SetTextColor(hdc, colorRef);
        SetBkMode(hdc, 1); // 设置透明背景,避免覆盖高亮或默认背景

        // 调用DrawText绘制文本
        Win32Api.DrawText(hdc, e.Node.Text, e.Node.Text.Length, ref textRect, 
            Win32Api.DT_LEFT | Win32Api.DT_VCENTER | Win32Api.DT_SINGLELINE | Win32Api.DT_NOPREFIX);
    }
    e.Graphics.ReleaseHdc();

    // 3. 绘制节点图标(如果TreeView绑定了ImageList)
    if (e.Node.ImageIndex != -1)
    {
        var imageRect = new Rectangle(
            e.Bounds.Left, 
            e.Bounds.Top + (e.Bounds.Height - treeView.ImageList.ImageSize.Height) / 2,
            treeView.ImageList.ImageSize.Width, 
            treeView.ImageList.ImageSize.Height);
        treeView.ImageList.Draw(e.Graphics, imageRect.X, imageRect.Y, e.Node.ImageIndex);
    }
}

// 补充需要的Win32 API声明
[DllImport("gdi32.dll")]
public static extern int SetTextColor(IntPtr hdc, int crColor);

[DllImport("gdi32.dll")]
public static extern int SetBkMode(IntPtr hdc, int iBkMode);

关键说明

  • 缩进处理:通过treeView.Indent调整文本起始位置,和默认TreeView的节点缩进保持一致。
  • 文本格式:DT_NOPREFIX确保TreeView默认的&下划线前缀被忽略,匹配原生行为。
  • 背景透明:SetBkMode(hdc, 1)让文本背景透明,避免覆盖自定义高亮框或默认背景。

验证效果

使用上述代码后,OwnerDraw模式的TreeView文本渲染(字符间距、行高、对齐)会和默认TreeView完全一致,同时保留自定义选中高亮框的功能,无需修改其他TreeView的绘制模式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 07:45:56