The program:

I'm writing a program to ease typing of accented characters (i.e. á or ñ) so that the user doesn't have to repeatedly pick them from the character map.

In order to do this I have a small program run in the background that has registered hotkeys for each possible accented character (CTRL-ALT-A gives you á for example).

The method I am using is to determine which hotkey was pressed, then use GetForegroundWindow() to determine which window the user is working with, then GetWindowThreadProcessId() to get the thread ID of that program. I use AttachThreadInput() to attach my thread to the foreground window's thread so that I can use GetFocus() in order to find the handle to the actual input that currently has the keyboard focus.

Once I have a handle to the actual input (i.e. an EDIT or RichEdit) I detach my thread from the other and then send a message to the input (using the handle I retreived) telling it to insert a string containing the accented character. My prefered method is to send an EM_REPLACESEL message to the input which caused the input to place the contents of my string at the current cursor position, replacing any selected text.

This works flawlessly in simple programs such as Notepad and Wordpad, as well as programs that I have written.

The problem:

This does not work with more complex programs such as Microsoft Word or Open Office nor even the editor for my C++ IDE. It seems that the common trait among these programs is that they are MDI programs. As such, when I attempt to use GetFocus() after having attached threads I am usually returned the handle to the main (frame) window, which is exactly what the earlier call to GetForegroundWindow() gave me.

I can, however, use GetTopWindow() to get the handle of the MDI client window, and either GetTopWindow() a second time or send WM_MDIGETACTIVE to get the handle to the top-most MDI child window. But this is where the well runs dry.

