Question

I am working on a C++ winapi app and I am struggling to get SetFocus() to work for me. I create the Main window with WS_OVERLAPPEDWINDOW | WS_VISIBLE and then within that I create its children (a couple of buttons and the beginnings of my own version of an Edit control) with WS_CHILD | WS_VISIBLE, which are all working fine except for the issue of focus.

In my searches I have battled to find much in terms of how you should handle Focus. When the windows are all created they individually receive the WM_SETFOCUS message and I handle this message in my edit control by creaing the caret, however it seems the children never receive the WM_KILLFOCUS message and so caret is never destroyed.

This is now where my problem comes in: I would like the main parent window to initially have focus and for there to be no caret in my edit control and then when the child Edit control is clicked for it have focus and then when the main window is clicked it should then have focus again and so on.

So my initial thought was to use SetFocus() to set focus to Main Window when handling the WM_CREATE message however that didn't seem to work: the child don't received the WM_KILLFOCUS message.

My next thought was that maybe the parent has to handle passing down WM_KILLFOCUS to the appropriate children so I wrote a method to do that for me but the children still did not receive the WM_KILLFOCUS message.

So my best guess is that I am not handling the Messages correctly in my WndProc.

I have created my own Window class and distribute the messages to the appropriate classes through the following WndProc:

LRESULT CALLBACK CBaseWindow::stWinMsgHandler(
    HWND hwnd, 
    UINT uMsg, 
    WPARAM wParam,  
    LPARAM lParam)
{
    CBaseWindow* pWnd;

    if (uMsg == WM_NCCREATE)
    {       
        SetWindowLong(hwnd, GWL_USERDATA, (long)((LPCREATESTRUCT(lParam))->lpCreateParams));
    }

    pWnd = GetObjectFromWindow(hwnd);

    if (pWnd)
        return pWnd->WinMsgHandler(hwnd, uMsg, wParam, lParam);
    else
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
 }

Then each class has its own WndProc where I handle the messages as they come in.

So does anyone have any thoughts for me?

If I am going about this is complete wrong way or if I am not following best practice please say so, I am doing this to learn so shoot away.

[UPDATE]

OK here is some code to demonstrate the problem:

Main.cpp

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "MainWnd.h"

int WINAPI WinMain(
    HINSTANCE hInstance, 
    HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine, 
    int nCmdShow)
{       
    MainWnd wnd(hInstance);

    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0 ) > 0)
    {       
        TranslateMessage(&msg); 
        DispatchMessage(&msg);
    }   
    return 0;
}

BaseWindow.cpp

 #include "BaseWindow.h"

 //...

 LRESULT CALLBACK CBaseWindow::stWinMsgHandler(
       HWND hwnd, 
       UINT uMsg, 
       WPARAM wParam, 
       LPARAM lParam)
 {
       CBaseWindow* pWnd;

       if (uMsg == WM_NCCREATE)
       {        
             SetWindowLong(hwnd, 
                  GWL_USERDATA, 
                  (long)((LPCREATESTRUCT(lParam))->lpCreateParams));
   }

        pWnd = GetObjectFromWindow(hwnd);

       if (pWnd)
             return pWnd->WinMsgHandler(hwnd, uMsg, wParam, lParam);
       else
             return DefWindowProc(hwnd, uMsg, wParam, lParam);
 }

 BOOL CBaseWindow::Create(DWORD dwStyles, RECT* rect)
 {  
      m_hwnd = CreateWindow(        
           szClassName, 
           szWindowTitle, 
           dwStyles, 
           rect->left, 
           rect->top, 
           rect->right - rect->left, 
           rect->bottom - rect->top, 
           NULL, 
           NULL, 
           hInstance, 
           (void *)this);   

     return (m_hwnd != NULL);
 }

MainWnd.cpp

#include "MainWnd.h"
#define WIDTH 400
#define HEIGHT 400

