.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
相关产品推荐
相关产品推荐

