Delphi Starter Edition でメモ帳クローンを作る - 2nd Edition - [編集(E)]（2）

# // これは...

これは [Delphi Advent Calendar 2017](https://qiita.com/advent-calendar/2017/delphi) 15 日目の記事です。
長くなってしまったので機能単位で分割しています。

## [編集(E)]
[編集(E)] メニューの残りをやりましょう。


### [行へ移動(G)]
このメニューは、

 - 別ダイアログで数字を入力する (数字以外は入力できない)。
 - ダイアログはモーダル
 - 総行数を超える位置にはジャンプできない。
 - 初期値は現在行

という処理を行う必要があります。本物のメモ帳ではこうなっています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/348995e0-1469-ab2f-f3ac-762c855038c2.png)
 
つまり、この **数字入力画面は自前で作る** しかありません。

Delphi で別のフォームを作るには [ファイル | 新規作成 | VCL フォーム - Delphi] を選びます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/02cd97d7-c2d4-2891-5733-f791826f4194.png)

新しい "空のフォーム付きのユニット" が作られました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/011a69a3-0966-c3fd-fa44-61124c6471ec.png)

Unit2.pas という名前になっていますので〔F2〕を押して名前を frmuGotoLine に変更します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/cc464052-f653-1904-eb5f-ab11764769f3.png)

マウスで二回ゆっくりクリックしても名前を変更できます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/ef1b77a2-9f87-e100-c442-31c3d4c5c43e.png)

新規作成されたフォームは自動生成の対象となっています。今回、このフォームはダイアログとして "呼び出される度に生成する" 事にしますので、[プロジェクト | オプション] で自動生成から外します。 

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/e93e3960-3430-7b04-09b9-c5c2f8900556.png)

自動生成から外したら、フォーム (**Form2**) のプロパティを変更します。



|プロパティ|値|説明|
|:-----------------|:------------------|:------------------|
|BorderICons|**[biSystemMenu]**|フォーム右上のアイコンです。?ボタンだけにします。|
|BorderStyle|**bsSingle**|サイズ変更のできないフォームにします。|
|Caption|**行へ移動**|フォームのキャプションバーのキャプションです。|
|ClientHeight|**113**|フォームのクライアント領域の高さです。|
|ClientWidth|**291**|フォームのクライアント領域の幅です。|
|Font.Name|**Tahoma**|フォームで使われるデフォルトのフォント名です。|
|Font.Size|**9**|フォームで使われるデフォルトのフォントサイズです。|
|Position|**poMainFormCenter**|フォームが表示される位置です。親ウィンドウの中心に表示します|

こんな見た目になったと思います。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/de342484-858e-4049-778c-3213dde71666.png)

フォームのサイズを Width / Height プロパティで設定しないのは、OS によってフォームの Width / Height は変化するからです。このような場合、キャプションバーとフレームを抜いた**クライアント領域**のサイズで指定する必要があります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/e861f1a1-ed53-8d95-87c1-fbd0faccd121.png)

さて、このフォームに以下のコントロールを貼り付けましょう。

 - TLabel (ラベル)
 - TEdit (エディットボックス)
 - TButton (ボタン) x 2

すべてツールパレットの [Standard] タブにあります。 そして大体の位置に置いてください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/5a40f8b9-f0cc-11a2-ce96-2251678a16ce.png)

TLabel (**Lablel1**) のプロパティを設定します。

|プロパティ|値|説明|
|:-----------------|:------------------|:------------------|
|Caption|**行番号(&L):**|ラベルのキャプションです。アクセラレータが設定されています。|
|FocusedControl|**Edit1**|ラベルのアクセラレータキーが押された時にフォーカス移動するコントロールです。|
|Left|**12**|ラベルの左位置です。|
|Top|**12**|ラベルの上位置です。|

TEdit (**Edit1**) のプロパティを設定します。

|プロパティ|値|説明|
|:-----------------|:------------------|:------------------|
|AutoSize|**False**|エディットコントロールの高さを自動で調整しないようにします。|
|Height|**26**|エディットの左位置です。|
|Left|**12**|エディットの左位置です。|
|NumbersOnly|**True**|数値以外の入力を許可しません。|
|TabOrder|**0**|タブキーでコントロールが移動する順序です。|
|Top|**32**|エディットの上位置です。|
|Width|**264**|エディットの幅です。|

