MFC CToolBar update mechanism bug? - c++

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:
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?

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.

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:
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.

Related

EN_UPDATE works unreliably?

I need to intercept key pressings and other user actions in my RichEdit box. I found it too complex to intercept user input in WM_KEYDOWN or WM_CHAR since some of the key pressings fire WM_CHAR, some of them not and also it has some other problems.
So I decided to listen EN_UPDATE messages, cuz it is said that this event fires on every change and just before RichEdit control starts to redraw itself ( https://learn.microsoft.com/en-us/windows/desktop/controls/en-update ). Well it sounds like trustworthy mechanism which allows to intercept all the changes.
But I found that not every WM_KEYDOWN causes firing of EN_UPDATE. I rapidly pressed many buttons (usual "char" buttons like "d", "f" and so on, no special keys), and found that when I inputed 100 chars, and WM_KEYDOWN also fired 100 times, but EN_UPDATE fired only 96 times (the number of activations of EN_UPDATE varies, sometimes it equals to the number of keypressings, don't know what it depends on). The number of WM_KEYDOWN and the number of characters entered are always equal of course.
Here's the code:
BOOL CEditorView::OnCommand( WPARAM wParam, LPARAM lParam )
{
static long count = 0;
if( HIWORD( wParam) == EN_UPDATE )
{
if( (HWND)lParam == g_hwnd_RE )
{
if( g_allowProcessing )
{
count++;
}
}
}
return CDockablePane::OnCommand( wParam, lParam );
}
///// and WM_KEYDOWN
case WM_KEYDOWN:
{
g_testCount++;
return DefSubclassProc( hwnd, msg, wp, lp );
break;
}
Am I doing something wrong or is it just specific working style of EN_UPDATE? Like may be it accumulates changes when user input is too fast.
EN_UPDATE is sent when the control is about to redraw itself. This does not imply that the control has to update after each WM_KEYDOWN, the control might have some caching mechanism to update delaying when keys are down within a little margin, for example.
This is not standard per control, you may have a control that updates on each keypress, even if there is 1 ms delay between keys, and you may have a control that updates each second, no matter how many keys it gets. It depends on the control.
Therefore, if you really want to get notified of every key, you have to use WM_KEYDOWN. EN_UPDATE is usually used to take the current state of the control and update another control.

Specifying a Window Procedure for child Windows

I would like to know if its possible to specify a WndProc for a Child Window created by CreateWindowEx.
I have created a Window Class, the Main Window, the Window Procedure and a Message Loop already. The code works and I decided to keep it out for the clarity of my question.
This is my Window Proc, so far:
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
// Creation of the Win32 Window
case WM_CREATE:
// Add an Edit Field
CreateWindowEx(
WS_EX_CLIENTEDGE,
"EDIT",
"",
WS_CHILD | WS_VISIBLE,
5, 5, 200, 24,
hwnd,
(HMENU)100,
g_Instance, // Comming from WinMain
NULL
);
return DefWindowProc(hwnd, uMsg, lParam, wParam);
case WM_KEYDOWN:
// Track key presses on the edit field
std::cout << "The key with the code " << wParam << " was pressed." << std::endl;
return 0;
case WM_PAINT:
// Some painting code...
return DefWindowProc(hwnd, uMsg, lParam, wParam);
default:
return DefWindowProc(hwnd, uMsg, lParam, wParam);
}
}
I expected key presses on the child Edit Field that I created to throw a WM_KEYDOWN message, but they dont! The keys just get added to the Edit Field in my Window but do not cause a WM_KEYDOWN message.
It seems that the created Edit Window does not use my WndProc. How can I change that?
Your WndProc don't get WM_KEYDOWN messages because, if the user is typing inside the edit control, it means that it has the focus (not your window), so they are sent to the edit control window proc, not yours. However, the edit control window proc will send notifications to your WndProc (his parent window proc).
So, if you only want to react to the user changing the content of your child edit control, you don't need another window procedure. Your current WndProc will receive EN_CHANGE notification code through a WM_COMMAND message.
See https://msdn.microsoft.com/en-us/library/windows/desktop/bb761676(v=vs.85).aspx
If you really want to catch WM_KEYDOWN messages, you need to subclass the edit control, like this:
OldWndProc = (WNDPROC)SetWindowLongPtr (hButton, GWLP_WNDPROC, (LONG_PTR)NewWndProc);
You also need to define a new windows procedure (the NewWndProc), that should handle WM_KEYDOWN message (and any other message you want to handle). You also need to call OldWndProc as you would call DefWndProc in a standard WndProc, unless you want to prevent the edit control to do its normal processing.
For details on subclassing, see https://msdn.microsoft.com/en-us/library/windows/desktop/bb773183(v=vs.85).aspx
Edit
Responding to OP comment here.
If your window is a dialog box, you should be notified of enter key, in your WndProc:
case WM_COMMAND:
if(wParam == IDOFDEFBUTTON || wParam == IDOK) ...
See https://support2.microsoft.com/Default.aspx?scid=kb;en-us;Q102589
To be honest, I never took the time to understand what a dialog box really is. But if I recall correctly, you can get your window to get theses special notifications by calling IsDialogMessage in your message pump:
if(!IsDialogMessage(hWnd,&msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
For interesting information about IsDialogMessage, see http://blogs.msdn.com/b/oldnewthing/archive/2012/04/16/10293933.aspx
If this doesn't give you enough control, you probably have to subclass the edit control.
Your call to CreateWindowsEx creates a new window with "EDIT" wnd class having its own Window procedure. You need a new WndProc and set it to the newly created window (whose handle is returned by CreateWindowEx) via SetClassLong function

Opening a dialog box

I don't quite understand how this works. So I've made my dialog box.. or boxes. And I don't know how to make them appear in my code. Right now I'm trying to just get them to pop up right when I start my program so I can get a basic understanding of how this works.
switch (message)
{
case WM_CREATE:
HINSTANCE hInstance = ((LPCREATESTRUCT) lParam)->hInstance;
CreateDialog(hInstance, "Whatever", hwnd, ABOUT_DIALOG);
That gives me an error in CreateDialog saying a parameter of type int is incompatible with DLGPROC. I'm assuming that I need to declare my dialog box somewhere?
And If I had a button on my very first start up window, how would I know that the user pressed the button? I'm going to once again assume and say that I need to catch it somewhere in the WM_COMMAND command?
The final parameter, the thing that you pass ABOUT_DIALOG to, needs to be a DLGPROC. That is a function of this form:
INT_PTR CALLBACK DialogProc(
HWND hwndDlg,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
The compiler is telling you that ABOUT_DIALOG is not a function of that form. In fact the compiler tells you that ABOUT_DIALOG is an int which is definitely not the right thing!
To get it up and running with a default do-nothing dialog procedure implement it like this:
INT_PTR CALLBACK DialogProc(
HWND hwndDlg,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
return FALSE;
}
The documentation says this:
Typically, the dialog box procedure should return TRUE if it processed the message, and FALSE if it did not. If the dialog box procedure returns FALSE, the dialog manager performs the default dialog operation in response to the message.
So by returning FALSE we are asking for default processing.
Once you have the dialog up and running, you can then fill out the dialog procedure with any functionality that you need.

How do I use Dialog resources in Win32?

Without resources I can create my UI with a complex array of CreateWindow() and CreateWindowEx(), and WndProc() to process my events.
I noticed if I right-click in the resource view and click "add resource", I can draw a dialog box with all the controls. This would save me a huge amount of time if I could draw the interface like I normally do with C#.
After I've drawn the interface with the resource editor, how do I then create the window from code? Can someone provide a very simple example with a button, and show how to handle a WM_COMMAND event on that button please?
Also, is this generally how people create the GUI? Is there any loss in flexible to do this way? Even in C# I often have to supplement designer-generated UI with my own code-generated UI, but the majority of the time I'm quite happy to use designer.
After creating the dialog in the resource editor, call CreateDialog(modeless dialog;you need to dispatch the messages manually just like when you use CreateWindow) or DialogBox(modal dialog; the function does not return until you close the dialog. it does the dispatching for you) to make the dialog show up. Just like you pass in the window proc to RegisterClass, you pass the dialog proc to those functions for the dialog call back. An example of DialogProc looks likes this:
BOOL DialogProc( HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam ){
switch( iMessage ){
case WM_COMMAND:
switch( LOWORD( wParam ) ){
case BTOK:
MessageBox( hDlg, "Hello, World!", NULL, NULL );
return TRUE;
break;
}
break;
}
return FALSE;
}
This is a basic way of creating a dialog. More sophisticated method would normally involve OOP, usually wrapping each resource( button, window, etc) as a C++ object or using MFC.
If you have placed your button or any control on some dialog, that control is already in created state. For handling the messages of these child controls on this dialog , you have to override OnCommand Method in the class which is implementing your dialog.
For Example:
//CDialog_ControlDlg is my Dialog class derived from CDialog
//IDC_BUTTON_SAMPLE is the ID of the button which was palced on the dialog in the resource Editor..
BOOL CDialog_ControlDlg::OnCommand(WPARAM wParam,LPARAM lparam){
int iNotiFicationMsg=HIWORD(wParam);//This is thenotification Msg from the child control
int iCommandId=LOWORD(wParam);//And Control ID of the Child control which caused that Msg
BOOL result=FALSE;
switch(iCommandId){
case IDC_BUTTON_SAMPLE:
if(iNotiFicationMsg==BN_CLICKED)
{
//Your Code for handling this type of Msg for this control..
}
break;
default:
{
//Specific Code;
}
return result;
}
}

Activate windows under mouse through mouse hook

Basically i make mousestruct in the hook
MOUSEHOOKSTRUCT* str;
Then make it from lparam,
LRESULT CALLBACK MouseProc( int nCode, WPARAM wParam, LPARAM lParam )
{
str = (MOUSEHOOKSTRUCT *) lParam;
...
Then I catch mousemovements
case WM_MOUSEMOVE:
wParm = AU3_WM_MOUSEMOVE;
fromp = WindowFromPoint(str->pt);
Then validate and try not to send to many messages...
if (fromp != currentwindow)
{
currentwindow= fromp;
PostMessage(m_hHwndMouse, wParm,(WPARAM)( (MOUSEHOOKSTRUCT*) lParam )->hwnd, LPARAM(fromp));
}
break;
This sends the mousemove message along with hwnd to my autoit app which inspects hwnd and if that hwnd is not active it activates it.
Func mouse_func($hWndGUI, $MsgID, $wParam, $lParam)
Select
Case $MsgID = $WM_AUTOITMOUSEMOVE
If GUICtrlRead($activateundermouse) = 1 And $sitting = 0 Then
;Local $starttime = _Timer_Init()
If StringInStr(WinGetTitle($lParam), "ID=") Then
If Not WinActive($lParam) Then
;ConsoleWrite("HOVERING NEW, Activate It: " & WinGetTitle($lParam) & #LF)
WinActivate($lParam)
EndIf
;ConsoleWrite("diff is > " & _Timer_Diff($starttime) & #LF)
EndIf
EndIf
This is how I am activating window that is hovered by the mouse but the problem is that rarely autoit wont read the message that should signal new window being hovered(or the dll with hook didnt send it, I dont know)
Also if the window is overlapping another window and both of them are valid windows that should be activate once hovered I get flickering as autoit is constantly trying to activate the current window and the overlapped one, in a loop
Is there something that perhaps I missed or could be doing wrong here?
It's easiest just to use the facility built into Windows. For example on Windows 7 it looks like this:
This capability is present in older versions of Windows too but is not exposed in such a simple interface. Instead you have to set it with a PowerToy or through SystemParametersInfo.
As Raymond Chen explains, this is a user preference which should not be changed without the user's consent.
You can use the blocking SendMessage instead depending on surrounding implementation. PostMessage will send to the window message queue but may not have the priority you expect as it returns without waiting for the processing of the message.
Also check out SetCapture and ReleaseCapture to change which window is capturing mouse events based on a hit test in mouse move as an alternative to forwarding. Only one window at a time can capture mouse events using these functions so that will solve your flicker issue of the windows forwarding messages to each other most likely.