Сделай сам: IE и Delphi – вместе веселее!

Сегодня мы займёмся самым популярным web-браузером в мире – Internet Explorer’ом. Кто-то обожает эту программу, кто-то терпеть её не может, но, как ни крути, пока что ни один конкурент IE не может приблизиться к нему по возможностям (не считая, конечно, браузеров, построенных на движке IE). Итак, что мы будем делать с IE? Да ничего страшного… Мы просто научимся управлять им так же, как и любым другим окном. Для закрепления полученных знаний сделаем программу для управления IE при помощи горячих клавиш. Для пущего эффекта добавим иконку программы на Системную панель (SysTray). Итак, поехали…
Первым делом разберёмся с двумя приёмами программирования, которые мы будем использовать и в последующих статьях данного цикла:
1) Как работать с «горячими» клавишами?
2) Как добавить свой значок на Системную Панель (SysTray, можно узнать по характерным для неё часикам)?
На первый вопрос я уже дал частичный ответ в статье Сделай сам: «Вскрывалка паролей», но нам нужно копнуть немного глубже, а второй вопрос мы ещё вообще не затрагивали.

Работа с «горячими» клавишами
Для работы с «горячими» клавишами вполне достаточно использования лишь двух функций:
1) Регистрирует «горячие» клавиши:
BOOL RegisterHotKey(
    HWND hWnd, // этому окну придёт уведомление о нажатии комбинации клавиш
    int id, // идентификатор «горячих» клавиш
    UINT fsModifiers, // должны ли быть нажаты клавиши Ctrl, Shift, Alt или Win
    UINT vk // код клавиши, на которую мы будем реагировать
);
2) Удаляет «горячие» клавиши
BOOL UnregisterHotKey(
    HWND hWnd, // окно, ассоциированное с «горячими» клавишами
    int id // идентификатор «горячих» клавиш
);
  

Всё бы ничего, да вот при работе с «горячими» клавишами из DLL (динамически подключаемой библиотеки) могут возникнуть проблемы с идентификаторами. Чтобы этого избежать следует использовать функцию GlobalAddAtom, которая возвратит нам уникальный идентификатор (атом) требуемого формата. После завершения работы с «горячими» клавишами атом нужно удалить при помощи функции GlobalDeleteAtom. Работу с атомами мы рассмотрим в одной из следующих статей, где будем использовать DLL.
Помещение своей иконки в SysTray
Бывают такие программы, которые постоянно должны быть активны в системе. Они довольно часто помещают свой значок на Системную панель, чтобы не надоедать пользователю присутствием своего окна и в то же время оставить возможность «достучаться» до программы. Для управления иконкой на Системной панели используется функция
Shell_NotifyIcon(dwMessage: DWORD; lpData: PNotifyIconData): BOOL;
dwMessage – сообщение, которое мы посылаем Панели задач, может принимать следующие значения: NIM_ADD – добавить новую иконку, NIM_DELETE – удалить иконку, NIM_MODIFY – изменить иконку.
lpData – указатель на структуру NotifyIconData, которая состоит из следующих полей:
cbSize: DWORD;
Wnd: HWND;
uID: UINT;
uFlags: UINT;
uCallbackMessage: UINT;
hIcon: HICON;
szTip: array [0..63] of AnsiChar;
Пояснения:
cbSize – размер структуры;
Wnd – идентификатор окна, которое будет получать сообщения, ассоциированные с иконкой;
uID – идентификатор иконки, который мы сами ей назначаем;
uFlags – комбинация из трёх флагов, которая обозначает, какие поля структуры мы хотим заполнить: NIF_ICON (хотим заполнить поле hIcon), NIF_MESSAGE (поле uCallbackMessage), NIF_TIP (поле szTip).
uCallbackMessage – определяемый нами идентификатор сообщения, ассоциированного с иконкой;
hIcon – идентификатор иконки;
szTip – текст подсказки к иконке, которая появляется при наведении на неё курсора мышки.
Пример использования функции Shell_NotifyIcon вы найдёте в коде нашей сегодняшней программы.
Настала пора приступить к созданию программы. Она будет выполнять две полезные функции:
1) Закрытие всех окон IE;
2) Переход «Вперёд\Назад» в коллекции некоторых объектов. Всё дело в том, что часто при просмотре какого-либо форума или галереи картинок, ссылки имеют вид «http://anysite/forum.php?page=2». Т.е мы находимся на второй странице форума. Если мы заменим цифру на ‘1’, то попадём на предыдущую страницу, на ‘3’ – на следующую. Примерно по такому принципу мы организуем Переход «Вперёд\Назад».
Закрытие всех окон IE
Предлагаю два способа реализации функции закрытия всех окон IE.
Способ I
Последовательно ищем окна Internet Explorer’a и закрываем их:
procedure CloseAllIE_1;
var
  ie: HWND;
