Win32 GUI: tab-sequence for edit controls - user-interface

I have a window with 10 child edit controls in it. I would like to move from one edit control to other by pressing the tab key - how do I do this?
I mean, even if I could find out whether I pressed the tab-key, how do I find the next edit control to focus into? I hope I do not have to keep track of the edit controls myself as I already added them to the parent window.
PS: by "next" I mean the order I created the edit controls...
Edit: I'm on Win32 using plain C.
Edit 2: sample
#include
#define NAME "test"
LRESULT CALLBACK WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HWND edit1, edit2;
switch (msg)
{
case WM_CREATE:
edit1 = CreateWindow("edit", "", WS_CHILD|WS_VISIBLE, 0, 0, 200, 50, hWnd, NULL, NULL, NULL);
edit2 = CreateWindow("edit", "", WS_CHILD|WS_VISIBLE, 250, 0, 200, 50, hWnd, NULL, NULL, NULL);
return 0;
case WM_CLOSE:
DestroyWindow(hWnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wc.lpszMenuName = NAME;
wc.lpszClassName = NAME;
RegisterClass(&wc);
HWND win;
win = CreateWindow(NAME, "test", WS_OVERLAPPEDWINDOW, 0, 0, 500, 500, NULL, NULL, hInstance, NULL);
ShowWindow(win, nCmdShow);
UpdateWindow(win);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}

The dialog manager does a lot of this for you, so if you don't have a good reason for creating your own window class, you might consider creating a dialog instead.
If you're still of a mind to roll your own, you'll have to intercept WM_CHAR and look for VK_TAB and VK_SHIFT | VK_TAB. The dialog manager uses something called "z-order" as the tab order (http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#zorder).
FWIW, my advice would be to not underestimate the burden you're taking on when trying to re-create an existing facility in Windows like this. For every behavior that you know about, there are usually at least as many that you don't. For example, how will your application behave on a pen-based device? What about accessibility extensions? Will screen readers be able to handle it properly? All of that stuff is already baked into the dialog manager.
I'm not sure I followed... I thought
dialogs are just a floating windows
above the "regular" (?) one. Is it
possible that the application uses
just a dialog, instead of proper
CreateWindow()?
Dialogs can be modal or modeless children of the main application window, but they can also be the main application window. That's typically called a dialog-based app.
If you want a dialog-based application (that is, an application with a dialog as it's main window), you'd do something like this:
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow )
{
MSG msg;
HWND hDlg = CreateDialog(hInst, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DialogProc);
if (hDlg != NULL)
{
ShowWindow(hDlg, SW_SHOWNORMAL);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
The full example is probably too long to list here, but if you Google for CreateDialog, you should find some examples of this.
...And is it possible to
add other windows inside a dialog?
Even with custom window procedures?
(In other words, in dialogs, am I not
restricted just to the default
controls, like edit and static?)
Yes, you can create custom controls within the dialog. Within the DIALOG portion of your .rc file, you can include something like:
CONTROL "",IDC_MYCUSTOM,"MyCtrlClassName",WS_TABSTOP,10,20,30,40
You can also create a new control and add it to the dialog dynamically (I'll let you Google for an example).
Also, if I wanted to roll my own, how
do I find out what's the next control?
I don't really think there will be
more use cases than a basic desktop
Use GetNextDlgTabItem() if you want to cycle through the controls within a dialog in tab-order: http://msdn.microsoft.com/en-us/library/ms645495%28VS.85%29.aspx
If you're rolling your own, then you probably want something like the EnumChildWindows() function: http://msdn.microsoft.com/en-us/library/ms633494%28VS.85%29.aspx
This may also be useful: http://msdn.microsoft.com/en-us/library/bb775501%28VS.85%29.aspx

This kind of processing can be easily added to an application :- Add a call to IsDialogMessage() in your message loop - all the controls have to have the WS_TABSTOP style.
The parent window might have to be of the dialog class as the dialog window class stores state allowing it to (for example) restore focus to the correct control when activation is lost and restored.

Related

Creating ComboBox in Win32 not working properly

I am creating a Win32 ComboBox for the first time. And I have a problem here.
When calling CreateWindow for the ComboBox, it calls the WndProc callback function again with the WM_CREATE message, so what happens is the ComboBox makes a child ComboBox, again and again like recursion.
Here is the code:
#include <stdio.h>
#include <conio.h>
#include <Windows.h>
#include <random>
#include <time.h>
#include <string>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = L"ComboBox";
const WCHAR *items[] = { L"Apple", L"Orange", L"Melon", L"Grape", L"Strawberry" };
HWND hwnd;
enum COMMAND_ID {
COMMAND_ID_CONTROL_COMBO_0
};
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
srand(time(NULL));
g_hInst = hInstance;
WNDCLASS wndClass;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndClass.hInstance = hInstance;
wndClass.lpfnWndProc = WndProc;
wndClass.lpszClassName = lpszClass;
wndClass.lpszMenuName = NULL;
wndClass.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndClass);
hwnd = CreateWindow(
lpszClass,
lpszClass,
WS_CAPTION | WS_SYSMENU | WS_THICKFRAME,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
(HMENU)NULL,
hInstance,
NULL);
ShowWindow(hwnd, nCmdShow);
MSG msg;
while (true)
{
GetMessage(&msg, NULL, 0, 0);
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
static HWND hCombo;
static WCHAR str[128];
switch (msg)
{
case WM_CREATE:
{
hCombo = CreateWindow(
L"combobox",
NULL,
WS_CHILD | WS_VISIBLE | WS_BORDER | CBS_DROPDOWN,
10, 10, 200, 200,
hWnd,
(HMENU)COMMAND_ID_CONTROL_COMBO_0,
g_hInst,
NULL);
for (int i = 0; i < 5; ++i)
{
SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)items[i]);
}
SendMessage(hCombo, CB_SETCURSEL, 0, NULL);
}
break;
case WM_COMMAND:
{
switch (LOWORD(wparam))
{
case COMMAND_ID_CONTROL_COMBO_0:
switch (HIWORD(wparam))
{
case CBN_SELCHANGE:
{
int iCurSel = SendMessage(hCombo, CB_GETCURSEL, NULL, NULL);
SendMessage(hCombo, CB_GETLBTEXT, iCurSel, (LPARAM)str);
SetWindowText(hWnd, str);
}
break;
case CBN_EDITCHANGE:
GetWindowText(hCombo, str, 128);
SetWindowText(hWnd, str);
break;
}
break;
default:
break;
}
}
return 0;
}
return DefWindowProc(hWnd, msg, wparam, lparam);
}
And here is the result:
I tried to put some boolean flag to execute WM_CREATE only once, and it works, I mean only creating one ComboBox without any child in it.
But, it just looked like only a white window with a border mark, there's no arrow button or anything to dropdown page that the ComboBox is supposed to have.
This recursive case never happened when I was creating different controls like Buttons, CheckBoxes, ListBoxes, etc.
And the ComboBox created doesn't look like it has the proper shape, too.
Hope I am just missing something simple.
When calling CreateWindow for the ComboBox, it calls the WndProc
callback function again with the WM_CREATE message, so what happens
is the ComboBox makes a child ComboBox, again and again like
recursion.
WM_CREATE message is sent to the window procedure of the new window after the window is created. Your first WM_CREATE message is generated by this line hwnd = CreateWindow(). Then you create another window in first WM_CREATE message, so it will generate second WM_CREATE message. Because you use the same registered class ("ComboBox" / "combobox", it is not case sensitive) to create all these windows, all of them use the same one window procedure. So you receive WM_CREATE message again and again until CreateWindow fail to create a window and return NULL.
But, it just looked like only a white window with a border mark,
there's no arrow button or anything to dropdown page that the ComboBox
is supposed to have.
The root cause is you register a class with the same name as the existing system class: "ComboBox" / "combobox". This new registered class override the existing one. It is just a common window instead of a predefined Combobox control as #RemyLebeau pointed out.
An application can register an application local class having the same
name as a system class. This replaces the system class in the context
of the application but does not prevent other applications from using
the system class.
Refer to "How the System Locates a Window Class".
To make the Combobox display in expected shape, what you need to do is changing the lpszClass to a non-predefined one, for example, like "SimpleComboBoxExample".
It is suggested to use predefined macro of Combobox Class Name: WC_COMBOBOX instead of L"combobox".
More reference: "How to Create a Simple Combo Box".
You are not actually creating a Win32 ComboBox at all. You are registering your own class named "ComboBox", and then creating a window of that class, which creates a window of that class, which creates a window of that class, and so on recursively.
You need to change this line:
LPCTSTR lpszClass = L"ComboBox";
To a different unique name, such as "MyWindowClass".
On a side note, your message loop is structured wrong. It should look like this instead:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
And in your WndProc(), the return 0; statement underneath the WM_COMMAND handler is in the wrong place. It needs to be moved inside of the WM_COMMAND handler instead:
case WM_COMMAND:
{
switch (...)
{
...
}
return 0; // <-- moved here
}
//return 0; // <-- from here

How to put an EDIT control in a custom popup window using Win32 API?

I'm trying to add an EDIT control to a window used as a dropdown for a custom combobox-like control. Initially this dropdown window was implemented as a child (WS_CHILD) window of the desktop, which is similar to the "ComboLbox" window used by the real combobox. This worked just fine, however an EDIT window seems to just refuse to accept focus when it is put into such dropdown window. I.e. it is enabled and reacts to right mouse clicks, for example, but clicking on it or calling SetFocus() fails (the latter sets last error to ERROR_INVALID_PARAMETER).
Because of this, and also because of the way custom popup windows are implemented in many examples including Raymond Chen's fakemenu sample, I've changed the dropdown implementation to use WS_POPUP, with the main application window as owner. This has a known problem with stealing activation from the owner window when the popup is shown, however this can be addressed by returning MA_NOACTIVATE from WM_MOUSEACTIVATE handler for the popup window and it indeed works well initially, i.e. the owner window keeps activation when the popup shows up. But as soon as I click the EDIT control inside the popup, it calls, from its default window proc, SetFocus() to set the focus to itself, which deactivates the parent window.
My question is how can I prevent this from happening? I know that it can be done because WinForms ToolStripManager manages to allow editing text in a dropdown without deactivating the parent window and it also uses WS_POPUP style for the popup window. But how does it do it?
A solution was suggested in comments "prevent the host window from visibly appearing inactive by handling WM_NCACTIVATE" This should work as shown in the example below.
When menu window is opened, the host window (HostProc) will receive WM_NCACTIVATE message. Host will look for "menuclass", if menu class is found then host will return DefWindowProc(hwnd, WM_NCACTIVATE, TRUE, lparam); to prevent the title bar for host window get painted inactive.
You also need to handle WM_NCACTIVATE in fake menu window. When menu window goes out of focus, WM_NCACTIVATE is received by MenuProc, at this point the menu can close itself.
#include <windows.h>
const wchar_t* menuclass = L"menuclass";
LRESULT CALLBACK MenuProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch(msg)
{
case WM_CREATE:
CreateWindow(L"Edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 10, 160, 30,
hwnd, NULL, NULL, NULL);
break;
case WM_NCACTIVATE:
{
if(!wparam)
{
//close the menu if its losing focus
PostMessage(hwnd, WM_CLOSE, 0, 0);
//tell parent to paint inactive, if user clicked on a different program
POINT pt;
GetCursorPos(&pt);
HWND hit = WindowFromPoint(pt);
HWND hparent = GetParent(hwnd);
if(hit != hparent && !IsChild(hparent, hit))
DefWindowProc(hparent, WM_NCACTIVATE, FALSE, 0);
}
break;
}
case WM_LBUTTONDOWN:
PostMessage(hwnd, WM_CLOSE, 0, 0);
break;
//also handles other mouse/key messages associated with a menu...
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
LRESULT CALLBACK HostProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch(msg)
{
case WM_NCACTIVATE:
//paint the window as active when custom menu starts
if(!wparam && FindWindow(menuclass, NULL))
return DefWindowProc(hwnd, WM_NCACTIVATE, TRUE, lparam);
break;
case WM_RBUTTONUP:
{
//show the custom menu
POINT pt;
GetCursorPos(&pt);
CreateWindow(menuclass, NULL, WS_VISIBLE | WS_POPUP | WS_BORDER,
pt.x, pt.y, 200, 400, hwnd, 0, 0, 0);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int)
{
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.lpfnWndProc = HostProc;
wcex.lpszClassName = L"hostwnd";
RegisterClassEx(&wcex);
wcex.lpfnWndProc = MenuProc;
wcex.lpszClassName = menuclass;
RegisterClassEx(&wcex);
CreateWindow(L"hostwnd", L"Right click for menu ...",
WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, 0, 600, 400, 0, 0, hInstance, 0);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}

Can't click on controls/menu when detach MDI child out of MDI client area

Whole sample project can be found here: Sample project
Normal MDI child:
MDI child is detached out of MDI client area:
Problem is after MDI child is detached, I am not able to click on menu/controls anymore.
I think one approach is to subclass winproc of MDI app, and then catching the messages and redirect them (like this one). But I dont know where to begin.
Any idea/ other approaches are welcome!
The code I used to detach MDI child:
HWND MDIHwnd = pMainFrame->m_hWndMDIClient;
HWND mdiChildHwnd = GetWindow(MDIHwnd, GW_CHILD);
unsigned int style = GetWindowLongPtr(mdiChildHwnd, GWL_STYLE);
style = (style & (~WS_CHILD) | WS_POPUP);
SetWindowLongPtr(mdiChildHwnd, GWL_STYLE, style);
WaitForInputIdle(mdiChildHwnd, INFINITE);
SetParent(mdiChildHwnd, NULL);
WaitForInputIdle(mdiChildHwnd, INFINITE);
SetWindowLongPtr(mdiChildHwnd, GWLP_HWNDPARENT, (long)MDIHwnd);
Some experts here said that it's impossible and I found out the solution.
Lesson learnt: when someone said it's impossible which mean that's only impossible to them not you.
Whole sample project can be found here: Sample Project Answer
#Experts: if you are really an good expert then be helpful and objective rather than trying to telling people that you are an expert and you know things that other people don't know. Moreover, giving out some advice that's not really helping and being subjective are really frustrating to people who asking.
For reference
For the record: I don't care about the downvote, what I care is someone willing to help and knowledge that I get.
WndProc Code:
LRESULT CALLBACK MDIAppWndProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam) // second message parameter
{
WNDPROC wpOrigMDIAppWndProc = (WNDPROC)GetWindowLongPtr(hwnd, GWL_USERDATA);
if (wpOrigMDIAppWndProc == NULL)
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
switch (uMsg)
{
case WM_ACTIVATE:
case WM_SETFOCUS:
return 0;
case WM_CLOSE:
SetWindowLong(hwnd, GWL_WNDPROC, (LONG)wpOrigMDIAppWndProc);
PostMessage(hwnd, WM_CLOSE, 0, 0);
return 0;
default:
return CallWindowProc(wpOrigMDIAppWndProc, hwnd, uMsg, wParam, lParam);
}
return 0;
}
Detaching code:
HWND MDIHwnd = pMainFrame->m_hWndMDIClient;
HWND mdiChildHwnd = GetWindow(MDIHwnd, GW_CHILD);
unsigned int style = GetWindowLongPtr(mdiChildHwnd, GWL_STYLE);
style = (style & (~WS_CHILD) | WS_POPUP);
SetWindowLongPtr(mdiChildHwnd, GWL_STYLE, style);
WaitForInputIdle(mdiChildHwnd, INFINITE);
SetParent(mdiChildHwnd, NULL);
WaitForInputIdle(mdiChildHwnd, INFINITE);
SetWindowLongPtr(mdiChildHwnd, GWLP_HWNDPARENT, (long)MDIHwnd);
HWND MDIAppHwnd = GetAncestor(MDIHwnd, GA_ROOT);
WNDPROC wpOrigMDIAppWndProc = (WNDPROC)SetWindowLong(MDIAppHwnd, GWL_WNDPROC, (LONG)MDIAppWndProc);
SetWindowLongPtr(MDIAppHwnd, GWL_USERDATA, (LONG)wpOrigMDIAppWndProc);

change the background color of static control text created using CreateWindow in vc++

I am working on a open source project in VC++ and want to change the backcolor of a static control..
hwndRenderMessage = CreateWindow(TEXT("STATIC"), Str("MainWindow.BeginMessage"),
WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS|SS_CENTER,
0, 0, 0, 0, hwndRenderFrame, NULL, hinstMain, NULL);
SendMessage(hwndRenderMessage, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
and the parent control of this control is
hwndRenderFrame = CreateWindow(OBS_RENDERFRAME_CLASS, NULL,
WS_CHILDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
0, 0, 0, 0,
hwndMain, NULL, hinstMain, NULL);
if(!hwndRenderFrame)
CrashError(TEXT("Could not create render frame"));
So how to change the Background color of Static Control..
I google it and getting tha same answer use
case WM_CTLCOLORSTATIC:
{
HDC hdcStatic = (HDC) wParam;
SetTextColor(hdcStatic, RGB(0,0,0));
SetBkColor(hdcStatic, RGB(230,230,230));
return (INT_PTR)CreateSolidBrush(RGB(230,230,230));
}
But there is no switch case in the file so what to do??
Acctually i worked on c# but this is the first time on vc++
I downloaded the OBS source code from sourceforge.
The Window Proc is OBS::RenderFrameProc located in WindowStuff.cpp
At the bottom of the proc (but before the "return"), add:
else if(message == WM_CTLCOLORSTATIC ) {
// HERE YOUR CODE
}
EDIT: Changing Button Background
First, an advice: "don't do that". Buttons are very important and common components of the windows GUI, and their look and feel should be consistent in all applications. Users have ways to customize things for their Desktop, as a whole, and this include "accessibility" issues and behavior. Applications that want do it in their "own special way"s only bring problems.
Second, try this code for changing the "Setting..." button background to an ugly green: Add a case in the WM_NOTIFY message processing in OBS::OBSProc, in the switch(wParam)
case ID_SETTINGS:
if(nmh.code == NM_CUSTOMDRAW)
{
LPNMCUSTOMDRAW lpcd = (LPNMCUSTOMDRAW)lParam;
if (lpcd->dwDrawStage == CDDS_PREPAINT )
{
SetDCBrushColor(lpcd->hdc, RGB(0, 255, 0));
SelectObject(lpcd->hdc, GetStockObject(DC_BRUSH));
LONG lBorders = 0;
LONG lElipse = 5;
RoundRect(lpcd->hdc, lpcd->rc.left + lBorders, lpcd- rc.top + lBorders,
lpcd->rc.right - lBorders, lpcd->rc.bottom - lBorders, lElipse, lElipse);
return CDRF_NOTIFYPOSTPAINT;
}
}
break;
An alternative, with more standard borders:
SetDCBrushColor(lpcd->hdc, RGB(0, 255, 0));
SetDCPenColor(lpcd->hdc, RGB(0, 255, 0));
SelectObject(lpcd->hdc, GetStockObject(DC_BRUSH));
SelectObject(lpcd->hdc, GetStockObject(DC_PEN));
LONG lBorders = 3;
To be complete, you may want to check the uItemState member of lpcd, for the CDIS_HOT flag, changing the color accordingly.
You need to put that code in a window procedure. The window procedure looks like this:
LRESULT CALLBACK RenderMessageWndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
switch (message)
{
case WM_CTLCOLORSTATIC:
// your code goes here
return ....
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
And you need to sub-class your window so that it uses this WndProc. Like this:
SetWindowLongPtr(hwndRenderMessage, GWLP_WNDPROC, (LONG_PTR)RenderMessageWndProc);
If you don't know what a window procedure is, or what sub-classing is, then you really need to step back and learn some basics. For instance, Petzold's classic book Programming Windows is still an excellent starting point.

Recursive message loop

I am having trouble correctly running a message loop from within a message handler. In effect replicating how DialogBox() processes messages, minus all of the windowing.
Simply invoking GetMessage() from within a message handler nearly works except when the WM_SYSKEYDOWN event opening the system menu also triggers the entry into a sub-loop. After this weird things happen, with keys presses being swallowed and WM_MOUSEMOVE messages relative to the system menu getting sent to the main window.
For the record this happens both in Windows 8 and XP.
To give some context I am attempting a threading model where a (windowless) worker thread communicates through blocking SendMessage calls back to the main window acting as a server. These actions may require further input or depend on other I/O and thus regular messages need to be processed until the reply is ready.
I'm fairly certain that this is a basic mistake or misunderstanding on my part, just like last time I posted here, but I can't quite seem to work out what I'm doing wrong on my own.
Here is my repro case. Try navigating after pressing ALT+SPACE to open the system menu,
#include <windows.h>
BOOL update;
LRESULT WINAPI WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
MSG msg;
char text[256];
switch(uMsg) {
case WM_DESTROY:
ExitProcess(0);
// Trigger an update on input
case WM_SYSKEYDOWN:
update = TRUE;
break;
// Display the update from the worker thread, returning once it is time to
// ask for the next one
case WM_USER:
wsprintf(text, TEXT("%u"), (unsigned int) lParam);
SetWindowText(hwnd, text);
while(!update && GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
update = FALSE;
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
DWORD WINAPI ThreadProc(void *hwnd) {
// Submit updates as quickly as possible
LONG sequence = 1;
for(;;)
SendMessage(hwnd, WM_USER, 0, sequence++);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCommandLine, int nCmdShow) {
HWND hwnd;
MSG msg;
// Create our window
WNDCLASS windowClass = { 0 };
windowClass.lpfnWndProc = WindowProc;
windowClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
windowClass.hCursor = NULL;
windowClass.lpszClassName = TEXT("Repro");
RegisterClass(&windowClass);
hwnd = CreateWindow(TEXT("Repro"), TEXT("Repro"),
WS_VISIBLE | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
hInstance, 0);
// Launch the worker thread
CreateThread(NULL, 0, ThreadProc, hwnd, 0, NULL);
// And run the primary message loop
while(GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
Modal message loops are perfectly fine. Raymond Chen has a series of articles on writing modal message loops properly.
One thing I notice: Your thread should post the message, not send it; SendMessage calls directly into the window proc. Don't use PostThreadMessage, either; that's designed for threads without visible UI (and the nested DispatchMessage won't know how to dispatch the thread message, resulting in dropped messages).

Resources