Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование для начинающих и не только


Информационный Канал Subscribe.Ru

Эксперты Delphi: сделаем IDE совершеннее

При интенсивном использовании программного продукта, будь то текстовый редактор или программа складского учета, веб-браузер или среда разработки, время от времени возникает желание подстроить его под себя. И если в текстовом редакторе есть макросы или настройка вида кнопок, то в IDE Delphi введено понятие Экспертов, которые влияют на само поведение среды разработки и при правильной реализации могут значительно увеличить производительность вашего труда.

*

Использование экспертов для расширения возможностей IDE Delphi было предусмотрено еще в Delphi 3, но до появления Delphi 7 более менее полного справочного руководства оформленного в виде файлов помощи не было вообще. Вся информация которую можно было отыскать на страницах Интернета или периодической прессы была результатом собственных исследований авторов. A большинство файлов в заветной директории ToolsAPI окутаны завесой тайны для большинства программистов и по сей день.

С появлением Delphi 7 ситуация несколько улучшилась ведь теперь в его состав входит файл справки по Open Tools API где довольно подробно описаны все интерфейсы среды.

Но к сожалению даже такая справка не всегда поможет если вы взялись за написание эксперта впервые.

Теперь мы и подошли к сути написания мной этой статьи. Во первых если Вы уже давно работаете с этой средой программирования то у Вас должно возникать желание что-то поменять в ней, сделать ее лучше, подстроить ее под себя. Но как и все новое и неизученное, написание первого эксперта для Delphi связано с кучей мелких и не очень проблем которые могут в лучшем случае подвесить IDE. Чтобы этого не возникало Вам необходимо придерживаться некоторых несложных правил речь о которых и пойдет в этой статье.

Постановка задачи

Наверняка у многих заядлых Дельфийцев иногда возникало желание назначить или переназначить горячие клавиши часто используемым командам, и если у базовых команд (Открытие, Сохранение, Компиляция и т.д.) есть свои сочетания горячих клавиш, то у некоторых экзотических команд их нет: Компиляция всех проектов, Сборка всех проектов, вывод информации о проекте и т.д.

Сейчас мы попытаемся используя Delphi 6 написать эксперт, который бы давал нам возможность переназначать сочетания горячих клавиш для разных команд.

План действий

Первым делом нам следует выбрать в каком виде будет реализован наш эксперт: в виде динамически подключаемой библиотеки, или же в виде Delphi-пакета. Оба эти способа очень хорошо описаны в статье Олега Гопанюка <Эксперты в Delphi, или Программист, упростите себе жызнь!> опубликованной в К+П №1 за 2000 год. Но в силу своих личных предпочтений, а также потому что использование Delphi-пакетов не требует для регистрации перезапуска среды я воспользовался вторым способом.

Далее нам следует нарисовать форму, в которой бы мы и проделывали все переназначения клавиш см. рис.1.

Также нам следует предусмотреть механизм регистрации эксперта, который бы создавал новый пункт меню в среде для открытия нашего окна, и механизм удаления нашего эксперта.

Кроме этого мы предусмотрим механизм отслеживания изменений горячих клавиш для того чтобы не допустить их изменения другими экспертами.

Реализация

Итак. Первым делом запустим Delphi и создадим новый пакет через пункт меню New->Other (см. рис.2). Далее создадим новый модуль (New -> Unit) под названием RegisterUnit и форму(см. рис.1). В событии формы OnCreate мы напишем следующий код:


procedure TShortCutsModifier.FormCreate(Sender: TObject);
var Services:INTAServices;
    i:integer;Key:String;
