本章将讨论系统的硬件输入模型。重点将考察按键和鼠标事件是如何进入系统并发送给适当的窗口过程的。微软设计输入模型的一个主要目标就是为了保证一个线程的动作不要对其他线程的动作产生不好的影响。这里是一个1 6位Wi n d o w s中的例子:如果一个任务引起一个死循环,所有的任务都被挂起,不能再响应用户。用户只能重新启动计算机。这样就给一个单个的任务太多的控制。强壮的操作系统,例如Windows 2000和Windows 98,不会使一个挂起的线程妨碍系统中其他线程接收硬件输入。
27.1 原始输入线程
图2 7 - 1概括描述了系统的硬件输入模型。当系统初始化时,要建立一个特殊的线程,即原始输入线程(raw input thread,R I T)。此外,系统还要建立一个队列,称为系统硬件输入队列(System hardware input queue, SHIQ)。R I T和S H I Q构成系统硬件输入模型的核心。
那么R I T怎么才能知道要向哪一个线程的虚拟输入队列里增加硬件输入消息?对鼠标消息,R I T只是确定是哪一个窗口在鼠标光标之下。利用这个窗口, R I T调用G e t Wi n d o w T h r e a dP r o c e s s I d来确定是哪个线程建立了这个窗口。返回的线程I D指出哪一个线程应该得到这个鼠标消息。
对按键硬件事件的处理稍有不同。在任何给定的时刻,只有一个线程同R I T“连接”。这个线程称为前景线程(foreground thread),因为它建立了正在与用户交互的窗口,并且这个线程的窗口相对于其他线程所建立的窗口来说处在画面中的前景。
当一个用户在系统上登录时, Windows Explorer 进程让一个线程建立相应的任务栏(t a s k b a r)和桌面。这个线程连接到R I T。如果你又要产生C a l c u l a t o r,那么就又有一个线程来建立一个窗口,并且这个线程变成连接到R I T的线程。注意现在Windows Explorer的线程不再与R I T连接,因为在一个时刻只能有一个线程同R I T连接。当一个按键消息进入S H I Q时,R I T就被唤醒,将这个事件转换成适当的按键消息,并将消息放入与R I T连接的线程的虚拟输入队列。
不同的线程是如何连接到R I T的呢?我们已经说过,当产生一个进程时,这个进程的线程可以建立一个窗口。这个窗口处于前景,其建立窗口的线程同R I T相连接。另外, R I T还要负责处理特殊的键组合,如A l t + Ta b、A l t + E s c和C t r l + A l t + D e l等。因为R I T在内部处理这些键组合,就可以保证用户总能用键盘激活窗口。应用程序不能够拦截和废弃这些键组合。当用户按动了某个特殊的键组合时,R I T激活选定的窗口,并将窗口的线程连接到R I T。Wi n d o w s也提供激活窗口的功能,使窗口的线程连接到R I T。这些功能在本章后面讨论。
从图2 7 - 1中可以看到如何保护线程,避免相互影响的。如果R I T向窗口B1或窗口B2发送一个消息,消息到达线程B的虚拟输入队列。在处理消息时,线程B在与五个内核对象同步时可能会进入死循环或死锁。如果发生这种情况,线程仍然同R I T连接在一起,并且可能有更多的消息要增加到线程的虚拟输入队列中。
这种情况下,用户会发现窗口B 1和B 2都没有反应,可能想切换到窗口A1。为了做这种切换,用户按A l t + Ta b。因为是R I T处理A l t + Ta b按键组合,所以用户总能切换到另外的窗口,不会有什么问题。在选定窗口A1之后,线程A就连接到R I T。这个时候,用户就可以对窗口A1进入输入,尽管线程及其窗口都没有响应。
27.2 局部输入状态
• 哪一个窗口有鼠标捕获。
• 鼠标光标的形状。
• 鼠标光标的可见性。
由于每个线程都有自己的输入状态变量,每个线程都有不同的焦点窗口、鼠标捕获窗口等概念。从一个线程的角度来看,或者它的某个窗口拥有键盘焦点,或者系统中没有窗口拥有键盘焦点;或者它的某个窗口拥有鼠标捕获,或者系统中没有窗口拥有鼠标捕获,等等。读者会想到,这种隔离应该有一些细节,对此我们将在后面讨论。
27.2.1 键盘输入与焦点
我们已经知道,R I T使用户的键盘输入流向一个线程的虚拟输入队列,而不是流向一个窗口。R I T将键盘事件放入线程的虚拟输入队列时不用涉及具体的窗口。当这个线程调用G e t M e s s a g e时,键盘事件从队列中移出并分派给当前有输入焦点的窗口。(由该线程所建立)。图2 7 - 2说明了这个处理过程。
图27-2 RIT将用户的键盘输入导向一个线程的虚拟输入队列
线程1当前正在从R I T接收输入,用窗口A、窗口B或窗口C的句柄作参数调用S e t F o c u s会引起焦点改变。失去焦点的窗口除去它的焦点矩形或隐藏它的插入符号,获得焦点的窗口画出焦点矩形或显示它的插入符号。
假定线程1仍然从R I T接收输入,并用窗口E的句柄作为参数调用S e t F o c u s。这种情况下,系统阻止执行这个调用,因为想要设置焦点的窗口不使用当前连接R I T的虚拟输入队列。在线的线程不一样,那么,对于建立失去焦点窗口的线程,要更新它的局部输入状态变量,说明它没有窗口拥有焦点。这时调用G e t F o c u s将返回N U L L,这会使线程知道当前没有窗口拥有焦点。
函数S e t A c t i v e Wi n d o w激活系统中一个最高层( t o p - l e v e l)的窗口,并对这个窗口设定焦点:
HWND SetActiveWindow(HWND hwnd);
与S e t A c t i v e Wi n d o w相配合的函数是G e t A c t i v e Wi n d o w函数:
HWND GetActiveWindow();
其他可以改变窗口的Z序(Z - o r d e r)、活动状态和焦点状态的函数还包括B r i n g Wi n d o w ToTo p和S e t Wi n d o w P o s:
BOOL BringWindowToTop(HWND hwnd); BOOL SetWindowPos( HWND hwnd, HWND hwndInsertAfter, int x, int y, int cx, int cy, UINT fuFlags);
有时候,一个线程想让它的窗口成为屏幕的前景。例如,有可能会利用Microsoft Qutlook安排一个会议。在会议开始前的半小时, O u t l o o k弹出一个对话框提醒用户会议将要开始。如果Q u t l o o k的线程没有连接到R I T,这个对话框就会藏在其他窗口的后面,有可能看不见它。因为了制止这种现象,微软对S e t F o r e g r o u n d Wi n d o w函数增加了更多的智能。特别规定,仅当调用一个函数的线程已经连接到R I T或者当前与R I T相连接的线程在一定的时间内(这个时间量由S y s t e m P a r a m e t e r s I n f o函数和S P I _ S E T F O R E G R O U N D _ L O C K T I M E O U T值来控制)没有收到任何输入,这个函数才有效。另外,如果有一个菜单是活动的,这个函数就失效。
如果不允许S e t F o r e g r o u n d Wi n d o w将窗口移到前景,它会闪烁该窗口的标题栏和任务条上该窗口的按钮。用户看到任务条按钮闪烁,就知道该窗口想得到用户的注意。用户应该手工激活这个窗口,看一看要报告什么信息。还可以用S y s t e m P a r a m e t e r s I n f o函数和S P I _ S E T F O R E G R O U N D -F L A S H C O U N T值来控制闪烁。
由于这些新的内容,系统又提供了另外一些函数。如果调用A l l o w S e t F o r e g r o u n d Wi n d o w的线程能够成功调用S e t F o r e g r o u n d Wi n d o w,第一个函数(见下面所列)可使指定进程的一个线程成功调用S e t F o r e g r o u n d Wi n d o w。为了使任何进程都可以在你的线程的窗口上弹出一个窗口,指定A S F W _ A N Y (定义为-1 )作为d w P r o c e s s I d参数:
BOOL AllowSetForegroundWindow(DWORD dwProcessId);
BOOL LockSetForegroundWindow(UINT uLockCode);
关于键盘管理和局部输入状态,其他的内容是同步键状态数组。每个线程的局部输入状态变量都包含一个同步键状态数组,但所有的线程要共享一个同步键状态数组。这些数组反映了在任何给定时刻键盘所有键的状态。利用G e t A s y n c K e y S t a t e函数可以确定用户当前是否按下了键盘上的一个键:
SHORT GetAsyncKeyState(int nVirtKey);
鼠标光标管理的另一个方面是使用C l i p C u r s o r函数将鼠标光标剪贴到一个矩形区域。
BOOL ClipCursor(CONST RECT *prc);
现在我们再讨论鼠标捕获。当一个窗口“捕获”鼠标(通过调用S e t C a p t u r e)时,它要求所有的鼠标消息从R I T发到调用线程的虚拟输入队列,并且所有的鼠标消息从虚拟输入队列发到设置捕获的窗口。在程序调用R e l e a s e C a p t u r e之前,要一直继续这种鼠标消息的捕获。
鼠标的捕获必须同系统的强壮性折衷,也只能是一种折衷。当一个程序调用S e t C a p t u r e时,R I T将所有鼠标消息放入线程的虚拟输入队列。S e t C a p t u r e还要为调用S e t C a p t u r e的线程设置局部输入状态变量。
通常一个程序在用户按一个鼠标按钮时调用S e t C a p t u r e。但是即使鼠标按钮没有被按下,也没有理由说一个线程不能调用S e t C a p t u r e。如果当一个鼠标按下时调用S e t C a p t u r e,捕获在全系统范围内执行。但当系统检测出没有鼠标按钮按下时, R I T不再将鼠标消息只发往线程的虚拟输入队列,而是将鼠标消息发往与鼠标光标所在的窗口相联系的输入队列。这是不做鼠标捕获时的正常行为。
但是,最初调用S e t C a p t u r e的线程仍然认为鼠标捕获有效。因此,每当鼠标处于有捕获设置的线程所建立的窗口时,鼠标消息将发往这个线程的捕获窗口。换言之,当用户释放了所有的鼠标按钮时,鼠标捕获不再在全系统范围内执行,而是在一个线程的局部范围内执行。
此外,如果用户想激活一个其他线程所建立的窗口,系统自动向设置捕获的线程发送鼠标按钮按下和鼠标按钮放开的消息。然后系统更新线程的局部输入状态变量,指出该线程不再具有鼠标捕获。很明显,通过这种实现方式,微软希望鼠标点击和拖动是使用鼠标捕获的最常见理由。
27.3 将虚拟输入队列同局部输入状态挂接在一起
从上面的讨论我们可以看出这个输入模型是强壮的,因为每个线程都有自己的局部输入状态环境,并且在必要时每个线程可以连接到R I T或从R I T断开。有时候,我们可能想让两个或多个线程共享一组局部输入状态变量及一个虚拟输入队列。
可以利用A t t a c h T h r e a d I n p u t函数来强制两个或多个线程共享同一个虚拟输入队列和一组局部输入状态变量:
BOOL AttachThreadInput( DWORD idAttach, DWORD idAttachTo, BOOL fAttach);
我们再考虑前面的例子,假定线程A调用A t t a c h T h r e a d I n p u t,传递线程A的I D作为第一个参数,线程B的I D作为第二个参数,T R U E作为最后一个参数:
SHORT GetKeyState(int nVirtKey);
当将两个线程的输入都挂接在一起时,就使线程共享单一的虚拟输入队列和同一组局部输入状态变量。但线程仍然使用自己的登记消息队列、发送消息队列、应答消息队列和唤醒标志(见第2 6章的讨论)。
如果让所有的线程都共享一个输入队列,就会严重削弱系统的强壮性。如果某一个线程接收一个按键消息并且挂起,其他的线程就不能接收任何输入了。所以应该尽量避免使用A t t a c h T h r e a d I n p u t函数。
在某些情况下,系统隐式地将两个线程挂接在一起。第一种情况是当一个线程安装一个日志记录挂钩(journal record hook)或日志播放挂钩(journal playback hook)的时候。当挂钩被卸载时,系统自动恢复所有线程,这样线程就可以使用挂钩安装前它们所使用的相同输入队列。
当一个线程安装一个日志记录挂钩时,它是让系统将用户输入的所有硬件事件都通知它。这个线程通常将这些信息保存或记录在一个文件上。因用户的输入必须按进入的次序来记录,所以系统中每个线程要共享一个虚拟输入队列,使所有的输入处理同步。
还有一些情况,系统会代替你隐式地调用A t t a c h T h r e a d I n p u t。假定你的程序建立了两个线程。第一个线程建立了一个对话框。在这个对话框建立之后,第二个线程调用G r e a t Wi n d o w,使用W S _ C H I L D风格,并向这个子窗口的双亲传递对话框的句柄。系统用子窗口的线程调用A t t a c h T h r e a d I n p u t,让子窗口的线程使用对话框线程所使用的输入队列。这样就使对话框的所有子窗口之间对输入强制同步。
27.3.1 LISLab 示例程序
L I S L a b程序(“2 7 L I S L a b . e x e”)清单列在清单2 7 - 1上。这是一个实验室,可以用它来实验局部输入状态。这个程序的源代码和资源文件在本书所附光盘的2 7 - L I S L a b目录下。
为了用局部输入状态做实验,需要两个线程作为实验品。L I S L a b进程有一个线程,这里选择N o t e p a d的线程作为另一个。如果当L I S L a b启动时N o t e p a d没有在运行,L I S L a b将启动N o t e p a d。在L I S L a b初始化之后,就会见到图2 7 - 4所示的对话框。这个窗口的左上角是Wi n d o w s编组框。
LISLab 示例程序清单2 7 - 1
/****************************************************************************** Module: LISLab.cpp Notices: Copyright (c) 2000 Jeffrey Richter ******************************************************************************/ #include "..\CmnHdr.h" /* See Appendix A. */ #include#include #include #include "Resource.h" /// #define TIMER_DELAY (500) // Half a second UINT_PTR g_uTimerId = 1; int g_nEventId = 0; DWORD g_dwEventTime = 0; HWND g_hwndSubject = NULL; HWND g_hwndNotepad = NULL; /// void CalcWndText(HWND hwnd, PTSTR szBuf, int nLen) { TCHAR szClass[50], szCaption[50], szBufT[150]; if (hwnd == (HWND) NULL) { _tcscpy(szBuf, TEXT("(no window)")); return; } if (!IsWindow(hwnd)) { _tcscpy(szBuf, TEXT("(invalid window)")); return; } GetClassName(hwnd, szClass, chDIMOF(szClass)); GetWindowText(hwnd, szCaption, chDIMOF(szCaption)); wsprintf(szBufT, TEXT("[%s] %s"), (PTSTR) szClass, (*szCaption == 0) ? (PTSTR) TEXT("(no caption)") : (PTSTR) szCaption); _tcsncpy(szBuf, szBufT, nLen - 1); szBuf[nLen - 1] = 0; // Force zero-terminated string } /// // To minimize stack use, one instance of WALKWINDOWTREEDATA // is created as a local variable in WalkWindowTree() and a // pointer to it is passed to WalkWindowTreeRecurse. // Data used by WalkWindowTreeRecurse typedef struct { HWND hwndLB; // Handle to the output list box HWND hwndParent; // Handle to the parent int nLevel; // Nesting depth int nIndex; // List box item index TCHAR szBuf[100]; // Output buffer int iBuf; // Index into szBuf } WALKWINDOWTREEDATA, *PWALKWINDOWTREEDATA; void WalkWindowTreeRecurse(PWALKWINDOWTREEDATA pWWT) { if (!IsWindow(pWWT->hwndParent)) return; pWWT->nLevel++; const int nIndexAmount = 2; for (pWWT->iBuf = 0; pWWT->iBuf < pWWT->nLevel * nIndexAmount; pWWT->iBuf++) pWWT->szBuf[pWWT->iBuf] = TEXT(' '); CalcWndText(pWWT->hwndParent, &pWWT->szBuf[pWWT->iBuf], chDIMOF(pWWT->szBuf) - pWWT->iBuf); pWWT->nIndex = ListBox_AddString(pWWT->hwndLB, pWWT->szBuf); ListBox_SetItemData(pWWT->hwndLB, pWWT->nIndex, pWWT->hwndParent); HWND hwndChild = GetFirstChild(pWWT->hwndParent); while (hwndChild != NULL) { pWWT->hwndParent = hwndChild; WalkWindowTreeRecurse(pWWT); hwndChild = GetNextSibling(hwndChild); } pWWT->nLevel--; } /// void WalkWindowTree(HWND hwndLB, HWND hwndParent) { WALKWINDOWTREEDATA WWT; WWT.hwndLB = hwndLB; WWT.hwndParent = hwndParent; WWT.nLevel = -1; WalkWindowTreeRecurse(&WWT); } /// BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) { chSETDLGICONS(hwnd, IDI_LISLAB); // Associate the Up arrow cursor with the dialog box's client area SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG_PTR) LoadCursor(NULL, IDC_UPARROW)); g_uTimerId = SetTimer(hwnd, g_uTimerId, TIMER_DELAY, NULL); HWND hwndT = GetDlgItem(hwnd, IDC_WNDFUNC); ComboBox_AddString(hwndT, TEXT("SetFocus")); ComboBox_AddString(hwndT, TEXT("SetActiveWindow")); ComboBox_AddString(hwndT, TEXT("SetForegroundWnd")); ComboBox_AddString(hwndT, TEXT("BringWindowToTop")); ComboBox_AddString(hwndT, TEXT("SetWindowPos-TOP")); ComboBox_AddString(hwndT, TEXT("SetWindowPos-BTM")); ComboBox_SetCurSel(hwndT, 0); // Fill the windows list box with our window hwndT = GetDlgItem(hwnd, IDC_WNDS); ListBox_AddString(hwndT, TEXT("---> This dialog box <---")); ListBox_SetItemData(hwndT, 0, hwnd); ListBox_SetCurSel(hwndT, 0); // Now add Notepad's windows g_hwndNotepad = FindWindow(TEXT("Notepad"), NULL); if (g_hwndNotepad == NULL) { // Notepad isn't running; run it. STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; TCHAR szCommandLine[] = TEXT("Notepad"); if (CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { // Wait for Notepad to create all its windows. WaitForInputIdle(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); g_hwndNotepad = FindWindow(TEXT("Notepad"), NULL); } } WalkWindowTree(hwndT, g_hwndNotepad); return(TRUE); } /// void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) { HWND hwndT; switch (id) { case IDCANCEL: if (g_uTimerId != 0) KillTimer(hwnd, g_uTimerId); EndDialog(hwnd, 0); break; case IDC_FUNCSTART: g_dwEventTime = GetTickCount() + 1000 * GetDlgItemInt(hwnd, IDC_DELAY, NULL, FALSE); hwndT = GetDlgItem(hwnd, IDC_WNDS); g_hwndSubject = (HWND) ListBox_GetItemData(hwndT, ListBox_GetCurSel(hwndT)); g_nEventId = ComboBox_GetCurSel(GetDlgItem(hwnd, IDC_WNDFUNC)); SetWindowText(GetDlgItem(hwnd, IDC_EVENTPENDING), TEXT("Pending")); break; case IDC_THREADATTACH: AttachThreadInput(GetWindowThreadProcessId(g_hwndNotepad, NULL), GetCurrentThreadId(), TRUE); break; case IDC_THREADDETACH: AttachThreadInput(GetWindowThreadProcessId(g_hwndNotepad, NULL), GetCurrentThreadId(), FALSE); break; case IDC_REMOVECLIPRECT: ClipCursor(NULL); break; case IDC_HIDECURSOR: ShowCursor(FALSE); break; case IDC_SHOWCURSOR: ShowCursor(TRUE); break; case IDC_INFINITELOOP: SetCursor(LoadCursor(NULL, IDC_NO)); for (;;) ; break; case IDC_SETCLIPRECT: RECT rc; SetRect(&rc, 0, 0, GetSystemMetrics(SM_CXSCREEN) / 2, GetSystemMetrics(SM_CYSCREEN) / 2); ClipCursor(&rc); break; } } /// void AddStr(HWND hwndLB, PCTSTR szBuf) { int nIndex; do { nIndex = ListBox_AddString(hwndLB, szBuf); if (nIndex == LB_ERR) ListBox_DeleteString(hwndLB, 0); } while (nIndex == LB_ERR); ListBox_SetCurSel(hwndLB, nIndex); } /// int Dlg_OnRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) { TCHAR szBuf[100]; wsprintf(szBuf, TEXT("Capture=%-3s, Msg=RButtonDown, DblClk=%-3s, x=%5d, y=%5d"), (GetCapture() == NULL) ? TEXT("No") : TEXT("Yes"), fDoubleClick ? TEXT("Yes") : TEXT("No"), x, y); AddStr(GetDlgItem(hwnd, IDC_MOUSEMSGS), szBuf); if (!fDoubleClick) SetCapture(hwnd); else ReleaseCapture(); return(0); } /// int Dlg_OnRButtonUp(HWND hwnd, int x, int y, UINT keyFlags) { TCHAR szBuf[100]; wsprintf(szBuf, TEXT("Capture=%-3s, Msg=RButtonUp, x=%5d, y=%5d"), (GetCapture() == NULL) ? TEXT("No") : TEXT("Yes"), x, y); AddStr(GetDlgItem(hwnd, IDC_MOUSEMSGS), szBuf); return(0); } /// int Dlg_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) { TCHAR szBuf[100]; wsprintf(szBuf, TEXT("Capture=%-3s, Msg=LButtonDown, DblClk=%-3s, x=%5d, y=%5d"), (GetCapture() == NULL) ? TEXT("No") : TEXT("Yes"), fDoubleClick ? TEXT("Yes") : TEXT("No"), x, y); AddStr(GetDlgItem(hwnd, IDC_MOUSEMSGS), szBuf); return(0); } /// void Dlg_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags) { TCHAR szBuf[100]; wsprintf(szBuf, TEXT("Capture=%-3s, Msg=LButtonUp, x=%5d, y=%5d"), (GetCapture() == NULL) ? TEXT("No") : TEXT("Yes"), x, y); AddStr(GetDlgItem(hwnd, IDC_MOUSEMSGS), szBuf); } /// void Dlg_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags) { TCHAR szBuf[100]; wsprintf(szBuf, TEXT("Capture=%-3s, Msg=MouseMove, x=%5d, y=%5d"), (GetCapture() == NULL) ? TEXT("No") : TEXT("Yes"), x, y); AddStr(GetDlgItem(hwnd, IDC_MOUSEMSGS), szBuf); } /// void Dlg_OnTimer(HWND hwnd, UINT id) { TCHAR szBuf[100]; CalcWndText(GetFocus(), szBuf, chDIMOF(szBuf)); SetWindowText(GetDlgItem(hwnd, IDC_WNDFOCUS), szBuf); CalcWndText(GetCapture(), szBuf, chDIMOF(szBuf)); SetWindowText(GetDlgItem(hwnd, IDC_WNDCAPTURE), szBuf); CalcWndText(GetActiveWindow(), szBuf, chDIMOF(szBuf)); SetWindowText(GetDlgItem(hwnd, IDC_WNDACTIVE), szBuf); CalcWndText(GetForegroundWindow(), szBuf, chDIMOF(szBuf)); SetWindowText(GetDlgItem(hwnd, IDC_WNDFOREGROUND), szBuf); RECT rc; GetClipCursor(&rc); wsprintf(szBuf, TEXT("left=%d, top=%d, right=%d, bottom=%d"), rc.left, rc.top, rc.right, rc.bottom); SetWindowText(GetDlgItem(hwnd, IDC_CLIPCURSOR), szBuf); if ((g_dwEventTime == 0) || (GetTickCount() < g_dwEventTime)) return; HWND hwndT; switch (g_nEventId) { case 0: // SetFocus g_hwndSubject = SetFocus(g_hwndSubject); break; case 1: // SetActiveWindow g_hwndSubject = SetActiveWindow(g_hwndSubject); break; case 2: // SetForegroundWindow hwndT = GetForegroundWindow(); SetForegroundWindow(g_hwndSubject); g_hwndSubject = hwndT; break; case 3: // BringWindowToTop BringWindowToTop(g_hwndSubject); break; case 4: // SetWindowPos w/HWND_TOP SetWindowPos(g_hwndSubject, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); g_hwndSubject = (HWND) 1; break; case 5: // SetWindowPos w/HWND_BOTTOM SetWindowPos(g_hwndSubject, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); g_hwndSubject = (HWND) 1; break; } if (g_hwndSubject == (HWND) 1) { SetWindowText(GetDlgItem(hwnd, IDC_PREVWND), TEXT("Can't tell.")); } else { CalcWndText(g_hwndSubject, szBuf, chDIMOF(szBuf)); SetWindowText(GetDlgItem(hwnd, IDC_PREVWND), szBuf); } g_hwndSubject = NULL; g_nEventId = 0; g_dwEventTime = 0; SetWindowText(GetDlgItem(hwnd, IDC_EVENTPENDING), TEXT("Executed")); } /// INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); chHANDLE_DLGMSG(hwnd, WM_MOUSEMOVE, Dlg_OnMouseMove); chHANDLE_DLGMSG(hwnd, WM_LBUTTONDOWN, Dlg_OnLButtonDown); chHANDLE_DLGMSG(hwnd, WM_LBUTTONDBLCLK, Dlg_OnLButtonDown); chHANDLE_DLGMSG(hwnd, WM_LBUTTONUP, Dlg_OnLButtonUp); chHANDLE_DLGMSG(hwnd, WM_RBUTTONDOWN, Dlg_OnRButtonDown); chHANDLE_DLGMSG(hwnd, WM_RBUTTONDBLCLK, Dlg_OnRButtonDown); chHANDLE_DLGMSG(hwnd, WM_RBUTTONUP, Dlg_OnRButtonUp); chHANDLE_DLGMSG(hwnd, WM_TIMER, Dlg_OnTimer); } return(FALSE); } /// int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) { DialogBox(hinstExe, MAKEINTRESOURCE(IDD_LISLAB), NULL, Dlg_Proc); return(0); } End of File //
//Microsoft Developer Studio generated resource script. // #include "Resource.h" #define APSTUDIO_READONLY_SYMBOLS / // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" / #undef APSTUDIO_READONLY_SYMBOLS / // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 / // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_LISLAB ICON DISCARDABLE "LISLab.Ico" / // // Dialog // IDD_LISLAB DIALOG DISCARDABLE 12, 38, 286, 178 STYLE WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Local Input State Lab" FONT 8, "MS Sans Serif" BEGIN GROUPBOX "Windows",IDC_STATIC,4,0,192,56 LTEXT "Focus:",IDC_STATIC,8,12,23,8 LTEXT "Focus window info",IDC_WNDFOCUS,52,12,140,8 LTEXT "Active:",IDC_STATIC,8,20,24,8 LTEXT "Active window info",IDC_WNDACTIVE,52,20,140,8 LTEXT "Foreground:",IDC_STATIC,8,28,40,8 LTEXT "Foreground window info",IDC_WNDFOREGROUND,52,28,140,8 LTEXT "Capture:",IDC_STATIC,8,36,29,8 LTEXT "Capture window info",IDC_WNDCAPTURE,52,36,140,8 LTEXT "Clip Cursor:",IDC_STATIC,8,44,39,8 LTEXT "Cursor clipping info",IDC_CLIPCURSOR,52,44,140,8 LTEXT "&Function:",IDC_STATIC,200,4,32,8 COMBOBOX IDC_WNDFUNC,200,14,82,54,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP PUSHBUTTON "Dela&y:",IDC_FUNCSTART,200,30,26,12 EDITTEXT IDC_DELAY,228,30,24,12,ES_AUTOHSCROLL LTEXT "Executed",IDC_EVENTPENDING,252,32,32,10 LTEXT "PrevWnd:",IDC_STATIC,200,46,34,8 LTEXT "Previous window info",IDC_PREVWND,208,54,76,18 LTEXT "&Notepad windows and Self:",IDC_STATIC,4,62,90,8 LISTBOX IDC_WNDS,4,72,192,32,WS_VSCROLL | WS_TABSTOP PUSHBUTTON "&Attach to Notepad",IDC_THREADATTACH,200,72,80,12 PUSHBUTTON "&Detach from Notepad",IDC_THREADDETACH,200,88,80,12 LTEXT "&Mouse messages received:",IDC_STATIC,4,102,89,8 LISTBOX IDC_MOUSEMSGS,4,112,192,32,WS_VSCROLL | WS_TABSTOP LTEXT "Click right mouse button to set capture.\n\nDouble-click right mouse button to release capture.", IDC_STATIC,200,110,80,40 LTEXT "Clipping rect:",IDC_STATIC,4,148,44,8 PUSHBUTTON "&Top, left",IDC_SETCLIPRECT,56,146,56,12 PUSHBUTTON "&Remove",IDC_REMOVECLIPRECT,116,146,56,12 LTEXT "Cursor visibility:",IDC_STATIC,4,164,47,8 PUSHBUTTON "&Hide",IDC_HIDECURSOR,56,162,56,12 PUSHBUTTON "&Show",IDC_SHOWCURSOR,116,162,56,12 PUSHBUTTON "&Infinite loop",IDC_INFINITELOOP,200,162,80,12,WS_GROUP | NOT WS_TABSTOP END #ifdef APSTUDIO_INVOKED / // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "Resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED / // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_LISLAB, DIALOG BEGIN RIGHTMARGIN, 283 BOTTOMMARGIN, 170 END END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources / #ifndef APSTUDIO_INVOKED / // // Generated from the TEXTINCLUDE 3 resource. // / #endif // not APSTUDIO_INVOKED
然后可以通过改变窗口的焦点来进行实验。首先在Local Input State Lab对话框右上角的F u n c t i o n组合框内选择S e t F o c u s。然后键入延迟时间(以秒计),即在调用S e t F o c u s之前你想让L I S L a b等待的时间。对这个实验,你很可能会指定延迟为0 s。后面将简单介绍如何使用D e l a y字段。
下一步选择一个窗口,作为调用S e t F o c u s时的参数。用Local Input State Lab对话框左边的Notepad Windows And Self列表框选择一个窗口。对这个实验,选择列表框中的[ N o t e p a d ]U n t i t l e d - N o t e p a d。现在已经为调用S e t F o c u s做好准备。只需点击D e l a y按钮,观察Wi n d o w s编组框会发生什么变化。什么也没发生。系统没有执行改变焦点的动作。
如果真想让S e t F o c u s将焦点改变到N o t e p a d,就点击Attach To Notepad按钮。点击这个按钮使L I S L a b调用下面的函数:
这个调用告诉L I S L a b的线程去使用Note- pad所使用的虚拟输入队列。另外, L I S L a b的线程也要与N o t e p a d共享局部输入状态变量。
AttachThreadInput(GetWindowThreadProcessId(g_hwndNotepad, NULL), GetCurrentThreadId(), TRUE);
图27-5 点击N o t e p a d窗口后的L I S L a b对话框
现在注意,由于两个线程的输入队列是挂接在一起的, L I S L a b可以服从N o t e p a d所做的窗口焦点改变。图2 7 - 5所示的对话框显示E d i t控制框当前具有焦点。如果我们显示N o t e p a d中的File Open对话框, L I S L a b将继续更新它的显示屏内容,告诉我们哪一个N o t e - p a d窗口具有焦点,哪个窗口是活动的等等。
现在我们再回到L I S L a b,点击D e l a y按钮,让S e t F o c u s给N o t e p a d焦点。这一次,对S e t F o c u s的调用成功,因两个线程的输入队列是接在一起的。
读者可以继续实验,通过在F u n c t i o n组合框中选择不同的函数,分别对S e t A c t i v e Wi n d o w、不过,R I T仍然同N o t e p a d的线程相“连接”。
关于窗口和焦点还要说明一点:S e t F o c u s函数和S e t A c t i v e Wi n d o w函数都返回原来拥有焦点或原来活动的窗口的句柄。有关这个窗口的信息显示在L I S L a b对话框的P r e v W n d字段里。而且,L I S L a b在调用S e t F o r e g r o u n d Wi n d o w之前,要先调用G e t F o r e g r o u n d Wi n d o w来取得原来处于前景的窗口的句柄。这些信息也显示在P r e v W n d字段。
现在我们对鼠标光标的内容进行实验。每当你在L I S L a b的对话框上移动鼠标(但没有在它的任何子窗口上移动),鼠标被显示成一个垂直箭头。当鼠标消息发送到这个对话框,消息要添加到Mouse Message Received列表框中。这样你就可以知道何时对话框在接收鼠标消息。如果你将鼠标移出对话框或移到某个子窗口之上,就会发现鼠标消息不再添加到Mouse MessageR e c e i v e d列表框中。
现在将鼠标移往对话框的右部,移在文本Click Right Mouse Button To Set Capture之上,然后点击并按住鼠标右键。这时, L I S L a b调用S e t C a p t u r e并传递L I S L a b对话框的句柄作为参数。注意L I S L a b更新Wi n d o w s编组框来反映它拥有鼠标捕获。
不要释放鼠标右键,在L I S L a b的子窗口上移动鼠标,并观察鼠标消息被添加到列表框里。注意,如果你将鼠标移出L I S L a b的对话框, L I S L a b会继续得知鼠标消息。不论你在屏幕上什么位置移动鼠标,鼠标光标都保持垂直箭头形状。
现在我们看一看系统的其他表现。释放鼠标右键,看会发生什么。在L I S L a b对话框的上部所反映的捕获窗口继续显示L I S L a b依然认为自己拥有鼠标捕获。如果你将鼠标移出L I S L a b的对话框,鼠标光标就不再保持垂直箭头的形状,鼠标消息也不再向Mouse Mssages Received列表框中添加,你会看到鼠标捕获依然有效,因为所有窗口都使用同一组局部输入状态变量。
当完成对鼠标捕获的实验时,可以使用下面两种办法将其关闭:
• 在Local Input State Lab对话框的任何地方双击鼠标右键,让L I S L a b安排R e l e a s e C a p t u r e的调用。
• 点击一个由L I S L a b的线程之外的线程所建立的窗口。这样系统会自动向L I S L a b的对话框发送鼠标按钮弹起和鼠标按钮按下的消息。
不论使用哪种办法,要观察Wi n d o w s编组框中的C a p t u r e字段是如何变化以反映没有窗口拥有鼠标捕获。
与鼠标有关的实验还有两个:其中之一涉及在一个矩形区域剪贴鼠标光标的移动,另一个涉及鼠标光标的可见性。当点击To p、L e f t按钮时,L I S L a b执行下面的代码:
点击H i d e或Show Cursor按钮,使L I S L a b执行下面的代码:
ShowCursor(FALSE);
ShowCursor(TRUE);
最后一个实验是使用Infinite Loop按钮。当点击这个按钮时,L I S L a b执行下面的代码:
SetCursor(LoadCursor(NULL, IDC_NO)); for(;;) ;
如果将鼠标移回到L I S L a b的对话框,系统看到L I S L a b没有响应,就自动将光标改回最近的形状——缺口圆。可以看到执行死循环的线程对用户是不方便的,但可以因此使用其他窗口。
注意,如果把一个窗口移到挂起的Local Input State Lab对话框,然后再把它移开,系统要发送一个W M _ PA I N T消息。但系统发现这个线程没有响应。系统为这个没有响应的程序重画窗口。当然,系统不能正确地重画这个窗口,因为系统不知道这个程序是干什么的。所以系统只是抹掉窗口的背景,并重画框架。
现在还有一个问题,如果屏幕上有一个窗口对我们做的任何事情(按键或击鼠标钮)都没有响应。我们如何清除这个窗口?在Windows 98中,必须按C t r l + A l t + D e l来显示图2 7 - 6的C l o s eP r o g r a m窗口。
在Windows 2000里,可以用鼠标右击Ta s k b a r上的程序按钮,或显示图2 7 - 7的Ta s k M a n a g e r窗口。
然后只要在窗口里选择我们想要结束的程序,在这里是Local Input State Lab,再点击E n dTa s k按钮。系统将试图用一种温和的方式(通过发送一个W M _ C L O S E消息)来结束L I S L a b,但发现该程序没有反应。在Windows 98里,这会引起系统显示图2 7 - 8的对话框。
图27-8 Local Input State Lab 对话框
选择End Ta s k(在Windows 98里)或End Now(在Windows 2000里)使系统强制性地将L I S L a b从系统中清除。C a n c e l按钮是告诉系统你改变了主意,不再想结束这个程序。这里,我们选择End Ta s k或End Now,从系统中清除L I S L a b。
这个实验的主要目的是为了说明系统的强壮性。一个程序不可能使操作系统处于这样一个状态——使其他程序不可用。还要注意在结束处理中, Windows 98和Windows 2000都可以自动释放线程所分配的资源,不会造成内存遗漏。
27.3.2 LISWa t c h示例程序
L I S Wa t c h程序(“2 7 L I S Wa t c h . e x e”)的源程序清单列在清单2 7 - 2中。这是一个有用的实用程序,用来监控活动窗口、焦点窗口和鼠标捕获窗口。在本书所附光盘的2 7 - L I S Wa t c h目录下是这个程序的源代码和资源文件。
当运行L I S Wa t c h,会显示图2 7 - 1 0的对话框。
图27-10 运行L I S Wa t c h时显示的对话框
当这个对话框接收到一个W M _ I N I T D I A L O G消息时,它调用S e t Ti m e r来设置一个计时器,每秒钟激发两次。当收到W M _ T I M E R消息,对话框的内容就更新以反映哪个窗口是活动的、哪个窗口拥有焦点、哪个窗口捕获了鼠标。在这个对话框以调用G e t F o c u s、G e t A c t i v e Wi n d o w和G e t C a p t u r e,所有这些函数都返回有效的窗口句柄。帮助函数C a l c W n d Te x t构造一个字符串,包含每个窗口的类名和窗口标题。然后每个窗口的串在L I S Wa t c h 的对话框中被更新。最后, D l g _ O n Ti m e r 在返回之前,再一次调用A t t a c h T h r e a d I n p u t,但这次是将最后一个参数设定为FA L S E,这样两个线程的局部输入状态就彼此断开。
前面解释了L I S Wa t c h的基本内容。然而,我们对L I S Wa t c h增加了其他一些特性,在这里解释一下。当启动L I S Wa t c h之后,它要监控系统中任何地方发生的窗口活动的变化。这就是对话框顶部的“全系统范围( S y s t e m - w i d e)”的意思。L I S Wa t c h还可以让你只限于观察一个线程的局部输入状态的变化。利用这种特性, L I S Wa t c h可以向你报告一个线程的确切情况。
为了让L I S Wa t c h监控一个线程的局部输入状态,所要做的只是在L I S Wa t c h的窗口上按下鼠标左键,在另外一个线程建立的窗口上拖动鼠标光标,然后释放鼠标按钮。在释放鼠标按钮之后, L I S Wa t c h将全局变量g - d w T h r e a d I d A t t a c h To设置成所选线程的I D。这个线程I D将替换L I S Wa t c h的对话框顶部的“S y s t e m - w i d e”。当这个全局变量不是零, D l g _ O n Ti m e r会略微改变它的行为。不是总将它的局部输入状态同前景线程的局部输入挂接在一起,而是将L I S Wa t c h本身同所选择的线程挂接在一起。用这种方式, L I S Wa t c h调用G e t A c t i v e Wi n d o w、G e t F o c u s和G e t C a p t u r e来反映所选择线程的局部输入状态的情况。
我们来做一个实验。运行C a l c u l a t o r,再用L I S Wa t c h来选择它的窗口。当激活C a l c u l a t o r的窗口时,L I S Wa t c h更新它的显示内容,见图2 7 - 11的对话框。
这里,C a l c u l a t o r的线程I D是0 x 0 0 0 0 0 4 e c。当前设定L I S Wa t c h来监控这一个线程的局部输入状态变化。如果点击C a l c u l a t o r的任何单选按钮或复选框, L I S Wa t c h都可以显示焦点的变化,因为所有这些窗口都是由线程0 x 0 0 0 0 0 4 e c建立的。
如果现在再激活一个由另外的程序(这里的例子是N o t e p a d)建立的窗口,L I S Wa t c h的对话框是下面的样子(见图2 7 - 1 2)。
图2 7 - 11 激活C a l c u l a t o r窗口时的L I S Watch 对话框
图27-12 由另一个程序激活窗口时显示的L I S Watch 对话框
/****************************************************************************** Module: LISWatch.cpp Notices: Copyright (c) 2000 Jeffrey Richter ******************************************************************************/ #include "..\CmnHdr.h" /* See Appendix A. */ #include#include #include "Resource.h" /// #define TIMER_DELAY (500) // Half a second UINT_PTR g_uTimerId = 1; DWORD g_dwThreadIdAttachTo = 0; // 0=System-wide; Non-zero=specifc thread /// BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) { chSETDLGICONS(hwnd, IDI_LISWATCH); // Update our contents periodically g_uTimerId = SetTimer(hwnd, g_uTimerId, TIMER_DELAY, NULL); // Make our window on top of all others SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); return(TRUE); } /// void Dlg_OnRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) { chMB("To monitor a specific thread, click the left mouse button in " "the main window and release it in the desired window.\n" "To monitor all threads, double-click the left mouse button in " "the main window."); } /// void Dlg_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) { // If we're attached to a thread, detach from it if (g_dwThreadIdAttachTo != 0) AttachThreadInput(GetCurrentThreadId(), g_dwThreadIdAttachTo, FALSE); // Set capture to ourself and change the mouse cursor SetCapture(hwnd); SetCursor(LoadCursor(GetModuleHandle(NULL), MAKEINTRESOURCE(IDC_EYES))); } /// void Dlg_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags) { if (GetCapture() == hwnd) { // If we had mouse capture set, get the ID of the thread that // created the window that is under the mouse cursor. POINT pt; pt.x = LOWORD(GetMessagePos()); pt.y = HIWORD(GetMessagePos()); ReleaseCapture(); g_dwThreadIdAttachTo = GetWindowThreadProcessId( ChildWindowFromPointEx(GetDesktopWindow(), pt, CWP_SKIPINVISIBLE), NULL); if (g_dwThreadIdAttachTo == GetCurrentThreadId()) { // The mouse button is released on one of our windows; // monitor local-input state on a system-wide basis g_dwThreadIdAttachTo = 0; } else { // The mouse button is released on a window that our thread didn't // create; monitor local input state for that thread only. AttachThreadInput(GetCurrentThreadId(), g_dwThreadIdAttachTo, TRUE); } } } /// static void CalcWndText(HWND hwnd, PTSTR szBuf, int nLen) { if (hwnd == (HWND) NULL) { lstrcpy(szBuf, TEXT("(no window)")); return; } if (!IsWindow(hwnd)) { lstrcpy(szBuf, TEXT("(invalid window)")); return; } TCHAR szClass[50], szCaption[50], szBufT[150]; GetClassName(hwnd, szClass, chDIMOF(szClass)); GetWindowText(hwnd, szCaption, chDIMOF(szCaption)); wsprintf(szBufT, TEXT("[%s] %s"), (PTSTR) szClass, (szCaption[0] == 0) ? (PTSTR) TEXT("(no caption)") : (PTSTR) szCaption); _tcsncpy(szBuf, szBufT, nLen - 1); szBuf[nLen - 1] = 0; // Force zero-terminated string } // void Dlg_OnTimer(HWND hwnd, UINT id) { TCHAR szBuf[100] = TEXT("System-wide"); HWND hwndForeground = GetForegroundWindow(); DWORD dwThreadIdAttachTo = g_dwThreadIdAttachTo; if (dwThreadIdAttachTo == 0) { // If monitoring local input state system-wide, attach our input // state to the thread that created the current foreground window. dwThreadIdAttachTo = GetWindowThreadProcessId(hwndForeground, NULL); AttachThreadInput(GetCurrentThreadId(), dwThreadIdAttachTo, TRUE); } else { wsprintf(szBuf, TEXT("0x%08x"), dwThreadIdAttachTo); } SetWindowText(GetDlgItem(hwnd, IDC_THREADID), szBuf); CalcWndText(GetFocus(), szBuf, chDIMOF(szBuf)); SetWindowText(GetDlgItem(hwnd, IDC_WNDFOCUS), szBuf); CalcWndText(GetActiveWindow(), szBuf, chDIMOF(szBuf)); SetWindowText(GetDlgItem(hwnd, IDC_WNDACTIVE), szBuf); CalcWndText(GetCapture(), szBuf, chDIMOF(szBuf)); SetWindowText(GetDlgItem(hwnd, IDC_WNDCAPTURE), szBuf); CalcWndText(hwndForeground, szBuf, chDIMOF(szBuf)); SetWindowText(GetDlgItem(hwnd, IDC_WNDFOREGRND), szBuf); if (g_dwThreadIdAttachTo == 0) { // If monitoring local input state system-wide, detach our input // state from the thread that created the current foreground window. AttachThreadInput(GetCurrentThreadId(), dwThreadIdAttachTo, FALSE); } } /// void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case IDCANCEL: EndDialog(hwnd, id); break; } } /// INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog); chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand); chHANDLE_DLGMSG(hwnd, WM_TIMER, Dlg_OnTimer); chHANDLE_DLGMSG(hwnd, WM_RBUTTONDOWN, Dlg_OnRButtonDown); chHANDLE_DLGMSG(hwnd, WM_LBUTTONDOWN, Dlg_OnLButtonDown); chHANDLE_DLGMSG(hwnd, WM_LBUTTONUP, Dlg_OnLButtonUp); } return(FALSE); } /// int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) { DialogBox(hinstExe, MAKEINTRESOURCE(IDD_LISWATCH), NULL, Dlg_Proc); return(0); } End of File //
//Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS / // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" / #undef APSTUDIO_READONLY_SYMBOLS / // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED / // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED / // // Dialog // IDD_LISWATCH DIALOG DISCARDABLE 32768, 5, 240, 41 STYLE WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "LISWatch" FONT 8, "MS Sans Serif" BEGIN LTEXT "Thread:",IDC_STATIC,4,0,31,8 LTEXT "ThreadId",IDC_THREADID,36,0,100,8 LTEXT "Focus:",IDC_STATIC,4,8,31,8 LTEXT "Focus window",IDC_WNDFOCUS,36,8,204,8 LTEXT "Active:",IDC_STATIC,4,16,31,8 LTEXT "Active window",IDC_WNDACTIVE,36,16,204,8 LTEXT "Capture:",IDC_STATIC,4,24,31,8 LTEXT "Capture window",IDC_WNDCAPTURE,36,24,204,8 LTEXT "Foregrnd:",IDC_STATIC,4,32,31,8 LTEXT "Foreground window",IDC_WNDFOREGRND,36,32,204,8 LTEXT "HELP: Click right mouse button",IDC_STATIC,140,0,99,8 END / // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_LISWATCH ICON DISCARDABLE "LISWatch.ico" / // // Cursor // IDC_EYES CURSOR DISCARDABLE "Eyes.cur" / // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_LISWATCH, DIALOG BEGIN RIGHTMARGIN, 190 BOTTOMMARGIN, 10 END END #endif // APSTUDIO_INVOKED #endif // English (U.S.) resources / #ifndef APSTUDIO_INVOKED / // // Generated from the TEXTINCLUDE 3 resource. // / #endif // not APSTUDIO_INVOKED
CZVC编程网出品,一剑[QQ:28077188]整理编译,欢迎联系 MSN:[email protected]