2

In Visual Studio, MFC CToolBar buttons are updated by ON_UPDATE_COMMAND_UI which is sent during idle state. I suppose this mechanism is buggy if I change enable/disable state of the button in ON_UPDATE_COMMAND_UI handler.

Specifically:

Suppose the button is now in enabled state. At a certain time, the code 'wants' the button to be disabled (and of-course should not be clicked by user), but the button will be really disabled at next idle period, as in following figure:

enter image description here

In red area in the figure, the code state, in my opinion, is unstable and the developer must handle this unstable state by checking the button state manually. I have no idea if I have missed something or have some misunderstanding of this mechanism?

PS: The procedure for Menu is OK. It always calls ON_UPDATE_COMMAND_UI handler and check the button state before ON_COMMAND handler.

My question is how to make the ON_UPDATE_COMMAND_UI handler be called before ON_COMMAND handler just like Menus do?

guan boshen
  • 724
  • 7
  • 15
  • Your question (if it is a question) is unclear. What is it that you are having problem(s) with? – rrirower May 24 '16 at 17:26
  • I think you are right, that's the behavior of the buttons. One must check in the click handler if the code is supposed to be executed. – Adrian Roman May 24 '16 at 18:03
  • I'll assume that when 'the code 'wants'' you are setting the flag from a worker thread. If so, this is expected when the mouse is not on the app window, the main thread is completely idle. Do try to get your mouse to the button and click it while it is active and should not be. I don't think you can get there. – lakeweb May 24 '16 at 23:06
  • @AdrianRoman If a normal Button is disabled, Mouse events are not passed to the button, therefore its event handler is certainly not called. – guan boshen May 25 '16 at 02:27
  • @lakeweb Yes, what you said is probably true! (I can't achieve this by myself). But I still think it is potentially dangerous. – guan boshen May 25 '16 at 02:30
  • 1
    It is by design. There is no good reason for the main thread to wake up if there is no activity, it is a waste of resources to do so. A mouse over wakes the thread and the ui gets updated. All you should have to do is post some harmless message to the main thread after you set the flag to rattle its cage, the ui will get updated. The main thread should not have to know you have a thread that requires GUI service. – lakeweb May 25 '16 at 03:18
  • I was talking about the toolbar buttons, that was the subject. It's not that big deal to check the condition in the command handler. – Adrian Roman May 25 '16 at 08:17
  • The bug is in your code, not MFC's. You must not modify the GUI (directly or indirectly) from any thread other than the thread owning the GUI. You need to fix your code. – IInspectable May 25 '16 at 21:15
  • @IInspectable No, I am not updating any UI from worker thread. Actually, It could happen even in UI thread only. For example, I set a flag to FALSE in OnButtonClick, assuming the button should not be clicked any more, but the button is grayed on next idle cycly. During the gap, the program state is unstable. – guan boshen May 26 '16 at 03:13

2 Answers2

0

After debugging and tracing, I finally found a possible solution. Key codes are listed here to help others with the same problem. Override OnCommand as follows:

BOOL CMainDlg::OnCommand(WPARAM wParam, LPARAM lParam)
{
    // Disable WM_COMMAND reflection for ToolBar control
    // makes the ON_UPDATE_COMMAND_UI handler be called 
    // right before ON_COMMAND handler.

    if ((HWND)lParam == m_wndToolBar.GetSafeHwnd())
        lParam = NULL;

    return CDialog::OnCommand(wParam, lParam);
}

The side effect is WM_COMMAND reflection is disabled for ToolBar, but it would be OK in most cases.

guan boshen
  • 724
  • 7
  • 15
0

As I just ran into this so I thought I'd add my solution. I have a button to paste records into a database and so it is clear for the client, I only wanted the button enabled if there is valid data on the clip board. Here is what it looks like:

enter image description here

My App in the back and notepad++ in front with records selected. When I 'ctrl C' the text in notepad++ the 'I' on my tool bar becomes active even though my app is idle. My app is part of the clipboard chain and gets notified. This is the WM_DRAWCLIPBOARD handler.

LRESULT CMainFrame::OnDrawClipboard(  WPARAM wparam, LPARAM lparam  )
{
    if( hWndClipboardChain )
        ::SendMessage( hWndClipboardChain, WM_DRAWCLIPBOARD, wparam, lparam );

    if( wparam )
        PostMessage( ID_CLIPBOARD_HASCHANGED, 0, 0 );

    return TRUE;
}

From there I post to my app not getting in the way of the WM_DRAWCLIPBOARD message, and there:

LRESULT CMainFrame::OnCheckClipboard(  WPARAM wparam, LPARAM lparam  )
{
    std::string data( GetClipboardStr( ) );
    std::string::size_type end_cnt= data.find( "\r\n" );
    if( end_cnt == std::string::npos )
        bClipboardHasValidRecords= false;
    else
    {
        auto header_end= data.begin( ) + end_cnt;
        csv_vect_t header;
        split( header, str_it_range_t( data.begin( ), header_end ), boost::is_any_of("\t") );

        bClipboardHasValidRecords= header.size( ) == RARECORD_SIZE;
    }
    return TRUE;
}

The main thread of my app is waken up by the messages and the 'I' will turn on and off without making the app an active window. And it just happens without any extra code.

lakeweb
  • 1,859
  • 2
  • 16
  • 21