The MDI child window should be a 'fully functioning' window in its own right, containing further child windows and controls (including the elusive input that I'm looking for). But calling GetTopWindow() or even FindWindowEx() or some such on the MDI child returns a NULL.

I have checked to make sure that the thread of the MDI child is the same as the thread for the target program, and it is. It seems that there is some barrier that I am unable to see preventing me from accessing the children of an MDI child window.

However, I know that I am able to access the MDI child itself, because in attempting to send WM_SETTEXT to the child (for test purposes) I was actually able to change the title of the MDI child window (I can tell by looking at the window with Spy), but not the contents.

The question

What gives? I'm baffled at the moment and don't see any way around it. Any and all suggestions welcome.

The code

This is still in the 'proof of concept' testing stage, so it's a little messy and missing a lot of desired functionality, but this is what I have so far:

Code:
/*
 * Auto accenter - Rutabega - 12/21/05 - v0.5
 */

#include <windows.h>
#define STAT 100

void AppendWindowText(HWND hWnd, const char * lpString);
HWND GetForegroundFocus(void);

LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
     {
     switch(msg)
          {
          case WM_CREATE:
               {
               CreateWindowEx(0,"STATIC","-",WS_CHILD | WS_VISIBLE,10,10,50,30,hwnd,(HMENU)STAT,NULL,NULL);
               }
               break;
          case WM_HOTKEY:
               {
               HWND editwnd = NULL;

               editwnd = GetForegroundFocus();

               if(editwnd == NULL)
                    {
                    //MessageBox(hwnd,"No edit present","Warning",MB_OK);
                    SetWindowText(GetDlgItem(hwnd,STAT),"!");
                    }

               switch(wParam)
                    {
                    case 0x0001:
                         {
                         //SendMessage(editwnd,WM_SETTEXT,(WPARAM)NULL,(LPARAM)"Testing 1 2 3!");
                         AppendWindowText(editwnd,"á");
                         SetWindowText(GetDlgItem(hwnd,STAT),"a");
                         }
                         break;
                    case 0x0002:
                         {
                         AppendWindowText(editwnd,"Á");
                         SetWindowText(GetDlgItem(hwnd,STAT),"A");
                         }
                         break;
                    case 0x0003:
                         {
                         AppendWindowText(editwnd,"é");
                         SetWindowText(GetDlgItem(hwnd,STAT),"e");
                         }
                         break;
                    case 0x0004:
                         {
                         AppendWindowText(editwnd,"É");
                         SetWindowText(GetDlgItem(hwnd,STAT),"E");
                         }
                         break;
                    case 0x0005:
                         {
                         AppendWindowText(editwnd,"í");
                         SetWindowText(GetDlgItem(hwnd,STAT),"i");
                         }
                         break;
                    case 0x0006:
                         {
                         AppendWindowText(editwnd,"Í");
                         SetWindowText(GetDlgItem(hwnd,STAT),"I");
                         }
                         break;
                    case 0x0007:
                         {
                         AppendWindowText(editwnd,"ó");
                         SetWindowText(GetDlgItem(hwnd,STAT),"o");
                         }
                         break;
                    case 0x0008:
                         {
                         AppendWindowText(editwnd,"Ó");
                         SetWindowText(GetDlgItem(hwnd,STAT),"O");
                         }
                         break;
                    case 0x0009:
                         {
                         AppendWindowText(editwnd,"ú");
                         SetWindowText(GetDlgItem(hwnd,STAT),"u");
                         }
                         break;
                    case 0x000A:
                         {
                         AppendWindowText(editwnd,"Ú");
                         SetWindowText(GetDlgItem(hwnd,STAT),"U");
                         }
                         break;
                    case 0x000B:
                         {
                         AppendWindowText(editwnd,"ñ");
                         SetWindowText(GetDlgItem(hwnd,STAT),"n");
                         }
                         break;
                    case 0x000C:
                         {
                         AppendWindowText(editwnd,"Ñ");
                         SetWindowText(GetDlgItem(hwnd,STAT),"N");
                         }
                         break;
                    default:
                         {
                         MessageBox(hwnd,"Unknown hotkey","Hotkey!",MB_OK);
                         }
                         break;
                    }
               }
               break;
          case WM_CLOSE:
               DestroyWindow(hwnd);
               break;
          case WM_DESTROY:
               PostQuitMessage(0);
               break;
          default:
               return DefWindowProc(hwnd, msg, wParam, lParam);
          }
     return(0);
     }


// main
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
     {
     HDC hdc;
     WNDCLASSEX wc;
     HWND hwnd;
     MSG Msg;

     //Register the window class
     wc.cbSize        = sizeof(WNDCLASSEX);
     wc.style         = 0;
     wc.lpfnWndProc   = WndProc;
     wc.cbClsExtra    = 0;
     wc.cbWndExtra    = 0;
     wc.hInstance     = hInstance;
     wc.hIcon         = NULL;
     wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
     wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
     wc.lpszMenuName  = NULL;
     wc.lpszClassName = "AutoAccent";
     wc.hIconSm       = NULL;

     if(!RegisterClassEx(&wc))
          {
          MessageBox(NULL,"Window Registration Failed!","Error!",MB_ICONEXCLAMATION | MB_OK);
          return(0);
          }

     //Create the Window

     hwnd = CreateWindowEx(0,"AutoAccent","Auto Accént v0.5",WS_OVERLAPPEDWINDOW,100,100,300,200,NULL,NULL,hInstance,NULL);

     if(hwnd == NULL)
          {
          MessageBox(NULL,"Window Creation Failed!","Error!",MB_ICONEXCLAMATION | MB_OK);
          return(0);
          }

     ShowWindow(hwnd, nCmdShow);
     UpdateWindow(hwnd);
     
     RegisterHotKey(hwnd,0x0001,MOD_ALT | MOD_CONTROL,VkKeyScan('a'));
     RegisterHotKey(hwnd,0x0002,MOD_SHIFT | MOD_ALT | MOD_CONTROL,VkKeyScan('a'));
     RegisterHotKey(hwnd,0x0003,MOD_ALT | MOD_CONTROL,VkKeyScan('e'));
     RegisterHotKey(hwnd,0x0004,MOD_SHIFT | MOD_ALT | MOD_CONTROL,VkKeyScan('e'));
     RegisterHotKey(hwnd,0x0005,MOD_ALT | MOD_CONTROL,VkKeyScan('i'));
     RegisterHotKey(hwnd,0x0006,MOD_SHIFT | MOD_ALT | MOD_CONTROL,VkKeyScan('i'));
     RegisterHotKey(hwnd,0x0007,MOD_ALT | MOD_CONTROL,VkKeyScan('o'));
     RegisterHotKey(hwnd,0x0008,MOD_SHIFT | MOD_ALT | MOD_CONTROL,VkKeyScan('o'));
     RegisterHotKey(hwnd,0x0009,MOD_ALT | MOD_CONTROL,VkKeyScan('u'));
     RegisterHotKey(hwnd,0x000A,MOD_SHIFT | MOD_ALT | MOD_CONTROL,VkKeyScan('u'));
     RegisterHotKey(hwnd,0x000B,MOD_ALT | MOD_CONTROL,VkKeyScan('n'));
     RegisterHotKey(hwnd,0x000C,MOD_SHIFT | MOD_ALT | MOD_CONTROL,VkKeyScan('n'));

     //Enter the Message Loop
     while(GetMessage(&Msg, NULL, 0, 0) > 0)
          {
          TranslateMessage(&Msg);
          DispatchMessage(&Msg);
          }

     UnregisterHotKey(hwnd,0x0001);
     UnregisterHotKey(hwnd,0x0002);
     UnregisterHotKey(hwnd,0x0003);
     UnregisterHotKey(hwnd,0x0004);
     UnregisterHotKey(hwnd,0x0005);
     UnregisterHotKey(hwnd,0x0006);
     UnregisterHotKey(hwnd,0x0007);
     UnregisterHotKey(hwnd,0x0008);
     UnregisterHotKey(hwnd,0x0009);
     UnregisterHotKey(hwnd,0x000A);
     UnregisterHotKey(hwnd,0x000B);
     UnregisterHotKey(hwnd,0x000C);

     return(Msg.wParam);
     }

void AppendWindowText(HWND hWnd, const char * lpString)
     {
/* not used for this program
     int iLength = GetWindowTextLength(hWnd);
     SendMessage(hWnd, EM_SETSEL, iLength, iLength);
*/

     //the following line is the way I want to do things (and the way it works in non-MDI programs), the other 2 were for testing purposes

     SendMessage(hWnd, EM_REPLACESEL, 0, (LPARAM) lpString);
     //SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM) lpString);
     //SendMessage(hWnd, WM_PASTE, 0, 0);

/* not used for this program
     SendMessage(hWnd, WM_VSCROLL, SB_BOTTOM, (LPARAM)NULL);
*/
     }


HWND GetForegroundFocus(void)
     { 
     DWORD foregroundThreadID; // foreground window thread
     DWORD MDIclientThreadID; // thread of any mdi client windows
     DWORD ourThreadID; // our active thread
     HWND focuswnd; //window with keyboard focus
     
     HWND child1 = NULL; //used to get the MDI child
     HWND child2 = NULL; //ditto

     // Check to see if we are the foreground thread 
     foregroundThreadID = GetWindowThreadProcessId(GetForegroundWindow(),NULL); 
     ourThreadID = GetCurrentThreadId(); 

     // If not, attach our thread's 'input' to the foreground thread's 
     if(foregroundThreadID != ourThreadID)
          {
          AttachThreadInput(foregroundThreadID, ourThreadID, TRUE);
          }

     focuswnd = GetFocus(); //should get the handle of the window with the keyboard focus

     child1 = focuswnd;//GetTopWindow(focuswnd);

     child2 = GetTopWindow(focuswnd);
     while(child2 != NULL)
          { //this forces a search into the child windows, this is what gets me the MDI child handle
          child1 = child2;
          child2 = GetTopWindow(child1);//GetNextWindow(child1,GW_HWNDNEXT);
          }

     focuswnd = child1;

     // If we attached our thread, detach it now 
     if(foregroundThreadID != ourThreadID)
          {
          AttachThreadInput(foregroundThreadID, ourThreadID, FALSE);
          }

     return(focuswnd);
     }