begin
1. try
2.  Services:=BorlandIDEServices as INTAServices;
3. except
4.  Exit;
5. end;
6. Actions:=Services.ActionList;
7. ShortCuts:=TStringList.Create;
8. ShortCuts.Clear;
9. if FileExists(FileName) then
10. begin
11.  ShortCutList.Strings.LoadFromFile(FileName);
12.  with Actions do
13.  For i:=0 to ActionCount-1 do
14.    if Actions[i] is TAction then
15.     with Actions[i] as TAction do
16.     begin
17.       Key:=DeleteSymbols(Caption);
18.       ShortCut:=TextToShortCut(ShortCutList.Values[Key]);
19.      end;
20.  end;
21. with Actions do
22.  For i:=0 to ActionCount-1 do
23.  if Actions[i] is TAction then
24.    with Actions[i] as TAction do
25.     begin
26.      ShortCutList.Strings.Add(DeleteSymbols(Caption)+'='+ShortCutToText(ShortCut));
28.      ShortCuts.Add(ShortCutToText(ShortCut));
29.     end;
end;
Здесь строки 1-5 отвечают за доступ к интерфейсу INTAServices, который опеределен в модуле ToolsAPI. Как следует из кода этот интерфейс получают из глобальной переменной BorlandIDEServices определенной там же. Из этой же переменной можно получить любой другой сервисный интерфейс среды (см. Таблицу. 1).

В строке 6 мы сохраняем ссылку на глобальный список команд используемых IDE. Строки 7-20 отвечают за открытие файла с нашими переопределенными значениями горячих клавиш и за их переприсвоение. Далее в строках 21-29 мы заполняем соответствующими значениями наш компонент ShortCutList типа TValueListEditor, а также заполняем список горячих клавиш (строка 28), который будет использован дальше для предотвращения коллизий.

Следующий участок кода заполняет компонент THotKey Существующим значением горячей клавиши, а также разрешает ее выделение на форме:


procedure TShortCutsModifier.ShortCutListSelectCell(Sender: TObject; ACol,
  ARow: Integer; var CanSelect: Boolean);
begin
 CanSelect:=True;
 Label1.Caption:=ShortCutList.Keys[ARow]; 
 HotKey1.HotKey:=TextToShortCut(ShortCutList.Values[ShortCutList.Keys[ARow]]);
end;
После того как пользователь эксперта изменил сочетания горячих клавиш выбранной команды на <правильные> мы проверяем нет ли совпадений горячих клавиш и если они есть мы просто обнуляем его:

procedure TShortCutsModifier.HotKey1Exit(Sender: TObject);
var S:String;
begin
S:=ShortCutToText(HotKey1.HotKey);
if ShortCuts.IndexOf(S)=-1 then
 begin
  ShortCuts.Add(S);
  ShortCutList.Values[ShortCutList.Keys[ShortCutList.Row]]:=S;
 end else HotKey1.HotKey:=0;
end;
Сохранение состояния назначенных клавиш осуществляется, естественно, нажатием на кнопку в которой размещен следующий код:

procedure TShortCutsModifier.OkBtnClick(Sender: TObject);
var i:integer;Key:String;
begin
with Actions do
 For i:=0 to ActionCount-1 do
  if Actions[i] is TAction then
   with Actions[i] as TAction do
    begin
     Key:=DeleteSymbols(Caption);
     ShortCut:=TextToShortCut(ShortCutList.Values[Key]);
    end;
 ShortCutList.Strings.SaveToFile(FileName);
 Self.Close;
end;

Регистрация эксперта

Регистрация и удаление эксперта написанного в виде Delphi-пакета осуществляется соответственно в секциях initialization и finalization модуля. Конечно мы можем его сделать и в модуле формы, но для наглядности и для поддержания правил хорошего тона мы вынесем этот код в отдельный модуль RegisterUnit созданный нами ранее.

Этот модуль будет отвечать за создание и удаление пункта меню, вызывающего нашу форму, а также за сам ее вызов. Кроме этого здесь также будет происходить регистрация и удаление дополнительного интерфейса TIDENotifierHandler. Суть которого - недопущение изменения сочетания горячих клавиш не нашим экспертом.

Весь код этого модуля составляют две процедуры InitExpert и DoneExpert которые будут вызваны нами в секциях initialization и finalization соответственно.

Код процедуры InitExpert выглядит следующим образом:


