はじめに
最近の Delphi はアンドックな IDE スタイルが利用できません。できなければできないで慣れはするのですが、一時的なメモを置いておく場所が欲しい事があります
- テキストファイルをコードエディタで開く事はできるのですが、タブを切り替える必要があります。
- フォームデザイナとコードエディタは排他なので、オブジェクトインスペクタの内容をコピペしたい時はかなりガチャガチャします。
- 外部のテキストエディタを使うとタスク切り替えになります。
- [新規編集ウィンドウ] は IDE の裏に行かないので使い勝手が悪いです。
IDE から離れず、ウィンドウをガチャガチャしなくていいようにするためには IDE のどこかにメモがあれば便利なのですが...。
ドッキング可能なメモフォーム
それなら IDE にドック可能なメモフォームを追加すればいいんじゃね? という結論に至り、重い腰を上げて IDE 拡張を作る事にしました。こういった拡張は既に誰かが作っている気がしますが、気にしない事にします。
Tools API
Delphi の IDE は Tools API を使って拡張します。
・Native Tools API (NTAPI)
パッケージ内から使える Tools API です。IDE の機能を利用する事ができます。
・Open Tools API (OTAPI)
パッケージを作らなくても使える Tools API です。IDE の一部の機能を利用する事ができます。
IDE 拡張の具体的な方法については DocWiki にドキュメントがあります。
Delphi 8 辺りでドキュメントから Tools API の記述が消えていて、IDE 拡張を作成する敷居が高かったのですが、現在ではドキュメントが復活しており、IDE 拡張の敷居も幾分低くなっています。
ドッキング可能なフォームのサンプル
ドッキング可能なフォームのサンプルが gexperts.org にありますので、これをベースにしたいと思います。
- How can I create a form that docks into the IDE like the Object Inspector? (gexperts.org)
- DockingForm.zip (gexperts.org)
インストールすると、[ヘルプ]
メニューに [Show Dockable Form]
というメニューが追加され、これをクリックするとドッキング可能な空のフォームが表示されます。
これにメモコンポーネントを貼り付けるだけで目的は達成できそうですね。
改変
では、Delphi 2007 以降で使える事を念頭に改変していきたいと思います。
・リネーム
全体的にリネームして DockMemo という名称にします。
・メモ
ドッキングフォームにメモを最大化で貼ります。
プロパティ | 値 |
---|---|
Align | alClient |
Font.Name | Tahoma |
Name | mmMemo |
ScrollBars | ssBoth |
WantTabs | True |
・メニュー
IOTAMenuWizard として登録されている ([ヘルプ]
メニューに表示される) ので、これをやめます。
procedure Register;
begin
//RegisterPackageWizard(TDockMemoFormExpert.Create as IOTAMenuWizard);
RegisterPackageWizard(TDockMemoFormExpert.Create);
TDockableMemoForm.CreateDockMemoForm;
end;
クラスから IOTAMenuWizard インターフェイスも外して、GetMenuText() メソッドも削除しておきます。
これにより、メニューへは自前で登録しなければならなくなりました。メニューの位置は [表示]
の [デスクトップの配置]
とセパレーターの間がいいと思いました。
最近の Delphi はメニューがまとめられているので [ツールウィンドウ]
の中がよさそうです。
メニューアイテムを追加するコードは次のようになりました。
procedure RegisterMenu;
var
NTA: INTAServices;
i, j: Integer;
Method: TMethod;
NewMenu: Boolean;
begin
NTA := BorlandIDEServices as INTAServices;
for i := 0 to NTA.MainMenu.Items.Count-1 do
begin
if NTA.MainMenu.Items[i].Name <> 'ViewsMenu' then
Continue;
for j := 0 to NTA.MainMenu.Items[i].Count-1 do
begin
if NTA.MainMenu.Items[i][j].Name = 'ViewToolWindowsItem' then
NewMenu := True
else if NTA.MainMenu.Items[i][j].Name = 'ViewToggleSeparator' then
NewMenu := False
else
Continue;
MemoMenu := TMenuItem.Create(nil);
MemoMenu.AutoHotkeys := maManual;
{$IF CompilerVersion >= 21.0}
if not SameText(PreferredUILanguages, 'JA') then
MemoMenu.Caption := Title_EN
else
{$IFEND}
MemoMenu.Caption := Title_JA;
Method.Code := @MenuClick;
Method.Data := MemoMenu;
MemoMenu.OnClick := TNotifyEvent(Method);
if NewMenu then
NTA.MainMenu.Items[i][j].Add(MemoMenu)
else
NTA.MainMenu.Items[i].Insert(j, MemoMenu);
Break;
end;
Break;
end;
end; { RegisterMenu }
余談ですが、挿入するメニューアイテムの位置を決定するには、既存のメニューアイテムの名前を知る必要があります。メニューアイテムの名前を調べるには適当なパッケージを作成して次のようなユニットを含めます。
unit EnumMenusExpert;
interface
uses
Sysutils, Classes, ToolsAPI, Menus, Dialogs;
type
TEnumMenusExpert = class(TNotifierObject, IOTANotifier, IOTAWizard, IOTAMenuWizard)
public
procedure Execute;
function GetIDString: string;
function GetMenuText: string;
function GetName: string;
function GetState: TWizardState;
procedure Destroyed;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterPackageWizard(TEnumMenusExpert.Create as IOTAMenuWizard);
end;
{ TEnumMenusExpert }
procedure TEnumMenusExpert.Destroyed;
begin
{ Do Nothing }
end;
procedure TEnumMenusExpert.Execute;
var
NTA: INTAServices;
i, j: Integer;
SL: TStringList;
begin
SL := TStringList.Create;
try
NTA := BorlandIDEServices as INTAServices;
for i := 0 to NTA.MainMenu.Items.Count-1 do
begin
SL.Add(Format('[%s]: %s', [NTA.MainMenu.Items[i].Caption, NTA.MainMenu.Items[i].Name]));
for j := 0 to NTA.MainMenu.Items[i].Count-1 do
SL.Add(Format(' %s: %s', [NTA.MainMenu.Items[i][j].Caption, NTA.MainMenu.Items[i][j].Name]));
SL.Add('');
end;
ShowMessage(SL.Text);
finally
SL.Free;
end;
end;
function TEnumMenusExpert.GetIDString: string;
begin
Result := 'EnumMenusExpert';
end;
function TEnumMenusExpert.GetMenuText: string;
begin
Result := 'Enum Menus...';
end;
function TEnumMenusExpert.GetName: string;
begin
Result := 'Enum Menus Expert';
end;
function TEnumMenusExpert.GetState: TWizardState;
begin
Result := [wsEnabled];
end;
initialization
finalization
end.
Requies に designide
を追加してインストールすれば、[ヘルプ | Enum Menus...]
でメニューアイテムのキャプションと名前を調べる事ができます。
〔Ctrl〕+〔C〕でクリップボードにテキストが入ります
・ショートカット
IDE のショートカットが優先されるため、メモのクリップボード関連のショートカットが使えません。これではあまりに不便なのでメモコンポーネントに入った時に IDE のショートカットを無効にし、メモコンポーネントから出た時に有効に戻すようにします。
procedure EnableShortcuts(AValue: Boolean);
procedure Apply(const Name, ShortCut: string);
var
action: TAction;
begin
action := TAction(Application.MainForm.FindComponent(Name));
if action = nil then
Exit;
if AValue then
action.ShortCut := TextToShortCut(ShortCut)
else
action.ShortCut := scNone;
end;
begin
Apply('EditUndoCommand' , 'Ctrl+Z' );
Apply('EditRedoCommand' , 'Shift+Ctrl+Z');
Apply('EditCutCommand' , 'Ctrl+X' );
Apply('EditCopyCommand' , 'Ctrl+C' );
Apply('EditPasteCommand' , 'Ctrl+V' );
Apply('EditSelectAllCommand', 'Ctrl+A' );
end;
・自動保存 / 自動読込
メモはプロジェクト毎に関連付け、プロジェクトファイルのある場所に *.memo
という名前で自動保存する事にします。
ノーティファイアを登録し、アクティブなプロジェクトが変更された時に保存/読込を行います。プロジェクトが閉じられた時にも保存を行います。
procedure TIDENotifier.FileNotification(NotifyCode: TOTAFileNotification;
const FileName: string; var Cancel: Boolean);
begin
if not Assigned(FormInstance) then
Exit;
case NotifyCode of
ofnFileClosing:
begin
if GetActiveProject = nil then
begin
SaveFile;
ChangeFile;
SetCaption;
TDockableMemoForm(FormInstance).mmMemo.Lines.Clear;
end;
end;
ofnActiveProjectChanged:
begin
SaveFile;
ChangeFile;
SetCaption;
LoadFile;
end;
end;
end;
Unicode 版 Delphi では UTF-8 で保存し、ANSI 版ではデフォルトのコードページ (日本語版 Windows なら Shift_JIS) で保存します。
procedure SaveFile;
begin
with TDockableMemoForm(FormInstance) do
if FileExists(ProjectFileName) then
mmMemo.Lines.SaveToFile(MemoFileName{$IFDEF UNICODE}, TEncoding.UTF8{$ENDIF});
end; { SaveFile }
・リネーム対応
本来はプロジェクトがリネームされた時に、それに追従してメモファイルもリネームすべきですが、ノーティファイアがうまく働かなかった (ちゃんと動かせなかった) ので、リネーム対応にはなっていません。
ここでリネームする事はあまりないので、そこまで不便ではないとは思います (今後の課題としておきます)。
・デバッグ
IDE 拡張はデバッグしにくいです。とりあえず、次のような手続きを作っておくと Print デバッグが可能となります。
procedure WriteLog(TabName, s: string);
var
IMessageServices: IOTAMessageServices;
MessageGroup: IOTAMessageGroup;
begin
IMessageServices := BorlandIDEServices as IOTAMessageServices;
MessageGroup := IMessageServices.AddMessageGroup(TabName);
IMessageServices.AddTitleMessage(s, MessageGroup);
end;
例えば
WriteLog('DockMemo', 'FileNotification: 7');
のように記述すると、
メッセージウィンドウに DockMemo
タブが作られ、そこにログが表示されます。
・パッケージ
とりあえず 2007 / XE / 11 Alexandria 用のパッケージを作りました。
Delphi | パッケージプロジェクト名 |
---|---|
Delphi 2007 | DockMemoPackageD2007.dproj |
Delphi XE | DockMemoPackageDXE.dproj |
Delphi 11 Alexandria | DockMemoPackageD11.dproj |
Delphi 10 Seattle 以降、IDE は Large Address Aware に対応しているので、IDE 拡張も LAA 対応にする必要があったハズです。
Delphi 11 Alexandria 用のパッケージはプロジェクトオプションで LAA を有効にしておきました。
完成
出来上がった IDE 拡張のアーカイブを用意しました。
パッケージをインストールするとメモが使えるようになります。
お好きな所にドッキングさせて使って下さい。デスクトップの保存
をするとドック状態が維持されます。
アンインストール
アンインストールは [コンポーネント | パッケージのインストール...]
から IDE Docking Memo Form
を探して削除してください。
おわりに
不満な所があったらご自由に改変してお使いください。
バグ修正コードやリネーム対応コードをお待ちしております (w
See also: