Pro server Delphi.cz napsal pan Cary Jensen.
Editory zdrojového kódu v Delphi obsahují mocnou, ale málo známou vlastnost, pomocí které můžete do prostředí editoru přidávat vlastní posloupnosti klávesových úhozů . Tato vlastnost se nazývá uživatelské klávesové vazby (custom key bindings), česky jim spíše říkáme klávesové zkratky. Je součástí otevřeného aplikačního programového rozhraní (API) pro nástroje (OTA – Open Tools API). OTA rozhraní vám poskytne množinu tříd a rozhraní, které můžete použít pro vytváření vlastních rozšíření integrovaného vývojového prostředí (IDE).
Tento článek vám poskytne přehled možností této vlastnosti IDE a předvede jednoduchou třídu pro klávesovou zkratku, která vám může posloužit jako odrazový můstek pro tvorbu vlastních klávesových zkratek. Tato zkratka udělá kopii stávající řádky v editoru zdrojového kódu. Jde o vlastnost známou z jiných editorů a díky OTA ji můžete používat i v editoru Delphi.
Klávesové zkratky, přehled
Klávesová zkratka je jednotka (unit), která se instaluje do design-time balíčku. Vytvoření klávesové zkratky editoru zahrnuje tvorbu deklarace typu třídy a implementaci jejích rozhraní. Tvorba a instalace klávesové zkratky vlastně zahrnuje několik jednoznačných kroků:
1. Odvození nové třídy od TNotifierObject. Tato třída musí být deklarována kvůli implementaci rozhraní IOTAKeyboardBinding. Tato třída se stane vaší klávesovou zkratkou.
2. Kromě čtyř metod rozhraní IOTAKeyboardBinding, které musíte implementovat ve své třídě pro klávesovou zkratku, přidáte jednu další metodu pro každou novou vlastnost, kterou zamýšlíte přidat do editoru. Této metodě je předán objekt, který implementuje rozhraní IOTAKeyContext. Tento objekt použijete v rámci své implementace, abyste mohli zjistit informace o chování editoru a zároveň jej i ovládat.
3. Dále je třeba deklarovat a implementovat samostatnou proceduru Register. V rámci této procedury vyvoláte metodu AddKeyboardBinding objektu BorlandIDEServices a předáte jí instanci třídy, kterou jste deklarovali v prvním kroku jako jediný argument.
4. K designtime balíčku přidáte jednotku, která obsahuje tuto proceduru Register.
Každý z těchto kroků je podrobně popsán v následujících odstavcích. Jak jsem se již zmínil, tyto kroky umožní definovat novou klávesovou zkratku, která přidá jednu kombinaci kláves. Jakmile ji implementujete a instalujete, umožní vám tato zkratka stiskem kombinace kláves Ctrl-D duplikovat současný řádek v editoru.
(Zdrojový kód pro tento projekt klávesové zkratky si můžete stáhnout z Embarcadero Code Central na adrese cc.embarcadero.com/item/27635.)
Deklarace třídy pro klávesovou zkratku
Třída definující vaši klávesovou zkratku musí být odvozena od TnotifierObject a musí implementovat rozhraní IOTAKeyboardBinding. Pokud s rozhraními umíte pracovat, vybavíte si, že kdykoli je deklarována třída implementující i rozhraní (interface), musí deklarovat a implementovat i všechny metody daného rozhraní. Podívejme se například na následující deklaraci rozhraní IOTAKeyboardBinding. Tato deklarace se objevuje v jednotce ToolsAPI:
IOTAKeyboardBinding = interface(IOTANotifier)
['{F8CAF8D7-D263-11D2-ABD8-00C04FB16FB3}']
function GetBindingType: TBindingType;
function GetDisplayName: String;
function GetName: String;
procedure BindKeyboard(const BindingServices:
IOTAKeyBindingServices);
property BindingType: TBindingType read GetBindingType;
property DisplayName: String read GetDisplayName;
property Name: String read GetName;
end;
Jak můžete vidět, toto rozhraní deklaruje čtyři metody a tři vlastnosti. Vaše vlastní třída pro klávesovou zkratku musí tyto metody implementovat. Nicméně pamatujte, že nemusí nutně implementovat vlastnosti. (Tohle je běžný okamžik zmatení programátora, když se zabývá rozhraními. Faktem ale je, že vlastnosti patří rozhraní a pro implementující objekt nejsou požadovány. Jasně: vlastnosti v objektu můžete implementovat. Ale nemusíte – a vlastnosti v tomto příkladu implementovány nebyly.)
Vaše třída klávesové zkratky musí kromě metod rozhraní IOTAKeyboardbinding obsahovat jednu další metodu pro každou novou vlastnost, kterou chcete přidat do editoru. Kvůli kompatibilitě s metodou AddKeyBinding použitou pro vazbu s těmito dalšími metodami, musí být tyto dodatečné metody typu TkeyBindingProc. Následující kód ukazuje deklaraci typu ukazatele na metodu TkeyBindingProc tak, jak je uvedena v jednotce ToolsAPI:
TKeyBindingProc = procedure (const Context: IOTAKeyContext;
KeyCode: TShortcut; var BindingResult: TKeyBindingResult)
of object;
Ve skutečnosti tento typ říká pouze to, že každá z dalších metod, které napíšete, tedy každá, jenž přidá jinou klávesovou kombinaci do editoru, musí mít tři parametry: IATOKeyContext, TShortcut a TKeyBindingResult.
V následující kusu kódu je deklarována třída klávesové zkratky v jednotce DupLine.pas. Tato třída nazvaná TDupLineBinding obsahuje pouze jednu novou klávesovou zkratku. Následuje deklarace typu třídy pro tuto třídu:
type
TDupLineBinding = class(TNotifierObject, IOTAKeyboardBinding)
private
public
procedure DupLine(const Context: IOTAKeyContext;
KeyCode: TShortCut;
var BindingResult: TKeyBindingResult);
{ IOTAKeyboardBinding }
function GetBindingType: TBindingType;
function GetDisplayName: String;
function GetName: String;
procedure BindKeyboard(const BindingServices:
IOTAKeyBindingServices);
end;
Implementace rozhraní IOTAKeyboardBindings
Jakmile máte svou třídu pro klávesovou zkratku deklarovánu, musíte implementovat požadované čtyři metody rozhraní IOTAKeyboardBinding, stejně tak jako vaše další metody TkeyBindingProc. Naštěstí je implementace rozhraní IOTAKeyboardBinding velmi snadná.
GetBindingType implementujete tak, že vrátíte typ vytvářené klávesové zkratky. Existují pouze dva typy klávesových zkratek: částečné a úplné. Úplná klávesová zkratka definuje všechny úhozy editoru a vy musíte označit svou klávesovou zkratku jako úplnou tím, že vrátite hodnotu btComplete. Úplná klávesová zkratka je ve skutečnosti úplné mapování všech kláves.
Částečnou klávesovou zkratku použijete v případě, že chcete přidat jeden nebo více úhozů kláves do úplného mapování, které používáte. Třída TDupLineBinding je částečnou klávesovou zkratkou. V následujícím výňatku kódu je implementace GetBindingType ve třídě TDupLineBinding:
function TDupLineBinding.GetBindingType: TBindingType;
begin
Result := btPartial;
end;
GetDisplayName a GetName implementujete proto, abyste editoru poskytli textové popisy svých klávesových zkratek. GetDisplayName by měla vrátit informativní jméno, které Delphi zobrazí v seznamu rozšiřujících modulů (Enhancement modules) na stránce Key Mappings volby Editor Options v dialogovém boxu Options.
Oproti tomu GetName je unikátní textový řetězec, který interně používá editor pro identifikaci vaší klávesové zkratky. Jelikož tento řetězec musí být unikátní v rámci všech klávesových zkratek, které uživatel kdy bude instalovat, je přijatou konvencí pojmenovat jej jménem vaší společnosti nebo jménem vaším, za kterým hned následuje jméno vaší klávesové zkratky.
Následující výpis obsahuje implementaci metod GetDisplayName a GetName pro třídu TDupLineBinding:
function TDupLineBinding.GetDisplayName: String;
begin
Result := 'Duplicate Line Binding';
end;
function TDupLineBinding.GetName: String;
begin
Result := 'jdsi.dupline';
end;
Metodu BindKeyboard implementujete proto, aby poskytla aktuální vazbu vašich metod TKeyBindingProc. Metodě BindKeyboard je předán objekt implementující rozhraní IOTAKeyBindingServices a vy tuto referenci objektu použijete k vyvolání metody AddKeyBinding.
AddKeyBinding požaduje alespoň tři parametry. První parametr je pole odkazů na TShortcut. TShortcut je typu word a představuje buď jediný stisk, nebo stisk a kombinaci jednoho nebo více z následujících kláves: CTRL, ALT, nebo SHIFT, stejně tak jako stisk levého, pravého, středního či dvojitého kliknutí tlačítka myši. Jelikož tento parametr může obsahovat pole, je možné svázat vaši proceduru TKeyBindingProc se dvěma nebo více stisky kláves nebo jejich kombinací.
Jednotka Menus v Delphi obsahuje funkci zvanou Shortcut; tu můžete snadno použít pro tvorbu odkazů na TShortcut. Syntaxe funkce je následující:
function Shortcut(Key: Word; Shift: TShiftState): TShortcut;
V této funkci je prvním znakem ANSI hodnota klávesy a druhým je množina žádného, jednoho, nebo více TShiftState. Následuje deklarace TShiftState tak, jak se objevuje v jednotce Classes:
TShiftState = set of (ssShift, ssAlt, ssCtrl,
ssLeft, ssRight, ssMiddle, ssDouble);
Druhý parametr předávaný BindKeyboard je odkaz na vaši vlastní metodu TKeyBindingProc, která implementuje chování, jež zamýšlíte přiřadit ke klávesovému úhozu či kombinaci kláves. Třetí parametr je pak ukazatel na textovou souvislost. V implementaci BindKeyboard ve třídě TDupLineBinding je metoda DupLine předána jako druhý parametr a nil je předán jako parametr třetí. Následuje implementace metody BindKeyboard, která se nachází v jednotce DupLine.pas:
procedure TDupLineBinding.BindKeyboard(
const BindingServices: IOTAKeyBindingServices);
begin
BindingServices.AddKeyBinding(
[ShortCut(Ord('D'), [ssCtrl])], DupLine, nil);
end;
Jak sami vidíte, tato implementace BindKeyboard přiřadí kód implementovaný v metodě DupLine stisku kombinace kláves CTRL-D.
Implementace metod TKeyBindingProc
Implementace metod IOTAKeyboardBindings je velice jednoduchá. Nicméně implementace vaší metody TKeyBindingProc už tak snadná není.
Jak můžete vidět na deklaraci typu ukazatele na metodu TKeyBindingProc, kterou jsem uvedl v předchozí části, TKeyBindingProc jsou předány tři parametry. První a nejdůležitější je objekt implementující rozhraní IOTAKeyContext. Tento objekt je vaším přímým propojením do editoru a jeho vlastnosti používáte k ovládání pozice kurzoru, operací s bloky a pohledů. Druhý parametr je TShortCut, který byl použit k vyvolání vaší metody. Tohle je užitečné v případě, kdy předáte více než jeden TShortCut v prvním parametru volání AddKeyBinding a zvláště když zamýšlíte odlišit chování pro různé klávesové úhozy či klávesové kombinace.
Poslední parametr vaší metody TKeyBindingProc je hodnota TKeyBindingResult předávaná odkazem. Tento parametr použijete pro oznámení editoru, co má dělat, jakmile vaše metoda skončí. V následujícím kódu je deklarace TKeyBindingResult tak, jak se objevuje v jednotce ToolsAPI:
TKeyBindingResult = (krUnhandled, krHandled, krNextProc);
Formální parametr BindingResult vaší metody TKeyBindingProc nastavíte na krHandled, pokud vaše metoda úspěšně dokončila svou činnost. Nastavení BindingResult na krHandled má též ten smysl, že zabrání libovolným jiným klávesovým zkratkám zpracovávat stisk kláves a zároveň zabrání položkám menu přiřazeným dané kombinaci kláves, aby příkaz zpracovávaly.
BindingResult nastavíte na hodnotu krUnhandled v případě, že nezpracujete klávesový úhoz nebo kombinaci stisk kláves. Nastavením BindingResult na krUnhandled říkate editoru, aby povolil zpracovat libovolnou jinou klávesovou zkratku přiřazenou stisku klávesy či jejich kombinaci stejně tak jako libovolnou položku menu přiřazenou kombinaci kláves.
Nastavení BindingResult na krNextProc použijete v případě, kdy jste zpracovali příkaz, ale zároveň chcete povolit další klávesové zkratky přiřazené kombinaci kláves a spustit jejich ošetření. Podobně jako u nastavení BindingResult na krHandled bude mít nastavení BindingResult na krNextProc za následek zabránění položkám menu příkaz zpracovávat.
Jak již jsem zmínil dříve, skutečná finta implementace vaší metody TKeyBindingProc je spojena s objektem implementujícím rozhraní IOTAKeyContext, který obdržíte ve formálním parametru Context. Naneštěstí hoši z Embarcadera zatím nepublikovali skoro žádnou dokumentaci o tom, jak by se to mělo dělat. Jedním ze střípků informací je poněkud nesouvislá skupina komentářů v jednotce ToolsAPI.
Úplné rozebrání vlastností IOTAKeyContent hodně přesahuje rámec tohoto článku. V následujícím kódu však najdete implementaci TKeyBindingProc ze třídy TDupLineBinding:
procedure TDupLineBinding.DupLine(const Context: IOTAKeyContext;
KeyCode: TShortcut; var BindingResult: TKeyBindingResult);
var
ep: IOTAEditPosition;
eb: IOTAEditBlock;
r,c: Integer;
begin
try
ep := Context.EditBuffer.EditPosition;
ep.Save;
//Save current cursor position
r := ep.Row;
c := ep.Column;
eb := Context.EditBuffer.EditBlock;
ep.MoveBOL;
eb.Reset;
eb.BeginBlock;
eb.Extend(EP.Row+1,1);
eb.EndBlock;
eb.Copy(False);
ep.MoveBOL;
ep.Paste;
//Restore cursor position
ep.Move(r, c);
finally
ep.Restore;
end;
BindingResult := krHandled;
end;
Jak můžete vidět z kódu uvedeného výše, objekt IOTAKeyContext předávaný prostřednictvím prvního parametru je Vaší možností, jak přistupovat k řadě objektů, které můžete použít pro implementaci chovaní vašich klávesových zkratek. A EditBuffer je zcela nepochybně vlastností nejužitečnější.
Tato vlastnost odkazuje na objekt, který implementuje rozhraní IOTAEditBuffer. Objekt použijete na získání odkazu na další objekty implementující rozhraní, včetně objektů IOTABufferOptions, IOTAEditBlock, IOTAEditPosition a IOTAEditView. Tyto objekty jsou dostupné použitím vlastností BufferOptions, EditBlock, EditPosition a TopView vlastnosti EditBuffer formálního parametru Context.
Objekt IOTABufferOptions můžete použít k přečtení informace o stavu editoru včetně různých nastavení, které mohou být konfigurovány na stránce obecných nastavení v dialogovém okénku pro vlastnosti editoru (Editor Properties).
Objekt IOTAEditBlock vám dovolí ovládat bloky kódu v editoru. Operace, které s bloky můžete provádět, zahrnují kopírování, ukládání do souboru, zvětšování či zmenšování bloků, mazání a další.
Objekt TOTAEditPosition použijete k ovládání bodu vložení či kurzoru. Operace, které můžete s pomocí tohoto objektu provádět, zahrnují určování pozice bodu vložení, jeho přesun, vložení jediného znaku, vložení kopírovaného bloku a tak dále.
A nakonec objekt TOTAEditView použijete k získání informací a do určité míry i k ovládání různých okének editoru. Například můžete tento objekt použít pro určení počtu otevřených jednotek, posunování jednotlivých oken, přepnutí daného okna do aktivního stavu či nastavovat, nalézat a přesunovat kurzor na záložky.
Přesuneme-li svou pozornost zpět na metodu DupLine, vidíme, že kód začíná přiřazením implementujícího objektu IOTAEditPosition k odkazu na IOTAEditPosition. Přestože toto nebyl ten podstatný krok, zjednodušil zdrojový kód této metody a snížil potřebu opakovaných odkazů na Context.EditBuffer.EditPosition. Tato proměnná IOTAEditPosition (ep) je pak použita pro uložení odkazu na stávající pozici kurzoru.
A dále: proměnné IOTAEditBlock (eb) je přiřazen objekt, na který odkazuje vlastnost Context.EditBuffer.EditBlock. Tak jako u proměnné ep toto přiřazení způsobí snížení potřeby psaní kódu – a stejně tak i možnost vyhnout se příkazu with, který může být dost matoucí, jelikož mnoho z těchto objektů má podobná či identická jména vlastností.
Použitím těchto dvou odkazů se pozice kurzoru přesune na první sloupec současného řádku. Poté se nastartuje nový blok, rozšíří se o jeden řádek a zkopíruje se do schránky. Dále se kurzor přesune na začátek řadku a vloží se blok. Celou operaci ukončí přesunutí kurzoru na původní pozici a oznámí se nastavením formálního parametru BindResult na hodnotu krHandled. Konečný výsledek je ten, že současný řádek je zdvojen, aniž by se kurzor v editoru viditelně posunul.
Deklarace a implementace registrační procedury
Aby se vaše klávesová zkratka mohla úspěšně instalovat do editoru, musíte ji zaregistrovat z instalovaného design-time balíčku pomocí procedury Register. Tato procedura, jejíž název je citlivý na velikost písmen, musí být předem deklarována v sekci rozhraní jednotky, která bude instalována do balíčku pro fázi návrhu. Dále musíte k implementaci této procedury přidat volání metody IOTAKeyBindingServices.AddKeyboardBinding a předat instanci své třídy klávesové zkratky jako jediný parametr. Tuto metodu zavoláte dynamickou vazbou odkazu BorlandIDEServices k rozhraní IOTAKeyboardServices a jako aktuální parametr předáte volání konstruktoru vašeho objektu klávesové zkratky.
Takto vypadá implementace procedury Register, uvedená v jednotce DupLine.pas:
procedure Register;
begin
(BorlandIDEServices as IOTAKeyboardServices).
AddKeyboardBinding(TDupLineBinding.Create);
end;
Instalace klávesové zkratky
Stažením zdrojového kódu získáte projekt pro design-time balíček a zároveň i jednotku Dupline.pas. Ještě je třeba učinit následující kroky, aby klávesová zkratka byla úspěšně instalována do prostředí Delphi:
1. Otevřete v Delphi projekt KeyBind
2. Zajistěte, aby složka ToolsAPI byla ve vyhledávací cestě vašich knihoven – implicitně tam není.
Přidání této složky mezi vyhledávací cesty se v posledních verzích Delphi provede takto: vyberte Tools | Options. Zvolte Delphi Options | Win32 Library. Klikněte na elipsu hned vedle roletového menu Library Path. Použijte zobrazené dialogové okénko pro přidání složky s ToolsAPI do vyhledávací cesty. V Delphi 2010 je tato složka implicitně umístěna zde: C:\Program Files\Embarcadero\RAD Studio\7.0\source\ToolsAPI.
3. Pomocí správce projektů klikněte pravým tlačítkem na projekt Keybind a vyberte možnost Install. Nová klávesová zkratka by se měla zkompilovat a instalovat. (Pokud se nezkompiluje, znovu zkontrolujte vyhledávací cestu, ověřte, že designide je mezi projektovými nastaveními requires a přesvědčete se, že projekt je jen designtime balíček.)
Klávesová zkratka je nyní aktivní. Teď již byste měli být schopni sticknout Ctrl-D v jednotce pro vytvoření duplikátu stávajícího řádku.
Pokud si chcete prohlédnout instalovanou klávesovou zkratku, zvolte z hlavního menu Delphi možnost Tools | Options. Pak vyberte Editor Options | Key Mappings z dialogu Options. Nově instalovaná klávesová zkratka se objeví v seznamu rozšíření (Enhancements) tak, jak je vidět na obrázku:
Nově instalovaná klávesová zkratka se objevuje v seznamu rozšiřujících modulů.
Něco o autorovi
Cary Jensen je velmi úspěšný autor více než 20 knížek o vývoji software a vítěz soutěží o nejlepší školení za roky 2002 a 2003, které pořádal magazín Delphi Informant formou čtenářských anket. Cary je častým řečníkem na konferencích a seminářích ve většině světa. Posluchači jej oceňují pro jeho sebezničující humor i pro jeho praktický přístup ke složitým problémům. Caryho společnost má webovou adresu JensenDataSystems.com. Cary a Marco Cantù společně prezentují během Delphi Developer Days 2010, řadě pěti prezentací v USA i Evropě (DelphiDeveloperDays.com). Cary má doktorát udělený Rice university a specializuje se na interakci člověka a počítače.