IT-Блог о программировании и операционных системах

Расширяем возможности Delphi / Builder IDE

В данной заметке, хочу поделиться с вами хорошим примером написания модуля, с помощью которого можно добавить новые возможности в вашу Delphi IDE.
Пример из этой статьи довольно простой, но между тем и очень эффективный. Он позволяет очень легко добавить обработчик события (назовём его обработчиком нажатия средней кнопки мыши), в среду Delphi, путём обработки сообщений Windows.
Пусть, для примера, в обработчике будет выполняться код, отвечающий за программное нажатие клавиши F12, тем самым позволяя нам переключаться между окнами исходного кода и дизайнера форм по средней кнопке мыши. Очень удобно, правда?
unit WinSwap;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms;

type
  TWinSwap = class(TObject)
    private
      fIDE:TApplication;
      procedure PressKey(Key:Word);
    protected
      procedure IDEMessage(var Msg: TMsg; var Handled: Boolean);
    public
      property IDE:TApplication read fIDE write fIDE;
  end;

var
  oSwaper: TWinSwap;

implementation

procedure SwapInit;
begin
  { связываем поле fIDE со средой Delphi }
  oSwaper.IDE := Application;
  { устанавливаем новый обработчик OnMessage }
  oSwaper.IDE.OnMessage := oSwaper.IDEMessage;
end;

{ TWinSwap }
procedure TWinSwap.IDEMessage(var Msg: TMsg; var Handled: Boolean);
begin
  if IDE.Active then
    begin
      if Msg.Message = WM_MBUTTONUP then
      PressKey(VK_F12);
    end
  else
    Exit;
end;

procedure TWinSwap.PressKey(Key: Word);
begin
  keybd_event(Key,0,KEYEVENTF_EXTENDEDKEY,0);
  keybd_event(Key,0,KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP,0);
end;

initialization
  oSwaper := TWinSwap.Create;
  { инициализируем расширение }
  SwapInit;

finalization
  { скидываем обработчик }
  oSwaper.IDE.OnMessage := nil;
  oSwaper.Free;
end.
Как видите, код не самый сложный и разобраться в нём не составит никакого труда. Ключевые строки располагаются в процедуре SwapInit. Происходит присвоение значения объекта Application (которое рассматривается как объект самой среды) полю fIDE типа TApplication и затем устанавливаем новый обработчик OnMessage уже самой IDE.
Таким образом можно обрабатывать многие полезные сообщения, приходящие из вне. Можно работать с отдельными частями IDE как с простыми компонентами, средствами объекта Application.  Вообщем можно много всего, главное приложить немного фантазии, которой у меня к сожалению не много.

Установка

Установка такого модуля сводится к обычной инсталляции нового компонента в новый, или существующий пакет. Другими словами, установка такого модуля и нового компонента ничем не отличается.
Надеюсь, этот пример был полезен для вас. С наилучшими пожеланиями. Читайте новые интересные заметки на страницах блога Delphi Day.

Правильная версия


Если вы прочитаете комментарии ниже, поймете, что данная версия кода мягко говоря не очень правильная. Как я там написал, что:


Если написать ещё один подобный (компонент?, даже не знаю как правильно назвать) модуль. То при его инициализации, обработчик OnMessage как бы перекроет обработчики всех остальных, инсталлированых до него, что собственно одно и тоже (с небольшими поправками), о чём говорил USoft.

Я немного дополнил код, следуя совету USoft. Проверял на 10ти таких установленных в систему расширений. Теперь всё работает прекрасно. Испытания проводил в двух средах: D7, D2k6.
Хотел бы отдельно поблагодарить Алексея Тимохина за расшифровку совета USoft и USoft'a, за дельный совет. Спасибо, коллеги.
 Итак, правильная версия скорее всего должна быть такая:



unit WinSwap;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms;

type
  TIDEHandler = procedure (var Msg:TMsg; var Handled: Boolean) of object;
  TWinSwap = class(TObject)
    private
      fIDE:TApplication;
      fIDEHandler:TIDEHandler;
      procedure PressKey(Key:Word);
    protected
      procedure IDEMessage(var Msg: TMsg; var Handled: Boolean);
      procedure BuffHandler;
    public
      property IDE:TApplication read fIDE write fIDE;
      property Handler:TIDEHandler read fIDEHandler write fIDEHandler;
  end;

var
  oSwaper: TWinSwap;

implementation

