Рассылка закрыта
При закрытии подписчики были переданы в рассылку "О карьере и профессиональном развитии IT-специалистов" на которую и рекомендуем вам подписаться.
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
← Октябрь 2005 → | ||||||
1
|
2
|
|||||
---|---|---|---|---|---|---|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
12
|
13
|
14
|
15
|
16
|
17
|
18
|
19
|
20
|
21
|
23
|
|
24
|
25
|
26
|
27
|
28
|
29
|
30
|
31
|
Статистика
-4 за неделю
Программирование для начинающих и не только
Информационный Канал 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 мы напишем следующий код:
Здесь строки 1-5 отвечают за доступ к интерфейсу INTAServices, который опеределен в модуле ToolsAPI. Как следует из кода этот интерфейс получают из глобальной переменной BorlandIDEServices определенной там же. Из этой же переменной можно получить любой другой сервисный интерфейс среды (см. Таблицу. 1).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;
В строке 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 выглядит следующим образом:
Здесь в строках 1-2 идет создание IDE - Уведомителя (вольный перевод фразы IDE Notifier J ), который будет отслеживать опасные процессы происходящие в IDE, т.е. те, которые потенциально могут привести к несанкционированному переопределению сочетаний горячих клавиш, и подавлять их действия.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;
Строки 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
Как уже было сказано раньше в некоторый случаях среда может самопроизвольно, без ведома нашего компонента переписывать значения горячих клавиш затирая тем самым внесенные нами изменения. Для предотвращения таких <нехороших> действий мы создадим класс который будет вызываться при некоторых действиях системы и заново присваивать наши значения горячих клавиш командам среды. Каркас этого класса выглядит следующим образом:
Как видите этот класс инкапсулирует методы интерфейса IOTAIDENotifier который вызывается при тех или иных событиях среды.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;
Так метод 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
Архив рассылкиОтписаться
Вспомнить пароль
В избранное | ||