Chinaunix首页 | 论坛 | 博客
  • 博客访问: 578554
  • 博文数量: 752
  • 博客积分: 40000
  • 博客等级: 大将
  • 技术积分: 5005
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-13 14:47
文章分类

全部博文(752)

文章存档

2011年(1)

2008年(751)

我的朋友

分类:

2008-10-13 16:50:12

        由于近来一直使用BCB(Borland C++ Builder), 好久没仔细推敲VC了。偶然发现一按钮使能方面的问题,权且称作按钮控件的BUG,及解决方法,与大家分享。
        下面开始我们的研究之旅。
        新建一对话框应用程序PraDlg,将对话框模板上的静态文件控件ID改为IDC_STATIC_TEST,再添加一按钮,改ID为IDC_BTN_TEST, Caption改为Test。
        双击Test按钮, 即可添加该按钮的消息映像函数。在此函数中做如下处理:
< 1 >.
    void
 CPraDlgDlg::OnBtnTest() 
    {
           CButton 
*pBut = (CButton *)GetDlgItem(IDC_BTN_TEST);    //获得按钮指针

        pBut->EnableWindow(FALSE);                                                    //将该按钮禁止使能,变灰。
        static int nNum = 0;                                                                       //统计该按钮被点击次数。我使用静态变量。
        SetDlgItemInt(IDC_STATIC_TEST, ++nNum);                         //将此数据显示在静态文本框中。
        Sleep(5000);                                                                                  //这里是问题的关键的关键。假定我们做五秒钟的工作
        pBut->EnableWindow(TRUE);                                                    //做完工作后再使能
    }

        可能你还没弄明白我要干什么,没关系,咱们运行,然后按照我说的做。
        在按钮上连续点击五次,当然这五次不要超过五秒,因为我刚才Sleep了五秒,注意观察按钮和文本框的变化。问题出来了。
        问题描述:
        我们原意是在按钮被点击时,为防止按钮再次被点击,先将该按钮禁止使能,直到要做的处理全部做完后再允许使用者点击。这里的Sleep(5000); 是假定我们要做的处理工作。但上面的例子运行的现象是,当第一次点击按钮时,按钮状态立即变灰,即非使能,静态文本框中出现数字1,这一点,完全在我们的预料之中。 每过五秒,文本框中的数字会一直累加直到五(因为刚才点击了五次)。
        嗯? 问题出来了,完了,按钮眞的有BUG,因为后四次肯定是在按钮变灰后点击的,可函数还是执行了五次,难道是按钮的使能状态没用? 但不对呀,以前做的按钮使能都没问题的呀。 那么,是不是在消息响应函数执行完之前点击的,按钮变灰不影响点击。 好,我们再做下面实验:
< 2 >
    
void CPraDlgDlg::OnBtnTest() 
    {
        CButton 
*pBut = (CButton *)GetDlgItem(IDC_BTN_TEST);    //获得按钮指针
        pBut->EnableWindow(FALSE);                                                     //将该按钮禁止使能,变灰。
        static int nNum = 0;                                                                         //统计该按钮被点击次数。我使用静态变量。
        SetDlgItemInt(IDC_STATIC_TEST, ++nNum);                           //将此数据显示在静态文本框中。
        Sleep(5000);                                                                                     //这里是问题的关键的关键。假定我们做五秒钟的工作
        //  pBut->EnableWindow(TRUE);                                                   //屏蔽掉按钮使能,验证下是否按钮响应退出前不受变灰状态的改变。
    }

        好了,将最后一条语句屏蔽掉后,运行,连续点击五次按钮,发现点击完一次后,按钮立即变灰,文本框出现数字1, 再等n久,按钮还是没反应。也就是说,该按钮只接