procedure InitExpert;
var I:INTAServices;
    A:IOTAServices;
begin
 1. A:=BorlandIDEServices as IOTAServices;
 2. NotifierIdx:=A.AddNotifier(TIdeNotifierHandler.Create as IOTAIDENotifier);
 3. I:=BorlandIDEServices as INTAServices;
 4. Container:=TExpertContainer.Create;
 5. MenuItem:=TMenuItem.Create(I.MainMenu.Owner);
 6. MenuItem.Caption:='ShortCuts...';
 7. MenuItem.OnClick:=Container.MenuItemClick;
 8. Divider:=TMenuItem.Create(I.MainMenu.Owner);
 9. Divider.Caption:='-';
10. Divider.Visible:=True;
11. MenuItem.Visible:=True;
12. I.MainMenu.Items.Find('View').Add(Divider);
13. I.MainMenu.Items.Find('View').Add(MenuItem);
end;
Здесь в строках 1-2 идет создание IDE - Уведомителя (вольный перевод фразы IDE Notifier J ), который будет отслеживать опасные процессы происходящие в IDE, т.е. те, которые потенциально могут привести к несанкционированному переопределению сочетаний горячих клавиш, и подавлять их действия.

Строки 4-11 отвечают за создание дополнительного класса TExpertContainer который содержит код вызова формы, а также за создание компонентов меню, которые в строках 12-13 добавляются к подменю View главного меню.

Процедура DoneExpert соответственно удаляет все созданные объекты и освобождает занятую ими память:


procedure DoneExpert;
var A:IOTAServices;
begin
 A:=BorlandIDEServices as IOTAServices;
 A.RemoveNotifier(NotifierIdx);
 A:=nil;
 Divider.Free;
 MenuItem.Free;
 Container.Free;
 if ShortCutsModifier<>nil then FreeAndNil(ShortCutsModifier);
end;  

Отслеживание событий IDE

Как уже было сказано раньше в некоторый случаях среда может самопроизвольно, без ведома нашего компонента переписывать значения горячих клавиш затирая тем самым внесенные нами изменения. Для предотвращения таких <нехороших> действий мы создадим класс который будет вызываться при некоторых действиях системы и заново присваивать наши значения горячих клавиш командам среды. Каркас этого класса выглядит следующим образом:


  TIDENotifierHandler = class(TInterfacedObject,IOTAIDENotifier)
   protected
    procedure FileNotification(NotifyCode: TOTAFileNotification;
      const FileName: string; var Cancel: Boolean);
    procedure BeforeCompile(const Project: IOTAProject; var Cancel: Boolean);
    procedure AfterCompile(Succeeded: Boolean);
    procedure AfterSave;
    procedure BeforeSave;
    procedure Destroyed;
    procedure Modified;
   end;
Как видите этот класс инкапсулирует методы интерфейса IOTAIDENotifier который вызывается при тех или иных событиях среды.

Так метод FileNotification вызывается при разных операциях IDE с файлами в том числе инсталляции и деинсталяции пакетов, открытии, закрытии проектов и отдельных файлов и т.д. (более подробно см. тип TOTAFileNotification). Методы BeforeCompile и AfterCompile вызываются соответственно до и после компиляции проекта; методы BeforeSave, AfterSave - до и после сохранения. Методы Destroyed и Modified - вызываются перед тем как связанный с данным уведомителем объект будет уничтожен и после того как был изменен.

По большому счету нам достаточно будет написать код переприсвоения значений горячих клавиш только в FileNotification, и естественно ничто не мешает нам засунуть его во все методы. Я же выбрал <золотую середину> т.е. методы которые реально могут вызвать переопределение значений: FileNotification т.к. он вызывается и в случае инсталляции и деинсталяции пакетов, а также методы AfterSave и Modified просто так, на всякий случай. Остальные же методы мы подставляем пустышки:


procedure TIDENotifierHandler.FileNotification;
begin
 AssignShortCuts;
end;

