Sign in to follow this  
Psychopathetica

Child Window Issues

Recommended Posts

Hi. I'm creating my first Child Window using Visual C++ 2010 but have a couple issues. First of all, when you close out of the child window, it closes out of the entire program rather than just close the window! I would like for it to close independently, so closing the main program will close the entire program. But since I'm some what a noob, I don't know how to. Secondly, I have a black background, and when I drag the child window, theres somewhat a miniature flicker with the edges of the child window. Its minor but somewhat a nuisance and I would like for it to not flicker. Here is the code I have as a basic layout with the 2 problems I have and thanks in advance :)

 

#include <windows.h>

HWND hWnd;
HWND hChildWnd;
MSG msg;
WNDCLASSEX wc;
HBRUSH brush;
HINSTANCE hChildInstance = NULL;

LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

#define ID_FILE_EXIT 1000

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow)
{
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = CS_VREDRAW|CS_HREDRAW|CS_OWNDC;
    wc.lpfnWndProc   = WindowProcedure;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = CreateSolidBrush(RGB(0, 0, 0));
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = "Window";
    wc.hIconSm       = NULL;
    RegisterClassEx(&wc);
    hWnd = CreateWindowEx (0, "Window", "Parent Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, HWND_DESKTOP, NULL, hInstance, NULL);
    hChildWnd = CreateWindowEx (WS_EX_CLIENTEDGE, "Window", "Child Window", WS_OVERLAPPEDWINDOW | WS_CHILD | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, hWnd, NULL, hChildInstance, NULL);
    ShowWindow (hWnd, SW_SHOWMAXIMIZED);
    while (1)
    {
        if (PeekMessage(&msg,NULL,0,0,PM_REMOVE) > 0)
        {
            if (WM_QUIT == msg.message) break;
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
        
    }
    return msg.wParam;
}

LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_CREATE:
        {
            HMENU hFile = CreateMenu();
            HMENU hExit = CreateMenu();
            AppendMenu(hFile, MF_POPUP, (UINT_PTR)hExit, "File");
            AppendMenu(hExit, MF_STRING, ID_FILE_EXIT, "Exit");
            SetMenu(hWnd, hFile);
            break;
        }
        case WM_COMMAND:
        {
            switch(LOWORD(wParam))
            {
                case ID_FILE_EXIT:
                {
                    DestroyWindow(hWnd);
                    return(0);
                    break;
                }
            }
            break;
        }
        case WM_DESTROY:
            PostQuitMessage (0);
            break;
        case WM_KEYDOWN:
            if(wParam == VK_ESCAPE)
            {
                DestroyWindow(hWnd);
                return(0);
            }
            break;
        default:
            return DefWindowProc (hWnd, msg, wParam, lParam);
    }
    return 0;
}

Share this post


Link to post
Share on other sites
Nypyren    12065
PostQuitMessage is the reason the program is exiting - you only need to do that on the main window's WndProc. In the code above, it looks like you've told both windows to use the same WndProc. You could make a second WndProc or store some data using SetWindowLong(GWL_USERDATA) to make sure that only the main window uses PostQuitMessage.

As far as flicker goes, I haven't done Win32 in a long time and only know how to do it in WinForms. Google for "Win32 flicker" and you should find a lot of tutorials on what you need to do. Edited by Nypyren

Share this post


Link to post
Share on other sites

Well you may be right but commenting out "if (WM_QUIT == msg.message) break;" will allow me to close the child window ok. But closing the program doesnt close all the way since I commented that out. It closes but doesnt fully close. If you run it from IDE the stop button is still clickable after closing it. If you run it from file, closing it will cause it to still be in the process list even though you closed it. However if you dont comment out "if (WM_QUIT == msg.message)", it closes all the way as should, but the child window affects it as well. So it looks like that message will have to be somewhere else or I have to code it differently. Problem is, what do I do?

Edited by Psychopathetica

Share this post


Link to post
Share on other sites
Nypyren    12065
That's exactly right. PostQuitMessage just sends WM_QUIT to the message loop. So if you don't exit the loop when you get it, your program will still run, even with no visible windows. The fact that the loop is still looping is controlling whether the program exits or not.