受了一次点击,和上面做的推测显然不符,那么究竟什么原因导致问题的呢?
        从1和2的现象综合分析,得出如下结论:
        a:  按钮消息处理的时候,鼠标点击事件被记录下来了,这说明按钮的点击事件优先级较高。
        b:  按钮消息处理完后,再处理下一次的按钮消息。这就是为什么消息能再次被响应。(最后一句又使能了嘛,所以能再次被处理)
        c:  按钮是否根据使能状态响应函数是在派遣消息时判断的,而不是在点击时判断的。
        通过上面b和c结论可以初步推测出鼠标点击到响应过程的眞面目。
        那么怎么解决这个问题, 以达到我们的目的呢? 定时器? 好,继续做上面的实验:
< 3 > 
    
void CPraDlgDlg::OnBtnTest() 
    {
        CButton 
*pBut = (CButton *)GetDlgItem(IDC_BTN_TEST);    //获得按钮指针
        pBut->EnableWindow(FALSE);                                                     //将该按钮禁止使能,变灰。
        static int nNum = 0;                                                                         //统计该按钮被点击次数。我使用静态变量。
        SetDlgItemInt(IDC_STATIC_TEST, ++nNum);                           //将此数据显示在静态文本框中。
        Sleep(5000);                                                                                     //这里是问题的关键的关键。假定我们做五秒钟的工作
        SetTimer(10, NULL);                                                                    //处理完后启动定时器,让定时器取消息按钮使能。
        //pBut->EnableWindow(TRUE);                                                    //做完工作后再使能
    }
    
void CPraDlgDlg::OnTimer(UINT nIDEvent) 
    {
        GetDlgItem(IDC_BTN_TEST)
->EnableWindow(TRUE);           //取消按钮使能。
        KillTimer(nIDEvent);                                                                     //去掉定时器。
        CDialog::OnTimer(nIDEvent);
    }

        运行,连续点击n次按钮,发现第一次点击后按钮变灰,之后经过五秒钟,按钮变可用,文本框中数字变为1而没变成5,哈哈成功了。
        你可能会问,为什么牠不先执行定时器的函数,使按钮可用,再执行鼠标点击的事件函数,继续响应按钮呢。嗯,这个呢,是因为定时器的优先级更低,没有按钮消息时
才会响应定时器。而在派遣按钮消息时,按钮又是非使能的,所以也没法运行。
        眞像差不多大白了,但上面的处理还要借助定时器,太麻烦了,有没有更简单的方法呢?
   
        回顾一下Windows的消息机制, Windows应用程序都有一个消息队列,系统会从消息队列中一个一个的取出消息,翻译,派遣,再来看一下所做的实验 1:
        当鼠标点击时, 点击消息被放到消息队列中,Windows从消息队列中取出一条消息,然后派遣,执行消息响应函数。在函数中,先将按钮禁止使能,再后做我们需要的工
作,这时又过来几条消息,因为当前工作尙未完成,所以仍然堆到消息队列中。当工作完成后,使按钮可用,退出响应函数,Windows再从消息队列中取出一条消息,因为此
时按钮已经可用,所以该消息仍然可以被派遣,执行。 实验1中就这样一次一次的将按钮消息执行完。
        完美解决方法: 在消息响应函数退出之前,将所有的按钮消息从消息队列中取出来,仍掉。(口喜口喜口喜口喜!!)
void CPraDlgDlg::OnBtnTest() 
{
    
// TODO: Add your control notification handler code here
    CButton *pBut = (CButton *)GetDlgItem(IDC_BTN_TEST);    //獲得按鈕指針
    SetTimer(10, NULL);
    pBut
->EnableWindow(FALSE);                                                     //將該按鈕禁止使能,變灰。
    static int nNum = 0;                                                                         //統計該按鈕被點擊次數。我使用靜態變量。
    SetDlgItemInt(IDC_STATIC_TEST, ++nNum);                           //將此數據顯示在靜態文本框中。
    Sleep(5000);                                                                                    //這裡是問題的關鍵的關鍵。假定我們做五秒鐘的工作
    pBut->EnableWindow(TRUE);                                                      //做完工作後再使能
    MSG msg;
    
while(PeekMessage(&msg, GetSafeHwnd(), WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE ))
        NULL;
}

        关于PeekMessage的作用和参数,参看MSDN吧。
发表于 水石 阅读(733) | |  

--------------------next---------------------

阅读(267) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~