win32通用控件TreeView滚动条自绘

来源:互联网 发布:c语言数组怎么用 编辑:程序博客网 时间:2024/06/09 22:18

《标题》win32通用控件TreeView滚动条自绘


     直接使用windows sdk 进行开发自绘滚动条是很让人蛋疼的,嫌消息HOOK 麻烦,又不了解第三方控件,别担心,你还有一条小路可走:使用子窗口模拟滚动条。

效果图如下,正常状态下的滚动条


鼠标进入滚动条时候滚动条的颜色:


左键按下拖拉滚动条时滚动条的颜色:



其实思路很简单,要注意的地方和大致过程如下:

1:创建 treeview 控件的时候记得加入属性 TVS_NOSCROLL  禁止使用滚动条

      代码示例:

HWND htree = CreateWindow("SysTreeView32", nullptr,WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | TVS_HASLINES | TVS_HASBUTTONS | TVS_NOSCROLL,petreeViewarea.left, petreeViewarea.top, petreeViewarea.right, petreeViewarea.bottom,hparent, (HMENU)id, appInst, NULL);


2:记得为新创建的 treeview 控件指定一个消息处理函数

3:创建 滚动条子窗口之后需要去去掉 caption 属性

      示例代码:

SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_CAPTION);SetWindowPos(hwnd, NULL, 0, 0, (int)FRAME_WIDTH, (int)FRAME_HEIGHT, SWP_DRAWFRAME | SWP_NOMOVE);

4:使用3个变量  min,pos,max  来薄记滚动条的状态,并且使用 pagesize 来保存页面的大小
,变量之间的关系可以使用下面两条函数来计算:

/** 计算垂直滚动条的高度* @pagesize: 当前页面大小* @pomax: pos最大* @返回值: 滚动条的高度*/int calScrollbarLen(const int& pagesize, const int& posmax){if (pagesize >= posmax)return 0;return (int)((float)(pagesize * pagesize) / (float)posmax);}/** 计算垂直滚动条的起始位置* */ int calScrollbarTop(const int& pagesize, const int& pos, const int& posmax){if (pagesize >= posmax)return 0;return (int)((float)(pagesize * pos) / (float)posmax);}

5:为了实现滚动条三态效果需要在滚动条的消息函数中处理 WM_MOUSEMOVE,WM_MOUSEHOVER,WM_MOUSELEAVE,追踪鼠标事件消息并设着相应状态颜色

6:分别在 滚动条 和 treeview 控件中处理消息 WM_MOUSEHEEL 更改 pos 的值响应用户滚动滚动条操作

7:最后也是最重要的一点,当 treeview 某个节点展开或收缩的时候 max 的值会发生变化,treeview控件消息以WM_NOTIFY形式发送给父窗口,为了响应这个消息我们应该在 treeview 的父窗口中使用下面的语句来处理 展开或收缩消息(TVN_ITEMEXPANDING比TVN_ITEMEXPANDED来的快一步,具体用哪个按喜好)

