Bug 修复:Vista 和 Win7 上的 TreeView 缩进






4.71/5 (5投票s)
原生 TreeView 在非默认缩进设置下无法使用。
引言
原生 TreeView (COMCTL 6.1) 部署在所有当前的 Vista 和 Windows7 安装中,引入了一个错误,当使用非默认缩进样式 (> 19) 时,此错误会显现出来。 展开/折叠按钮绘制位置与计算位置之间的不匹配导致用户无法展开或折叠节点。 虽然 Vista 视觉样式的添加可能是导致此错误的原因,但它也会影响非主题操作。 据我所知,Windows 操作系统和 Microsoft 产品仅使用默认缩进,因此这是一个低优先级的问题,并且找不到即将修复的公告。 (抱怨开始 -- MS 在 COMCTL 4.71 中引入了 TVITEMEX
结构,其中 iIntegral
成员允许一个节点具有多个项目高度。 直到今天,它都无法使用,因为 MS 从未关心修复滚动例程中的错误。 (详情) -- 抱怨结束)。 我实际上经常使用 25 的缩进,因为它增大了逻辑按钮区域,从而方便了鼠标展开/折叠操作。 本文通过在派生的 .NET TreeView
中转换 Win32 鼠标消息来提供解决方法。 它可以很容易地适应其他语言。
背景
仔细检查表明,TVM_HITTEST
消息返回的点击测试值在 <= WinXP (<=COMCTL 6.0) 和 >=Vista (COMCTL 6.1) 系统之间有所不同。 WinXP 将节点的最初三个像素视为 TVHT_ONITEMINDENT
;增加缩进会导致更大的 TVHT_ONITEMBUTTON
区域。 Vista 增大了 TVHT_ONITEMINDENT
区域,并保持 TVHT_ONITEMBUTTON
区域不变。 Vista 在左侧正确绘制按钮,但希望它直接位于复选框或图像之前,因此包括花哨的悬停状态指示在内的鼠标操作都存在缺陷。 如果没有分配图像列表,Vista 将把按钮向右移动,将它们在逻辑上和图形上放置在缩进区域的中间 --- 可信计算。
修复方法
令我惊讶的是,捕获 TVM_HITTEST
消息并更正返回值没有任何效果。 相反,在 WM_MOUSEMOVE
、WM_LBUTTONDOWN
、WM_LBUTTONUP
消息上,鼠标水平位置被设置为逻辑按钮中心,而光标位于节点的缩进区域中。
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
//case WM_MOUSEHOVER:
if (isWinVistaOrAbove && ShowPlusMinus && Indent > 19 && ImageList != null)
{
// lParam: cursor in client coords
Point pt = new Point((int) m.LParam);
if (translateMouseLocationCore(ref pt))
{
m.LParam = MAKELPARAM(pt.X, pt.Y);
}
}
break;
}
base.WndProc(ref m);
}
按钮逻辑 x 位置的计算取决于 Indent
和当前的 TreeNode.Level
属性,以及水平滚动位置。 当根节点没有按钮时需要稍作修改 (ShowRootLines == false
)。
private bool translateMouseLocationCore(ref Point pt)
{
TreeViewHitTestInfo info = HitTest(pt);
return translateMouseLocationCore(info, ref pt);
}
private bool translateMouseLocationCore(TreeViewHitTestInfo info, ref Point pt)
{
Debug.Assert((isWinVistaOrAbove && ShowPlusMinus &&
Indent > 19 && ImageList != null));
if (info.Node == null || info.Node.Nodes.Count == 0)
{
return false;
}
if ((info.Location & TreeViewHitTestLocations.Indent) != 0)
{
SCROLLINFO si = new SCROLLINFO { cbSize = Marshal.SizeOf(typeof(SCROLLINFO)),
fMask = SIF_POS };
SafeNativeMethods.GetScrollInfo(Handle, SB_HORZ, ref si);
const int btnWidth2 = 8; // half of button width
int newX = pt.X + si.nPos; // mouse in dc coords
int level = info.Node.Level;
if (ShowRootLines)
{
if (newX >= 3 + level * Indent)
{
// center of button in dc coords, that Vista expects
newX = 3 + (level + 1) * Indent - btnWidth2;
pt.X = newX - si.nPos;
return true;
}
}
else if (level > 0)
{
if (newX >= 3 + (level - 1) * Indent)
{
newX = 3 + level * Indent - btnWidth2;
pt.X = newX - si.nPos;
return true;
}
}
}
return false;
}
使用代码
显然,上述光标位置的转换可能在客户端代码中产生副作用,尽管使用缩进区域是不寻常的,而且上下文菜单照常运行。 但是,如果重写了 OnMouseMove
、OnMouseDown/Up
(仅限鼠标左键),则可以很容易地为客户端代码提供真实的鼠标位置。
protected override void OnMouseMove(MouseEventArgs e)
{
Point pt = PointToClient(Cursor.Position);
if (pt != e.Location)
{
e = new MouseEventArgs(e.Button, e.Clicks, pt.X, pt.Y, e.Delta);
}
base.OnMouseMove(e);
}
如果 Microsoft 最终决定修复此错误,我们将不得不更改条件逻辑,以确定何时应用所提出的解决方法。 目前,它只检查主要的操作系统版本。
private static bool isWinVistaOrAbove
{
get
{
OperatingSystem OS = Environment.OSVersion;
return (OS.Platform == PlatformID.Win32NT) && (OS.Version.Major >= 6);
}
}
历史
- 2010 年 11 月:文章发布。
- 20?? 年 11 月:MS 修复了缩进并破坏了
ItemHeight
。