MFC: Controlling Notepad From C++ Applications | CodeGuru

MFC: Controlling Notepad From C++ Applications

Introduction This article presents how to start then control a third party GUI application from our own C++ applications, particularly the Notepad text editor which is shipped with Windows operating system. Also it shows two C++ classes designed for this purpose: CApplication – a class for launching a GUI application then control it by sending […]

Written By
CodeGuru Staff
CodeGuru Staff
Mar 2, 2011
3 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

Introduction

This article presents how to start then control a third party GUI application from our own C++ applications, particularly the Notepad text editor which is shipped with Windows operating system.
Also it shows two C++ classes designed for this purpose:

  • CApplication – a class for launching a GUI application then control it by sending standard commands;
  • CNotepad – extends CApplication by adding Notepad-specific tasks, like writing text into the editor.

If using Microsoft Visual Studio 2005 or newer, you can include CApplication and CNotepad classes in every MFC, ATL, Win32 or Console Application project.

Starting the Application

To start a third-party application then get a handle to its main window, programmers may be tempted to use an easy way as follows:

  1. call ShellExecute to start application’s process;
  2. call Sleep to wait until initialization is complete;
  3. call FindWindow or GetForegroundWindow to get a handle to the application’s main window.
   if(::ShellExecute(NULL, _T("open"), _T("notepad.exe"),
                     NULL, NULL, SW_SHOW) > (HINSTANCE)32)
   {
      ::Sleep(666); // some "magic" time to wait
      HWND hWndMain = ::FindWindow(_T("Notepad"), NULL);
      // HWND hWndMain = ::GetForegroundWindow();
      // ...
   }

That may be handy and may work many times but has several leaks:

  1. ShellExecute gives us nothing in order to further control the process;
  2. we cannot know how long time takes the initialization, so using Sleep is not a good way;
  3. neither FindWindow nor GetForegroundWindow guarantee that we’ll always get the started application’s main window.

One better approach is to do the following:

  1. call CreateProcess to start application’s process; unlike ShellExecute it gives the process and main thread handles and identifiers in a PROCESS_INFORMATION structure;
  2. call WaitForInputIdle to wait until the application’s process has finished its initialization; it takes as parameter the process handle returned by CreateProcess;
  3. use data from PROCESS_INFORMATION structure to find the application’s main window; this may be done in several ways, one is by enumerating top level windows and call GetWindowThreadProcessId to compare the process identifier and/or thread identifier with the process identifier and/or thread identifier got by CreateProcess.

Note: because an application may have more than one top-level window, comparing the window class names may be still necessary.

HWND StartApplication(LPTSTR pszExeName, LPCTSTR pszWndClass)
{
   HWND hWndMain = NULL;
   STARTUPINFO startupInfo = {0};
   PROCESS_INFORMATION processInfo = {0};

   if(::CreateProcess(NULL, pszExeName,
                      NULL, NULL, FALSE, 0, NULL, NULL,
                      &startupInfo, &processInfo))
   {
      // wait for process initialization
      ::WaitForInputIdle(processInfo.hProcess, 10000);

      // find the main window
      DWORD dwProcessId = 0;
      HWND hWnd = ::GetWindow(::GetDesktopWindow(), GW_CHILD);
      while(NULL != hWnd)
      {
         DWORD dwThreadId =
               ::GetWindowThreadProcessId(hWnd, &dwProcessId);
         if((dwThreadId == processInfo.dwThreadId) &&
            (dwProcessId == processInfo.dwProcessId))
         {
            const int nMaxCount = 256;
            TCHAR pszClassName[nMaxCount];
            ::GetClassName(hWnd, pszClassName, nMaxCount);
            if(!_tcsicmp(pszClassName, pszWndClass))
            {
              hWndMain = hWnd;
               break;
            }
         }
         hWnd = ::GetWindow(hWnd, GW_HWNDNEXT);
      }
   }
   return hWndMain;
}
Advertisement

Sending Commands

Once we know the main window handle we can easily send commands to the application as follows:

