Local Input State

[Previous] [Next]

Having threads independently handle input—preventing one thread from adversely affecting another thread—is just part of what makes the input model robust. However, this feature alone is not enough to keep threads isolated from one another, so the system has additional infrastructure. This additional infrastructure is called local input state.

Each thread has its own local input state, which is managed inside a thread's THREADINFO structure (discussed in Chapter 26). This input state consists of the thread's virtualized input queue as well as a set of variables. These variables keep track of the following input state management information:

Keyboard input and window focus information, such as

  • Which window has keyboard focus
  • Which window is active
  • Which keys are considered pressed down
  • The state of the caret

The variables also keep track of mouse cursor management information, such as

  • Which window has mouse capture
  • The shape of the mouse cursor
  • The visibility of the mouse cursor

Because each thread gets its very own set of input state variables, each thread has a different notion of focus window, mouse capture window, and so on. From a thread's perspective, either one of its windows has keyboard focus or no window in the system has keyboard focus, either one of its windows has mouse capture or no window has mouse capture, and so on. As you might expect, this separatism has some ramifications, which we'll discuss in this chapter.

Keyboard Input and Focus

As we know, the RIT directs the user's keyboard input to a thread's virtualized input queue—not to a window. The RIT places the keyboard events into the thread's virtualized input queue without referring to a particular window. When the thread calls GetMessage, the keyboard event is removed from the queue and assigned to the window (created by the thread) that currently has input focus. Figure 27-2 illustrates this process.

click to view at full size.

Figure 27-2. The RIT directs the user's keyboard input to one thread's virtualized input queue at a time.

To instruct a different window to accept keyboard input, you need to specify to which thread's virtualized input queue the RIT should direct keyboard input and you need to tell the thread's input state variables which window will have keyboard focus. Calling SetFocus alone does not accomplish both tasks. If Thread 1 is currently receiving input from the RIT, a call to SetFocus—passing the handle of Window A, Window B, or Window C—causes the focus to change. The window losing focus removes its focus rectangle or hides its caret, and the window gaining focus draws a focus rectangle or shows its caret.

However, let's say that Thread 1 is still receiving input from the RIT, and it calls SetFocus, passing the handle of Window E. In this case, the system prevents the call to SetFocus from doing anything because the window for which you are trying to set focus is not using the virtualized input queue that is currently "connected" to the RIT. After Thread 1 executes this call, there is no change in focus, and the appearance of the screen doesn't change.

In another situation, Thread 1 might be connected to the RIT and Thread 2 might call SetFocus, passing the handle of Window E. In this case, Thread 2's local input state variables are updated to reflect that Window E is the window to receive keyboard input the next time the RIT directs keystrokes to Thread 2. The call doesn't cause the RIT to direct input to Thread 2's virtualized input queue.

Because Window E now has focus for Thread 2, it receives a WM_SETFOCUS message. If Window E is a pushbutton, it draws a focus rectangle for itself, so two windows with focus rectangles (Window A and Window E) might appear on the screen. This can be quite disconcerting to an end user. You should be careful when you call SetFocus so that this situation doesn't occur. Call SetFocus only if your thread is connected to the RIT.

By the way, if you give focus to a window that displays a caret when it receives a WM_SETFOCUS message, you can produce several windows on the screen that simultaneously display flashing carets. This can be a bit bewildering to a user.

When focus is transferred from one window to another using conventional methods (such as clicking on a window with the mouse), the window losing focus receives a WM_KILLFOCUS message. If the window receiving focus belongs to a thread other than the thread associated with the window losing focus, the local input state variables of the thread that created the window losing focus are updated to reflect that no window has focus. Calling GetFocus at this time returns NULL, which makes the thread think that no window currently has the focus.

The SetActiveWindow function activates a top-level window in the system and sets focus to that window:

 HWND SetActiveWindow(HWND hwnd); 

Like the SetFocus function, this function does nothing if the calling thread did not create the window passed to it.

The complement of SetActiveWindow is the GetActiveWindow function:

 HWND GetActiveWindow(); 

This function works just like the GetFocus function except that it returns the handle of the active window indicated by the calling thread's local input state variables. So if the active window is owned by another thread, GetActiveWindow returns NULL.

Other functions that can alter a window's z-order, activation status, and focus status include BringWindowToTop and SetWindowPos:

 BOOL BringWindowToTop(HWND hwnd); BOOL SetWindowPos( HWND hwnd, HWND hwndInsertAfter, int x, int y, int cx, int cy, UINT fuFlags); 

