Trap a special click event on an Edit/Textbox control with C++/WinAPI/MFC - c++

I'm coding in C++/MFC on a Windows platform (using MS VS2008.) I have this Edit control (which is basically a text box) that is set to be read-only. The control displays some basic information. I want to add an "Easter Egg" to my app, i.e. when a user Ctrl+Shift clicks on this edit control it must display some additional info. The question is how to trap such a click event using MFC/native WinAPIs?

The most straight forward way is to subclass the edit control using SetWindowLong and catch WM_LBUTTONDOWN event. You'd then want to call GetAsyncKeyState or equivalent to check whether the specific key is being pressed or not, and show the message.

There is no need to do subclassing. Just catch WM_PARENTNOTIFY
case WM_PARENTNOTIFY: {
if (LOWORD(wParam) == WM_LBUTTONDOWN)
printf("x: %i, y: %i\n", LOWORD(lParam), HIWORD(lParam));
}
break;
By default, child windows in a dialog box have the WS_EX_NOPARENTNOTIFY style and so doesn't notify the parent window. You should remove this style.
case WM_INITDIALOG: {
HWND hChildWnd = GetDlgItem(hWnd, IDC_CHILD);
LONG style = GetWindowLong(hChildWnd, GWL_EXSTYLE);
style &= ~WS_EX_NOPARENTNOTIFY;
SetWindowLong(hChildWnd, GWL_EXSTYLE, style);
...
P.S. I hope it's not too late :D

Related

Tab Control: How to prevent the selection from changing?

My project is simple WIN32 dialog box with a tab control.
Under some condition, I want to prevent the user from changing tab. MS documentation is quite straightforward -
Returns TRUE to prevent the selection from changing, or FALSE to allow the selection to change.
However, this just does not work!
The code:
case WM_NOTIFY:
if (((LPNMHDR)lParam)->idFrom == IDC_DEVTABS)
{
if (((LPNMHDR)lParam)->code == TCN_SELCHANGING)
return (INT_PTR)TRUE;
//return (INT_PTR)OnSelChanging(hDlgTab);
if (((LPNMHDR)lParam)->code == TCN_SELCHANGE)
OnSelChanged(hDlgTab);
return (INT_PTR)TRUE;
}
I can see that my parent dialog box receives the message, but the tab is changing anyhow. Any idea how I can prevent the tab from changing?
If your control is in a dialog, as I assume it is, you need to return the value through the DWLP_MSGRESULT window data.
SetWindowLongPtr(hWndDlg, DWLP_MSGRESULT, TRUE);
return TRUE;
The return value from the dialog procedure indicates to the dialog manager whether or not the message was handled. The actual return value for the message is provided via DWLP_MSGRESULT.
As always, see Raymond's blog for a good discussion of this.

Switch between edit controls using Tab?

The Window is non DialogBox based so WS_TABSTOP doesn't work. Moreover I don't want to Tab through all the controls, I just want to Tab through few Edit controls.
What I did is I superclassed the Edit control and handled the WM_KEYDOWN message, switching between edit controls, by getting next window in the line thorugh ::GetWindow(hwnd,GW_HWNDNEXT); Also I would like to switch focus back to the first Edit control when I have reached the last one.
The Code doesn't work for when I have reached the last Edit control, the ::GetWindow simply returns the next window in the line(?), which happens to be a non superclassed edit control. And there are more hidden child windows(SW_HIDE).
Maybe if I know how to know the class name of the window's HWND ?
Note: Pure Win32 api, c++ oop.
else if ( ( int ) wParam == VK_TAB )
{
HWND nextInLine;
nextInLine = ::GetWindow ( hwnd, GW_HWNDNEXT );
if ( hwnd == NULL ) nextInLine = ::GetWindow ( hwnd, GW_HWNDPREV );
::SendMessage ( nextInLine, EM_SETSEL, ( WPARAM ) 0, ( LPARAM ) -1 );
::SetFocus ( nextInLine );
return 0;
}
You get keyboard navigation for free in any window by using the IsDialogMessage API call. To consume the service a window message loop has to be modified to include a call to IsDialogMessage and only pass the message on to regular message handling if it hasn't been handled by the dialog manager already.
MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0)) {
if (IsDialogMessage(hwnd, &msg)) {
/* Already handled by dialog manager */
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Don't forget to set the WS_EX_CONTROLPARENT extended window style on the parent window, so that the dialog manager recurses into child windows.
It's possible to get away with just calling IsDialogMessage, but the result isn't quite 100% dialog-like. To make an ordinary window behave like a dialog:
Specify DLGWINDOWEXTRA as the cbWndExtra field of your WNDCLASS (don't forget to add on extra space you might already be using and offset your data's indexes)
Call DefDlgProc rather than DefWindowProc
Since this makes your window a dialog, you need to use the DWLP_USER window long instead of GWLP_USERDATA, if you're using that, when calling GetWindowLongPtr or SetWindowLongPtr.
(From memory, the main thing you get from doing the above is support for WM_NEXTDLGCTL, which I've found useful to use for supporting changing focus using the Enter key, using Method I described in http://support.microsoft.com/kb/102589.)
Then in your message pump, call IsDialogMessage for each dialog-like window in your message pump.
Finally, when creating controls for your dialog-like window, set the WS_TABSTOP window style for each window you want to participate in the tabbing, and set the WS_EX_CONTROLPARENT window exstyle (aka Control Parent in the resource editor) for child windows that contain dialog controls.

Flickering using WM_DRAWITEM

It seems like I can't get pass this issue with owner-draw controls. I've super-classed a status control. I am trying to customize but still retain the same functionality. Basically, I want to change the background and text. I'm using Direct2d (or ID2D1DCRenderTarget interface) for the drawing. I have successful changed the background by using WM_NCPAINT; although, you can use WM_ERASEBKGRND if you want. However, both methods acted as a control in my experiment and flickering still occurred. Moreover, flickering doesn't occur when the WPARAM of SB_SETTEXT is NOT SET to SBT_OWNERDRAW. Therefore, I came to a conclusion that WM_DRAWITEM is the culprit. Is there anyway I can fix this flickering issue with owner-draw statusbar?
You can avoid flickering if you turn on double buffering for you control.
Set the WS_EX_COMPOSITED extended style:
http://msdn.microsoft.com/en-us/library/windows/desktop/ff700543(v=vs.85).aspx
e.g. when handling WM_CREATE, call (WTL or MFC):
ModifyStyleEx(0, WS_EX_COMPOSITED);
Well, seems like I figured it out. When super-classing a status-bar follow these sets to avoid flickering.
**Note: This has only been tested with visual styles turned off. SetWindowTheme(hWndStatus, L"", L"");
Also, the parent window must have WS_CLIPCHILDREN set in the style parameter during window creation.
1: Override WM_SIZE. Make a call to InvalidateRect(m_hWnd, NULL, TRUE) and return 0 unless you want the default sizing; in this case, call CallWindowProc.
2: Override WM_ERASEBKGND and return -1.
3: Override WM_NCPAINT and place your drawing code here.
Handling WM_NCPAINT. People seem to have trouble understanding how to handle WM_NCPAINT. Here is how I do it.
if (wParam == 1) {
hdc = GetWindowDC(m_hWnd);
} else {
hdc = GetDCEx(m_hWnd, (HRGN) wParam, DCX_WINDOW | DCX_INTERSECTRGN | DCX_CACHE);
}
Then do drawing with the DC.
4: In the parent procedure (WndProc or whatever) call SetWindowPos(..., SWP_DRAWFRAME) with the handle to the statusbar. This will resize your statusbar.
5: Send a message via SendMessage(hWndStatusbar, SB_SETPARTS, 1, (LPARAM) &parts);
6: Send a message via SendMessage(hWndStatusbar, SB_SETTEXT, LOBYTE(0) | SBT_OWNERDRAW, L"Ready"). Sample code for WM_DRAWITEM:
...
WM_DRAWITEM:
LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT) lParam;
m_pFramework->m_pD2D1RenderTarget->BindDC(lpDIS->hDC, &lpDIS->rcItem);
m_pFramework->m_pD2D1RenderTarget->BeginDraw();
m_pFramework->m_pD2D1RenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::CadetBlue));
D2D1_RECT_F rf = D2D1::RectF(
PixeltoDipX(lpDIS->rcItem.left),
PixeltoDipY(lpDIS->rcItem.top),
PixeltoDipX(lpDIS->rcItem.right),
PixeltoDipY(lpDIS->rcItem.bottom)
);
m_pFramework->m_pD2D1RenderTarget->DrawText(
(LPCWSTR) lpDIS->itemData,
wcslen((WCHAR*) lpDIS->itemData) + 1,
m_pFramework->m_pTextFormat,
rf,
m_d2dCaptionTextColor
);
m_pFramework->m_pD2D1RenderTarget->EndDraw();
break;
....
This should stop flickering. Also, do not call InvalidateRect(hWndStatus, NULL, TRUE) in the parent's WM_SIZE. This was the main reason it flickered.

