Блог | О ПК
Управление окнамиДанная стстья посвящена описанию программных методов управления окнами. Прочитав статью вы научитесь управлять окнами форм с помощью их дескрипторов, выполнять управление "чужим" приложением, осуществлять перебор окон верхнего уровня, сворачивать окна всех приложений, а также работать с окном DOS.
На этой странице вы найдёте следующую информацию:
Управление окнами форм с помощью их дескрипторов
Дочерние окна, управление "чужим" приложением
Перебор окон верхнего уровня, свёртывание окон всех приложений
Досуп к окну DOS
Управление окнами форм с помощью их дескрипторов
Обычно на компьютере в любой момент открыто множество окон форм различных приложений. Каждое окно формы в свою очередь обычно содержит окна размещённых в нём оконных компонентов: панелей, окон редактирования и т.п.
Доступ к любым окнам может осуществляться через их дескрипторы. У каждого оконного компонента имеется свойство Handle. Это свойство является дескриптором окна. Позднее мы увидим, как можно получить дескриптор любого окна, открытого в данный момент в Windows. Но пока для тестирования рассмотренных далее функций приведу оператор, который определяет дескриптор стандартной программы Windows Калькулятор:
HWND hWnd = FindWindow("SciCalc", "Калькулятор");
Если Калькулятор в данный момент выполняется на компьютере, то приведённый оператор запишет в переменную hWnd дескриптор окна этой программы. Если же нет уверенности, что Калькулятор выполняется, имеет смысл заменить приведённый оператор следующим кодом:
HWND hWnd;
if(! (HWND = FindWindow("SciCalc", "Калькулятор")))
{
WinExec("Calc", SW_SHOWNA);
hWnd = FindWindow("SciCalc", "Калькулятор");
}
Если Калькулятор в данный момент не выполняется, то функция FindWindow возвращает нуль. В этом случае Калькулятор вызывается функцией FindWindow. после чего в переменную hWnd заносится требуемый дескриптор.
Приведённых операторов достаточно, чтобы вы могли тестировать рассмотренные далее функции. Но если вы хотите выбрать для тестирования не программу Калькулятор, а, например C++Builder, то дескриптор можете определить оператором:
hWnd = FindWindow("TAppBuilder", NULL);
Далее мы рассмотрим ряд функций, позволяющих управлять окном, заданным его дескриптором. Но, конечно, самое гибкое управление осуществляется посылкой окну сообщений Windows функцией SendMessage:
LRESULT SendMessage(IN HWND hWnd, IN UINT Msg,
IN WPARAM wParam, IN LPARAM lParam);
Параметр Msg указывает тип посылаемого сообщения, а параметры wParam и lParam - соответствующие параметры сообщения. В последующих подразделах эта функция будет постоянно использоваться.
Зная дескриптор, можно получить доступ к некоторым характеристикам окна. Функция GetWindowText позволяет получить текст окна. Функция объявлена следующим образом:
int GetWindowText(IN HWND hWnd, OUT LPSTR lpString,
IN int nMaxCount);
Параметр hWnd задаёт дескриптор окна, параметр lpString является указателем на буфер, в который функция заносит текст, а параметр nMaxCount определяет размер этого буфера. Если текст превышает указанный размер, он усекается. При успешном выполнении функция заносит в бувер текст и возвращает число занесённых символов, не учитывая завершающего нулевого символа. Под текстом окна подразумевается или текст заголовка для окон форм, или текст окна редактирования, или имя панели и т.п. Если окно не имеет текста, или текст пустой, или указан ошибочный дескриптор, функция возвращает нуль.
Например, если вы записали в переменную hWnd дескриптор какого-то окна, то следующий код отобразит в метке Label1 его текст:
char buf[256];
GetWindowText(hWnd, buf, sizeof(buf));
Label1->Caption = buf;
Если дескриптор hWnd относится к окну какой-то формы (например, получен приведённым ранее оператором для программы Калькулятор), то в метке отобразится текст заголовка окна. А если дескриптор получен, например, оператором
HWND hWnd = Edit1->Handle;
то в метке отобразится текст соответствующего окна редактирования.
Перед использованием функции GetWindowText можно вызвать функцию GetWindowTextLength:
int GetWindowTextLength(IN HWND hWnd);
Эта функция возвращает в случае успешного выполнения число символов текста, которое можно использовать для выделения буфера достаточного размера перед вызовом функции GetWindowText.
Функция SetWindowText:
BOOL SetWindowText(IN HWND hWnd, IN LPCSTR lpString);
позволяет изменить текст окна. Например:
SetWindowText(hWnd, "Это мой текст");
Функции GetWindowText и SetWindowText иногда не дают ожидаемого вами результата, поскольку для некоторых окон редактирования (например, для Memo, а иногда и для Edit) понятия текст окна и действительное содержимое окна могут различаться. Поэтому, если вам требуется получить или изменить именно содержимое окна, то лучше использовать сообщения Windows WM_GETTEXT и WM_SETTEXT соответственно. Тогда в приведённых выше примерах вызов функции GetWindowText можно заменить оператором
SendMessage(hWnd, WM_GETTEXT, sizeof(buf), (LPARAM)(LPCTSTR)buf);
а вызов функции SetWindowText можно заменить оператором
SendMessage(hWnd, WM_SETTEXT, 0, (LPARAM)(LPCTSTR)"Это мой текст");
Первый из приведённых операторов занесёт в буфер buf текст окна, а второй - запишет в окно строку "Это мой текст".
Для окон редактирования, включая окна Memo и RichEdit, сообщения WM_GETTEXT и WM_SETTEXT работают с полными текстами окон, пока их размер не превышает 64К. Для текстов большего размера в окнах RichEdit надо использовать сообщения EM_GETSELTEXT, EM_STREAMOUT, EM_STREAMIN. В выпадающих списках сообщения WM_GETTEXT и WM_SETTEXT работают с текстом в окне редактирования списка. В кнопках - с надписями на кнопках.
Имеются функции GetWindowPlacement и SetWindowPlacement, которые позволяют соответственно получить и установить характеристики, определяющие местоположение окна формы. Они объявлены следующим образом:
BOOL GetWindowPlacement(IN HWND hWnd,
OUT WINDOWPLACEMENT *lpwndpl);
BOOL SetWindowPlacement(IN HWND hWnd,
IN CONST WINDOWPLACEMENT *lpwndpl);
Параметр lpwndpl является указателем на структуру типа WINDOWPLACEMENT:
typedef struct tagWINDOWPLACEMENT {
UINT length;
UINT flags;
UINT showCmd;
POINT ptMinPosition;
POINT ptMaxPosition;
RECT rcNormalPosition;
#endif
} WINDOEPLACEMENT;
typedef WINDOWPLACEMENT *PWINDOWPLACEMENT, *LPWINDOWPLACEMENT;
Прле length содержит размер структуры в байтах. Так что перед вызовом описываемых функций значение этого поля надо задать равным sizeof(WINDOWPLACEMENT). Поле flags при вызове функции GetWindowPlacement всегда равно нулю, а при вызове функции SetWindowPlacement задаёт флаги, определяющие положение окна в свёрнутом виде и метод его последующего восстановления. Данное поле может содержать один или два следующих флага:
WPF_RESTORETOMAXIMIZED | Флаг указывает, что после восстановления окно должно быть развёрнуто, независимо от того, в каком состоянии оно было до свёртывания. Установка этого флага влияет только в случае, ели окно свёрнуто (showCmd = SW_SHOWMINIMIZED), и только на одно очередное восстановление. Флаг не изменяет поведение окна при последующих восстановлениях. |
WPF_SETMINPOSITION | Флаг указывает, что координаты свёрнутого окна могут быть заданы полем ptMinPosition. |
Поле showCmd показывает или устанавливает состояние окна. Чаще всего используются значения SW_SHOWNORMAL или SW_RESTORE - окно активизируется и восстанавливается в своей нормальной позиции и нормальных размерах, SW_HIDE - окно делается невидимым, SW_MINIMIZE - окно свёртывается.
При вызове функции GetWindowPlacement поле showCmd может принимать только значения и - развёрнутое состояние, SH_SHOWMINIMIZED - свёрнутое, SW_SHOWNORMAL - нормальное.
Поля ptMinPosition, ptMaxPosition и rcNormalPosition определяют координаты левого верхнего окна соответственно в свёрнутом, развёрнутом и нормальном состоянии.
При успешном завершении функции GetWindowPlacement и SetWindowPlacement возвращают ненулевое значение, в случае ошибки - 0.
Приведу пример использования рассмотренных функций. пример чисто демонстрационный. Следующий код обеспечит изменение положения левой границы окна программы Калькулятор, что приведёт к изменению его ширины:
HWND hWnd = FindWindow("SciCalc", "Калькулятор");
WINDOWPLACEMENT WP;
WP.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(hWnd, &WP);
for(int i=0; i<=100; i++)
{
WP.rcNormalPosition.left += 1;
SetWindowPlacement(hWnd, &WP);
}
В этом примере изменяется положение только левой границы окна. Если же заменить оператор, задающий значение поля rcNormalPosition, оператором:
WP.rcNormalPosition = Rect(WP.rcNormalPosition.left + 1,
WP.rcNormalPosition.top,
WP.rcNormalPosition.right + 1,
WP.rcNormalPosition.bottom);
то будет осуществляться сдвиг окна без изменения его размера.
Управлять состоянием окна можно, задавая значение поля showCmd. Например, если перед вызовом функции SetWindowPlacement выполнить оператор
WP.showCmd = SW_SHOWMINIMIZED;
то окно свернётся.
Функция SetWindowPlacement позволяет одновременно изменять несколько характеристик окна. Для изменения только состояния окна удобнее использовать функцию ShowWindow:
BOOL ShowWindow(IN HWND hWnd, IN int nCmdShow);
Параметр nCmdShow может принимать те же значения, что и описанное ранее поле showCmd структуры WINDOWPLACEMENT. Правда, определено ещё одно значение - SW_SHOWDEFAULT, означающее состояние по умолчанию, заданное структурой STARTUPINFO, использованной в функции CreateProcess при создании процесса, связанного с данным окном.
Например, свернуть окно можно оператором:
ShowWindow(hWnd, SW_SHOWMINIMIZED);
Как видите, это намного проще, чем использование функции SetWindowPlacement, требующее предварительного объявления и заполнения структуры WINDOWPLACEMENT. Впрочем, ещё проще для свёртывания окна воспользоваться функцией CloseWindow:
CloseWindow(hWnd);
Перемещать окно тоже удобнее не функцией SetWindowPlacement, а функцией MoveMindow:
BOOL MoveWindow(IN HWND hWnd, IN int X, IN int Y,
IN int nWidth, IN int nHeight,
IN BOOL bRepaint);
Параметр X, Y, nWidth, nHeight определяют соответственно координаты левого верхнего угла, длину и ширину окна. Параметр bRepaint определяет, перерисовывается ли окно во время перемещения: при значении true перерисовывается.
Текущее положение и размер окна можно получить функцией GetWindowRect:
BOOL GetWindowRect(IN HWND hWnd, OUT LPRECT lpRect);
Ниже приведён пример совместного применения функций GetWindowRect и MoveWindow для сдвига окна:
RECT R;
GetWindowRect(hWnd, &R);
for(int i=0; i<=100; i++)
MoveWindow(hWnd, R.left + i, R.top, R.right - R.left,
R.bottom - R.top, true);
Функция GetClassName позволяет определить класс окна:
int GetClassName(IN HWND hWnd, OUT LPSTR lpClassName,
IN int nMaxCount);
Функция заносит в буфер lpClassName размера nMaxCount имя класса и возвращает число занесённых символов, или 0, если произошла ошибка. Если окно создано в C++Builder, то класс окна - это соответствующий класс C++Builder. Если же это программа, которая создавалась не в C++Builder, то имена классов будут иными.
Дочерние окна, управление "чужим" приложением
Некоторые из окон Windows являются дочерними - т.е. помещаются в клиентской области другого, родительского окна. Это, прежде всего, окна дочерних компонентов, помещённых на формах. Но могут существовать и окна вторичных форм приложения, для которых в их свойстве Parent указана в качестве родительского компонента другая форма. Если известен дескриптор некоторого окна, то получить дескриптор его родительского окна можно функцией GetParent:
HWND GetParent(IN HWND hWnd);
Функция возвращает дескриптор окна, родительского по отношению к окну с дескриптором hWnd. Если родительского окна нет, возвращается NULL. Так что если, например, объявить следующие переменные hWnd и H:
HWND hWnd, H;
и присвоить переменной hWnd дескриптор какого-то окна, то добраться до родительского окна можно оператором:
while(H = GetParent(hWnd))hWnd = H;
Если окно с дескриптором hWnd не имеет родительского окна, приведённый цикл не сработает ни разу. А если родительское окно имеется, то в результате выполнения цикла в переменную hWnd будет занесён дескриптор самого верхнего окна в этой иерархии. Можете опробовать функцию GetParent, поместив, например, на свою форму панель, на панель окно Edit1, и выполнив код:
char buf[256];
HWND hWnd = Edit1->Handle, H;
while(H = GetParent(hWnd)) hWnd = H;
GetWindowText(hWnd, buf, sizeof(buf));
Label1->Caption = buf;
Окна, не имеющие родительского окна, называются окнами верхнего уровня (top-level). Функция GetParent возвращает для них NULL. Но если окно не имеет родительского, это ещё не значит, что данное окно является главным окном своего приложения. приложение может открывать множество самостоятельных окно, среди которых одно - главное. Не редко требуется определить, к какому приложению относится то или иное окно. Приложение обычно является владельцем (owner) своих вторичных окон. Определить владельца окна можно функцией GetWindow, в которую в качестве первого аргумента передаётся дескриптор окна, а в качестве второго аргумента записывается константа GW_OWNER. Так что, имея дескриптор hWnd какого-то окна, можно получить информацию об этом окне и его владельце, например, следующим кодом:
char buf[256], buf2[256], buf3[256];
GetWindowText(HWnd, buf, sizeof(buf));
GetClassName(HWnd, buf2, sizeof(buf2));
HWND H = GetWindow(HWnd, GW_OWNER);
GetWindowText(H, buf3, sizeof(buf3));
Form1->Label1->Caption = "окно" + AnsiString(buf) +
", класс " + AnsiString(buf2) +
", приложение " + AnsiString(buf3));
Описанные функции позволяют перемещаться вверх по иерархии окон. Теперь посмотрим, как можно перемещаться по иерархии вниз. Перебрать все дочерние окна некоторого родительского окна можно с помощью функции EnumChildWindows:
BOOL EnumChildWindows(IN HWND hWndParent,
IN WNDENUMPROC lpEnumFunc,
IN LPARAM lParam);
Эта функция переберает все дочерние окна того окна, дескриптор которого задан параметром hWndParent. Найдя дескриптор очередного дочернего окна, функция посылает его в качестве первого параметра в написанную пользователем функцию обработки - так называемую функцию обратного вызова (callback function). Адрес этой пользовательской функции указывается параметром lpEnumFunc. Третий параметр lParam функции EnumChildWindows передаётся в качестве второго параметра в пользовательскую функцию. Его пользователь может использовать по своему усмотрению. Пользовательская функция должна принимать два параметра: дескриптор родительского окна и переданный в неё параметр lParam. она должна возвращать true, если требуется продолжение перебора, или false, если перебор следует остановить. В C++Builder такая функция с двумя параметрами в вызове EnumChildWindows должна приводиться явным образом к типу WNDENUMPROC.
Рассмотрим пример. Пусть в вашем приложении имеется окно Memo1, и вы хотите занести в Memo1 список всех дочерних оконных компонентов какого-то окна с указанием их классов. Например, вы хотите получить список дочерних компонентов окна программы Калькулятор. Напришем для этого приложение с названием ChildWind1. Окно этого приложения приведено на рисунке №1 (а). Чтобы можно было сопоставить результат с окном программы Калькулятор, на рисунке №1 (б) приведено окно этой программы.
Рисунок №1 - Приложение (а), формирующее список дочерних окон и меню окна программы Калькулятор (б)
Ниже приведён код, обеспечивающий получение списка дочерних окон (левая часть рисунка №1 (а)).
char buf[256], buf2[256];
bool __stdcall EnumChild(HWND hWind, LPARAM lParam)
{
SendMessage(hWind, WM_GETTEXT, sizeof(buf), (LPARAM)(LPCTSTR)buf);
GetClassName(hWind,buf2,sizeof(buf2));
Form1->Memo1->Lines->Add(AnsiString(buf) +
" - класс " + AnsiString(buf2));
return true;
}
void __fastcall TForm1::FormCreate(TObject *Sender)
{
HWND hWnd;
// получение дескриптора окна
if(! (hWnd = FindWindow("SciCalc","Калькулятор")))
{
WinExec("Calc", SW_SHOWNA);
hWnd = FindWindow("SciCalc","Калькулятор");
}
// получение списка дочерних окон
EnumChildWindows(hWnd, (WNDENUMPROC)EnumChild, 0);
...
}
Функция EnumChild - это функция обратного вызова, обрабатывающая передаваемый в неё дескриптор дочернего окна hWind. Сначала с помощью сообщения WM_GETTEXT в буфер buf заносится текст окна. Затем функцией GetClassName в буфер buf2 заносится имя класса окна. Строка, содержащая текст окна и имя класса записывается в окно Memo1. В заключение функция возвращает true, что обеспечивает продолжение перебора дочерних окон. Если бы нам надо было не перебирать все окна, а найти окно с каким-то заданным текстом, то, получив дескриптор такого окна, мы могли бы вернуть false. Тогда дальнейший перебор прекратился бы.
Функция FormCreate является обработчиком события OnCreate формы. Первые операторы этой функции определяют дескриптор окна Калькулятора. Эти операторы были рассмотрены в подразделе "Управление окнами форм с помощью их дескриптров". А последний из приведённых операторов вызывает функцию EnumChildWindows. Первым её параметром указан найденный дескриптор окна Калькулятора. В качестве второго параметра указано имя описанной выше функции обратного вызова EnumChild. Тип этой функции явным образом приводится к типу WNDENUMPROC, который предусмотрен описанием функции EnumChildWindows. Последний параметр задан равным нулю. После того как функция EnumChildWindows вызвана, она перебирает все дочерние компоненты и для каждого вызывает функцию EnumChild.
В данном примере последний аргумент в вызове EnumChildWindows равен нулю. Но в качестве этого параметра можно было бы указать какую-то дополнительную информацию, которую надо передать в функцию EnumChild. Пусть, например, задача заключается не в том, чтобы перебрать все дочерние окна, а в том, чтобы найти окно с заданным текстомили окно заданного класса. Например, вам надо найти окно редактора, которое, как можно видеть на рисунке №1(а), имеет класс Edit. Эту задачу можно было бы решить так. Ввести глобальные переменные:
AnsiString S = "Edit1";
HWND hEdit;
В функции FormCreate заменить вызов EnumChildWindows следующим оператором:
EnumChildWindows(hWnd, (WNDENUMPROC)EnumChild, (LPARAM)&S);
В качестве последнего аргумента в EnumChildWindows передаётся адрес строки, в которой записано имя класса искомого окна. Адрес явным образом приводится к типу LPARAM. В функцию EnumChild перед заключетельным оператором надо вставить код:
if(AnsiString(buf2) == *(AnsiString *)lParam)
{
hEdit = hWind;
return false;
}
Если класс окна совпадает с заданным, то в глобальную переменную hEdit передаётся дескриптор этого окна, и функция возвращает false, прерывая дальнейший перебор окон.
Мы рассмотрели получение дескрипторов всех дочерних окон. Далее, если требуется, можно посылать этим окнам сообщения Windows, и таим образом управлять кнопками и окнами внешнего приложения. Немного позже мы рассмотрим подобный пример. Но для полного контроля над вашим приложением надо ещё получить доступ к его меню. Дескриптор полосы главного меню окна возвращает функция GetMenu:
HMENU GetMenu(IN HWND hWnd);
Единственный аргумент, передаваемый в эту функцию - дескриптор окна hWnd. Например, следующий оператор вернёт дескриптор главного меню окна, заданного его дескриптором hWnd:
HMENU hMenu = GetMenu(hWnd);
Определив дескриптор меню, можно узнать число его разделов (выпадающих меню) функцией GetMenuItemCount:
int GetMenuItemCount(IN HMENU hMenu);
Функция GetMenuString позволяет получить тексты разделов:
int GetMenuString(IN HMENU hMenu, IN UINT uIDItem,
OUT LPSTR lpString, IN int nMaxCount,
IN UINT uFlag);
Параметр hMenu является дескриптором меню. Параметры lpString и nMaxCount определяют соответственно буфер, в который будет записан текст, и его размер. Параметры uIDItem и uFlag определяют раздел, текст которого надо найти. Если uFlag = MF_BYPOSITION, то uIDItem - позиция раздела. Первый раздел имеет позицию 0, второй - 1 и т.д. Если uFlag = MF_BYCOMMAND, то uIDItem - команда. Функция GetMenuString возвращает число символов, скопированных в буфер, или 0 в случае ошибки.
Надо отметить, что рассмотренная функция GetMenuString сейчас считается устаревшей. Вместо неё рекомендуется использовать функцию GetMenuItemInfo:
BOOL GetMenuItemInfo(IN HMENU hMenu, IN UINT uItem,
IN BOOL fByPosition,
IN OUT LPMENUITEMINFO lpmii);
Параметр uItem эквивалентен uIDItem в функции GetMenuString. Параметр fByPosition должен равняться true, если uItem указывает позицию раздела. А параметр lpmii является указателем на структуру типа MENUITEMINFO, в которую записывается информация о разделе. Эта структура содержит не только текст раздела, но и множество другой информации. Но в наших примерах нам не будет требоваться ничего, кроме текста раздела. Так что для простоты будем далее испльзовать функцию GetMenuString.
Функция GetSubMenu позволяет получить дескриптор выпадающего меню:
HMENU GetSubMenu(IN HMENU hMenu, IN int nPos);
Параметр nPos указывает позицию раздела, который соответствует выпадающему меню. Если в данном разделе выпадающего меню нет, возвращается 0.
Теперь мы можем реализовать вторую часть приложения ChildWind1, обеспечивающую отображение в окне Memo2 (справа на рисунке №1(б)). состав меню программы Калькулятор. Приведённый ниже код записан в функции FormCreate вместо многоточия, которое было в приведённом ранее листинге.
// получение списка разделов меню
HMENU HM = GetMenu(hWnd);
HMENU SubM = NULL;
int CountM = GetMenuItemCount(HM);
int CountS;
for(int i=0; i<CountM; i++)
{
GetMenuString(HM, i, buf, sizeof(buf), MF_BYPOSITION);
Memo2->Lines->Add(buf);
if(SubM = GetSubMenu(HM, i))
{
CounsS = GetMenuItemCount(SubM);
for(int k=0; k<CountS; k++)
{
GetMenuString(SubM, k, buf, sizeof(buf), MF_BYPOSITION);
Memo2->Lines->Add(" " + AnsiString(buf));
}
}
}
Вряд ли этот код требует особых комментариев. В нём сначала определяется функцией GetMenu дескриптор главного меню HM. В переменную CountM записывается число разделов меню, полученное функцией GetMenuItemCount. Затем следует цикл по разделам. Функция GetMenuString даёт текст раздела. Функция GetSubMenu проверяет, имеется ли в разделе выпадающее меню. Если имеется, то его дескриптор заносится в переменную SubM, и организуется цикл по разделам выпадающего меню.
Теперь мы умеем получать доступ ко всем дочерним компонентам окна и к разделам его меню. так что можно получить полный контроль над окном внешнего приложения. В качестве примера создадим проект с названием ChildWind. Окно приложения показано на рисунке №2(а). В левом списке ListBox1 перечислены все разделы меню программы Калькулятор, а в правом списке ListBox2 перечислены кнопки этой программы. Заполнение этих двух списков производится автоматически. Более того, поскольку программа Калькулятор может работать в двух режимах - обычный вид и инженерный (рисунок №2(б)), то при переключении режима автоматически изменяются списки меню и кнопок. В списке кнопок, как вы можете видеть, имеются не только обычные кнопки, но и радиокнопки ("Градусы", "Радианы" и др.), а также индикаторы ("Inv").
Двойной щелчёк на любой строке списков эквивалентен щелчку на соответствующей кнопке Калькулятора или на соответствующем разделе меню. Текст, появляющийся при этом в окне Калькулятора, читается в окно Edit1 вверху формы приложения. А цифры вводимые в окно редактирования приложения, автоматически передаются в окно Калькулятора. Таким образом, из окна приложения можно выполнять любые действия, доступные Калькулятору.
Рисунок №2 - Приложение (а), полностью контролирующее программу Калькулятор (б)
Ниже приведён код этого приложения.
TList *MList = new TList;
TList *BList = new TList;
HWND hWnd, hEgit;
char buf[256];
//---------------------------------------------------------------------------
bool __stdcall EnumChild(HWND hWind, LPARAM lParam)
{
HWND *P;
char buf2[256];
GetClassName(hWind,buf2,sizeof(buf2));
if(AnsiString(buf2) == "Button")
{
SendMessage(hWind, WM_GETTEXT, sizeof(buf), (LPARAM)(LPCTSTR)buf);
if(buf[0] != 0)
{
P = (HWND *)malloc(sizeof(HWND));
*P = hWind;
BList->Add(P);
Form1->ListBox2->Items->Add(buf);
}
}
else if(AnsiString(buf2) == "Edit")
{
hEgit = hWind;
SendMessage(hEgit, WM_GETTEXT, sizeof(buf), (LPARAM)(LPCTSTR)buf);
Form1->Edit1->Text = buf;
}
return true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
if(! (hWnd = FindWindow("SciCalc","Калькулятор")))
{
WinExec("Calc", SW_SHOWNA);
hWnd = FindWindow("SciCalc","Калькулятор");
}
HMENU HM = GetMenu(hWnd);
HMENU SubM = NULL;
int CountM = GetMenuItemCount(HM);
int CountS;
UINT *P;
char *t;
// очистка списков
for(int i = 0; i < MList->Count; i++)
free(MList->Items[i]);
for(int i = 0; i < BList->Count; i++)
free(BList->Items[i]);
MList->Clear();
BList->Clear();
ListBox1->Clear();
ListBox2->Clear();
for(int i = 0; i < CountM; i++)
{
if(SubM = GetSubMenu(HM, i))
{
CountS = GetMenuItemCount(SubM);
for(int k = 0; k < CountS; k++)
{
GetMenuString(SubM, k, buf, sizeof(buf), MF_BYPOSITION);
if(buf[0] != 0)
{
P = (UINT *)malloc(sizeof(UINT));
*P = GetMenuItemID(SubM, k);
MList->Add(P);
// удаление амперсанда
if(t = strchr(buf, '&'))
{
*t = 0;
strcat(buf, t + 1);
}
if(t = strchr(buf, '\t')) *t = 0;
ListBox1->Items->Add(buf);
}
}
}
}
EnumChildWindows(hWnd, (WNDENUMPROC)EnumChild, 0);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ListBox2DblClick(TObject *Sender)
{
SendMessage(*(HWND *)BList->Items[ListBox2->ItemIndex],
WM_LBUTTONDOWN, 0, 0);
SendMessage(*(HWND *)BList->Items[ListBox2->ItemIndex],
WM_LBUTTONUP, 0, 0);
SendMessage(hEgit, WM_GETTEXT, sizeof(buf),
(LPARAM)(LPCTSTR)buf);
Edit1->Text = buf;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Edit1Change(TObject *Sender)
{
SendMessage(hEgit, WM_SETTEXT, 0, (LPARAM)(LPCTSTR)Edit1->Text.c_str());
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ListBox1DblClick(TObject *Sender)
{
SendMessage(hWnd, WM_COMMAND,
*(UINT *)MList->Items[ListBox1->ItemIndex], 0);
if((ListBox1->Items->Strings[ListBox1->ItemIndex] == "Инженерный") ||
(ListBox1->Items->Strings[ListBox1->ItemIndex] == "Обычный"))
FormCreate(Sender);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
for(int i = 0; i < MList->Count; i++)
free(MList->Items[i]);
delete MList;
for(int i = 0; i < BList->Count; i++)
free(BList->Items[i]);
delete BList;
}
//---------------------------------------------------------------------------
Рассмотрим приведённый код. Глобальные переменные MList и BList - списки типа TList, в которых будут храниться соответственно идентификаторы разделов меню и дескрипторы кнопок. Переменные hWnd и hEgit предназначены для хранения дескрипторов окна формы и окна редактирования.
Функция EnumChild является функцией обратного вызова при переборе дочерних окон. Если окно имеет класс Button, то с помощью сообщения WM_GETTEXT читается его текст. Если строка текста не пустая, то функцией malloc выделяется память для хранениядескриптора окна. Указатель на это место памяти заносится в список BList, а текст заносится в список ListBox2. Для окна класса Edit выполняются другие операции: дескриптор сохраняется в переменной hEdit, а текст читается и заносится в окно Edit1 приложения.
Функция FormCreate является обработчиком события OnCreate формы. Эта же функция вызывается, как вы увидите далее, при необходимости обновить списки разделов меню и кнопок. В начале функции, как уже не раз рассматривалось, производится определение дескриптора окна Калькулятора. Затем производится очистка списков. В частности, освобождается память, отведённая под хранение всех объектов, на которые указывают элементы списков MList и BList. Конечно, в первый момент при запуске приложения это не требуется и не производится, если списки пустые. Но при повторных вызовах этой функции освобождать память необходимо.
Затем следует уже рассмотренный ранее перебор разделов меню. При этом проверяется, имеется ли текст в очередном разделе. Это позволяет исключить включение в списки разделителей, которые имеются в меню.
Для последующего обращения к разделу меню требуется знать его идентификатор. Этот идентификатор может быть получен функцией GetMenuItemID:
UINT GetMenuItemID(IN HMENU hMenu, IN int nPos);
Параметр hMenu является дескриптором меню, а параметр nPos указывает позицию раздела в этом меню. Возвращаемый этой функцией идентификатор каждого раздела, имеющего текст, помещается в динамически распределяемую память, а указатель на него добавляется в список MList. Из текста раздела удаляется символ амперсанда, а также обрезается конец строки, начинающийся с символа табуляции и содержащий указание горячих клавиш. Отредактированный текст заносится в список ListBox1. После формирования списков разделов меню вызывается функция EnumChildWindows, обеспечивающая совместно с рассмотренной ранее функцией EnumChild формирование списков кнопок.
Функция ListBox1DblClick является обработчиком двойного щелчка на строке списка ListBox1. Сначала разделу меню, текст которого находится в выделеной строке списка ListBox1, посылается сообщение WM_COMMAND. Это сообщение направляется разделу меню, указанному идентификатором, и вызывает выполнение команды, связанной с разделом. Указатель на идентификатор раздела меню извлекается из списка MList по индексу ListBox1->ItemIndex, поскольку индексы списков MList и ListBox1 согласованы друг с другом. Этот указатель приводится к типу указателя на UINT и разыменовывается. После отправки сообщения проверяется, не является ли раздел разделом Инженерный или Обычный. Дело в том, что при выборе этих разделов вид окна Калькулятора изменяется (си. рисунок №1(б) и рисунок №2(б)). Соответственно меняется и состав меню, и состав кнопок. Значит, надо обновить все списки. Для этого вызывается описанная ранее функция FormCreate.
Функция ListBox2DblClick является обработчиком двойного щелчка на строке списка ListBox2. Дескрипор кнопки, выделенной в списке ListBox2, определяется примерно так же, как описано выше при определении идентификатора раздела меню. Кнопке посылаются сообщения WM_LBUTTONDOWN и WM_LBUTTONUP, вызывающие нажатие и отпускание кнопки. Затем окну редактирования программы Калькулятор посылается сообщение WM_GETTEXT, чтобы прочитать текст окна, изменившийся после срабатывания кнопки. Этот текст заносится в окно Edit1 приложения.
Функция Edit1Change является обработчиком события OnChange окна Edit1. С помощью сообщения WM_SETTEXT текст этого окна пересылается в окно редактирования программы Калькулятор. Функция FormDestroy является обработчиком события OnDestroy формы. Эта функция очищает память, выделенную ранее под списки MList и BList.
Описанное приложение можно, конечно, усовершенствовать. Вместо списков ListBox1 и ListBox2 было бы удобнее создавать в приложении автоматически соответствующие меню и кнопки. Всё выглядило бы красивее. Но я не стал усложнять код, чтобы было более понятно основное - получение списков меню и кнопок и управление с их помощью внешним приложением.
Надо также отметить, что созданное приложение ориентировано на управление программой Калькулятор, но в действительности является универсальным. Достаточно изменить в функции FormCreate вызов FindWindow, и можно управлять любым внешним приложением. Так что если вы решите развить это приложение, то укажу не некоторые его недостатки, которые можно достаточно легко устранить. При работе с меню предполагается, что все разделы главного меню являются головными разделами выпадающих меню. Если это может быть не так, то нетрудно добавить в раздел else к оператору if(SubM = GetSubMenu(HM, i)), в котором занести в списки соответствующий раздел главного меню. В приложении полагается также, что все разделы выпадающих меню связаны с конкретными командами, а не являются в свою очередь головными разделами других выпадающих меню. Устранить этот недостаток можно, добавив в список разделов ещё один вложенный цикл по разделам таких каскадных меню, если они обнаружатся.
Ещё одним недостатком, сужающим возможности описанного приложения, является то, что в нём окно, для которого производится поиск дочерних компонентов, предполагается окном формы. Если приложение содержит несколько окон, то надо добавить ещё циклы по этим окнам. Думаю, что если подобная задача у вас возникнет, то сказанного в данной статье будет достаточно, чтобы вы с нею справились.
Перебор окон верхнего уровня, свёртывание окон всех приложений
Параметр lpClassName указывает на строку, содержащую имя класса. Параметр lpWindowName указывает на строку, содержащую имя окна. Если этот параметр равен NULL, то считается, что под критерий поиска подходит любое окно указанного класса. Если поиск прошёл успешно, то функция возвращает дескриптор окна, имеющего указанное имя класса и имя окна. В противном случае возвращается NULL.
Эту функцию легко использовать, если вы знаете имя класса искомого окна. В предыдущих статьях она уже не раз использовалась для получения дескриптора окна программы Калькулятор. При этом указывалось имя класса этого окна - SciCalc. Откуда можно получить имя этого класса?
Одна из возможностей узнать имя класса какого-то приложения - воспользоваться поставляемой вместе с C++Builder программой WinSight32 (файл...\Bin\ws32.exe). Запустите интересующее вас приложение, затем запустите WinSight32, выполните команду Spy->Find Window, и вы увидите список всех окон, зарегистрированных в данный момент в Windows. Лучше, чтобы в этот момент у вас было открыто не очень много окон, чтобы проще было найти среди них нужное.
В списке, который вы увидите, для кождого окна будут указаны среди прочей информации имя класса в фигурных скобках "{}" и заголовок окна - последний элемент данных в строке каждого окна. например, запустив Калькулятор, вы можете с помощью WinSight32 найти, что имя класса окна этого приложения - "SciCalc".
Другой способ найти дескриптор окна - воспользоваться функцией GetNextWindow:
HWND GetNextWindow(hWnd hWnd, unsigned int wCmd);
Она определяет дескриптор следующего или предыдущего окна в Z-последовательности. Параметр hWnd - дескриптор окна, от которого начинается отсчёт. Параметр wCmd определяет направление поиска. Если wCmd = GW_HWNDNEXT, то ищется следующее окно, находящееся ниже. Если wCmd = GW_HWNDPREV, то ищется предыдущее окно, находящееся выше.
Следующее окно в Z-последовательности - это то, которое вызывалось из указанного или к которому пользователь обращался после создания указанного окна. Если указано дочернее окно, то поиск ведётся среди дочерних окон.
Если искомое окно найдено, то возвращается его дескриптор. Если следующего или предыдущего окна нет (в зависимомти от значения wCmd), то возвращается 0.
С помощью этой функции можно решить задачу определения дескриптора окна выполняемого приложения, например, программы Калькулятор. Ниже приведён соответствующий код:
HWBD H=Handle;
char Pch[128];
do
{
H = GetNextWindow(H, GW_HWNDNEXT);
GetWindowText(H, Pch, 128);
if(!CompareText(Pch, "Калькулятор"))
break;
}while (H != NULL);
if(H != NULL)
...
Первый выполняемый оператор присваивает переменной H значение свойства Handle формы вашего приложения. Далее в цикле просматриваются окна, лежащие ниже в Z-последовательности, и среди них ищется окно с текстом "Калькулятор". Для этого используется функция CompareText, сравнивающая без учёта регистра строку, на которую указывает Pch, со строкой "Калькулятор". Если строки совпадают, функция CompareText возвращает нуль, и цикл просмотра прерывается.
Окно Калькулятора будет найдено, если оно получало фокус после запуска вашего приложения. Если цикл завершается со значением H=NULL, значит приложение Калькулятор в данный момент не открыто.
Рассмотренная функция GetNextWindow, как мы видели, позволяет организовать перебор окон. Однако для этих целей более удобна функция EnumWindows, объявленная в файле Winuser.h следующим образом:
BOOL EnumWindows(IN WNDENUMPROC lpEnumFunc, IN LPARAM lParam);
Функция перебирает все окна верхнего уровня (см. об этом в подразделе "Дочерние окна, управление чужим приложением") и поочерёдно посылает их дескрипторы в качестве первого параметра в написанную пользователем функцию обратного вызова. Адрес этой пользовательской функции указывается параметром lpEnumFunc. Второй параметр lParam функции EnumWindow передаётся в качестве второго параметра в пользовательскую функцию. Его пользователь может использовать по своему усмотрению. Пользовательская функция должна принимать два параметра: дескриптор родительского окна и переданный в неё параметр lParam. Она должна возвращать true, если требуется продолжение, или false, если пребор следует остановить. С подобной функцией вы уже встречались в подразделе "Дочерние окна, управление чужим приложением". Напомню, что в C++Builder такаю функция с двумя параметрами в вызове EnumWindows должна приводиться явным образом к типу WNDENUMPROC.
Предположим, вы хотите по щелчку на кнопке Button1 вашего приложения получить в окне Memo1 список всех окон верхнего уровня, открытых в двнный момент в Windows. Это можно сделать следующим кодом.
bool __stdcall EnumProc1(HWND WinHandle, long Param)
{
char buf[256], buf2[256];
GetWindowText(WinHandle, buf, sizeof(buf));
GetClassName(WinHandle, buf2, sizeof(buf2));
Form1->Memo1->Lines->Add(Ansistring(buf) + " - класс " +
AnsiString(buf2));
return true;
}
// -------------------------------------------------------------------
void __fastcall TForm1^^ButtonClick(TObject *Sender)
{
EnumWindows((WNDENUMPROC)EnumProc1, 0);
}
Если перед вами стоит иная задача - свернуть окна всех приложений, открытых в данный момент, то в приведённом коде функции EnumProc1 можно все операторы, предшествующие оператору return, заменить одним оператором:
PostMessage(WinHandle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
Этот оператор посылает всем окнам сообщение WM_SYSCOMMAND с параметром SC_MINIMIZE - свернуться. В приведённом варианте кода сообщение о свёртывании посылается всем окнам верхнего уровня без разбора. Можно ввести предварительный анализ состояния окна, чтобы избежать подобной избыточности:
if((GetWindow(WinHandle, GW_OWNER)) // главное окно приложения
&& (!IsIconic(WinHandle)) // не свёрнуто
&& (IsWindowVisible(WinHandle))) // видимо
PostMessage(WinHandle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
Эта структура if, прежде всего, отбирает главные окна приложений. Далее проверяется, не свёрнуто ли окно. Для этого вызывается функция IsIconic, которая возвращает true, если окно свёрнуто. Затем проверяется, видимо ли окно: функция IsWindowCisible возвращает true, если стиль окна WS_VISIBLE - видимое (реально в данный момент оно может быть невидимо, если загорожено другими окнами). Только если окно прошло все эти проверки, ему посылается сообщение WM_SYSCOMMAND.
Приведённый код сворачивает окна всех приложений, включая окно собственного приложения. Иногда может потребоваться свернуть все окна, кроме окна данного приложения. Это можно достичь небольшой переделкой приведённой выше функции EnumProc:
bool __stdcall EnumProc1(HWND WinHandle, long Param)
{
if((GetWindow(WinHandle, GW_OWNER)) // главное окно приложения
&& (!IsIconic(WinHandle)) // не свёрнуто
&& (IsWindowVisible(WinHandle)) // видимо
&& (WinHandle!=Application->Handle)
&& (WinHandle!=Form1->Handle)))
PostMessage(WinHandle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
return true;
}
Две выделенные жирным шрифтом проверки отличают данный код от предыдущего. Они предотвращают свёртывание окна данной формы (предполагается, что это форма Form1) и свёртывание данного приложения (Application).
Выше была рассмотрена методика перебора окон, основанная на функции EnumWindows. Возможен и другой путь, основанный на рассмотренной ранее функции GetNextWindow. В файле winuser.h эта функция сделана идентичной функции GetWindow, так, что из приложения с равным успехом можно вызвать GetNextWindow, или GetWindow.
С помощью этой функции можно организовать свёртывание окон всех приложений следующим образом:
HWND WinHandle;
WinHandle = Handle;
while(WinHandle != 0)
{
WinHandle = GetNextWindow(WinHandle, GW_HWNDNEXT);
if((GetWindow(WinHandle, GW_OWNER)) // главное окно приложения
&& (!IsIconic(WinHandle)) // не свёрнуто
&& (IsWindowVisible(WinHandle))) // видимо
PostMessage(WinHandle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
}
Отличие от предыдущего примера заключается только в организации цикла по окнам. В качестве начального значения для поиска задаётся WinHandle = Handle. Затем окна перебираются функцией GetNextWindow. А проверка окон и их свёртывание осуществляется так же, как в первом примере.
Для того чтобы при таком подходе свернуть все окна, кроме окон данного приложения, достаточно заменить первый оператор, задающий начальное значение WinHandle, на следующий:
WinHandle = Application->Handle;
Видимыми останутся все окна текущего приложения, а все прочие будут свёрнуты.
В заключение рассмотрения функций доступа к окнам надо упомянуть ещё об одной функции - GetForegroundWindow:
HWND GetForegroundWindow(VOID);
Функция возвращает дескриптор окна, являющегося активным в данный момент. Этой функцией можно воспользоваться для построения приложения, осуществляющего мониторинг компьютера. Если периодически с помощью таймера Timer вызывать эту функцию, можно вести протокол: в какой момент с каким окном и приложением работает пользователь.
Доступ к окну DOS
Дескриптор окна выполняющейся программы DOS можно получит функцией FindWindow, которая уже не раз использовалась нами. Но в неё надо передать имя класса окна, дескриптор которого требуется получить. А имя класса окна DOS зависит от версии Windows. Для Windows 9.x это имя "tty", а для Windows NT/200/Xp/7 имя класса "ConsoleWindowClass". так что дескриптор окна DOS можно получит следующим кодом:
AnsiString ClName;
HWND H;
if(GetVersion() <0x80000000)
ClName = "ConsoleWindowClass";
else ClName = "tty";
H = FindWindow(ClName.c_str(), NULL);
Сначала функцией GetVersion (см. статью "Определение версии Windows") проверяется версия Windows и в переменную ClName заносится соответствующее имя класса. А затем функцией FindWindow получается дескриптор окна. Приведённый код находит дескриптор окна DOS, независимо от его заголовка. Если на компьютере открыто только одно такое окно, в переменную H занесётся его дескриптор. Если нет ни одного открытого окна DOS, переменная H будет равна 0. А если есть вероятность, что открыто несколько окон DOS, то вторым параметром в вызов FindWindow надо передать строку, содержащую заголовок окна.
После того как получендескриптор окна, его можно использовать в любых функциях API Windows для управления этим окном. Например, оператор
CloseWindow(H);
свернёт это окно. А оператор
SendMessage(H, WM_CLOSE, 0, 0);
закроет его. Правда, в Windows NT/2000/XP/7 при этом, возможно, будет задан вопрос, закрывать ли его немедленно. В Windows 9.х окно закроется без всяких вопросов.
Операторы
char buf[127];
GetWindowText(H, buf, 128);
занесут в буфер buf заголовок окна DOS. А оператор
SetWindowText(H, "Окно управляется моим приложением");
изменит текст заголовка окна.
Оператор
EnableWindow(H, true);
восстановить доступность окна.
В окне DOS можно рисовать. Для этого надо получить функцией GetDC или GetWindowDC дескриптор контекста этого окна и далее можно рисовать на этом контексте, как на любом другом.
1879 визитов ↳ 0 ответов | Ваше мнение о материале | Голосовало: |