TButton (**Button1**) のプロパティを設定します。

|プロパティ|値|説明|
|:-----------------|:------------------|:------------------|
|Caption|**移動**|ボタンのキャプションです。|
|Defalut|**True**|〔Enter〕キーが押されたらこのボタンを押した事にします。|
|Height|**26**|ボタンの左位置です。|
|Left|**96**|ボタンの左位置です。|
|ModalResult|**mrOK**|モーダルなフォームでボタンが押された時の戻り値です。|
|TabOrder|**1**|タブキーでコントロールが移動する順序です。|
|Top|**73**|ボタンの上位置です。|
|Width|**88**|ボタンの幅です。|

TButton (**Button2**) のプロパティを設定します。

|プロパティ|値|説明|
|:-----------------|:------------------|:------------------|
|Cancel|**True**|〔Esc〕キーが押されたらこのボタンを押した事にします。|
|Caption|**キャンセル**|ボタンのキャプションです。|
|Height|**26**|ボタンの左位置です。|
|Left|**191**|ボタンの左位置です。|
|ModalResult|**mrCancel**|モーダルなフォームでボタンが押された時の戻り値です。|
|TabOrder|**2**|タブキーでコントロールが移動する順序です。|
|Top|**73**|ボタンの上位置です。|
|Width|**88**|ボタンの幅です。|

次に TForm2 に GotoLine プロパティと LineCount プロパティを追加します。プロパティのスコープは public にし、他のフォームからも参照できるようにしておきます。

```pascal:frmuGotoLine.pas
unit frmuGotoLine;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm2 = class(TForm)
    Label1: TLabel;
    Edit1: TEdit;
    Button1: TButton;
    Button2: TButton;
  private
    { Private 宣言 }
    FGotoLine: Integer;                                           // <-- 追加
    FLineCount: Integer;                                          // <-- 追加
    procedure SetGotoLine(Value: Integer);                        // <-- 追加
  public
    { Public 宣言 }
    property GotoLine: Integer read FGotoLine write SetGotoLine;  // <-- 追加
    property LineCount: Integer read FLineCount write FLineCount; // <-- 追加
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.SetGotoLine(Value: Integer);
// SetGotoLine プロパティのセッター
begin
  FGotoLine := Value;
  Edit1.Text := FGotoLine.ToString;
end;

end.
```

|プロパティ|説明|
|:-----------------|:------------------|
|GotoLine |移動先の行を格納します。|
|LineCount|ここにメモ帳の最大行数を渡すようにします。|


次に Form2 の OnCloseQuery イベントにイベントハンドラを生成します。オブジェクトインスペクタで値の部分をダブルクリックします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/c96d2e14-c741-c910-d786-6a0414b7c224.png)

このイベントはフォームを閉じようとしたときに発生し、パラメータ CanClose に False を設定するとフォームを閉じれなくする事ができます。このイベントハンドラでは、指定した行が 0 以下だったり最大行を超えていたりした場合にはエラーメッセージを出して閉じれなくします。以下コードです。

```pascal:frmuGotoLine.pas
unit frmuGotoLine;

interface

uses
  ..., System.UITypes; // <- 追加

...

procedure TForm2.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
// フォーム修了確認時
var
  dGoToLine: Integer;
begin
  // mrOK でないボタンが押された場合には処理しない
  if ModalResult <> mrOK then
    begin
      CanClose := True;
      Exit;
    end;
  dGoToLine := StrToInt(Edit1.Text);
  CanClose := (dGoToLine > 0) and (dGoToLine <= LineCount);
  if CanClose then
    begin
      // 移動可能
      FGotoLine := dGoToLine;
    end
  else
    begin
      // 移動不可
      MessageDlg('指定した行番号は行の総数を超えています', TMsgDlgType.mtCustom, [TMsgDlgBtn.mbOK], -1);
      GotoLine := FGotoLine;
      Edit1.SelectAll;
      Edit1.SetFocus;
    end;
end;
```
[行へ移動] 用のダイアログは完成したので、これをメインウィンドウから開きます。frmuMain のタブに移動し、コードエディタにします。**implementation** の下に **uses** を記述します。ここにユニット名 (ユニットファイル名から拡張子を抜いたもの) を記述する事で参照できるようになります。