begin
  //ищем окно IE
  ie := FindWindow(‘IEFrame’, nil);
  //пока найдено окно IE…
  while (ie<>0) do
  begin
    //…закрываем его
    postmessage(ie, WM_CLOSE, 0, 0);
    //ищем следующее
    ie := FindWindow(‘IEFrame’, nil);
  end;
end;
Способ II
Перебираем все родительские окна в системе и, если увидели окно IE, то закрываем его. Таким образом, мы совершаем только один проход по окнам, а значит, работает этот способ намного быстрее предыдущего.
procedure CloseAllIE_2;
//эта функция будет применяться к каждому окну
functionCloseIE (Wnd: HWND): boolean; stdcall;
var
  winclass: array [0..255] of char;
begin
  //получаем класс окна
  GetClassName (Wnd, WinClass, sizeof(WinClass));
  //если у нас окно IE, то закрываем его
  if (WinClass=’IEFrame’) then
  PostMessage (Wnd, WM_CLOSE, 0, 0);
  //принимаем следующее окно
  CloseIE := true;
end;
begin
//перебираем все родительские окна, указывая,
//какой функцией будем их обрабатывать
  EnumWindows(@CloseIE, 0);
end;
Переход «Вперёд\Назад»
Разделим нашу задачу на три составляющие:
1) Получить адрес (URL) текущей странички IE;
2) Увеличить (уменьшить) последнее число адреса на единицу;
3) Записать изменённый адрес в окно IE и заставить браузер перейти по нему.
Пункт 1: получить адрес текущей странички
Окно IE, как и любое порядочное окно, содержит множество дочерних окон (всякие там кнопочки, поля ввода, надписи и т.д.). В одном из таких окон содержится URL странички. После недолгого изучения иерархии окон в IE при помощи специальной программы, добраться до адреса совсем несложно. Следующая функция возвращает идентификатор поля ввода (класс ‘Edit’), в котором содержится URL:
function FindEdit(var h:hwnd):boolean;
var
  wclass: array[0..255] of char;
begin
  //получаем идентификатор и класс «верхнего» окна
  h := getforegroundwindow;
  getclassname(h, wclass, sizeof(wclass));
  //если это окно IE, то «распутываем» иерархию дочерних окон
  if (wclass=’IEFrame’) then
  begin
    h := findwindowex(h, 0, ‘WorkerA’, nil);
    h := findwindowex(h, 0, ‘ReBarWindow32’, nil);
    h := findwindowex(h, 0, ‘ComboBoxEx32’, nil);
    h := findwindowex(h, 0, ‘ComboBox’, nil);
    h := findwindowex(h, 0, ‘Edit’, nil);
    result := true;
  end else
    result := false;
end;
После того, как мы добрались до поля ввода, в котором хранится адрес странички, извлечём его при помощи следующей функции:
function GetText(WindowHandle: hwnd): String;
var
  txtLength: Integer;
  buffer: String;
begin
  //Узнаём длину текста
  TxtLength := SendMessage(WindowHandle, WM_GETTEXTLENGTH, 0, 0);
  if (txtlength>0) then
  begin
    txtlength := txtlength + 1;
    setlength (buffer, txtlength);
    //записываем текст окна в buffer
    sendmessage(WindowHandle, wm_gettext, txtlength, longint(@buffer[1]));
    result := buffer;
  end else result := »;