This is a good thing - it means you can choose to leave a program running in the background without a window, which is what programs like virus scanners do. It even means you can make an app that never opens any windows in the first place!


Ok, I got off on a tangent; Back to your original goal. One possible solution is:

- Make a copy of your window procedure function and rename it to something else.
- Make a second window class (WNDCLASSEX wcChild; or something like that) and have that window class use the second window procedure instead.
- In the second window procedure, don't handle WM_DESTROY at all.

If I'm remembering how win32 operates correctly, this will make it so that closing the child window does not close the app, but closing the main one does. Edited by Nypyren

Share this post


Link to post
Share on other sites

I don't think I did it right. But I think I have to make a separate msg. Not sure. Its doing the same thing. And the 2nd window didnt receive a different color background and remained black:

#include <windows.h>

HWND hWnd;
HWND hChildWnd;
MSG msg;
WNDCLASSEX wc;
WNDCLASSEX wcChild;
HBRUSH brush;
HINSTANCE hChildInstance = NULL;

LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ChildWindowProcedure (HWND, UINT, WPARAM, LPARAM);

#define ID_FILE_EXIT 1000

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow)
{
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = CS_VREDRAW|CS_HREDRAW|CS_OWNDC;
    wc.lpfnWndProc   = WindowProcedure;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = CreateSolidBrush(RGB(0, 0, 0));
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = "Window";
    wc.hIconSm       = NULL;
    RegisterClassEx(&wc);
    hWnd = CreateWindowEx (0, "Window", "Parent Window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, HWND_DESKTOP, NULL, hInstance, NULL);
    
    wcChild.cbSize        = sizeof(WNDCLASSEX);
    wcChild.style         = CS_VREDRAW|CS_HREDRAW|CS_OWNDC;
    wcChild.lpfnWndProc   = ChildWindowProcedure;
    wcChild.cbClsExtra    = 0;
    wcChild.cbWndExtra    = 0;
    wcChild.hInstance     = hChildInstance;
    wcChild.hIcon         = NULL;
    wcChild.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wcChild.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));
    wcChild.lpszMenuName  = NULL;
    wcChild.lpszClassName = "Window";
    wcChild.hIconSm       = NULL;
    RegisterClassEx(&wcChild);
    hChildWnd = CreateWindowEx (WS_EX_CLIENTEDGE, "Window", "Child Window", WS_OVERLAPPEDWINDOW | WS_CHILD | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, hWnd, NULL, hChildInstance, NULL);
    ShowWindow (hWnd, SW_SHOWMAXIMIZED);
    
    while (1)
    {
        if (PeekMessage(&msg,hWnd,0,0,PM_REMOVE) > 0)
        {
            if (WM_QUIT == msg.message) break;
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
        
    }
    return msg.wParam;
}

LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_CREATE:
        {
            HMENU hFile = CreateMenu();
            HMENU hExit = CreateMenu();
            AppendMenu(hFile, MF_POPUP, (UINT_PTR)hExit, "File");
            AppendMenu(hExit, MF_STRING, ID_FILE_EXIT, "Exit");
            SetMenu(hWnd, hFile);
            break;
        }
        case WM_COMMAND:
        {
            switch(LOWORD(wParam))
            {
                case ID_FILE_EXIT:
                {
                    DestroyWindow(hWnd);
                    return(0);
                    break;
                }
            }
            break;
        }
        case WM_DESTROY:
            PostQuitMessage (0);
            break;
        case WM_KEYDOWN:
            if(wParam == VK_ESCAPE)
            {
                DestroyWindow(hWnd);
                return(0);
            }
            break;
        default:
            return DefWindowProc (hWnd, msg, wParam, lParam);
    }
    return 0;
}

LRESULT CALLBACK ChildWindowProcedure (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        default:
            return DefWindowProc (hWnd, msg, wParam, lParam);
    }
    return 0;
}
Edited by Psychopathetica

Share this post


Link to post
Share on other sites
Nypyren    12065
OK so in your latest code the most important problem is your window class names.

Notice the lines:
 
wc.lpszClassName = "Window";