#define ID_FILE_SAVE 777 // hard-coded command ID
   // ...
   ::SendMessage(::hWndMain, WM_COMMAND, (WPARAM)ID_FILE_SAVE, NULL);

Let’s say, we can get the “magic” number 777 by taking a look in the application menu resource.
However, this is not very good because the command IDs may vary from one application to another,
from one version to another.
Another and better approach is to dynamically get the command IDs from accelerators.
Many Windows applications use “standard” accelerator keystrokes like Ctrl+S (Save), Ctrl+C (Copy), Ctrl+V (Paste) and so on,
then will be no more troubles because of different IDs in different applications or versions.

All we have to do is to find the matching ACCELTABLEENTRY in the application’s accelerator resources.

UINT FindAcceleratorCommandId(LPCTSTR pszExeName, char Key,
                              BOOL ctrlKey, BOOL shiftKey,
                              BOOL altKey)
{
   UINT nCommandID = 0;

   HMODULE hModule = ::LoadLibrary(pszExeName);
   if(NULL != hModule)
   {
      HRSRC hRsrc =
         FindResource(hModule, _T("MAINACC"), RT_ACCELERATOR);
      if(NULL != hRsrc)
      {
         DWORD dwSize = ::SizeofResource(hModule, hRsrc) / 8;
         HGLOBAL hGlobal = ::LoadResource(hModule, hRsrc);
         if(NULL != hGlobal)
         {
            ACCELTABLEENTRY* table =
               (ACCELTABLEENTRY*)::LockResource(hGlobal);
            for(DWORD dwIndex = 0; dwIndex < dwSize; dwIndex++)
            {
               ACCELTABLEENTRY& entry = table[dwIndex];

               WORD wLo = FVIRTKEY;
               if(ctrlKey) wLo |= FCONTROL;
               if(shiftKey) wLo |= FSHIFT;
               if(altKey) wLo |= FALT;
               DWORD dwToFind = MAKELONG(wLo, (WORD)Key);

               wLo = entry.fFlags;
               wLo &= ~FNOINVERT;
               wLo &= ~0x0080;
               DWORD dwFound = MAKELONG(wLo, entry.wAnsi);
               if(dwToFind == dwFound)
               {
                  // accelerator has been found
                  nCommandID = entry.wId;
                  break;
               }
            }
            ::FreeResource(hGlobal);
         }
      }
      ::FreeLibrary(hModule);
   }
   return nCommandID;
}

Now we can send, let's say the "Save" command (Ctrl+S):

   UINT nID = FindAcceleratorCommandId(_T("notepad.exe"),
                                       'S', TRUE, FALSE, FALSE);
   if(nID > 0)
   {
      WPARAM wParam = MAKEWPARAM(nID, 1);
      ::SendMessage(hWndMain, WM_COMMAND, wParam, NULL);
   }

Note: ACCELTABLEENTRY structure is not present in any SDK header file, so we have to declare it in our own sources.

struct ACCELTABLEENTRY
{
  WORD fFlags;
  WORD wAnsi;
  WORD wId;
  WORD padding;
};

Notepad-Specific Issues

As well known, Notepad application is a simple but often used text viewer/editor. Beside launching and sending standard commands would be useful to write text in it.
The text is written in an edit Windows common control. So first, we have to find the child of main window which has class name "Edit".

HWND FindEditControl(HWND hWndMain)
{
   HWND hWndEdit = NULL;
   HWND hWnd = ::GetWindow(hWndMain, GW_CHILD);
   while(NULL != hWnd)
   {
      const int nMaxCount = 256;
      TCHAR pszClassName[nMaxCount];
      ::GetClassName(hWnd, pszClassName, nMaxCount);
      if(!_tcsicmp(pszClassName, _T("Edit")))
      {
         hWndEdit = hWnd;
         break;
      }
      hWnd = ::GetWindow(hWnd, GW_HWNDNEXT);
   }
   return hWndEdit;
}

Once having the handle, we can do anything can be done with any other edit control: replace text, append text and so on.

In the next page, you can find a brief description of CApplication and CNotepad classes and a presentation of the demo application.

CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.