CWaitingTreeCtrl






4.90/5 (12投票s)
2000年10月17日

203431

2968
一个CTreeCtrl派生类,它仅在必要时填充树的节点,并带有可选的视觉效果
它是什么 & 为什么?
我最初开始这个项目时的真实意图是开发一个显示网络资源的树控件。枚举网络资源可能是一个耗时的操作,所以我决定只在用户尝试展开某个项时才进行实际的枚举。我很快意识到这种行为可能对许多其他目的很有用,所以我将所有需要的代码放入了一个用于树控件的基类中,该基类可以与其他类一起使用。我还添加了对视觉反馈的支持,以在项正在填充时显示。
当用户单击展开一个项时,会添加一个新项,其中包含一个等待消息。如果您在树控件内实现一些动画,消息将可见,否则它将保持隐藏。蓝色矩形是您可以绘制的区域:您可以做任何您想做的事情,甚至完全覆盖等待消息,只要您在该区域内。还支持定时动画。
与此同时,您可以填充正在展开的项,并添加新的子项,树控件将仅在您完成时重绘自身以反映新内容:用户将只看到您的动画。您也可以选择在填充时不要显示任何内容,这非常容易。
我还添加了在填充过程中重绘控件的功能。主线程在填充过程完成之前是忙碌的,这是“按设计”的,但我注意到它仍然会收到WM_ERASEBKGND
消息,至少在Win2K上是这样。所以现在我在进程开始之前获取控件的快照,并使用该位图来重绘控件的背景,直到展开的项完全填充完毕。(请在其他平台上报告此功能是否正常工作)
使用该类
这个类为两种派生类提供了一个通用的接口:一种是提供动画效果的,另一种是为树控件提供项的。显然,您可以实现一个提供内容和动画的类,但可能会损失代码重用性。
如果您将两个实现分开,您可以
- 将注意力集中在内容提供程序类上,该类将处理底层的树控件
- 选择一个现成的动画提供程序类来为您的内容提供程序类添加视觉反馈,或者创建您自己的
- 将控件留空,不带任何动画(用户将看不到等待消息)
- 当您使用内容提供程序类时,启用等待消息
提供树内容
只需从CTreeCtrl
派生您的类,然后在您的“.h”和“.cpp”文件中将每个CTreeCtrl
的出现替换为CWaitingTreeCtrl
。
class CMyTreeCtrl : public CWaitingTreeCtrl
{
...
};
提示:如果您稍后想使用Class Wizard来生成消息处理程序,您可以暂时将BEGIN_MESSAGE_MAP
宏中的基类更改为CTreeCtrl
,但请记住将其恢复为正确的基类(CWaitingTreeCtrl
),并更改生成的消息处理程序中对基类实现的每次调用。
BEGIN_MESSAGE_MAP(CMyTreeCtrl, CWaitingTreeCtrl)
//{{AFX_MSG_MAP(CMyTreeCtrl)
...
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
要用项填充树控件,您需要重写PopulateItem
,并在某个地方调用PopulateRoot
以插入第一级项。您还可以重写WantsRefresh
以在用户展开某些项时自动刷新它们。
CWaitingTreeCtrl::PopulateItem
virtual void PopulateItem(HTREEITEM hParent)
当项被展开并且等待消息显示后,该函数在主线程中被调用。动画在函数执行期间发生,并在函数返回时停止。
通常,您使用hParent
参数来获取有关要填充的父项的信息,然后开始添加新项。直到此函数完成之前,不会发生重绘,这是按设计进行的。
此外,目前没有办法中止此函数,因为它会阻塞主线程,但您可以将底层树控件创建在另一个用户界面线程中,并提供您自己的方法。
在实现此函数时,您必须记住以下规则
- 您应该只添加一个级别的子项,并且应该只处理
hParent
项及其子项 - 您始终可以假定在调用函数时
hParent
项没有子项 - 您需要处理
hParent
是TVI_ROOT
的特殊情况 - 如果您希望子项可展开,您应该将
TVITEM
结构体的cChildren
字段设置为<SPAN CLASS="cpp-literal">1
- 您应该将您的进度通知基类(参见下方)
返回TRUE
表示基类将检查您是否添加了任何项,如果它没有找到任何项,您将无法再次展开hParent
项(加号按钮将被移除)。
返回FALSE
表示hParent
项将始终可展开,即使它现在没有子项。请注意,这并不意味着hParent
项每次展开时都会被刷新,而是它将可展开,直到它获得一些子项。如果您想每次展开时刷新hParent
项,您必须重写WantsRefresh
。
注意:如果您为每个项关联了一些数据,您可能会处理TVN_DELETEITEM
通知。如果您这样做,您应该忽略lParam
字段等于零的项。它们可能是用于显示等待消息的项,并且没有关联数据。然而,这不应该是一个问题,因为零值通常是一个NULL
指针。
CWaitingTreeCtrl::WantsRefresh
virtual BOOL WantsRefresh(HTREEITEM hParent)
该函数在PopulateItem
之前被调用,仅当hParent
项已经有子项时,用于询问派生类是否希望刷新该项的子项。
如果您希望项在用户每次展开它时刷新其子项,您必须重写此函数。请记住,要刷新没有子项的项,您必须通过在PopulateItem
重写中返回FALSE
来告诉基类不要检查插入的项。
如果您希望自动刷新该项(按上述方式),则返回TRUE
;如果您不希望自动刷新,则返回FALSE
。基类实现仅返回FALSE
。
CWaitingTreeCtrl::PopulateRoot
void PopulateRoot()
如果您想看到一些项,您必须在派生类中的某个地方调用此函数。一个好的地方可能是您的PreSubclassWindow
或OnCreate
重写,但您可以决定有一个函数来初始化与根项关联的一些数据,然后模拟根展开,填充第一级项(PopulateItem
被调用,并将TVI_ROOT
作为父项)。
您还可以填充树的第一级或更深的几级,而无需用户展开任何项。已经有子项的父项不会被传递给PopulateItem
,除非WantsRefresh
指示必须刷新它。因此,静态项是完全合法的,并且可以与动态项一起使用。
CWaitingTreeCtrl::SetPopulationCount
void SetPopulationCount(int iMaxSubItems, int iFirstSubItem = 0)
您应该在PopulateItem
重写中调用此函数,在添加任何项之前,以设置您计划插入的项的总数(iMaxSubItems
)和可选的初始值(iFirstSubItem
)。
如果您不知道将要插入多少项,应该将总数设置为零。
CWaitingTreeCtrl::IncreasePopulation
void IncreasePopulation(int iSubItemsToAdd = 1)
当您插入一个新项或一组项时,您应该在PopulateItem
重写中调用此函数。当前计数增加iSubItemsToAdd
参数,该参数可以是负值。
CWaitingTreeCtrl::IncreasePopulation
void UpdatePopulation(int iSubItems)
当您插入一个新项或一组项时,您应该在PopulateItem
重写中调用此函数。当前计数将更新为iSubItems
的值。
提供动画
创建一个通用类并派生自CWaitingTreeCtrl
。稍后,您可以将基类替换为模板,这样您就可以使用该类作为插件,为通用的CWaitingTreeCtrl
派生类添加自定义动画和视觉效果。(我不太熟悉模板,所以可能有更好的方法。)
template <class BASE_TYPE>
class CMyAnimationFX : public BASE_TYPE
{
...
};
有两种类型的动画:一种是在指定时间间隔刷新,另一种是在内容提供程序类插入新项时才更新。
要显示动画帧,您必须重写DoAnimation
。如果您想初始化与消息项相关的某些变量,例如动画帧的位置,您需要重写PreAnimation
和PostAnimation
(仅当您需要在最后释放内存时)。
在所有这些函数中,您应该调用基类实现,尤其是在不直接派生自CWaitingTreeCtrl
时。这样,并使用模板,您可以为您的内容提供程序类添加一个以上的动画。
CWaitingTreeCtrl::PreAnimation
virtual void PreAnimation(HTREEITEM hItemMsg)
该函数在动画开始前在主线程中调用。其唯一参数是显示等待消息的树项的句柄。您可以使用它来计算显示动画的位置,但不应将其存储以供以后使用。您不能假定该项在动画期间仍然存在。
等待消息项默认是不可见的,如果您想使用它的属性来显示靠近它的内容,例如动画项的图像,或者如果您更改了它的视觉外观,您必须在构造函数中(或在此函数之前)调用ShowWaitMessage
。
您通常实现此函数来初始化动画期间可能需要的附加变量。请记住,hItemMsg
项是一个临时项:您只能在此函数内部使用它,并且只有在等待消息可见时才能使用。
如果您使用hItemMsg
来绘制内容,则需要使其可见。如果消息项可见,您还可以更改树控件的视觉外观,例如滚动客户区以正确显示动画。如果等待消息隐藏,树控件将无法重绘自身。如果您不绘制动画但使用另一个窗口或控件,您可能会遇到这种情况。
CWaitingTreeCtrl::DoAnimation
virtual void DoAnimation(BOOL bTimerEvent, int iMaxSteps, int iStep)
每次需要更新动画时,该函数将在高优先级工作线程中调用。如果bTimerEvent
参数为TRUE
,则在计时器间隔过去时调用此函数,否则则更新了项计数。如果您想处理计时器事件,您必须在动画开始前,在构造函数或PreAnimation
中调用SetAnimationDelay
来指定计时器周期。
其他两个参数反映了枚举过程的当前进度:iMaxSteps
是过程的总步数(零表示未知),而iStep
是当前步数。您可以在计时器事件期间选择忽略这些。您通常使用这些信息来显示进度条。
您需要实现此函数来绘制动画的新帧。如果您在树控件内绘制,您应该只在调用GetItemRect(hItemMsg, lpRect, FALSE)
得到的矩形区域内绘制。请记住,您不能在此函数中调用此函数:您只能在PreAnimation
内部使用CTreeCtrl::GetItemRect
或CWaitingTreeCtrl::GetItemImageRect
。
CWaitingTreeCtrl::PostAnimation
virtual void PostAnimation()
动画结束后,该函数将在主线程中调用。
只有在您需要清理一些附加变量时,才需要实现此函数。
CWaitingTreeCtrl::SetAnimationDelay
void SetAnimationDelay(UINT nMilliseconds)
您可以在构造函数或PreAnimation
重写中调用此函数来设置计时器事件之间的延迟。零延迟表示您不需要计时器事件,并且是初始值。如果您需要计时器事件,则必须显式调用此函数。
请注意,只有一个计时器,因此在动画开始前最后一次调用此函数将优先(如果您想使用多个动画类)。
CWaitingTreeCtrl::GetItemImageRect
BOOL GetItemImageRect(HTREEITEM hItem, LPRECT pRect)
如果您需要绘制在项的图像上,通常是在等待消息旁边,可以调用此函数。
如果函数成功,则返回值为TRUE
,否则返回FALSE
。
公共函数
您的内容提供程序类还将继承一些public
函数。
CWaitingTreeCtrl::SetWaitMessage
void SetWaitMessage(LPCTSTR pszText, HICON hIcon = NULL)
您可以调用此函数来更改等待消息的文本和图像。如果hIcon
参数为NULL
,则该项将获得一个空白图标。
CWaitingTreeCtrl::ShowWaitMessage
void ShowWaitMessage()
如果您想在树控件内绘制,您需要在您的动画提供程序类中调用此函数,可以在构造函数中或在调用PreAnimation
之前的任何地方。您应该只在等待消息占据的矩形区域内绘制,因此它必须是可见的。如果您在另一个窗口中绘制动画,或者使用另一个控件来提供视觉反馈,则不需要使等待消息可见。
如果您没有任何动画,但想要静态等待消息,也可以调用此函数。切勿在您的内容提供程序类中调用此函数,显示消息的选择留给您的树控件的最终用户。
请记住,动画提供程序类应始终假定等待消息是不可见的,因此如果它需要在控件内绘制,则必须显示它。
CWaitingTreeCtrl::RefreshSubItems
void RefreshSubItems(HTREEITEM hParent)
您可以调用此函数来手动刷新hParent
项的子项。您只能刷新可展开的项,无论它们是否实际有子项。事实上,您只为用户可展开的项分配一个按钮,并且只有那些项可以被刷新。
更新
- 2000年10月17日
- 首次公开发布
- 2001年9月27日
- 修复了刷新空项的错误以及可能存在的其他问题
- 添加了忙碌时的背景重绘(至少在Win2k上)
- 许可证更改为 Artistic License
结论
这个类可能需要一些调整,但对我来说已经足够了。我认为如果您看到一些实现,您会更好地理解这个类,所以请看其他的文章(参见顶部)。我将非常感谢任何评论、建议或贡献。任何有助于改进这篇文章(以及其他文章)的帮助都受欢迎,我知道阅读起来不容易,对我来说写起来也很困难。