Часть
2. Использование API в VB.
|
||||||
11.04.2005 | ||||||
Окна.
|
||||||
Поиск
окна по заголовку. Координаты курсора мыши.
Определение хендла окна по точке и заголовку. Структура POINTAPI. |
||||||
В прошлом параграфе
мы добрались до окна нашего проекта - формы, просто использовав свойство .hWnd,
которое выложило нам на тарелечке хендл окна. Но что делать, если нам надо добраться
до чужого окна? Давайте продолжим исследование окон.
Давайте пытаемся найти окно и сбить, т.е. я хотел сказать, произведем с ним
какие-то действия. Для этого сделаем себе учебную цель. Создадим новый exe-проект,
изменим свойство формы Form1.Caption="F-16 Fighter" и откомпилируем
(то бишь создадим исполняемый файл exe). Запустим наш проект и пусть полученное
окно болтается на Десктопе до поры.
Теперь создадим новый exe-проект. Для наших экспериментов нам потребуются четыре кнопки Command, один CheckBox, один таймер Timer1 и пять текстбоксов, причем для Text1 желательно включить свойства Multiline=True и ScrollBars=2 (Вертикаль).
В форме объявим
две переменные, которые будут у нас использоваться для: HandleWin - хранения
хендла (чего делать нельзя, я имею ввиду хранить нельзя, это мы отметим ниже
особо) и KillWin - куда будут возвращать свои значения функции (хотел
ее назвать KillBill, потом KillGates, но перепутал и стало лень менять. Но тоже
по существу).
Option Explicit
Dim HandleWin As Long
Dim KillWin As Long
Примечание:
При работе с API использование Option Explicit уже не просто рекомендовано,
а крайне необходимо и сугубо обязательно! Во всех модулях и формах.
Далее, в процедуре загрузки формы очистим текстбоксы, в для Text2 присвоим значение
нашего учебного окна "F-16 Fighter". Кроме того установим интервал
таймера и выключим его (он пока не нужен).
Еще примечание: Да, ты уж извини,
но я не буду писать свойство .Text для текстбоксов. Пора уже отвыкать есть руками,
раз уж полезли в API. Свойста, являющиеся главными для объектов понимаются по
умолчанию и их явно писать не обязательно(.Text для текстбоксов, .Capture для
лейблов и т. д.)
Private
Sub
Form_Load()
Text1 = ""
Text2 = "F-16 Fighter"
Text3 = ""
Text4 = ""
Text5 = ""
Timer1.Interval = 100
Timer1 = False
End Sub
Попробуем получить
хендл окна по его (окна естественно) заголовку. Заголовок - это все то, что
выводится в синенькой верхней полосочке почти в каждом окне. Для нашего окна-мишени
это - Form1.Caption = "F-16 Fighter". Используем функцию FindWindow.
Давай добавим в проект стандартный модуль и объявим ее (все что пишется в модуле
я буду выделять коричневым цветом):
Public
Declare Function FindWindow Lib "user32" Alias "FindWindowA"
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Как мы видим, в этой функции
есть два аргумента, по которым можно найти окно: по имени класса (lpClassName)
и по имени заголовка
(lpWindowName).
Как правило окно ищется по одному какому-нибудь аргументу. Поскольку мы не знаем
имя класса искомого окна (да и вообще не знаем, что это такое), а знаем заголовок,
то его и надо подставить в функцию вместо. Но и второе значение (lpClassName)
нельзя пускать на
самотек. Ему надо передать ноль (в API всегда надо чего-то передавать). Но аргумент
этот имеет строковый тип, поэтому нужно использовать константу vbNullString.
Заголовок окна мы передадим в Text2, и использование функции будет выглядеть
очень просто:
HandleWin
= FindWindow(vbNullString, Text2)
В переменную HandleWin мы
получим искомый хендл искомого окна. Напишем теперь процедуру:
Private
Sub
Command1_Click()
HandleWin = FindWindow(vbNullString, Text2)
Text1 = Text1 & "Хендл окна с именем " & Text2 & "
" & HandleWin & vbCrLf
Text3 = HandleWin
End Sub
Нажимая на кнопку Command1,
мы получаем хендл того окна, чей заголовок прописан в Text2. При этом в Text1
будет выводится общая информация о найденом окне.
Теперь попробуем произвести кое-какие манипуляции с окном по его хендлу: уничтожить,
свернуть и развернуть. Для этого используются следующие функции
DestroyWindow - убивает окно. В Windows XP эта функция не работает. Она
в общем-то и в Win98 тоже не хочет убивать окно. Ну я решил, что и черт с ней.
Но пусть остается для примера.
CloseWindow - сворачивает окно
OpenIcon - восстанавливает свернутое окно
Их объявление совершенно идентично и смысл их использования сводится лишь к
подстановке вместо аргумента переменной, содержащей хендл. Объявим их в модуле:
Public
Declare Function DestroyWindow& Lib "user32" (ByVal hwnd As Long)
Public Declare Function CloseWindow Lib "user32" (ByVal hwnd As Long)
As Long
Public Declare Function OpenIcon Lib "user32" (ByVal hwnd As Long)
As Long
Обрати внимание, что первая
функия объявлена с применением значка типа & вместо As Long
в конце. Такое объявление ничем не отличается по смыслу и работе от двух других.
Теперь пропишем каждую в свои процедуры соответствующих кнопок Command2, Command3
и Command4. Кроме того, мы еще и проанализируем результат действия каждой функции
по возвращаемому значению (если ноль, то действие не удалось) и выведем соответствующее
сообщения в Text1:
Private
Sub
Command2_Click()
KillWin = DestroyWindow&(HandleWin)
If KillWin = 0 Then
Text1 = Text1 & " Убийство окна " & HandleWin & "
не удалось!" & vbCrLf
Else
Text1 = Text1 & " Мы убили окно " & HandleWin & vbCrLf
End If
End Sub
Private Sub Command3_Click()
KillWin = CloseWindow(HandleWin)
If KillWin = 0 Then
Text1 = Text1 & " Свернуть окно " & HandleWin & "
не удалось!" & vbCrLf
Else
Text1 = Text1 & " Мы свернули окно " & HandleWin & vbCrLf
End If
End Sub
Private Sub Command4_Click()
KillWin = OpenIcon(HandleWin)
If KillWin = 0 Then
Text1 = Text1 & " Восстановить окно " & HandleWin & "
не удалось!" & vbCrLf
Else
Text1 = Text1 & " Мы восстановили окно " & HandleWin &
vbCrLf
End If
End Sub
Теперь я еще
раз объясню, почему так делать нельзя. В процедуре Command1 мы нашли и сохранили
в переменной HandleWin хендл окна. А используем его совершенно в другой процедуре
по другому событию Command_Click. Между двумя нажатиями пользователем на кнопки
может произойти черт знает что. Если ты определишь хендл , а затем закроешь
и снова откроешь программку "F-16 Fighter", то ее окно будет иметь
уже совершенно иной хендл и сделать с ним ничего будет нельзя, до тех пор, пока
не определишь его новый хендл. Поэтому действия с окнами должны производится
сразу же после определения хендла. Но я сделал это специально, чтобы для простоты
разделить функции по процедурам и иметь показательный отрицательный пример под
рукой.
Далее мы сделаем так, чтобы
указав на окно курсором мыши мы могли посмотреть (не получить для использования,
а именно посмотреть), какой у окна хендл и какой у него заголовок. Реализуется
это с помощью функции WindowFromPoint, которая возвращает хендл окна
по точке на этом окне. Точкой этой будут координаты курсора мыши. Получить их
можно с помощью функции GetCursorPos. Вот с нее и начнем. Точнее начнем
с процедуры, которая будет включать весь этот наш механизм. И процедура эта
будет Check1_Click. CheckBox в данном случае удобнее кнопки, так как имеет два
положения: включено и выключено. Именно здесь мы будем включать и выключать
таймер. Зачем он нам нужен? А нужен он для того, чтобы не пытаться найти событие,
которое происходит при наведении мыши на окно. Так как в нашем проекте его и
не происходит. А таймер исправно , через каждые 100 миллисекунд будет нам включать
наш механизм определения хендла окна, на котором находится курсор мыши.
Итак начнем. Короткая процедура для включения-выключения таймера:
Private
Sub
Check1_Click()
If Check1 = 0 Then
Timer1 = False
If Check1 = 1 Then
Timer1 = True
End Sub
И теперь самое интересное
- процедура самого таймера.
Private Sub
Timer1_Timer()
'объявим тройку нужных нам переменных
Dim PosCur As Long
Dim DlinaTexta As Long
Dim WindowCaption As String
В модуле объявим функцию для определения координат мыши:
Public
Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As
Long
И опааа, видим всего один
аргумент, в то время координат должно быть две. А дело в том, что lpPoint представляет
структуру POINTAPI, которая содержит как раз эти две координаты. Структура -
это по-сути тот же пользовательский тип данных, который мы разбирали в
Главе 17 первой части самоучителя, только задан жестко и никаких здесь вольностей
быть не должно. Поэтому в модуле объявляем тип POINTAPI так как это требуется
для API-функций:
Type
POINTAPI
X As Long
Y As Long
End Type
и затем объявляем какую-нибудь
переменную с типом POINTAPI, например MouseCoordinat:
Public
MouseCoordinat As POINTAPI
Вот теперь, возвращаемся
к нашей процедуре таймера в форме и получаем, наконец, координаты мыши:
PosCur = GetCursorPos(MouseCoordinat)
'записываем координаты в текстбоксы для большей
наглядности
Text4 = MouseCoordinat.X
Text5 = MouseCoordinat.Y
Теперь нам надо по этим координатам
получить хендл окна. Объявим в модуле функцию:
Public
Declare Function WindowFromPoint Lib "user32" (ByVal xPoint As Long,
ByVal yPoint As Long) As Long
Ну здесь все просто, как
апельсин. Два аргумента - две координаты, что еще нужно джигиту, чтобы встретить
старость. Пишем в нашей процедуре:
HandleWin = WindowFromPoint(MouseCoordinat.X,
MouseCoordinat.Y)
Text3 = HandleWin
Теперь у нас в переменной
HandleWin есть хендл и еще по нему мы можем узнать заголовок экрана с помощью
функции GetWindowText. Объявим ее в модуле:
Public
Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA"
(ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Ну с первым аргументом (hwnd)
- хендлом понятно, мы его имеем. Второй аргумент (lpString),
это как раз то, что мы хотим получить, т.е. заголовок , а третий (cch)
- длина этого самого заголовка. Поскольку мы этой длины не знаем, то ставим
по-максимуму - 255 (вряд ли заголовок будет более 256 символов). А вот что касается
второго аргумента, тут дело несколько сложнее. Если мы подставим просто нашу
переменную WindowCaption, которую мы и объявили для заголовка окна
DlinaTexta = GetWindowText(HandleWin,
WindowCaption, 255)
то это результатов не даст.
Дело в том, что строковые аргументы API-функций требуют, чтобы для них был в
памяти зарезервирован буфер размером не меньше строки, которую мы хотим получить.
А для этого надо передать в функцию строку пробелов, соответствующую размеру
буфера. Короче, смысл в том , что мы присваиваем переменной строку пробелов,
а вместо нее получаем строку данных. И если строка пробелов длинее , например
"____________", чем строка данных "123", то мы получим "123_____________",
т.е. данные плюс лишние пробелы, а если строка пробелов короче, то данные усекутся
по длине строки пробелов. Исходя из этих мудреных рассуждений возвращаемся опять
к нашей процедуре и резервируем буфер (на самом деле просто присваиваем переменной
WindowCaption строку пробелов по-длинее
WindowCaption =
Space(256)
и теперь используем функцию
по-правильному
DlinaTexta = GetWindowText(HandleWin,
WindowCaption, 255)
Раз у нас есть длина заголовка
(а это то, что возвратила функция), мы можем отсечь ненужные нам пробелы и вывести
даныые в текстбокс
Text2 = Left(WindowCaption,
DlinaTexta)
End Sub
На этом пока все. В это параграфе
мы разобрали технику использования API. Теперь ты можещь, двигая мышью узнавать
хендл любых окон и их заголовки (если есть). Причем на собственной форме ты
увидишь содержимое текстов, названия кнопок и т.п. Исходник примера можно скачать
вверху страницы.
|
Copyright
|