How to handle closing MessageBox

my environment is C++, MFC, compact-framework for WM 6.0+ devices.
In many places, I am showing pop-up messages using 'MessageBox()' to give a simple warning or get Yes/No repsonse from user. What I want to do is that whenever any message is closed, call some common function before I perform specific codes.
I tried WM_SHOWWINDOW in parent window but it doesn't seem to occur.
Any suggestion will be appreciated.
[Added] my screen has many buttons and I have to make sure only one button is focused all the time. When I show message box, button seems to loose its focus so I want to focus it back when message is closed. Of course, I can do it in every place where message is used but looking for a better way to handle this situation.
The MessageBox function returns specific return codes when it is closed, you can wrap the MessageBox function and check the return values and run some code based on that.
Here are the return codes from MSDN :
IDABORT 3 The Abort button was selected.
IDCANCEL 2 The Cancel button was selected.
IDCONTINUE 11 The Continue button was selected.
IDIGNORE 5 The Ignore button was selected.
IDNO 7 The No button was selected.
IDOK 1 The OK button was selected.
IDRETRY 4 The Retry button was selected.
IDTRYAGAIN 10 The Try Again button was selected.
IDYES 6 The Yes button was selected.
So the following code can be used to run different functions based on the return code.
void MyMessageBox(wstring title,wstring message)
{
int msgboxID = MessageBox(
NULL,
(LPCWSTR)message.c_str(),
(LPCWSTR)title.c_str(),
MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2
);
switch (msgboxID)
{
case IDCANCEL:
// TODO: add code
break;
case IDTRYAGAIN:
// TODO: add code
break;
case IDCONTINUE:
// TODO: add code
break;
//so on
}
}
More info here :
http://msdn.microsoft.com/en-us/library/windows/desktop/ms645505(v=vs.85).aspx
You might try intercepting the WM_ACTIVATE message in the parent window.