end;

Таким образом, чтобы узнать текущий адрес в IE, достаточно применить эти две функции:

FindEdit(h);
Adress := GetText(h);

Пункт 2: Увеличить (уменьшить) последнее число адреса на единицу
Здесь нам потребуется только умение оперировать со строками и знание основ арифметики.
//увеличение последнего числа в строке s
function IncURL(s:string): String;
const
  num = [‘0’..’9′];
var
  i,  j, cnt: word;
  plus, ch: byte;
  code: integer;
  s1: string;
begin
  i := length(s);
  plus := 0;
  //ищем первую цифру с конца
  while (i>0) and not(s[i] in num) do
    dec(i);
  //если нашли…
  if (i<>0) then
  begin
    //считаем количество девяток, которыми кончается число
    cnt := 0;
    while (i>0) and (s[i]=’9′) do
    begin
      dec(i);
      inc(cnt);
    end;
    if (i=0) or not( s[i] in num) then
    begin
      ch := 0;
    {если число сплошь состоит из девяток, то мы должны первой цифрой сделать ‘1’ и заменить все девятки на нули}
      if not(s[i] in num) then
        plus := 1;
    end else
      val(s[i], ch, code);
    inc(ch);
    str(ch, s1);
    if (cnt>0) then
      //заменяем последние девятки на нули
      for j := 1 to cnt do
        s1 := s1 + ‘0’;
    //составляем итоговую строку
    s := copy(s, 1, i-1+plus)+s1+copy(s, i+cnt+1, length(s)-i-cnt);
  end;
  result := s;
end;

//уменьшение последнего числа в строке s
function DecURL(s:string):string;
const
  num = [‘0’..’9′];
var
  i, j, cnt: word;
  minus, ch: byte;
  code: integer;
  s1: string;
begin
  i := length(s);
  minus := 0;
  //ищем последнюю цифру в строке
  while (i>0) and not (s[i] in num) do
    dec(i);
  if (i<>0) then
  begin
    cnt := 0;
    //считаем количество нулей
    while (i>0) and(s[i]=’0′) do
    begin
      dec(i);
      inc(cnt);
    end;
    if (i>0) and (s[i] in num) then
    begin
      val(s[i],ch,code);
      dec(ch);
      if (ch=0) and (i>0) and not(s[i-1] in num) then
        s1 := »
      else
        str(ch, s1);
      if (cnt>0) then
        //заменяем последние нули на девятки
        for j := 1 to cnt do
          s1 := s1 + ‘9’;
      //составляем итоговый URL
      s := copy(s, 1, i-1-minus) + s1 + copy(s, i+cnt+1, length(s)-i-cnt);
    end;
  end;
  result := s;
end;

Пункт 3: записать изменённый адрес в окно IE и заставить браузер перейти по нему.
Для выполнения третьей части задачи нужно просто послать полю ввода два сообщения:

//»вводим» изменённый адрес
SendMessage(h, WM_SETTEXT, 0, longint(Pchar(s)));
//»нажимаем» клавишу «Ввод» (Enter)
SendMessage(h, WM_KEYDOWN, VK_RETURN, 0);

Теперь соберём эти три пункта в нужные нам процедуры:
//переход «Вперёд»
procedure NextPage;
var
  s: String;
  h: HWnd;
begin
  if FindEdit(h) then
  begin
    //получаем URL
    s := gettext(h);
    delete(s, length(s), 1);
    //увеличиваем последнее число на единицу
    s := IncURL(s);
    //записываем новый URL
    SendMessage(h, WM_SETTEXT, 0, longint(Pchar(s)));
    //нажимаем клавишу «Ввод»
    SendMessage(h, WM_KEYDOWN, VK_RETURN, 0);
  end;
end;

//переход «Вперёд»
procedure PrevPage;
var
  s: String;
  h: HWnd;
begin
  if FindEdit(h) then
  begin
    s := string(gettext(h));
    delete(s, length(s), 1);
    s := DecURL(s);
    SendMessage(h, WM_SETTEXT, 0, longint(Pchar(s)));
    SendMessage(h, WM_KEYDOWN, VK_RETURN, 0);
  end;