procedure TIDENotifierHandler.BeforeCompile;
begin
end;

procedure TIDENotifierHandler.AfterCompile;
begin
end;

procedure TIDENotifierHandler.AfterSave;
begin
 AssignShortCuts;
end;

procedure TIDENotifierHandler.BeforeSave;
begin
end;

procedure TIDENotifierHandler.Destroyed;
begin
end;

procedure TIDENotifierHandler.Modified;
begin
 AssignShortCuts;
end;

Как видно, для упрощения кода вся процедура переприсвоения горячих клавиш выведена отдельным блоком - AssignShortCuts, исходный код которого представлен ниже:


procedure AssignShortCuts;
var Services:INTAServices;
    ShortCuts:TStringList;
    Key:String;i:integer;
begin
Services:=BorlandIDEServices as INTAServices;
if FileExists(FileName) then
 begin
  ShortCuts:=TStringList.Create;
  ShortCuts.LoadFromFile(FileName);
  with Services.ActionList do
   For i:=0 to ActionCount-1 do
    if Actions[i] is TAction then
     with Actions[i] as TAction do
      begin
       Key:=DeleteSymbols(Caption);
       ShortCut:=TextToShortCut(ShortCuts.Values[Key]);
      end;
 end;
end;

Заключение

В заключении хотелось бы отметить что приведенный в листинге код эксперта хоть и является работоспособным, но все таки далек от совершенства. Среди основных направлений совершенствования этого программного продукта - доведение до ума метода хранения информации о горячих клавишах т.к. задание жесткого пути и имени файла хранения в переменной FileName (см. листинг) хоть и допустимо, но не есть признаком хорошего тона, и может быть рекомендовано только лишь для тестовых версий этого эксперта. В финальной же версии надо предусмотреть сохранение результатов в рабочей папке IDE Delphi в более современном нежели .txt формате (к примеру новомодном xml). Еще одно направление по совершенствованию эксперта - наделение его возможностью не только переопределять значения горячих клавиш, но также: переименовывать пункты меню, перемещать их или создавать новые пункты меню их наборы и т.д.

В конечном счете создание экспертов Delphi это не просто возможность подстроить среду под себя, но также и некий показатель вашего профессионализма в работе в этой средой, который может и должен быть оценен. Так что старайтесь.

Таблица.1. Интерфейса доступные через глобальную переменную BorlandIDEServices.
ИнтерфейсОписание
IOTAEditorServicesИнтерфейс, предоставляющий базовый доступ к функциям и методам редактирования, просмотра и изменения исходного кода
IOTAKeyboardServicesИнтерфейс предоставляющий возможность для записи, сохранения, воспроизведения и т.д. строк кода (некоторый аналог макрорекордера в Word)
IOTAActionServicesБазовый интерфейс для открытия, сохранения, закрытия проектов и отдельных файлов
IOTAModuleServicesИнтерфейс для создания удаления и обращения к открытым модулям среды и последующей их обработкой
IOTADebuggerServicesИнтерфейс для доступа к функциям отладчика Delphi, с возможностями создания и обработки исполняемых процессов, установки точек останова и т.д.
IOTAWizardServicesИнтерфейс позволяющий добавлять и удалять <Волшебников> в/из среды.
IOTAPackageServicesИнтерфейс предоставляющий доступ к Delphi-пакетам используемым в системе.
IOTAMessageServicesИнтерфейс для вывода сообщений
IOTAServicesИнтерфейс, который дает доступ к глобальным настройкам всей среды, включая базовый ключ реестра, идентификатор приложения, идентификатор главного окна приложения и т.д.
IOTAKeyBindingServicesИнтерфейс который позволяет оперировать <закреплениями> клавиш
IOTAToDoServicesПредоставляет возможность по управлению списком задач (To-Do List)
INTAServicesПредоставляющий <низкоуровневый> доступ к пунктам меню среды, списку действий, а также списку иконок использующихся в среде.

Листинг.1 Модуль формы


unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Grids, ValEdit,ActnList, ComCtrls,VirtIntf,ToolsAPI;

type
  TShortCutsModifier = class(TForm)
    OkBtn: TButton;
    CancelBtn: TButton;
    ShortCutList: TValueListEditor;
    Label1: TLabel;
    HotKey1: THotKey;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure OkBtnClick(Sender: TObject);
    procedure CancelBtnClick(Sender: TObject);
    procedure ShortCutListSelectCell(Sender: TObject; ACol, ARow: Integer;
      var CanSelect: Boolean);
    procedure FormShow(Sender: TObject);
    procedure HotKey1Exit(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    Actions:TCustomActionList;
    ShortCuts:TStringList;
    { Private declarations }
  public
    { Public declarations }
  end;

  TIDENotifierHandler = class(TInterfacedObject,IOTAIDENotifier)
   protected
    procedure FileNotification(NotifyCode: TOTAFileNotification;
      const FileName: string; var Cancel: Boolean);
    procedure BeforeCompile(const Project: IOTAProject; var Cancel: Boolean);
    procedure AfterCompile(Succeeded: Boolean);
    procedure AfterSave;
    procedure BeforeSave;
    procedure Destroyed;
    procedure Modified;
   end;
var
  ShortCutsModifier: TShortCutsModifier;

procedure AssignShortCuts;

implementation
uses Menus;

{$R *.dfm}
var FileName:String = 'C:\Shortcuts.txt';

function DeleteSymbols(Val:String):String;
begin
 Result:=Val;
 while Pos('&',Result)>0 do Delete(Result,Pos('&',Result),1);
end;

procedure AssignShortCuts;
var Services:INTAServices;
    ShortCuts:TStringList;
    Key:String;i:integer;
begin
Services:=BorlandIDEServices as INTAServices;
if FileExists(FileName) then
 begin
  ShortCuts:=TStringList.Create;
  ShortCuts.LoadFromFile(FileName);
  with Services.ActionList do
   For i:=0 to ActionCount-1 do
    if Actions[i] is TAction then
     with Actions[i] as TAction do
      begin
       Key:=DeleteSymbols(Caption);
       ShortCut:=TextToShortCut(ShortCuts.Values[Key]);
      end;
 end;
end;

procedure TIDENotifierHandler.FileNotification;
begin
 AssignShortCuts;
end;

procedure TIDENotifierHandler.BeforeCompile;
begin
end;

procedure TIDENotifierHandler.AfterCompile;
begin
end;

procedure TIDENotifierHandler.AfterSave;
begin
 AssignShortCuts;
end;

procedure TIDENotifierHandler.BeforeSave;
begin
end;

procedure TIDENotifierHandler.Destroyed;
begin
end;

procedure TIDENotifierHandler.Modified;
begin
 AssignShortCuts;
end;

procedure TShortCutsModifier.FormCreate(Sender: TObject);
var Services:INTAServices;
    i:integer;Key:String;
begin
try
 Services:=BorlandIDEServices as INTAServices;
except
 Exit;
end;
Actions:=Services.ActionList;
ShortCuts:=TStringList.Create;
ShortCuts.Clear;
if FileExists(FileName) then
 begin
  ShortCutList.Strings.LoadFromFile(FileName);
  with Actions do
  For i:=0 to ActionCount-1 do
   if Actions[i] is TAction then
    with Actions[i] as TAction do
     begin
      Key:=DeleteSymbols(Caption);
      ShortCut:=TextToShortCut(ShortCutList.Values[Key]);
     end;
 end;
with Actions do
 For i:=0 to ActionCount-1 do
  if Actions[i] is TAction then
   with Actions[i] as TAction do
    begin
     ShortCutList.Strings.Add(DeleteSymbols(Caption)+'='+ShortCutToText(ShortCut));
     ShortCuts.Add(ShortCutToText(ShortCut));
    end;
end;

procedure TShortCutsModifier.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
 Action:=caHide;
end;

procedure TShortCutsModifier.OkBtnClick(Sender: TObject);
var i:integer;Key:String;
begin
with Actions do
 For i:=0 to ActionCount-1 do
  if Actions[i] is TAction then
   with Actions[i] as TAction do
    begin
     Key:=DeleteSymbols(Caption);
     ShortCut:=TextToShortCut(ShortCutList.Values[Key]);
    end;
 ShortCutList.Strings.SaveToFile(FileName);
 Self.Close;
end;

procedure TShortCutsModifier.CancelBtnClick(Sender: TObject);
begin
 Self.Close;
end;

procedure TShortCutsModifier.ShortCutListSelectCell(Sender: TObject; ACol,
  ARow: Integer; var CanSelect: Boolean);
begin
CanSelect:=True;
Label1.Caption:=ShortCutList.Keys[ARow];
HotKey1.HotKey:=TextToShortCut(ShortCutList.Values[ShortCutList.Keys[ARow]]);
end;

procedure TShortCutsModifier.FormShow(Sender: TObject);
begin
 ShortCutList.SetFocus;
end;

procedure TShortCutsModifier.HotKey1Exit(Sender: TObject);
var S:String;
begin
S:=ShortCutToText(HotKey1.HotKey);
if ShortCuts.IndexOf(S)=-1 then
 begin
  ShortCuts.Add(S);
  ShortCutList.Values[ShortCutList.Keys[ShortCutList.Row]]:=S;
 end else HotKey1.HotKey:=0;
end;

procedure TShortCutsModifier.FormDestroy(Sender: TObject);
begin
 FreeAndNil(ShortCuts);
end;

end.

Листинг. 2. Модуль регистрации/удаления эксперта


unit RegisterUnit;
interface
uses Menus,ToolsAPI;

procedure InitExpert;
procedure DoneExpert;

implementation
uses Main,SysUtils;
type
 TExpertContainer = class(TObject)
  public
   procedure MenuItemClick(Sender:TObject);
 end;
var NotifierIdx:Integer;
var Container:TExpertContainer;
    MenuItem:TMenuItem;
    Divider:TMenuItem;

procedure TExpertContainer.MenuItemClick(Sender:TObject);
begin
 if ShortCutsModifier=nil then ShortCutsModifier:=TShortCutsModifier.Create(nil);
 ShortCutsModifier.ShowModal;
 FreeAndNil(ShortCutsModifier);
end;

procedure InitExpert;
var I:INTAServices;
    A:IOTAServices;
begin
 A:=BorlandIDEServices as IOTAServices;
 NotifierIdx:=A.AddNotifier(TIdeNotifierHandler.Create as IOTAIDENotifier);
 I:=BorlandIDEServices as INTAServices;
 Container:=TExpertContainer.Create;
 MenuItem:=TMenuItem.Create(I.MainMenu.Owner);
 MenuItem.Caption:='ShortCuts...';
 MenuItem.OnClick:=Container.MenuItemClick;
 Divider:=TMenuItem.Create(I.MainMenu.Owner);
 Divider.Caption:='-';
 Divider.Visible:=True;
 MenuItem.Visible:=True;
 I.MainMenu.Items.Find('View').Add(Divider);
 I.MainMenu.Items.Find('View').Add(MenuItem);
end;

procedure DoneExpert;
var A:IOTAServices;
begin
 A:=BorlandIDEServices as IOTAServices;
 A.RemoveNotifier(NotifierIdx);
 A:=nil;
 Divider.Free;
 MenuItem.Free;
 Container.Free;
 if ShortCutsModifier<>nil then FreeAndNil(ShortCutsModifier);
end;


initialization
 InitExpert;
finalization
 DoneExpert;
end.
©Gigabyte 2005

Subscribe.Ru
Поддержка подписчиков
Другие рассылки этой тематики
Другие рассылки этого автора
Подписан адрес:
Код этой рассылки: comp.soft.prog.programmershelp
Архив рассылки
Отписаться
Вспомнить пароль

В избранное