Rich Edit Control changes the DialogBox return value behavior?

I'm a bit puzzled: i created a dialogbox with Edit Control, then i noticed the text isn't word wrapped, so i googled and found out that i should use Rich Edit Control instead. So i did. Now, when ever there is a Rich Edit Control in my dialog box, the functionality changes: without Rich Edit Control the dialogbox returned either IDOK or IDCANCEL, which i handle outside of the message handler code. BUT, if there is a Rich Edit Control anywhere in the dialogbox, it always returns something else than IDOK, before i even click any buttons in the dialog box: the dialogbox seems to not even be created at all.
Here is the message handler:
INT_PTR CALLBACK MyDialogBox(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam){
switch(message){
case WM_INITDIALOG: {
SetDlgItemText(hDlg, IDC_EDIT1, (LPCTSTR)some_string.c_str());
return (INT_PTR)TRUE;
}
case WM_COMMAND:
switch(LOWORD(wParam)){
case IDOK: case IDCANCEL: {
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
}
break;
}
return (INT_PTR)FALSE;
}
And here is the code where i use the dialogbox:
if(DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, MyDialogBox) == IDOK){
// without rich edit control it goes here or below depending on the user choice.
}else{
// with rich edit it always goes here.
}
So, the ultimate question here is: how do i get this thing work like it works with normal Edit Control ?
Edit: when it fails, the values are: -1 for DialogBox(), and 0 for GetLastError(), if that helps ?
Edit2: Problem solved by antinome's link: include afxwin.h and call AfxInitRichEdit2() at the window WM_CREATE message.
This thread has some good tips for resolving this problem. To summarize:
If using pure WinAPI:
Make sure to call LoadLibrary("RichEd20.dll"); or LoadLibrary("Msftedit.dll");. The latter is the newer version of the control.
According to Rich Edit Control in raw Win32, you can also call InitCommonControlsEx() with the appropriate class constant (MSFTEDIT_CLASS apparently) — but it's only needed if you want windows visual styles to work.
If using MFC:
Make sure to call AfxInitRichEdit2() at initialization stage, for example in InitInstance()