//========================================================================

//TITLE:

//    让无效按钮不再困扰

//AUTHOR:

//    norains

//DATE:

//    Wednesday  16-January-2008

//Environment:

//    VS2005 + SDK-WINCE5.0-MIPSII  

//    EVC + SDK-WINCE5.0-MIPSII 

//========================================================================


    相信很多朋友会有这么一个经验,窗口中有这么一个按钮,按下的时候会进行相关的运算处理,但在处理的过程中不打算让用户再次点击该按钮,所以很当然地在相关运算处理开始前调用EnableWindow令按钮无效,计算完毕之后再次调用EnableWindow恢复.逻辑没有任何问题,所有人都会这么想,难道不是么?只是,结果真的是如此么?我们不妨举个实例看看.


    我们的实例中有两个按钮,一个显示Exit,一个显示Disable,前者点击时即退出程序,后者先令Exit按钮无效,然后Sleep一段时间用来模拟耗时较久的工作,接着恢复Exit按钮状态.


    主要代码如下(完整的工程代码可在此下载

//消息过程

LRESULT CMainWnd::WndProc(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)

{

    switch(wMsg)

    {

        case WM_COMMAND:

        {

            if(HIWORD(wParam) ==  BN_CLICKED  && LOWORD(wParam) == IDC_BTN_EXIT)

            {

                PostQuitMessage(0x00);    

                return 0;

            }

            else if(HIWORD(wParam) ==  BN_CLICKED  && LOWORD(wParam) == IDC_BTN_ENABLE)

            {

                OnClickedBtnEnable(hWnd, wMsg, wParam, lParam);

                return 0;

            }

            break;

        }

    }

    return CWndBase::WndProc(hWnd,wMsg,wParam,lParam);

}


//Enable按钮响应函数

void CMainWnd::OnClickedBtnEnable(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)

{

    TestCode();

}



//模拟测试代码

void CMainWnd::TestCode()

{

    EnableWindow(m_hBnExit,FALSE);

    Sleep(2000);

    EnableWindow(m_hBnExit,TRUE);

}


    我们在TestCode中禁止了Exit按钮,然后Sleep了2秒,最后恢复Exit按钮.一切看起来都很美好,不是么?好吧,我们将工程编译运行吧!


    点击Disable按钮,让Exit无效,然后我们在轻轻点击一次Exit按钮,恩,不用紧张,其实你也可以点击多次,只要Exit没有恢复有效状态.好,停止点击,让我们静待Exit恢复.恩,在两秒之后,Exit恢复有效.啊!怎么回事?程序为什么退出了?为什么在无效状态点击Exit按钮,在它恢复后会收到Exit的WM_COMMAND消息让它退出程序?世界是不是要乱了?


    不必紧张,其实问题很简单.在我们按下Disable后,WndProc函数一直在处理Disable按钮的BN_CLICKED消息.接着我们Sleep了两秒,在这两秒的时间内我们点击了Exit按钮,这时候Exit按钮将发送WM_LBUTTONCLICK,而此时因为Disable按钮的BN_CLICKED处理函数还没返回,所以系统将Exit按钮的WM_LBUTTONCLICK存储在发送队列.当Disable按钮的BN_CLICKED处理函数函数返回后,系统才将WM_LBUTTONCLICK发送,而此时Exit按钮已经恢复有效状态.因此在接收到WM_LBUTTONCLICK消息后,默认的处理函数将发送Exit的BN_CLICKED消息,从而调用Exit按钮的处理函数.


    知道原因,问题解决就简单了.不就是因为Disable的BN_CLICKED处理函数没有及时返回嘛?那我们创建个线程,让线程去做相关处理不就可以了么?


    由此,OnClickedBtnEnable函数更改如下:


void CMainWnd::OnClickedBtnEnable(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)

{

  //创建线程

    HANDLE hd = CreateThread(NULL,NULL,ThreadProc,this,0,NULL);

    CloseHandle(hd);

}


DWORD CMainWnd::ThreadProc(PVOID pArg)

{

    CMainWnd *pObject = (CMainWnd *)pArg;

    pObject->TestCode();

    return 0;

}

    一切都很正常,难道不是么?


    上面这个方法是采用线程方式,那么不创建线程可不可以呢?答案当然是肯定的.原因之前已经说了,是因为WM_LBUTTONCLICK消息滞后处理,那么我们在使有效之后或之前从消息队列中移除该消息不也皆大欢喜?在此先题外话一下,如此天马行空的想法并不是出自于我,而是张挺朋友的奇思妙想,在此对张挺朋友深表感谢!


    根据思路,OnClickedBtnEnable函数更改如下:

void CMainWnd::OnClickedBtnEnable(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)

{    

  //方法二:删除消息

    TestCode();

    MSG msg;

    while(::PeekMessage(&msg,m_hWnd,WM_LBUTTONDOWN,WM_LBUTTONDBLCLK,PM_REMOVE));    


}


    两种方法都能达到我们的目的,那么如何取舍呢?如果处理过程比较短,在处理工程中不必让界面响应任何按钮信息,那么方法二则最为清爽适合;如果处理所耗费的时间很长,而在这时间段内需要处理界面响应,那则是方法一的创建线程为佳.