自动重复按钮类






4.45/5 (6投票s)
2002年4月15日
5分钟阅读

143848

1845
一个可按设定时间自动重复的按钮控件
引言
过去,我曾从零开始制作过自定义的自动重复按钮,也就是说,我从一个原始的CWnd
开始,并在其之上进行构建。最近,在microsoft.public.vc.mfc新闻组中出现了一些关于自动重复按钮的提问。我最初的回答是“只需给一个子类化的按钮添加一个定时器”,但在思考了几天之后,我意识到这其中存在一些问题。
我不认为这是一个优雅的解决方案,但它确实有效,并且可以避免重新发明所有按钮的绘制、样式等工作。
基本问题在于,大多数自动重复按钮的特点是,当按下左键时,它们会向父窗口发送一个消息,然后在按住不放的情况下重复发送消息。
常规按钮不是这样工作的。它在左键*释放*时向父窗口发送一个消息。这意味着,如果您只是设置一个定时器,您将收到每个定时器滴答一次事件,然后在按钮释放时收到一个事件。
好了,我作弊了。大大的作弊了。令人惊讶的是,我似乎蒙混过关了!所以我要指出,这种技术并非没有风险。本文档不提供任何明示或暗示的担保,包括适销性或特定用途的适用性(如果我有一个律师,他会让我在所有大写字母中说出这样的话)。
首先,我使用ClassWizard创建了一个CButton
的子类CAutoRepeatButton
。然后,我添加了OnLButtonDown
、OnLButtonUp
和OnTimer
的事件处理程序。我定义了两个常量(您可以将其设为变量,并作为初始设置以实现可编程性)用于初始延迟和重复延迟。另外,还有一个用于定时器ID的常量。
#define INITIAL_DELAY 500 #define REPEAT_DELAY 200 #define IDT_TIMER 1
然后我编写了事件处理程序。
在类的头文件中,我添加了一个新的成员变量sent
。
protected:
UINT sent;
我修改了OnLButtonDown
事件处理程序,使其如下所示。
void CAutoRepeatButton::OnLButtonDown(UINT nFlags, CPoint point) { SetTimer(IDT_TIMER, INITIAL_DELAY, NULL); sent = 0; CButton::OnLButtonDown(nFlags, point); }
我启动了一个定时器,基于初始延迟,并将发送的点击次数计数器归零。稍后我将讨论它的必要性。
在OnTimer
事件处理程序中,我添加了指示的代码。
void CAutoRepeatButton::OnTimer(UINT nIDEvent) { if( (GetState() & BST_PUSHED) == 0) return; SetTimer(IDT_TIMER, REPEAT_DELAY, NULL); GetParent()->SendMessage(WM_COMMAND, MAKELONG(GetDlgCtrlID(), BN_CLICKED), (LPARAM)m_hWnd); sent++; CButton::OnTimer(nIDEvent); }
这里发生了什么?嗯,首先,如果您将鼠标从按钮上拖走,它会弹回来。这是标准的按钮行为。所以,我查找了指示此状态的变量,并发现它是BST_PUSHED
状态。所以我检查该状态以查看该位是否已设置。如果未设置,则不希望生成BN_CLICKED
事件。我决定也不想更改定时器间隔。所以,如果按钮状态不是“按下”,我直接返回。如果按钮状态*是*按下,我将定时器重置为较短的间隔(对同一个定时器进行多次SetTimer
操作总是可以的;定时器只是以新间隔重新启动)。我向父窗口生成一个BN_CLICKED
通知。SendMessage
只是创建与按钮在OnLButtonUp
事件中生成的WM_COMMAND
消息相同的消息。然后,我增加了一个计数器,记录我发送的项的数量。
这一切都运行良好,除了如果我通过CButton
的超类事件进行正常的OnLButtonUp
,我总是会得到一次额外的点击。如果将时间常量设置为较大的值,例如1000和1000,很容易看到这一点。您会看到计数器点击到“27”,然后释放按钮,它显示“28”。这是不好的。
所以我将以下代码添加到OnLButtonUp
事件处理程序中,并移除了对超类的调用。
void CAutoRepeatButton::OnLButtonUp(UINT nFlags, CPoint point) { KillTimer(IDT_TIMER); if(GetCapture() != NULL) { /* release capture */ ReleaseCapture(); if(sent == 0 && (GetState() & BST_PUSHED) != 0) GetParent()->SendMessage(WM_COMMAND, MAKELONG(GetDlgCtrlID(), BN_CLICKED), (LPARAM)m_hWnd); } /* release capture */ //CButton::OnLButtonUp(nFlags, point); }
显而易见的事情是杀死定时器。这很简单。但我必须“伪造”正常的按钮行为。所以,我做的第一件事就是注释掉对超类的调用。现在,我知道这会导致严重的故障;事实上,如果您只做这一点,您会发现按钮永远不会释放捕获。所以我作弊了,并自己强制释放了捕获。令我惊讶的是,按钮实际上弹回并正确重绘,这是我确信不会起作用的事情,并且会迫使我必须手动编码整个内容。我蒙混过关了,但我对这个想法并不满意。
它是这样工作的。
首先,如果我只是在对话框的某个地方单击鼠标,将其拖入按钮区域然后释放鼠标,那么OnLButtonUp
事件就没有意义了。所以我只希望在我确实拥有捕获时才执行此操作。因此,进行GetCapture() != NULL
测试。如果我没有捕获,我就不需要做任何事情,因为按钮没有被激活。如果我拥有捕获,我现在就释放它,执行默认的OnLButtonUp
事件处理程序所做的事情。现在,如果用户在INITIAL_DELAY
间隔之前释放了按钮,什么都没有发送,所以我想发送一些东西,以便一次快速点击也能被看到。因此,使用了sent
计数器(它也可以是BOOL
,但我决定计数而不是仅仅标记为已发送。这是一个可有可无的选择)。但是,假设用户在按钮内单击,并在INITIAL_DELAY
时间内将鼠标拖出按钮,*然后*释放了它?所以我添加了对BST_PUSHED
状态的检查,并且只有当两个条件(尚未发送任何内容且按钮实际上被按下)都为真时,才向父窗口发送消息。
这些文章中表达的观点是作者的观点,不代表,也不被微软认可。