wcChild.lpszClassName = "Window";
The way CreateWindowEx knows which one to use is the one you ask for in the second parameter:
hChildWnd = CreateWindowEx(..., "Window", ...);
You need to give each window class a unique name, or else the RegisterClassEx function will fail. (It's failing because a class called "Window" already exists, but you aren't checking to see if it failed or not - this is a common problem with APIs that return error codes instead of throwing an exception; It's very easy to totally forget the possibility that a function can fail).

First off, for the child window, just try changing "Window" in both spots to "ChildWindow". Edited by Nypyren

Share this post


Link to post
Share on other sites

OMG! That wast it!  Wow even the background color changed to the one I wanted. And the flickering is gone!!! I should slap myself for missing that. Thank you so much =D

 

[EDIT] Well its kind of gone. It only flickers if both windows are the same color I believe. Either that or it was the border of the window that was flickering not the window itself. Yeah it was the border flickering. Thats kind of odd.

Edited by Psychopathetica

Share this post


Link to post
Share on other sites

After digging deep in Google, I think I found the solution. It may not be the best solution but it works and thats all that matters. If anyone has a better solution let me know. This you just simply add to the Parent window message pump:

 

        case WM_PAINT:
            InvalidateRect(hChildWnd, NULL, FALSE);
            break;

Share this post


Link to post
Share on other sites

Yup, that will work since all the painting for all child windows should be done all at once. If you don't do that when the main window redraws it won't invalidate the child window until after the main window painting is done (so another paint message). I think anyway ;)

 

You can also clip the child window rect to the main window rect if you want... then you wont redraw the child if the child window is outside the main window area (and you only need to partially redraw the child if it is partially clipped by the main window, i.e. they overlap). (EDIT: You do that by passing the dirty rectangle to InvalidateRect, you are passing NULL which means redraw the whole window). But that is a micro-optimisation so I wouldn't worry about it.

Edited by Paradigm Shifter

Share this post


Link to post
Share on other sites

One last question. I wanted the Escape key to close out of the child window rather than the whole program. So after cutting and pasting the case statement of WM_KEYDOWN from parent WindowProcedure to ChildWindowProcedure and changing it to DestroyWindow(hChildWnd), the Escape key had no effect. Is there something I'm missing? Thanks in advance, this is going great so far.

Share this post


Link to post
Share on other sites

Does the WM_KEYDOWN message handler get called for the child window? You need to return that the message is unhandled from the main window WM_KEYDOWN handler (I think it might be return FALSE? Could be return TRUE though ;)) in order for the message to get routed to all the child windows, otherwise when you handle the message in the main window the message gets removed from the queue.

 

You probably only want to say the message is unhandled when the child window exists and has focus though.

 

EDIT: Another option is to handle all the input in the main window and if you get an escape when the child window is active, destroy the child window by sending it a close message.

Edited by Paradigm Shifter

Share this post


Link to post
Share on other sites

Well this is the code I have so far, and whether it has focus or not, there is no response:

#include <windows.h>

HWND hWnd;
HWND hChildWnd;
MSG msg;
WNDCLASSEX wc;
WNDCLASSEX wcChild;
HBRUSH brush;
HINSTANCE hChildInstance = NULL;

LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ChildWindowProcedure (HWND, UINT, WPARAM, LPARAM);