```pascal:frmuMain.pas
...
implementation

{$R *.dfm}

uses            // <-- 追加
  frmuGotoLine; // <-- 追加

procedure TForm1.FormCreate(Sender: TObject);
...
```


今度は フォームエディタにして acGoTo アクションのイベントハンドラを作ります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/a0adb879-798f-d0dd-d772-82a98e5aec68.png)

acGoToExecute イベントハンドラの実装は以下のようになります。

```pascal:frmuMain.pas
procedure TForm1.acGoToExecute(Sender: TObject);
// Action: 行へ移動(G)
begin
  Form2 := TForm2.Create(Self);
  try
    // 現在行と最大行を Form2 に渡す
    Form2.GotoLine := Memo1.Perform(EM_LINEFROMCHAR, WPARAM(-1), 0) + 1; // 0 オリジンなので + 1 する
    Form2.LineCount := Memo1.Perform(EM_GETLINECOUNT, 0, 0);
    // Form2 をモーダルウィンドウで表示
    if Form2.ShowModal = mrOK then
      begin
        // [OK] ボタンが押されたら
        Memo1.SelLength := 0;
        Memo1.SelStart := Memo1.Perform(EM_LINEINDEX, Form2.GotoLine - 1, 0);
        Memo1.Perform(EM_SCROLLCARET,0 , 0);
      end;
  finally
    Form2.Free;
  end;
end;
```
モーダルウィンドウというのはこのウィンドウを処理しない限り親ウィンドウを操作できないものを言います。エラーメッセージのウィンドウなんかがそうですね。

Perform() は Windows API の SendMessage() と同じものと考えてください。

 - [SendMessage (Microsoft Developer Network)](https://msdn.microsoft.com/ja-jp/library/cc411022.aspx)

メモコントロールに送っているメッセージの詳細は以下にあります。

 - [Edit Control Messages (Windows Dev Center)](https://msdn.microsoft.com/en-us/library/windows/desktop/ff485923(v=vs.85).aspx)
 - [Delphi Tips - メモコンポーネントとメッセージ (Embarcadero SupportKB)](http://support.embarcadero.com/jp/article/35953)

ここでやっている事は Form2 のインスタンスを生成し、ダイアログ (フォーム) を Showmodal() メソッドでモーダルで表示、[OK] ボタンが押されたら Showmodal() メソッドは mrOK を返すので、移動可能な行位置であればそこへ移動する...というものです。

最終的にはこのようなダイアログになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/274382ed-d76d-2030-5b55-cd5a030044ba.png)

〔Alt〕,〔E〕,〔G〕や〔Ctrl〕+〔G〕で [行へ移動(G)...] が実行される事を確認してください。


### [すべて選択(A)]
このメニューは、

 - 文字をすべて選択。

という処理を行う必要があります。TMemo に SelectAll() メソッドがあるのでこれを呼べばいいです。 acSelectAll アクションのイベントハンドラを作ります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/fdfa9a67-e7f1-afa8-7808-811852baae53.png)

イベントハンドラは以下のようになります。

```pascal:frmuMain.pas
procedure TForm1.acSelectAllExecute(Sender: TObject);
// Action: すべて選択(A)
begin
  Memo1.SelectAll;
end;
```

〔Alt〕,〔E〕,〔A〕や〔Ctrl〕+〔A〕で [すべて選択(A)] が実行される事を確認してください。

### [日付と時刻(D)]
このメニューは、

 - カーソル位置に日付文字列を挿入

という処理を行う必要があります。正確には、

 - 選択文字列を日付文字列で置換

です。acTimeDate アクションのイベントハンドラを作ります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/93c169aa-6876-fc5d-06a4-dc203bc151b2.png)

イベントハンドラは以下のようになります。

