Using WinAPI in C, there are two ways to create a dialog with WinAPI: the more common one is to create a dialog resource in the project's .rc file and then use it with DialogBox(), which automates the creation of a standart dialog. The other way is to use CreateWindowEx with specific parameters so that the created window acts like a dialog.
An example of dialog creation with DialogBox can be seen at winprog.org: http://www.winprog.org/tutorial/dialogs.html
Out of pure interest, I've tried to recreate the dialog created with DialogBox(), using CreateWindowEx. To do this, I simply disabled the main window and then CreateWindowEx'ed the dialog. However, what I got still had one difference from the dialog created with DialogBox: when I click on the disabled main window, a DialogBox-created dialog flashes (most probably with the FlashWindowEx function).
Here's my code for creating a dialog box with CreateWindowEx:
HWND hwndParent;
HINSTANCE ghInstance;
LPCWSTR g_szDialogClassName = L"DialogClass";
void populateDialog(HWND hwnd){
/* Create various dialog controls */
}
LRESULT CALLBACK aboutDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam){
switch(Message){
case WM_CREATE:
populateDialog(hwnd);
return DefWindowProc(hwnd, Message, wParam, lParam);
case WM_COMMAND:
switch(LOWORD(wParam)){
case IDC_CLOSEDLG:
EnableWindow(hwndParent, TRUE);
DestroyWindow(hwnd);
UnregisterClass(g_szDialogClassName, ghInstance);
break;
}
break;
case WM_CLOSE:
EnableWindow(hwndParent, TRUE);
DestroyWindow(hwnd);
UnregisterClass(g_szDialogClassName, ghInstance);
break;
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return DefWindowProc(hwnd, Message, wParam, lParam);
}
int createDialogBox(HWND hwnd, HINSTANCE hInstance){
if (registerClass(hInstance, g_szDialogClassName, (WNDPROC)aboutDlgProc) == 0){
MessageBoxA(NULL, "Dialog Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
return 0;
}
EnableWindow(hwnd, FALSE);
CreateWindowEx(WS_EX_DLGMODALFRAME | WS_EX_TOPMOST | WS_EX_TOOLWINDOW, g_szDialogClassName, L"About", WS_VISIBLE | WS_CAPTION | WS_POPUP | WS_SYSMENU, 100, 100, 450, 150, NULL, NULL, hInstance, NULL);
hwndParent = hwnd;
}
Now I am very interested in how is this done inside DialogBox()? How can a disabled window recieve mouse input? Or maybe it wasn't disabled by standart means (by something different than EnableWindow(hwnd, FALSE))? Or is it impossible to reproduce this effect with normal WinAPI calls?
The problem with your code is that you created the window un-owned. Specify the main window as the owner when you call CreateWindowEx.
Related
I have created a main window (CreateWindowEx) with events procedure set using WNDCLASSEXW:
wcex.lpfnWndProc = WndProc;.
Next I have created a child window which is used as tree view:
HWND hwndTRV = CreateWindowEx(
WS_EX_CLIENTEDGE,
WC_TREEVIEW,
TEXT("Tree View"),
WS_VISIBLE | WS_CHILD | WS_BORDER | TVS_HASLINES | TVS_EDITLABELS,
0,
0,
rcClient.right,
rcClient.bottom,
hwndParent,
NULL,
hInstance,
NULL);
I would like to handle a tree view specific events in the separate function. I use the following code:
SetWindowLongPtr(hwndTRV, GWLP_WNDPROC, (LONG_PTR)TRVProc);
How should I implement the TRVProc ? Here is an example code, TVN_BEGINLABELEDIT and TVN_ENDLABELEDIT events are not visible in TRVProc:
LRESULT CALLBACK TRVProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_NOTIFY:
{
LPNMHDR l = (LPNMHDR)lParam;
switch (l->code)
{
case TVN_BEGINLABELEDIT:
{
return 0;
}
case TVN_ENDLABELEDIT:
{
return 1;
}
}
}
}
return CallWindowProc(WndProc, hWnd, message, wParam, lParam);
}
A message loop looks this way:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Implement a separate events procedure for tree view is possible based on the document:
Calling SetWindowLongPtr with the GWLP_WNDPROC index creates a
subclass of the window class used to create the window. An application
must pass any messages not processed by the new window procedure to
the previous window procedure by calling CallWindowProc. This allows
the application to create a chain of window procedures.
You want to replace the default window procedure of parent window (hwndParent) instead of tree view window (hwndTRV). So the related code line will like this:
SetWindowLongPtr(hwndParent, GWLP_WNDPROC, (LONG_PTR)TRVProc);
Additionally, to receive TVN_BEGINLABELEDIT and TVN_ENDLABELEDIT notification code you may have add some items to the tree view first. Then when you edit item's label the TRVProc will receive above notification code. For an example of how to add some items you can refer to this official document.
I want to create a window with CreateWindow() when clicking on a menu item that will be a child of the main window. I know I can use DialogBox() or CreateDialog() but I want to use CreateWindow(). I'm using this code
resource.rc file
#include "resource.h"
IDM_MENU MENU
{
POPUP "&Help"
{
MENUITEM "&About", IDM_HELP
}
}
About window procedure
LRESULT CALLBACK AboutProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
Main window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDM_HELP:
{
WNDCLASSEX wc;
HWND hDlg;
MSG msg;
SecureZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = (HICON)GetClassLong(hwnd, GCL_HICON);
wc.hIconSm = (HICON)GetClassLong(hwnd, GCL_HICONSM);
wc.hInstance = GetModuleHandle(0);
wc.lpfnWndProc = AboutProc;
wc.lpszClassName = TEXT("AboutClass");
if(!RegisterClassEx(&wc))
break;
hDlg = CreateWindowEx(0, wc.lpszClassName, TEXT("About"), WS_OVERLAPPEDWINDOW, 0, 0, 300, 200, hwnd, 0, wc.hInstance, 0);
ShowWindow(hDlg, SW_SHOWNORMAL);
while(GetMessage(&msg, 0, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
UnregisterClass(wc.lpszClassName, wc.hInstance);
}
break;
}
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
Is this a good idea? Can you register more than one class to the same instance? Also, is it a good idea to assign the main window icons to this child window or should I load them each time? Will those icons be deleted when I call UnregisterClass() in IDM_HELP? I have tried this program and everything works and the icons still show in the main window after I close this child window. But I still wonder if it's ok to assign the main window icons to this window since I call UnregisterClass() after the child window closes
There is nothing wrong with using CreateWindow/Ex() instead of CreateDialog()/DialogBox(). And there is nothing wrong with running your own modal message loop, as long as you implement it correctly. For instance, take heed of this warning:
Modality, part 3: The WM_QUIT message
The other important thing about modality is that a WM_QUIT message always breaks the modal loop. Remember this in your own modal loops! If ever you call the PeekMessage function or the GetMessage function and get a WM_QUIT message, you must not only exit your modal loop, but you must also re-generate the WM_QUIT message (via the PostQuitMessage message) so the next outer layer will see the WM_QUIT message and do its cleanup as well. If you fail to propagate the message, the next outer layer will not know that it needs to quit, and the program will seem to "get stuck" in its shutdown code, forcing the user to terminate the process the hard way.
The example you showed is not doing that, so you would need to add it:
ShowWindow(hDlg, SW_SHOWNORMAL);
do
{
BOOL bRet = GetMessage(&msg, 0, 0, 0);
if (bRet > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
if (bRet == 0)
PostQuitMessage(msg.wParam); // <-- add this!
break;
}
}
while (1);
UnregisterClass(wc.lpszClassName, wc.hInstance);
However, your modal window should NOT be using WM_QUIT just to break its modal loop, as doing so would exit you entire application! Use a different signal to make your modal loop break when the window is closed. For example:
ShowWindow(hDlg, SW_SHOWNORMAL);
while (IsWindow(hDlg) && (GetMessage(&msg, 0, 0, 0) > 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
LRESULT CALLBACK AboutProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
Also, a modal window is supposed to disable its owner window, and then re-enable it when closed. Your example is not doing that either, so that needs to be added as well:
ShowWindow(hDlg, SW_SHOWNORMAL);
EnableWindow(hwnd, FALSE); // <-- add this
...
LRESULT CALLBACK AboutProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CLOSE:
EnableWindow(GetWindow(hwnd, GW_OWNER), TRUE); // <-- add this
DestroyWindow(hwnd);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
The Old New Thing blog has a whole series of posts on how to work with modal windows.
Modality, part 1: UI-modality vs code-modality
Modality, part 2: Code-modality vs UI-modality
Modality, part 3: The WM_QUIT message
Modality, part 4: The importance of setting the correct owner for modal UI
Modality, part 5: Setting the correct owner for modal UI
Modality, part 6: Interacting with a program that has gone modal
Modality, part 7: A timed MessageBox, the cheap version
Modality, part 8: A timed MessageBox, the better version
Modality, part 9: Setting the correct owner for modal UI, practical exam
The correct order for disabling and enabling windows
Make sure you disable the correct window for modal UI
Update: based on your comment that "No I don't want a modal window", you can ignore everything said above. All of that applies to modal windows only. Since you do not want a modal window, simply remove the secondary loop altogether and let the main message loop handle everything. Also, you do not need to call UnregisterClass(). It will unregister automatically when the process ends. Call RegisterClass() one time, either at program startup, or at least the first time you display the About window. You can use GetClassInfo/Ex() to know whether the class is already registered, or keep track of it yourself. Think of what happens if the user wants to display the About window more than one time during the process's lifetime. So let it re-use an existing class registration each time.
Try this:
LRESULT CALLBACK AboutProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDM_HELP:
{
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = GetModuleHandle(0);
wc.lpszClassName = TEXT("AboutClass");
if (!GetClassInfoEx(wc.hInstance, wc.lpszClassName, &wc))
{
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = (HICON)GetClassLong(hwnd, GCL_HICON);
wc.hIconSm = (HICON)GetClassLong(hwnd, GCL_HICONSM);
wc.lpfnWndProc = AboutProc;
if (!RegisterClassEx(&wc))
break;
}
HWND hDlg = CreateWindowEx(0, wc.lpszClassName, TEXT("About"), WS_OVERLAPPEDWINDOW, 0, 0, 300, 200, hwnd, 0, wc.hInstance, 0);
if (hDlg)
ShowWindow(hDlg, SW_SHOWNORMAL);
}
break;
}
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
Yes, you can use CreateWindow(). You can do anything in your event handler. Using DialogBox() just gives you a modal loop for free (so your main window cannot be interacted with until the dialog box is closed).
Yes, you can register multiple window classes. You are free to register all your window classes in advance; you do not need to call RegisterClass() and UnregisterClass() each time someone clicks your menu item.
I'm not sure if UnregisterClass() frees the various GDI resources allocated to your window class; anyone who knows the answer is free to comment.
I want a proper way in which I can output a character string and display it on a Window created.
I had been using textout() function, but since it only paints the window, once the window is minimized and restored back, the data displayed on the window disappears.
Also when the data to be displayed is exceeds the size of Window, only the data equal to window size is displayed and other data is truncated.
Is there any other way to output data on a Window?
You can put a Static or an Edit control (Label and a text box) on your window to show the data.
Call one of these during WM_CREATE:
HWND hWndExample = CreateWindow("STATIC", "Text Goes Here", WS_VISIBLE | WS_CHILD | SS_LEFT, 10,10,100,100, hWnd, NULL, hInstance, NULL);
Or
HWND hWndExample = CreateWindow("EDIT", "Text Goes Here", WS_VISIBLE | WS_CHILD | ES_LEFT, 10,10,100,100, hWnd, NULL, hInstance, NULL);
If you use an Edit then the user will also be able to scroll, and copy and paste the text.
In both cases, the text can be updated using SetWindowText():
SetWindowText(hWndExample, TEXT("Control string"));
(Courtesy of Daboyzuk)
TextOut should work perfectly fine, If this is done in WM_PAINT it should be drawn every time. (including on minimizing and re-sizing)
LRESULT CALLBACK MyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 10, 10, TEXT("Text Out String"),strlen("Text Out String"));
EndPaint(hWnd, &ps);
ReleaseDC(hWnd, hdc);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
You might also be interested in DrawText
LRESULT CALLBACK MyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rec;
// SetRect(rect, x ,y ,width, height)
SetRect(&rec,10,10,100,100);
// DrawText(HDC, text, text length, drawing area, parameters "DT_XXX")
DrawText(hdc, TEXT("Text Out String"),strlen("Text Out String"), &rec, DT_TOP|DT_LEFT);
EndPaint(hWnd, &ps);
ReleaseDC(hWnd, hdc);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Which will draw the text to your window in a given rectangle,
Draw Text will Word Wrap inside of the given rect.
If you want to have your whole window as the draw area you can use GetClientRect(hWnd, &rec); instead of SetRect(&rec,10,10,100,100);
I want to draw a line in a static control:
case WM_CREATE:
{
hgraph=CreateWindow(WC_STATIC,NULL,WS_CHILD|WS_VISIBLE|SS_CENTER,20,20,660,80,hWnd,NULL,NULL,NULL);
SendMessage(hgraph,WM_SETTEXT,NULL,(LPARAM) "My Static");
break;
}
case WM_PAINT:
{
hdc=GetDC(hgraph);
hp=CreatePen(0 ,5,RGB(0,100,0));
SelectObject(hdc,hp);
MoveToEx(hdc, 0, 0, 0);
LineTo(hdc, 100, 100);
ReleaseDC(hgraph, hdc);
}
break;
but it goes under the static control:
When drawing to any child window, you need to do your drawing within the WM_PAINT of the child window procedure, not within the WM_PAINT of the parent window as you are doing.
For system controls (e.g. statics), you need to subclass the window, which means that you need to replace the system-defined window procedure with your own. Once you have installed your own window procedure into the system control, you can catch the WM_PAINT event on the system control to do your painting.
The complete procedure is as follows:
Define your Replacement Window Procedure for the Static Control.
We also must define a variable that we can use to store the original system Window Procedure for the control, which we must call at some point to allow the control to be drawn as normal.
static WNDPROC pFnPrevFunc;
static LRESULT CALLBACK ProcessStaticMessages(HWND hWindow,
UINT uMessage,
WPARAM wParam,
LPARAM lParam)
{
/*
* call the original system handler so the control
* gets painted as normal.
*/
(*pFnPrevFunc)(hWindow, uMessage, wParam, lParam);
/*
* perform our custom operations on this control in
* addition to system operations.
*/
switch (uMessage)
{
...
case WM_PAINT:
/*
* static control has just been painted by system.
*/
hDC = GetDC(hWindow);
/* draw your lines on the static control */
ReleaseDC(hWindow, hDC);
return TRUE;
}
return TRUE;
}
Create your static control window.
hWndStatic = CreateWindow(WC_STATIC, (LPSTR) NULL, WS_CHILD|... );
Subclass your static control window (install your window procedure)
pFnPrevFunc = SetWindowLongPtr(hWndStatic,
GWLP_WNDPROC,
(LONG_PTR) ProcessStaticMessages);
If this works correctly, then you should receive WM_PAINT messages inside your private message processing function for the static, and your drawing should occur correctly.
Hei bro! Don't forget to add DefWindowProc in the Static Procedure. Sometime you cannot paint your controls without DefWindowProc function.
Example:
LRESULT CALLBACK StaticProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch(Msg)
{
case WM_PAINT:
// Do paint here.
break;
}
return DefWindowProc(hWnd, Msg, wParam, lParam); // Call Default Window Procedure.
}
I'm writing an application following this tutorial. I'm aware that this tutorial dates, and as such, I have adapted the code to take into consideration the unicode.
I have a main window which looks like an MDI. Then, I have a View menu which toggles a Toolbar dialog as to be shown and hidden.
When I show the dialog, it is displayed, but the PUSHBUTTONs are not displayed correctly. They only appear when I click my main window again.
Plus, I don't seem to be able to click neither of the PUSHBUTTONs into my toolbar dialog.
The resources (resource.h) are defined as follows (only showing what is relevant to this question):
#define IDD_TOOLBAR 102
#define IDC_PRESS 1000
#define IDC_OTHER 1001
#define ID_VIEW_SHOWTOOLBAR 40002
#define ID_VIEW_HIDETOOLBAR 40003
And the dialog as follows in my .rc file:
IDD_TOOLBAR DIALOGEX 0, 0, 85, 50
STYLE DS_FIXEDSYS | DS_MODALFRAME | WS_CAPTION | WS_POPUP
EXSTYLE WS_EX_TOOLWINDOW
CAPTION L"Toolbar"
FONT 8, "MS Shell Dlg"
BEGIN
PUSHBUTTON L"&Press this button", IDC_PRESS, 7, 7, 70, 14
PUSHBUTTON L"&Or this one", IDC_OTHER, 7, 28, 70, 14
END
And showing it as follows in my WndProc function:
// As a global variable I have my toolbar handler.
HWND g_hToolbar = NULL;
BOOL CALLBACK ToolbarDlgProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
case IDC_OTHER:
MessageBoxW(hWnd, L"You just clicked IDC_OTHER!", L"Information", MB_OK | MB_ICONINFORMATION);
break;
case IDC_PRESS:
MessageBoxW(hWnd, L"You just clicked ODC_PRESS!", L"Information", MB_OK | MB_ICONINFORMATION);
break;
default:
return FALSE;
}
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
case WM_COMMAND:
switch (LOWORD(wParam)) {
case ID_VIEW_HIDETOOLBAR:
ShowWindow(g_hToolbar, SW_HIDE);
break;
case ID_VIEW_SHOWTOOLBAR:
if (NULL == g_hToolbar)
g_hToolbar = CreateDialogW(GetModuleHandle(NULL)
, MAKEINTRESOURCE(IDD_TOOLBAR)
, hWnd
, ToolbarDlgProc);
ShowWindow(g_hToolbar, SW_SHOW);
break;
}
break;
default:
return DefWindowProcW(hWnd, Msg, wParam, lParam);
}
}
And here's the way I handle the different messages for my main window and my dialog in my message loop in my WinMain function.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
// Declaring, registring and creating my main window to hWnd here...
MSG Msg;
ShowWindow(hWnd, nShowCmd);
UpdateWindow(hWnd);
while (GetMessageW(&Msg, hWnd, 0, 0) > 0) {
if (!IsDialogMessageW(g_hToolbar, &Msg)) {
TranslateMessage(&Msg);
DispatchMessageW(&Msg);
}
}
}
My problem is:
I don't seem to be able to click on my dialog's buttons.
When I attempt to click on my dialog's buttons, my main window becomes very slow to respond to its own messages.
That is, when I want to show my Toolbar dialog as a modeless dialog, because when I show it modal, it works perfectly!
Any clue to solve this issue?
Thanks!
The problem is, as DReJ said in the above comment, in my message pump.
The trouble is that I write:
while (GetMessageW(&Msg, hWnd, 0, 0) > 0) {
// Processing message here...
}
And that I shall write:
while (GetMessageW(&Msg, NULL, 0, 0) > 0) {
// Processing message here...
}
So, because I was getting the messages for a given window, the hWnd instance, my ToolbarDialog seemed to lack of time to draw itself completely or something like it. Replacing hWnd for NULL in that scenario solved the problem entirely.