end;

Все основные процедуры нашей программы готовы, теперь настало время сделать для них оболочку. Создайте новый проект и поместите на форму один-единственный компонент – PopupMenu1:TPopupMenu1. В нём должен быть один пункт – «Выход». Процедура при выборе этого пункта:
procedure TForm1.ExitClick(Sender: TObject);
begin
  //завершаем работу программы
  halt;
end;

Добавьте к списку подключаемых модулей (раздел Uses) модуль ShellApi. Объявите константу

const
  WM_ICONTRAY = wm_user + 1;

Этим мы определили сообщение, которое будем получать от иконки в SysTray.
В раздел Private добавьте три строчки:

//информация о нашей иконке
TrayIconData: TNotifyIconData;
//эта процедура будет обрабатывать поступающие от иконки сообщения
procedure TrayMessage (var Msg: TMessage); message WM_ICONTRAY;
//процедура обработки нажатия «горячих» клавиш
procedure hotykey(var msg:TWMHotkey); message WM_HOTKEY;

Запишите процедуру для события OnCreate формы:
procedure TForm1.FormCreate(Sender: TObject);
begin
  //заполняем информацию об иконке
  with TrayIconData do
  begin
    cbSize := SizeOf(TrayIconData);
    Wnd := Handle;
    uID := 0;
    uFlags := NIF_MESSAGE + NIF_ICON + NIF_TIP;
    uCallbackMessage := WM_ICONTRAY;
    hIcon := application.icon.handle;
    szTip := ‘My TrayIcon!’;
  end;
  //добавляем иконку в SysTray
  Shell_NotifyIcon(NIM_ADD, @TrayIconData);
  //регистрируем «горячие» клавиши Alt+P
  RegisterHotKey(handle, 1,.MOD_Alt, 80);
  //регистрируем «горячие» клавиши Alt+Q
  RegisterHotKey(handle, 2,.MOD_Alt, 81);
  //регистрируем «горячие» клавиши Alt+R
  RegisterHotKey(handle, 3,.MOD_Alt, 82);
end;

Теперь процедура при уничтожении формы:
procedure TForm1.FormDestroy(Sender: TObject);
begin
  //удаляем иконку
  ShellApi.Shell_NotifyIcon(NIM_DELETE, @TrayIconData);
  //удаляем «горячие» клавиши
  UnRegisterHotKey(handle, 1);
  UnRegisterHotKey(handle, 2);
  UnRegisterHotKey(handle, 3);
end;

//обработка сообщений от иконки
procedure TForm1.TrayMessage(var Msg: TMessage);
var
  p: TPoint;
begin
  case Msg.lParam of
  //если нажата правая кнопка мыши,
    WM_RBUTTONDown, WM_RBUTTONDBLCLK:
    begin
      SetForegroundWindow(handle);
      GetCursorPos(p);
      //то показываем наше меню в текущей позиции курсора
      popupmenu1.Popup(p.x,p.y);
    end;
  end;
end;

//нажатие на «горячие» клавиши
procedure TForm1.hotykey(var msg:TWMHotkey);
var
  n: Integer;
begin
  n := msg.HotKey;
  //если нажали Alt+P, то закрываем все окна IE
  if (n=1) then CloseAllIE_2;
  else
    //если нажали Alt+Q, то переход «Вперёд»
    if (n=2) then NextPage
    else
      //если нажали Alt+R, то переход «Назад»
      if (n=3) then PrevPage;
end;

На этом создание программы, которая расширяет функциональность браузера Internet Explorer, завершено. Мы научились:
1) работать с «горячими» клавишами;
2) помещать иконку на Системную панель;
3) работать с сообщениями и окнами.
На сегодня всё.

Иван Ширко
ishyrko@gmail.com

2 ответа на “Сделай сам: IE и Delphi – вместе веселее!”

  1. Неплохо что существует такой способ управления браузерами. Это может стать нужным в определенных случаях.

Обсуждение закрыто.