```pascal:frmuMain.pas
procedure TForm1.acTimeDateExecute(Sender: TObject);
// Action: 日付と時刻(D)
begin
  Memo1.SelText := FormatDateTime('hh:nn yyyy/mm/dd', Now);
end;
```

 - [System.SysUtils.FormatDateTime (DocWiki)](http://docwiki.embarcadero.com/Libraries/Tokyo/ja/System.SysUtils.FormatDateTime)
 - [System.SysUtils.Now (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.Now)

〔Alt〕,〔E〕,〔D〕や〔F5〕で [日付と時刻(D)] が実行される事を確認してください。

# // 戻る

今回はここまでです。ここまでの全ソースを掲載しておきます。

[メインフォーム]

```pascal:frmuMain.pas
unit frmuMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Actions,
  Vcl.ActnList, Vcl.Menus, Vcl.ExtDlgs, System.IOUtils, Vcl.Clipbrd,
  System.RegularExpressions, System.UITypes;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    MainMenu1: TMainMenu;
    ActionList1: TActionList;
    acNew: TAction;
    acOpen: TAction;
    acSave: TAction;
    acSaveAs: TAction;
    acPageSetup: TAction;
    acPrint: TAction;
    acExit: TAction;
    F1: TMenuItem;
    N1: TMenuItem;
    O1: TMenuItem;
    S1: TMenuItem;
    A1: TMenuItem;
    U1: TMenuItem;
    P1: TMenuItem;
    X1: TMenuItem;
    N2: TMenuItem;
    N3: TMenuItem;
    OpenTextFileDialog1: TOpenTextFileDialog;
    SaveTextFileDialog1: TSaveTextFileDialog;
    acUndo: TAction;
    acCut: TAction;
    acCopy: TAction;
    acPaste: TAction;
    acDelete: TAction;
    acFind: TAction;
    acFindNext: TAction;
    acReplace: TAction;
    acGoTo: TAction;
    acSelectAll: TAction;
    acTimeDate: TAction;
    E1: TMenuItem;
    U2: TMenuItem;
    N4: TMenuItem;
    C1: TMenuItem;
    T1: TMenuItem;
    P2: TMenuItem;
    L1: TMenuItem;
    N5: TMenuItem;
    F2: TMenuItem;
    N6: TMenuItem;
    R1: TMenuItem;
    G1: TMenuItem;
    N7: TMenuItem;
    A2: TMenuItem;
    D1: TMenuItem;
    FindDialog1: TFindDialog;
    ReplaceDialog1: TReplaceDialog;
    procedure FormCreate(Sender: TObject);
    procedure acExitExecute(Sender: TObject);
    procedure acOpenExecute(Sender: TObject);
    procedure acSaveExecute(Sender: TObject);
    procedure acSaveAsExecute(Sender: TObject);
    procedure acNewExecute(Sender: TObject);
    procedure acUndoExecute(Sender: TObject);
    procedure ActionList1Update(Action: TBasicAction; var Handled: Boolean);
    procedure acCutExecute(Sender: TObject);
    procedure acCopyExecute(Sender: TObject);
    procedure acPasteExecute(Sender: TObject);
    procedure E1Click(Sender: TObject);
    procedure acFindExecute(Sender: TObject);
    procedure Dialog_Find(Sender: TObject);
    procedure acFindNextExecute(Sender: TObject);
    procedure acDeleteExecute(Sender: TObject);
    procedure acReplaceExecute(Sender: TObject);
    procedure ReplaceDialog1Replace(Sender: TObject);
    procedure acGoToExecute(Sender: TObject);
    procedure acSelectAllExecute(Sender: TObject);
    procedure acTimeDateExecute(Sender: TObject);
  private
    { Private 宣言 }
    FFileName: String;
    FEncodingIndex: Integer;
    procedure Init;
    procedure SaveFile;
    procedure UpdateCaption;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  frmuGotoLine;

procedure TForm1.FormCreate(Sender: TObject);
// フォーム作成時
begin
  Init;
end;

// [FILE] Actions

procedure TForm1.acNewExecute(Sender: TObject);
// Action: 新規(N)
begin
  Init;
end;

procedure TForm1.acOpenExecute(Sender: TObject);
// Action: 開く(O)...
var
  Enc: TEncoding;
begin
  if OpenTextFileDialog1.Execute then
    begin
      // 値を保存
      FFileName := OpenTextFileDialog1.FileName;
      FEncodingIndex := OpenTextFileDialog1.EncodingIndex;
      // キャプションを変更
      UpdateCaption;
      // EncodingIndex によりエンコーディングを指定
      case FEncodingIndex of
        1: Enc := TEncoding.Unicode;
        2: Enc := TEncoding.BigEndianUnicode;
        3: Enc := TEncoding.UTF8;
      else
        Enc := TEncoding.Default;
      end;
      // エンコーディングを指定して読み込み
      Memo1.Lines.LoadFromFile(FFileName, Enc);
    end;
end;

procedure TForm1.acSaveExecute(Sender: TObject);
// Action: 上書き保存(S)
begin
  if (FFileName = '') then
    begin
      // 名前を付けて保存
      acSaveAs.Execute;
    end
  else
    begin
      // 上書き保存
      SaveFile;
    end;
end;

procedure TForm1.acSaveAsExecute(Sender: TObject);
// Action: 名前を付けて保存(A)...
begin
  if SaveTextFileDialog1.Execute then
    begin
      // 値を保存
      FFileName := SaveTextFileDialog1.FileName;
      FEncodingIndex := SaveTextFileDialog1.EncodingIndex;
      // キャプションを変更
      UpdateCaption;
      // ファイルへ保存
      SaveFile;
    end;
end;

procedure TForm1.acExitExecute(Sender: TObject);
// Action: メモ帳の終了(X)
begin
  Self.Close;
end;

// [EDIT] Actions

procedure TForm1.E1Click(Sender: TObject);
begin
  acDelete.ShortCut := TextToShortCut('Del');
end;

procedure TForm1.acUndoExecute(Sender: TObject);
// Action: 元に戻す(U)
begin
  Memo1.Undo;
end;

procedure TForm1.acCutExecute(Sender: TObject);
// Action: 切り取り(T)
begin
  Memo1.CutToClipboard;
end;

procedure TForm1.acCopyExecute(Sender: TObject);
// Action: コピー(C)
begin
  Memo1.CopyToClipboard;
end;

procedure TForm1.acPasteExecute(Sender: TObject);
// Action: 貼り付け(P)
begin
  Memo1.PasteFromClipboard;
end;

procedure TForm1.acDeleteExecute(Sender: TObject);
// Action: 削除(L)
begin
  Memo1.ClearSelection;
end;

procedure TForm1.acFindExecute(Sender: TObject);
// Action: 検索(F)
begin
  // 検索ダイアログを開く
  FindDialog1.Execute;
end;

procedure TForm1.acFindNextExecute(Sender: TObject);
// Action: 次を検索(N)
begin
  if FindDialog1.FindText = '' then
    acFind.Execute
  else
    Dialog_Find(FindDialog1);
end;

procedure TForm1.acReplaceExecute(Sender: TObject);
// Action: 置換(H)
begin
  // 置換ダイアログを開く
  ReplaceDialog1.Execute;
end;

procedure TForm1.Dialog_Find(Sender: TObject);
// 検索イベントハンドラ
var
  RegEx: TRegEx;
  Exp, Msg: String;
  Options: TRegExOptions;
  Match, dMatch: TMatch;
  StartPos, EndPos: Integer;
begin
  // 検索文字列を正規表現文字列へエスケープ
  Exp := TRegEx.Escape(FindDialog1.FindText);

  // "大文字と小文字を区別する" にチェックが入っていなければ
  // オプションに roIgnoreCase (大文字小文字を区別しない) を追加
  if not (frMatchCase in FindDialog1.Options) then
    Include(Options, roIgnoreCase);

  // 正規表現オブジェクトの生成
  RegEx := TRegEx.Create(Exp, Options);

  if (frDown in FindDialog1.Options) then
    begin
      // 順方向検索 (下へ)
      StartPos  := Memo1.SelStart + Memo1.SelLength + 1;
      Match := RegEx.Match(Memo1.Lines.Text, StartPos);
    end
  else
    begin
      // 逆方向検索 (上へ)
      StartPos := 0;
      EndPos   := Memo1.SelStart + 1;
      Match := RegEx.Match(Memo1.Lines.Text, StartPos, EndPos);
      while Match.Success do
        begin
          // 最後にマッチした箇所を探す
          dMatch := Match.NextMatch;
          if dMatch.Success then
            Match := dMatch
          else
            break;
        end;
    end;

  // 検索文字列を共有
  FindDialog1.FindText := (Sender as TFindDialog).FindText;
  ReplaceDialog1.FindText := (Sender as TFindDialog).FindText;

  // 検索文字列を正規表現文字列へエスケープ
  Exp := TRegEx.Escape((Sender as TFindDialog).FindText);

  // "大文字と小文字を区別する" にチェックが入っていなければ
  // オプションに roIgnoreCase (大文字小文字を区別しない) を追加
  if not (frMatchCase in (Sender as TFindDialog).Options) then
    Include(Options, roIgnoreCase);

  // 正規表現オブジェクトの生成
  RegEx := TRegEx.Create(Exp, Options);
  if (frDown in (Sender as TFindDialog).Options) then
    begin
      // 順方向検索 (下へ)
      StartPos  := Memo1.SelStart + Memo1.SelLength + 1;
      Match := RegEx.Match(Memo1.Lines.Text, StartPos);
    end
  else
    begin
      // 逆方向検索 (上へ)
      StartPos := 0;
      EndPos   := Memo1.SelStart + 1;
      Match := RegEx.Match(Memo1.Lines.Text, StartPos, EndPos);
      while Match.Success do
        begin
          // 最後にマッチした箇所を探す
          dMatch := Match.NextMatch;
          if dMatch.Success then
            Match := dMatch
          else
            break;
        end;
    end;

  if Match.Success then
    begin
      // 一致する文字列があった
      SetFocus;
      Memo1.SelStart := Match.Index - 1;
      Memo1.SelLength := Match.Length;
    end
  else
    begin
      // 一致する文字列がなかった
      Msg := Format('"%s" が見つかりません。', [(Sender as TFindDialog).FindText]);
      MessageDlg(Msg, TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK], -1);
    end;
end;

procedure TForm1.ReplaceDialog1Replace(Sender: TObject);
// 置換イベントハンドラ
var
  srFlgs: TReplaceFlags;
  FindText: string;
  SelectFlg: Boolean;
begin
  // 検索文字列を共有
  FindDialog1.FindText := (Sender as TFindDialog).FindText;
  ReplaceDialog1.FindText := (Sender as TFindDialog).FindText;

  // フラグ
  if (frReplaceAll in ReplaceDialog1.Options) then
    begin
      // [すべて置換]
      srFlgs := [rfReplaceAll]; // すべて置換
      if  not (frMatchCase in ReplaceDialog1.Options) then
        Include(srFlgs, rfIgnoreCase);
      Memo1.Lines.Text := StringReplace(Memo1.Lines.Text, ReplaceDialog1.FindText, ReplaceDialog1.ReplaceText, srFlgs);
    end
  else
    begin
      // [置換して次へ]
      srFlgs := []; // 最初の一回だけ置換
      if  not (frMatchCase in ReplaceDialog1.Options) then
        Include(srFlgs, rfIgnoreCase);
      // 検索済みかどうかの判定
      FindText := (Sender as TFindDialog).FindText;
      if (frMatchCase in ReplaceDialog1.Options) then
        SelectFlg := SameStr(Memo1.SelText, FindText)   // 大文字小文字を区別する
      else
        SelectFlg := SameText(Memo1.SelText, FindText); // 大文字小文字を区別しない
      // 検索済みか？
      if SelectFlg then
        Memo1.SelText := ReplaceDialog1.ReplaceText;
      // 検索
      Dialog_Find(ReplaceDialog1);
    end;
end;

procedure TForm1.acGoToExecute(Sender: TObject);
// Action: 行へ移動(G)
begin
  Form2 := TForm2.Create(Self);
  try
    // 現在行と最大行を Form2 に渡す
    Form2.GotoLine := Memo1.Perform(EM_LINEFROMCHAR, WPARAM(-1), 0) + 1; // 0 オリジンなので + 1 する
    Form2.LineCount := Memo1.Perform(EM_GETLINECOUNT, 0, 0);
    // Form2 をモーダルで表示
    if Form2.ShowModal = mrOK then
      begin
        // [OK] ボタンが押されたら
        Memo1.SelLength := 0;
        Memo1.SelStart := Memo1.Perform(EM_LINEINDEX, Form2.GotoLine - 1, 0);
        Memo1.Perform(EM_SCROLLCARET,0 , 0);
      end;
  finally
    Form2.Free;
  end;
end;

procedure TForm1.acSelectAllExecute(Sender: TObject);
// Action: すべて選択(A)
begin
  Memo1.SelectAll;
end;

procedure TForm1.acTimeDateExecute(Sender: TObject);
// Action: 日付と時刻(D)
begin
  Memo1.SelText := FormatDateTime('hh:nn yyyy/mm/dd', Now);
end;

// リスト更新

procedure TForm1.ActionList1Update(Action: TBasicAction; var Handled: Boolean);
begin
  // [元に戻す(U)] の有効/無効
  acUndo.Enabled := Memo1.CanUndo;
  // [切り取り(T)] の有効/無効
  acCut.Enabled := Memo1.SelLength <> 0;
  // [コピー(C)] の有効/無効
  acCopy.Enabled := Memo1.SelLength <> 0;
  // [貼り付け(P)] の有効/無効
  acPaste.Enabled := ClipBoard.HasFormat(CF_TEXT);
  // [削除(L)] の有効/無効
  acDelete.Enabled := Memo1.SelLength <> 0;
  acDelete.ShortCut := 0;
  // [検索(F)] の有効/無効
  acFind.Enabled := Memo1.Lines.Text.Length > 0;
  // [次を検索(N)] の有効/無効
  acFindNext.Enabled := Memo1.Lines.Text.Length > 0;
end;

// メソッド

procedure TForm1.Init;
// エディタの初期化
begin
 // 値を初期化
  FFileName := '';
  FEncodingIndex := 0;
  Memo1.Lines.Clear; // Memo1 の中身を消去する。
  // キャプションを変更
  UpdateCaption;
end;

procedure TForm1.SaveFile;
var
  Enc: TEncoding;
begin
  // EncodingIndex によりエンコーディングを指定
  case FEncodingIndex of
    1: Enc := TEncoding.Unicode;
    2: Enc := TEncoding.BigEndianUnicode;
    3: Enc := TEncoding.UTF8;
  else
    Enc := TEncoding.Default;
  end;
  // エンコーディングを指定して保存
  Memo1.Lines.SaveToFile(FFileName, Enc);
end;

procedure TForm1.UpdateCaption;
// キャプション (ファイル名) の更新
var
  Dmy: String;
begin
  if FFileName = '' then
    Dmy := '無題'
  else
    Dmy := TPath.GetFileName(FFileName);
  Self.Caption := Dmy + ' - メモ帳クローン';
end;

end.
```
[行へ移動] ダイアログ

```pascal:frmuGotoLine.pas
unit frmuGotoLine;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.UITypes;

type
  TForm2 = class(TForm)
    Label1: TLabel;
    Edit1: TEdit;
    Button1: TButton;
    Button2: TButton;
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  private
    { Private 宣言 }
    FGotoLine: Integer;                                           // <-- 追加
    FLineCount: Integer;                                          // <-- 追加
    procedure SetGotoLine(Value: Integer);                        // <-- 追加
  public
    { Public 宣言 }
    property GotoLine: Integer read FGotoLine write SetGotoLine;  // <-- 追加
    property LineCount: Integer read FLineCount write FLineCount; // <-- 追加
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.SetGotoLine(Value: Integer);
// SetGotoLine プロパティのセッター
begin
  FGotoLine := Value;
  Edit1.Text := FGotoLine.ToString;
end;

procedure TForm2.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
// フォーム修了確認時
var
  dGoToLine: Integer;
begin
  // mrOK でないボタンが押された場合には処理しない
  if ModalResult <> mrOK then
    begin
      CanClose := True;
      Exit;
    end;
  dGoToLine := StrToInt(Edit1.Text);
  CanClose := (dGoToLine > 0) and (dGoToLine <= LineCount);
  if CanClose then
    begin
      // 移動可能
      FGotoLine := dGoToLine;
    end
  else
    begin
      // 移動不可
      MessageDlg('指定した行番号は行の総数を超えています', TMsgDlgType.mtCustom, [TMsgDlgBtn.mbOK], -1);
      GotoLine := FGotoLine;
      Edit1.SelectAll;
      Edit1.SetFocus;
    end;
end;

end.
```

[[前の記事へ]](https://qiita.com/ht_deko/private/f2f330ebdcb3f9f49e96) [[親記事へ]](https://qiita.com/ht_deko/items/525cd67aaba16681c3ba) [[次の記事へ]](https://qiita.com/ht_deko/private/4309e5fc56419a8aa05d)