#define ID_FILE_EXIT 1000

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow)
{
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = CS_VREDRAW | CS_HREDRAW | CS_OWNDC;
    wc.lpfnWndProc   = WindowProcedure;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = CreateSolidBrush(RGB(0, 0, 0));
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = "Window";
    wc.hIconSm       = NULL;
    RegisterClassEx(&wc);
    hWnd = CreateWindowEx (0, "Window", "Parent Window", WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, hWnd, NULL, hInstance, NULL);
    
    wcChild.cbSize        = sizeof(WNDCLASSEX);
    wcChild.style         = CS_VREDRAW | CS_HREDRAW | CS_OWNDC;
    wcChild.lpfnWndProc   = ChildWindowProcedure;
    wcChild.cbClsExtra    = 0;
    wcChild.cbWndExtra    = 0;
    wcChild.hInstance     = hChildInstance;
    wcChild.hIcon         = NULL;
    wcChild.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wcChild.hbrBackground = CreateSolidBrush(RGB(0, 0, 0));
    wcChild.lpszMenuName  = NULL;
    wcChild.lpszClassName = "ChildWindow";
    wcChild.hIconSm       = NULL;
    RegisterClassEx(&wcChild);
    hChildWnd = CreateWindowEx (0, "ChildWindow", "Child Window", WS_OVERLAPPEDWINDOW | WS_CHILD | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, hWnd, NULL, hChildInstance, NULL);

    ShowWindow (hWnd, SW_SHOWMAXIMIZED);
    //while(1)
    //{
    //    if (PeekMessage(&msg,NULL,0,0,PM_REMOVE) > 0)
    //    {
    //        if (WM_QUIT == msg.message) break;
    //        TranslateMessage (&msg);
    //        DispatchMessage (&msg);
    //    }
    //}
    while (GetMessage (&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_CREATE:
        {
            HMENU hFile = CreateMenu();
            HMENU hExit = CreateMenu();
            AppendMenu(hFile, MF_POPUP, (UINT_PTR)hExit, "File");
            AppendMenu(hExit, MF_STRING, ID_FILE_EXIT, "Exit");
            SetMenu(hWnd, hFile);
            break;
        }
        case WM_COMMAND:
        {
            switch(LOWORD(wParam))
            {
                case ID_FILE_EXIT:
                {
                    DestroyWindow(hWnd);
                    return(0);
                    break;
                }
            }
            break;
        }
        case WM_DESTROY:
            PostQuitMessage (0);
            break;
        case WM_PAINT:
            InvalidateRect(hChildWnd, NULL, FALSE);
            break;
        default:
            return DefWindowProc (hWnd, msg, wParam, lParam);
    }
    return 0;
}

LRESULT CALLBACK ChildWindowProcedure (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_KEYDOWN:
            if(wParam == VK_ESCAPE)
            {
                DestroyWindow(hChildWnd);
                return(0);
            }
            break;
        default:
            return DefWindowProc (hWnd, msg, wParam, lParam);
    }
    return 0;
}
Edited by Psychopathetica

Share this post


Link to post
Share on other sites

I added an edit as well, you could handle the input in the main window.

 

But the problem with the code posted is you don't handle the WM_CHAR message in the main window, it's handled by DefWindowProc, which eats the message. You need to return that the message is unhandled in a WM_CHAR handler in the main window, so that it then forwards the message to the child windows. (It could be WM_SYSCHAR that is required to handle the escape key as well, I'm not sure).

Share this post


Link to post
Share on other sites

Hmmm I did what you did in the edit and put this chunk of code in the Parent window messages and it worked!

 

        case WM_KEYDOWN:
            if(wParam == VK_ESCAPE)
            {
                DestroyWindow(hChildWnd);
                return(0);
            }
            break;

Share this post


Link to post
Share on other sites

Yep ;)

 

If you return 0 without calling DestroyWindow the WM_KEYDOWN should get sent to the child window too. Messages only get bounced to child windows if the parent says it can't handle it (by returning 0). (I think you return 0 to say the message wasn't handled. Note you return DefWindowProc for all messages which you don't explicitly handle, for WM_CHAR/WM_KEYDOWN that eats the message and says it has been handled, so no child windows get sent the message).

Edited by Paradigm Shifter

Share this post


Link to post
Share on other sites
Endurion    5408

Just to chime in, there is NO general return value that means the message was handled. Look up the message you handle in the MSDN docs and read there what you should return.

 

Also, if not really explicitely mentioned in the docs, always pass on messages to DefWindowProc.

 

 

WM_KEYDOWN is not passed on to child windows, it is sent to the focused HWND only.

Share this post


Link to post
Share on other sites

Yeah, I was wrong about returning 0 to say the message has not been handled, it depends on the specific message. (EDIT: And returning 0 is likely to mean that the message WAS handled too). Sorry about that ;)

 

I have definitely input handled messages in child windows before but it could have been the framework (MFC, ugh) that was forwarding them to child windows rather than an automatic thing. I know that in C# (using WinForms) you have to check a checkbox in order to get input messages forwarded to child windows. (It's the "Key Preview" checkbox, or by setting the KeyPreview property of the child window to true).

Edited by Paradigm Shifter

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this