Share via


From the August 2001 issue of MSDN Magazine

MSDN Magazine

Getting the Text of a Window in Another Application; Making Backspace Work in the Explorer Bar
Paul DiLascia
Download the code for this article: MSDN Code Gallery
Q

I want to get the text from the edit box of another application. I tried GetWindowText, but it always returns NULL. Is it possible to get the text from a window in another application? If so, how do I go about it?

Many Readers


A

Oh, I love softball questions; they make me look so smart. To get the text of a window in another process, all you have to do is send WM_GETTEXT directly.
CWnd* pWnd = GetOtherAppWindow(); TCHAR buf[512]; pWnd->SendMessage(WM_GETTEXT, sizeof(buf)/sizeof(TCHAR), (LPARAM)(void*)buf);      If you're programming in C, you'd naturally use the HWND instead of CWnd. Some of you are no doubt wondering: hey, wait a minute—if that works, why doesn't GetWindowText work? After all, GetWindowText just sends WM_GETTEXT, right? Wrong. GetWindowText doesn't just send WM_GETTEXT. GetWindowText only sends WM_GETTEXT if the window belongs to the current process. If the window belongs to a different process, GetWindowText does something else. The documentation is explicit:

If the target window is owned by another process and has a caption, GetWindowText retrieves the window caption text. If the window does not have a caption, the return value is a null string. This behavior is by design.

      In other words, you can get the caption from the main window of another process, but not the text of a child window such as an edit control, combobox, or button within it.
      And why is that? Why is this behavior "by design?" Because, the documentation further explains, "it allows applications to call GetWindowText without hanging if the process that owns the target window is hung." Preeeetty clever, eh? GetWindowText affords protection from calling into never-never land. Of course a cynical programmer might counter: what good is that? GetWindowText doesn't get the text, so if you want the text you have to SendMessage WM_GETTEXT, which can hang. So where's the protection? Ah, but you don't have to use SendMessage! You can use SendMessageTimeout, which is guaranteed to return even if you call into a dead process. Amazing, all the pesky things to consider in a multitasking world!
      The real question is: why didn't the Redmondtonians simply make GetWindowText do the right thing? That is, why doesn't GetWindowText do a SendMessage(WM_GETTEXT) if the window is in the current process and a SendMessageTimeout if it's in another? Good question. Only the Windows® Wizard knows. I suspect the friendly Redmondtonians had to accommodate some naughty legacy apps in the move from fake to real multitasking....

Figure 1 GetWindowText versus WM_GETTEXT
Figure 1 GetWindowText versus WM_GETTEXT

      In any case, I wrote a little program, GWTTest, that illustrates the point. GWTTest displays the text obtained from GetWindowText and SendMessageTimeout(WM_GETTEXT) for top-level windows and their edit controls. Figure 1 shows it running. GWTTest is a useless program whose only purpose is to illustrate the difference between GetWindowText and WM_GETTEXT. As you can see, both operations return the caption for top-level windows, but only WM_GETTEXT works for edit controls in other processes. Amazing. GWTTest is a straightforward CListView app; the function that gets the text is CMyView::AddWindowInfo, shown in Figure 2.

Q

I'm using your code from the November and December 1999 issues of MSJ to write my own Explorer bar, which has an edit control as a child window. Everything works fine, except when I type Backspace into the edit control nothing happens. How can I make the Backspace key work in my Explorer bar?

Many Readers


A

The hows, whys, and wherefores of keyboard input are enigmatic even in ordinary apps, let alone COM controls and ActiveX® add-ins, desk bands and Explorer bars. Judging from the number of readers who ask this question, it's quite a common conundrum.
      To shed some light into the gloom, I modified the desk band from my Desk Band article in the November 1999 issue of Microsoft Systems Journal. The new program, SearchBar, works either as an Explorer bar or normal desk band. Figure 3 shows it running in Microsoft® Internet Explorer. SearchBar has an edit control where you can type some text; when you press Enter it searches your favorite search engine for the text you typed. I originally wrote SearchBar as a dumb demo program, but it turned out to be actually useful and earned its way into my Internet Explorer toolbar even though I generally despise add-ins of any kind (because they never work smoothly and just clutter up my screen). If you want to learn more about desk bands and Explorer bars, see the aforementioned article; I'll only address the Backspace problem here.

Figure 3 SearchBar in Action
Figure 3 SearchBar in Action

      So, if you try to use my original search band from November 1999 as an Explorer bar, the Backspace key doesn't work. Why not? To answer, I have to take you on a little tour of Windows keyboard basics, always a confusing topic. So consider an ordinary old pre-COM Windows-based app. At the heart of every such app is a central loop called the message pump.