These two functions work identically (in fact, BringWindowToTop calls SetWindowPos internally, passing HWND_TOP as the second parameter). If the thread calling these functions is not connected to the RIT, the functions do nothing. However, if the thread calling these functions is connected to the RIT, the system activates the specified window. Note that this works even if the calling thread did not create the specified window. This means that the window becomes active and the thread that created the specified window is connected to the RIT. This also causes the calling thread's and the newly connected thread's local input state variables to be updated.

At times, a thread wants to have its window come to the foreground. For example, you might have a meeting scheduled using Microsoft Outlook. About a half hour before the meeting time, Outlook pops up a dialog box reminding you that the meeting is coming up soon. If Outlook's thread is not connected to the RIT, this dialog box will come up behind another window and you will not see it. There needs to be some way to draw the user's attention to windows even if the user is using another application's window.

The following function brings a window to the foreground and connects its thread to the RIT:

 BOOL SetForegroundWindow(HWND hwnd); 

The system also activates the window and gives it the focus. The complementary function is GetForegroundWindow:

 HWND GetForegroundWindow(); 

This function returns the window handle that is currently in the foreground.

In earlier versions of windows, the SetForegroundWindow function always worked. That is, the thread calling this function could always bring the specified window to the foreground (even if the window was not created by the calling thread). However, developers started abusing this capability and were popping up windows on top of each other. For example, I would be writing a magazine article and all of a sudden the Print Job Finished dialog box would pop up on the screen. If I wasn't looking at the screen, I wouldn't even see the dialog box and I'd be typing text into it instead of into my document. Even more annoying is when you try to select a menu item and suddenly another window pops up, closing the menu.

To stop these annoyances, Microsoft has added a lot more intelligence to the SetForegroundWindow function. In particular, the function only works if the thread calling the function is already connected to the RIT or if the thread currently connected to the RIT has not received any input in a certain amount of time (the time is controlled using the SystemParametersInfo function and the SPI_SETFOREGROUNDLOCKTIMEOUT value). In addition, the function fails if a menu is active.

If SetForegroundWindow is not allowed to bring the window to the foreground, it flashes the window's caption and the window's button on the taskbar. The user will see the taskbar button flashing and know that the window is trying to get the user's attention. The user will have to manually activate the window to see what information it has to report. The SystemParametersInfo function is also used to control the flashing using the SPI_SETFOREGROUNDFLASHCOUNT value.

Because of this new behavior, the system offers some additional functions. The first function (listed below) allows a thread in the specified process to successfully call SetForegroundWindow if the thread calling AllowSetForegroundWindow can successfully call SetForegroundWindow. To allow any process to pop a window up over your thread's windows, pass ASFW_ANY (defined as -1) for the dwProcessId parameter:

 BOOL AllowSetForegroundWindow(DWORD dwProcessId); 

In addition, a thread can lock the SetForegroundWindow function so that it always fails by calling LockSetForegroundWindow:

 BOOL LockSetForegroundWindow(UINT uLockCode); 

You can pass either LSFW_LOCK or LSFW_UNLOCK for the uLockCode parameter. This function is called by the system internally when a menu is activated so that a window attempting to come to the foreground does not close the menu. Windows Explorer needs to call these functions explicitly when it displays the Start menu, since this is not a built-in menu.

The system automatically unlocks the SetForegroundWindow function when the user presses the Alt key or if the user explicitly brings a window to the foreground. This prevents an application from keeping SetForegroundWindow locked all the time.

Another aspect of keyboard management and the local input state is that of the synchronous key state array. Every thread's local input state variables include a synchronous key state array, but all threads share a single asynchronous key state array. These arrays reflect the state of all keys on a keyboard at any given time. The GetAsyncKeyState function determines whether the user is currently pressing a key on the keyboard:

 SHORT GetAsyncKeyState(int nVirtKey); 

The nVirtKey parameter identifies the virtual key code of the key to check. The high bit of the result indicates whether the key is currently pressed (1) or not (0). I have often used this function during the processing of a single message to check whether the user has released the primary mouse button. I pass the virtual key value VK_LBUTTON and wait for the high bit of the return value to be 0. Note that GetAsyncKeyState always returns 0 (not pressed) if the thread calling the function did not create the window that currently has the input focus.