MainWnd::MainWnd(HINSTANCE hInst): CBaseWindow(hInst), hInstance(hInst)
{
     SetWindowTitle(_T("Main Window"));

     WNDCLASSEX wcx;
     FillWindowClass(&wcx);                 

     if(RegisterWindow(&wcx))
     {      
         RECT rc;
     BuildRect(&rc);

         if(Create(WS_OVERLAPPEDWINDOW | WS_VISIBLE, &rc))
         {          
               customTextBox = new CustomTextBox(hInst, m_hwnd);            
         }
     }  
 }

 void MainWnd::FillWindowClass(WNDCLASSEX *wcx)
 {
      wcx->cbSize = sizeof(WNDCLASSEX);
      wcx->style = CS_HREDRAW | CS_VREDRAW | CS_DROPSHADOW;                     
      wcx->lpfnWndProc = CBaseWindow::stWinMsgHandler;          
      wcx->cbClsExtra = 0;                                      
      wcx->cbWndExtra = 0;                                      
      wcx->hInstance = hInstance;                                   
      wcx->hIcon = LoadIcon(NULL, IDI_APPLICATION);             
      wcx->hCursor = LoadCursor(NULL, IDC_ARROW);
      wcx->hbrBackground = CreateSolidBrush(RGB(255,255,255));      
      wcx->lpszMenuName = NULL;                                 
      wcx->lpszClassName = _T("MainWindow");
      wcx->hIconSm = LoadIcon(NULL, IDI_APPLICATION);
 }

 LRESULT CALLBACK MainWnd::WinMsgHandler(
       HWND hwnd, 
       UINT uMsg, 
       WPARAM wParam, 
       LPARAM lParam)
 {          
       switch (uMsg)
       {
       case WM_DESTROY:
            delete customTextBox;
            PostQuitMessage(0);     
            break;
       case WM_LBUTTONUP:
            SetFocus(hwnd);
            break;
       default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
       }
       return 0;
 }

CustomTextBox.cpp

 #include "CustomTextBox.h"

 CustomTextBox::CustomTextBox(
      HINSTANCE hInst, 
      HWND hParent): CBaseWindow(hInst), 
                 hParent(hParent),
 {  
      WNDCLASSEX wcx;
      CreateWndClassEX(wcx);
      if(RegisterWindow(&wcx))
      {
           RECT clientRect;
           CreateClientRect(clientRect);
           CreateChild(WS_VISIBLE | WS_CHILD | WS_BORDER | WS_TABSTOP, &clientRect, hParent);       
      }
 }

 void CustomTextBox::CreateWndClassEX(WNDCLASSEX& wcx)
 {
      wcx.cbSize = sizeof(WNDCLASSEX);
      wcx.style = CS_HREDRAW | CS_VREDRAW;    
      wcx.lpfnWndProc = CBaseWindow::stWinMsgHandler;
      wcx.cbClsExtra = 0;                                           
      wcx.cbWndExtra = 0;
      wcx.hInstance = hInstance;
      wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
      wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
      wcx.hbrBackground = CreateSolidBrush(RGB(255,255,255));
      wcx.lpszMenuName = NULL;
      wcx.lpszClassName = _T("Edit Control");
      wcx.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
 }

 LRESULT CALLBACK CustomTextBox::WinMsgHandler(
      HWND hwnd, 
      UINT uMsg, 
      WPARAM wParam, 
      LPARAM lParam)
 {
      switch(uMsg)
      {
      /* Handling the caret */
      case WM_SETFOCUS:
           CreateCaret(hwnd, NULL, 0, nWindowY); 
           SetCaretPos(GetEndOfLinePoint(), nCaretPosY * nCharY); 
           ShowCaret(hwnd); 
           return 0;    
      case WM_MOUSEACTIVATE:
           SetFocus(hwnd);
           return MA_ACTIVATE;
      case WM_KILLFOCUS:
           DestroyCaret();
           return 0;
      default:
           return DefWindowProc(hwnd, uMsg, wParam, lParam);
      }         
 }

Discovery

In writing up this code I came across one of the causes of my problems: in my actual application I don't have a title bar and so to move the window I was sending WM_NCLBUTTONDOWN to my main window on WM_LBUTTONDOWN:

SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, NULL);

I then had my SetFocus() after this and this wasn't working but if I switch them around and handle the SetFocus() first then the clicking does change focus.

There is still a problem with initially setting the focus though. At the moment after starting the app the custom edit control still displays the caret even though it doesn't have focus and you need to click on it to give it focus, whereby it will receive keyboard input. After that the focus works as desired: if I click on the main window it has focus; if I click on the custom edit, it has focus etc.

Was it helpful?

Solution

OK so it turns out that my entire problem lied with that line SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, NULL); Once I switched this:

case WM_LBUTTONDOWN:                        
    SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, NULL);       
    SetFocus(hwnd);
break;

to:

case WM_LBUTTONDOWN:                        
    SetFocus(hwnd);
    SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, NULL);
break;

everything came together.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top