switch (message)
    {

。。。。。。。。。

    case WM_NOTIFY:            // 通知消息
        tvinfo = (LPNMTREEVIEW)lparam;
        switch (tvinfo->hdr.code)
        {
        case TVN_ITEMEXPANDING:
            // pnm->action=2 表示打开
            // pnm->action=1 表示关闭
            pnm = (NMTREEVIEW*)lparam;
            if (pnm)
            {
        
            }
            break;

。。。。。。。

MSDN没有明确说明但需要注意的是 NMTREEVIEW 结构体成员 action 为1表示 子项关闭/收缩,2表示打开。但是为了精确的计算 max 还得必须知道打开的子项有多少个子节点或者收缩子项有多少个子节点, treeview 已经为我们提供大量“宏”可用,例如你可以通过 BOOL TreeView_GetItem( HWND hwndTV, LPTVITEMEX pitem )  来取得 hendTV 某个节点的信息,只要把上面代码中的 pnm->itemNew.hItem传入pitem ,并且设置好 pitem 其它几项就可以得到诸如 节点名称,节点图像,节点是否有子节点的信息(注意pitem->int cChildren;只会返回1或0来表示是否有子节点),然后为了统计到底有多少个子节点,假如有100个子节点的话又得使用 TreeView_GetNextItem 宏101次来遍历统计节点数量,要知道这是个灾难,因为这些宏是通过向 treeview 发送消息来取得数据的,况且还是调用的 sendmessage 函数。

废话这么多因此明白了要想知道当前节点有多少子节点是绝对使用其他方法来实现的,还记得添加节点时候使用的宏 TreeView_InsertItem,如果你设置了 TVINSERTSTRUCT.item.lParam的值,那么 接受

TVN_ITEMEXPANDING 或 TVN_ITEMEXPANDED 消息的时候就可以通过 LPNMTREEVIEW(lparam)->itemNew.lParam 来重新取得这个值,然后轻松读取预先设置的节点数量,标准而不失个性化的设计,很好。接下来该怎么做就显而易见了,

使用 calScrollbarTop 和 calScrollbarLen重新计算滚动条的起始位置和长度,并使用 setWindowPos 重新设置 滚动条 和 treeview控件的位置和大小。

8:今天再补充一点关于鼠标拖动滚动条时绘制效率的问题.。显然为了模拟原生滚动条就需要在鼠标拖动垂直模拟滚动条上下移动时不断更新 treeeview 控件和滚动条的位置并重新绘制两者,因为 WM_MOUSEMOVE 消息的过于频繁往往导致单核CPU占用达到100%,频繁的绘制完全就是浪费,我测试后发现及时将绘制频率降低到1/5绘制效果也是可以接受的,因此我在滚动条消息处理函数中处理 WM_MOUSEMOVE 消息处加入以下代码:

++delaycount;if (delaycount > 2){delaycount = 0;if (isLBTNdown){GetCursorPos(&newcurpos);yhit = newcurpos.y - oldcurpos.y;if ((petreeScrollbararea.top + yhit) <= 0){petreeScrollbararea.top = 0;SetWindowPos(hpetree, 0, petreeViewarea.left, 0, 0, 0, SWP_NOSIZE);SendMessage(hpetree, WM_PAINT, 0, 0);} else if ((petreeScrollbararea.top + yhit) >= (contentarea.bottom - petreeScrollbararea.bottom)) {petreeScrollbararea.top = contentarea.bottom - petreeScrollbararea.bottom;SetWindowPos(hpetree, 0, petreeViewarea.left, -(petreeScrollbarMax - petreeViewarea.bottom), 0, 0, SWP_NOSIZE);SendMessage(hpetree, WM_PAINT, 0, 0);} else {petreeScrollbararea.top += yhit;petreeScrollbarPos += (int)(factor * (float)yhit);if (petreeScrollbarPos <= 0)petreeScrollbarPos = 0;else if (petreeScrollbarPos >= (petreeScrollbarMax - petreeViewarea.bottom))petreeScrollbarPos = (petreeScrollbarMax - petreeViewarea.bottom);SetWindowPos(hpetree, 0, petreeViewarea.left, -petreeScrollbarPos, 0, 0, SWP_NOSIZE);SendMessage(hpetree, WM_PAINT, 0, 0);}// 更新 oldcursorposoldcurpos = newcurpos;// 更新滚动条位置SetWindowPos(hwnd, 0, petreeScrollbararea.left, petreeScrollbararea.top, 0, 0, SWP_NOSIZE);SendMessage(contentViewWnd, WM_PAINT, 0, 0);}}

delayCount 是预先定义的整形数据,绘制频率减为三分之一后在连续拖动滚动条的情况下最多只占用了不到5%CPU(treeview展开子项在百行以内,更多不敢保证),虽然手段粗糙,但还是蛮有效果的

本篇结束。

0 0
原创粉丝点击