The GetKeyState function, which follows, differs from the GetAsyncKeyState function because it returns the keyboard state at the time the most recent keyboard message was removed from the thread's queue:

 SHORT GetKeyState(int nVirtKey); 

This function is not affected by which window has input focus and can be called at any time.

Mouse Cursor Management

Mouse cursor management is another component of the local input state. Because the mouse, like the keyboard, must be shared among all the different threads, Windows must not allow a single thread to monopolize the mouse cursor by altering its shape or confining it to a small area of the screen. In this section, we'll take a look at how the mouse cursor is managed by the system.

One aspect of mouse cursor management is the cursor's hide/show capability. If a thread calls ShowCursor(FALSE), the mouse cursor is hidden when it is over any window created by this thread. If the mouse cursor is moved over a window created by another thread, the mouse cursor is visible.

Another aspect of mouse cursor management is the ability to clip the cursor to a rectangular region of the screen using the ClipCursor function:

 BOOL ClipCursor(CONST RECT *prc); 

This function causes the mouse to be constrained within the screen coordinates specified in the rectangle pointed to by the prc parameter. When a thread calls ClipCursor, what should the system do? Allowing the cursor to be clipped could adversely affect other threads, and preventing the cursor from being clipped would adversely affect the calling thread. Microsoft has implemented a compromise. When a thread calls this function, the system does clip the mouse cursor to the specified rectangle. However, if an asynchronous activation event occurs (when the user clicks on another application's window, when a call to SetForegroundWindow is made, or when Ctrl+Esc is pressed), the system stops clipping the cursor's movement, allowing the cursor to move freely across the entire screen.

Now we move to the issue of mouse capture. When a window "captures" the mouse (by calling SetCapture), it requests that all mouse messages be directed from the RIT to the thread's virtualized input queue and that all mouse messages from the virtualized input queue be directed to the window that set capture. This capturing of mouse messages continues until the application later calls ReleaseCapture.

Again, capturing the mouse compromises the robustness of the system—there must be a compromise. When an application calls SetCapture, the RIT is directed to place all mouse messages in the thread's virtualized input queue. SetCapture also sets the local input state variables for the thread that called SetCapture.

Usually an application calls SetCapture when the user presses a mouse button. However, there is no reason why a thread could not call SetCapture even if a mouse button is not down. If SetCapture is called when a mouse button is down, capture is performed system-wide. However, when the system detects that no mouse buttons are down, the RIT no longer directs mouse messages solely to the thread's virtualized input queue. Instead, the RIT directs mouse messages to the input queue associated with the window that is directly beneath the mouse cursor. This is normal behavior when the mouse is not captured.

However, the thread that originally called SetCapture still thinks that mouse capture is in effect. Thus whenever the mouse is positioned over any window created by the thread that has capture set, the mouse messages will be directed to the capture window for that thread. In other words, when the user releases all mouse buttons, mouse capture is no longer performed on a system-wide level—it is now performed on a thread-local level.

In addition, if the user attempts to activate a window created by another thread, the system automatically sends mouse button down and mouse button up messages to the thread that sets capture. Then the system updates the thread's local input state variables to indicate that the thread no longer has mouse capture. It is clear from this implementation that Microsoft expects mouse clicking and dragging to be the most common reason for using mouse capture.

The final local input state variable pertaining to the mouse is its cursor shape. Whenever a thread calls SetCursor to change the shape of the mouse cursor, the local input state variables are updated to reflect the mouse cursor shape. In other words, the local input state variables always remember the most recent shape of the mouse cursor set by the thread.

Let's say that the user moves the mouse over your window, your window receives a WM_SETCURSOR message, and you call SetCursor to change the mouse cursor to an hourglass. After the call to SetCursor, you have code that enters into a lengthy process. (An infinite loop is a good example of a lengthy process.) Now the user moves the mouse cursor out of your window and over a window belonging to another application. When the mouse moves over the other window, that window can control the cursor's shape.

Local input state variables are not required for accomplishing this change. But now let's move the mouse cursor back into your window, which is still executing its lengthy procedure. The system wants to send WM_SETCURSOR messages to the window, but the window is unable to retrieve them because it is still looping. So the system looks at the most recently set mouse cursor shape (contained in the thread's local input state variables) and automatically sets the mouse cursor back to this shape (the hourglass, in this example). This gives the user visual feedback that the process is still working and that the user must wait.



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net