procedure SwapInit;
begin
  { связываем поле fIDE со средой Delphi }
  oSwaper.IDE := Application;
  oSwaper.BuffHandler;
  { устанавливаем новый обработчик OnMessage }
  oSwaper.IDE.OnMessage := oSwaper.IDEMessage;
end;

{ TWinSwap }
procedure TWinSwap.BuffHandler;
begin
  Handler := oSwaper.IDE.OnMessage;
end;

procedure TWinSwap.IDEMessage(var Msg: TMsg; var Handled: Boolean);
begin
  if IDE.Active then
    begin
      if Msg.Message = WM_MBUTTONUP then
        PressKey(VK_F12);
      Handler(Msg,Handled);
    end
  else
    begin
      Handler(Msg,Handled);
      Exit;
    end;
end;

procedure TWinSwap.PressKey(Key: Word);
begin
  keybd_event(Key,0,KEYEVENTF_EXTENDEDKEY,0);
  keybd_event(Key,0,KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP,0);
end;

initialization
  oSwaper := TWinSwap.Create;
  { инициализируем расширение }
  SwapInit;

finalization
  { скидываем обработчик }
  oSwaper.IDE.OnMessage := nil;
  oSwaper.Free;
end.

6 комментариев:

  1. хороший пример, но с ошибкой
    нухно отрабатывать обработчик OnMessage тот который уже есть у Tapplication

    для этого нужно объявить процедуру of object
    забуферить туда метод OnMessage а потом уже переписывать его, а в обработчике вызывать забуференный onMessage иначе будет куча проблем с различными экспертам типа CnPack

    ОтветитьУдалить
  2. Спасибо за комментарий. Я не совсем понял, что Вы имеете ввиду, нельзя ли неможко подробнее?

    ОтветитьУдалить
  3. USoft говорит о том, что в своём обработчики нужно обязательно вызывать оригианальный обработчик. Без такого обработчика данный код представляет интерес, только как пример говнокода.
    Но идея интересная!

    ОтветитьУдалить
  4. Ааа, спасибо Алексей, завтра перепишу код. Я что-то не сразу продуплил, что имееи ввиду Usoft.

    ОтветитьУдалить
  5. Итак, проведены следующие наблюдения:

    CnWiz. Работает совсем иначе. CnWizards - пакет мастеров, а не пакетов работающих по такому же принципу, что

    соответственно абсолютно не мешает им сосуществовать по крайней мере с данным примером класса. (я проверил это

    на 7х и 2k6 дельфах - CnPack чувствует себя прекрасно)

    Другое дело, если написать ещё один подобный (компонент?, даже не знаю как правильно назвать) модуль. То при его инициализации, обработчик OnMessage как бы перекроет обработчики всех остальных, инсталлированых до него, что собственно одно и тоже (с небольшими поправками), о чём говорил USoft.

    Я немного дополнил код, следуя совету USoft. Проверял на 10ти таких установленных в систему расширений. Теперь всё работает прекрасно. Испытания проводил в двух средах: D7, D2k6.

    Хотел бы отдельно поблагодарить Алексея Тимохина за расшифровку совета USoft и USoft'a, за дельный совет. Спасибо, коллеги.

    ОтветитьУдалить
  6. Пожалуйста.
    Ещё пара замечаний:
    1) что будет если в Delphi будут загружены 2 таких файла (не знаю как его лучше назвать)? А что случится, если сначала будет выгружен первый файл, а потом второй? (*1 см. ответ в конце). Использовать себя этот способ для себя - может и нормально, но делать такое в коде предназначенном для публичного использования, всё-таки лучше не стоит. Имхо.
    2) Нет нужды объявлять тип TIDEHandler, вместо этого лучше воспользоваться готовыми типом TMessageEvent из юнита Forms.pas.
    3) Перед вызовом Handler, стоит проверить, существует ли он с помощью конструкции if assigned(Handler) then вызвать его.
    4) Необходимо восстанавливать оригинальный обработчик Application.OnMessage в деструкторе TWinSwap. Иначе IDE может быть бо-бо.
    5) Замечание касающееся ООП и красоты. Удобно вынести код заменяющий обработчик и восстанавливающий оригинальный в отдельные методы класса, напримр HookEvents и UnHookEvents.

    *1 правильно, оригинальный обработчик будет потерян и вместо него будет подставлен мусорный обработчик. Я писал об похожем случае здесь.

    ОтветитьУдалить