MSG msg; while (GetMessage(&msg,...)) { TranslateMessage(&msg); DispatchMessage(&msg); }      The message pump gets messages from a queue and dispatches them to the appropriate window procedure. Amazing that in 2001 I'm still writing about message pumps; but that's how every app processes its messages. In the old days, Windows wasn't a true preemptive multitasking operating system. Apps couldn't be interrupted any old time; they had to request their messages in an orderly fashion. Today, Windows has true multitasking, but the message pump remains. The message pump is actually a bit more complicated than I've shown because there are various special functions you have to call for different situations. For example, TranslateMessage translates WM_KEYDOWN/WM_KEYUP events into WM_CHAR and there's another translation function, TranslateAccelerator, which you must use if your app has accelerators like Ctrl-X for Cut or Ctrl-O for Open.
MSG msg; while (GetMessage(&msg,...)) { if (TranslateAccelerator(...)) { // handled—don't dispatch } else { TranslateMessage(&msg); DispatchMessage(&msg); } }      TranslateAccelerator translates WM_KEYDOWN/WM_CHAR/WM_KEYUP sequences into WM_COMMAND messages. If the user types Ctrl-O, TranslateAccelerator translates the keystrokes into a WM_COMMAND message with ID = ID_FILE_OPEN or whatever ID you mapped Ctrl-O to. With TranslateAccelerator, you don't have to look for special command keys; they arrive the normal way via WM_COMMAND, just as if the user had invoked a menu item.
      OK, so much for basics. Now step into the heady world of COM, desk bands, and Explorer bars. What happens now? Let's say you have an Explorer bar with an edit control and the user types into the edit control. At the heart of Internet Explorer is a message pump more or less like the one shown previously. If the user types a character like a, b, or c, the keystroke goes through the message pump. Since a, b, and c are presumably not accelerators, ::TranslateAccelerator ignores them and returns FALSE. The keystrokes go through TranslateMessage, which also generates a WM_CHAR, and DispatchMessage dispatches the messages to the edit control (since that's the window they're destined for, assuming it has focus). The upshot is that the characters go to the edit control. Everything is just hunky-dory.
      But suppose the key pressed is an accelerator for Internet Explorer—like Backspace, Home, or End. What happens then? If the user presses Backspace, TranslateAccelerator gobbles it and converts it to a WM_COMMAND. The edit control never sees the keystroke, even if it has focus. All this because messages are dispatched centrally by the main app/container, not by each window that has focus. To set matters straight, what's needed is some way to give the Explorer bar first crack at the keystroke.
// fictitious pseudo-code // inside Internet Explorer MSG msg; while (GetMessage(&msg)) { if (pExplorerBar->TranslateAccelerator(...)) { // handled by Explorer bar; don't dispatch } else { ••• } }      In fact, this is more or less exactly what Internet Explorer does. The details, however, are as usual more complex. They involve an elaborate COM handshaking ritual. But the basic idea is simple.
      To see how it works in real life, let's take a look inside SearchBar. SearchBar implements two band objects: CMyDeskBand and CMyExplrBar. Both are essentially the same desk band from the MyBands program in my November 1999 article, modified to make Backspace work. For those of you who don't remember off the top of your head every issue of MSJ going back two years, MyBands uses a class CBandObj that implements band objects generically. CBandObj implements basic band object interfaces: IContextMenu, IDockingWindow, IOleWindow, IDeskBand, and IInputObject. CBandObj is a kind of band object framework: to implement a desk band or Explorer bar, just derive from CBandObj and override the few virtual functions that actually do something. CBandObj hides all the COM stuff and lets you work in C++. It's pretty cool.
      The interfaces relevant to Backspace are IInputObject (in the band object) and IInputObjectSite (in Internet Explorer). In general, any object that can accept keyboard input must implement IInputObject (this goes for ActiveX controls, too), and any container that can accept such an object must implement IInputObjectSite. IInputObject has three methods:
UIActivateIO(BOOL fActivate, LPMSG pMsg) HasFocusIO() TranslateAcceleratorIO(LPMSG pMsg)      When Internet Explorer wants to activate your Explorer bar, it calls your object's UIActivateIO. This happens typically when the user Tabs from some other control to yours. (Actually, I've never been able to get Internet Explorer to call my UIActivateIO; however, I am able to make it happen by Tabbing when my object lives as a desk band in the task bar.) The proper UIActivateIO response is to set the focus to your band object or child window. CBandObj implements UIActivateIO generically like so:
STDMETHODIMP CBandObj::XInputObject::UIActivateIO(BOOL fActivate, LPMSG pMsg) { METHOD_PROLOGUE(CBandObj, InputObject); if (fActivate && pThis->GetSafeHwnd()) pThis->SetFocus(); return S_OK; }That is, when the container calls UIActivateIO, CBandObj gives itself focus. In real life, you typically don't want focus to go to the band window itself, but rather some other child window (such as your edit control). For that, all you have to do is handle WM_SETFOCUS in the main bank window.
void CMyDeskBand::OnSetFocus(CWnd* pOldWnd) { m_wndEdit.SetFocus(); }      This should seem familiar; it's the normal Windows way of transferring focus from parent to child. In general, you should never have to override CBandObj's implementation of IInputObject::UIActivateIO since all you have to do is handle WM_SETFOCUS.
      So much for UIActivateIO. Next, let's examine HasFocusIO. The container calls HasFocusIO when it wants to know if your object or one of its children has the focus. Once again, CBandObj provides a generic implementation.
STDMETHODIMP CBandObj::XInputObject::HasFocusIO() { METHOD_PROLOGUE(CBandObj, InputObject); HWND hwndFocus = ::GetFocus(); HWND hwndMe = pThis->GetSafeHwnd(); return (hwndFocus==hwndMe || ::IsChild(hwndMe, hwndFocus)) ? S_OK : S_FALSE; }      In English: if I or one of my children has focus, return TRUE; otherwise FALSE. (In COMspeak: S_OK or S_FALSE.) Once again, the generic CBandObj implementation works in all cases and there's no reason ever to override.
      Finally, let's get to TranslateAcceleratorIO. This is the place where you solve your Backspace problem. When Internet Explorer gets a keystroke message, it checks to see if your Explorer bar implements IInputObject; if so, it calls your bar's HasFocusIO, and if your bar has focus, it calls your TranslateAcceleratorIO. This is your one and only big chance in life to intercept the keystroke. CBandObj provides a generic implementation that passes the buck to a new virtual function.
STDMETHODIMP CBandObj::XInputObject::TranslateAcceleratorIO (LPMSG pMsg) { METHOD_PROLOGUE(CBandObj, InputObject); return pThis->OnTranslateAccelerator(pMsg) ? S_OK : S_FALSE; }      This is the main modification I made to the original CBandObj code in the November 1999 issue. CBandObj::OnTranslateAccelerator provides a default implementation that translates the accelerator using your band object's accelerator table.
BOOL CBandObj::OnTranslateAccelerator(LPMSG pMsg) { return m_hAccel && m_hWnd && ::TranslateAccelerator(m_hWnd, m_hAccel, pMsg); }      CBandObj works like document templates in MFC: you add the band class to your app from your InitInstance function and when you do, you give it a resource ID, just like IDR_MAINFRAME for a document template. The band object looks for an accelerator table with the same ID and, if it finds one, loads it, stores it in a member m_hAccel, and uses it to translate your accelerators. So if you want to use accelerators, all you have to do is give your accelerator table the same ID as your band object class and CBandObj does the rest. You don't have to write a single line of code.
      Unfortunately, however, this magic fails for SearchBar because there's no accelerator table to properly map keys like Backspace, Home, End, and others to their edit control functions. Nor are there any WM_COMMANDs you could use, nor is there any magic TranslateEditAccelerator function to translate edit control keys. So what's a poor programmer to do?
      Whenever you wish you had a function that you don't, the only thing to do is roll up your sleeves and write it yourself! Or better yet, get someone else to write it for you—like me! Figure 4 shows a function I wrote to translate edit keys. Use it with my blessing. CMyDeskBand::OnTranslateAccelerator calls it like so:
BOOL CMyDeskBand::OnTranslateAccelerator(LPMSG pMsg) { if (TranslateEditAccelerator(m_wndEdit, pMsg)) return TRUE; return CBandObj::OnTranslateAccelerator(pMsg); }      TranslateEditAccelerator is fairly straightforward. It passes various keys to the edit control, using either WM_CHAR or WM_ KEYDOWN. It passes VK_BACK (Backspace) as a WM_CHAR message; it passes Home, End, Left, and Right using WM_ KEYDOWN. Trial and error alone reveals which way edit controls want the keys—this is Windows voodoo programming at its best! If there are any keys I've forgot, feel free to add them yourself.
      Before I leave, there's one more detail to fret. Remember I mentioned another interface, IInputObjectSite? Where does that come in? Well, there's more than one way to skin a cat, and more than one way your band object can get focus. Most often you don't get a UIActivateIO call because most often the user doesn't Tab to your control, but simply clicks the mouse on it. When that happens, Windows passes focus directly to your edit control. The container needs to know so it can start feeding you keystrokes. That's what IInputObjectSite is for. When your band object gets focus, you're supposed to notify the container by calling IInputObjectSite::OnFocusChangeIS. It's easy to remember which IInputObjectSite method to call because there's only one. In SearchBar, when the band object gets EN_SETFOCUS or EN_KILLFOCUS, it calls a special function CBandObj::OnFocusChange, which in turn calls IInputObjectSite::OnFocusChangeIS. Once again, with CBandObj, you don't have to mess with COM; all you have to do is call a virtual CBandObj function, OnFocusChange. Figure 5 shows the details. When the edit control gets or loses focus, it sends EN_SETFOCUS or EN_KILLFOCUS to its parent (the desk band); CMyDesBand handles these notifications by calling OnFocusChange. What could be easier?
      One of the best ways to understand COM—who calls which methods when—is to sprinkle your code liberally with TRACE statements in every interface method, and study the results. Figure 6 shows a sample TRACE stream from SearchBar, with running commentary I added after the fact. I was quite surprised to see how many method calls go back and forth on just a single keystroke—COM certainly doesn't do anything to speed up your app!
      Feel free to download the full source from the link at the top of this article and go spelunking. Happy programming!

Send questions and comments to cppqa@microsoft.com.

Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++(Addison-Wesley, 1992). Paul can be reached at askpd@pobox.com or https://www.dilascia.com.