Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2012年05月08日] [更新 2012年08月24日] Delphi サンプルプログラム集
673_自アプリ内の TRichEdit のリッチテキスト ( RTF ) 送受信
動作確認等 Windows XP(SP3) + Delphi XE Pro
Windows 7 U64(SP1) + Delphi XE Pro
673_RichEdit_Stream.zip [1,916 KB] 2012年08月24日版 (EXE 同梱)



  • 2012年08月24日
  • STREAMINを使用するコードを追加




自アプリ内でのリッチテキストの送受信


自アプリ,同じアプリケーション内の TRichEdit のテキストの全体または一部を,書式付き,つまりリッチテキストとして送信または受信するサンプルです.

テキストのみのコピーや貼り付けは,プロパティやメソッドでも操作可能です.書式付のコピーや貼り付けも専用のメソッドがあります.EM_XXX 関係のメッセージでも処理できます.
本ページのサンプルは,主にストリームを使用したコードとなっています.





01_TRichEdit のリッチテキストのコード


RTF 形式のテキストを,メモリストリームに保存し,それを TRichEdit で読み込むと書式付きのテキストとして表示することができます.この確認のテストです.もちろん,拡張子が RTF のファイルを,直接 TRichEdit に読み込めば書式付きのテキストとして表示できます.


図1
設計時画面
図2
[Button1] クリック
  • TMemo に RTF ファイルを読み込む
  • RTF 形式の文字列が表示される
図3
[Button2] クリック
  • TMemo の文字列をメモリストリームに格納
  • TRichEdit で,そのメモリストリームを読み込むと,書式付きテキストとして表示される

リスト1
RTF 形式のテキストの表示
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    Splitter1: TSplitter;
    RichEdit1: TRichEdit;
    OpenDialog1: TOpenDialog;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    InitDir : String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.ScrollBars        := ssVertical;
  RichEdit1.ScrollBars    := ssBoth;
  RichEdit1.HideSelection := False;
end;

//=============================================================================
//  [Button1]クリック
//  リッチテキスト形式のテキストをTMemoに読み込んで表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AFilePath : String;
begin
  OpenDialog1.Filter := 'RTFファイル|*.RTF';
  if InitDir = '' then begin
    OpenDialog1.InitialDir := ExtractFileDir(Application.ExeName);
  end;
  if OpenDialog1.Execute then begin
    AFilePath := OpenDialog1.FileName;
    Memo1.Lines.LoadFromFile(AFilePath);
  end;
end;

//=============================================================================
//  [Button2]クリック
//  Memo1に表示しているリッチテキストのコードをストリームに格納
//  RichEditで,そのストリームを読み込むと,書式付きのテキストとして表示される
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  AStream : TMemoryStream;
begin
  AStream := TMemoryStream.Create;
  try
    Memo1.Lines.SaveToStream(AStream);

    RichEdit1.Clear;
    AStream.Position := 0;
    RichEdit1.Lines.LoadFromStream(AStream);
  finally
    AStream.Free;
  end;
end;

end.




02_EM_STREAMOUT と EM_STREAMIN メッセージによる送受信


TRichEdit のリッチテキストを,他の TRichEdit のリッチテキストとして送信,転送します.
EM_STREAMOUT メッセージを使用します.このメッセージのコールバック関数内で送信元のリッチテキストをメモリストリームに格納します.受信側の TRichEditでは,EM_STREAMIN メッセージで,このメモリストリームを読み込みます.

クリップボードは使用しません.選択範囲だけの転送や,受信側では,挿入モードで受信可能なようにしてみました.
もし,常に全てのリッチテキストを転送するのであれば,EM_STREAMOUT メッセージを使用する必要はあません.また,受信する TRichEdit 側も,全てを受け取り,上書きモードでいいのであれば,次のコードのように,単にメモリストリームを仲介するだけで済みます.必要に応じて CopyFrom 等の関数類も使用できます.

リスト2
リッチテキストを他の TRichEdit に代入
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
 FStream : TMemoryStream;
begin
  FStream := TMemoryStream.Create;
  try
    //RichEdit1のリッチテキストをFStreamに格納
    FStream.Position := 0;
    RichEdit1.Lines.SaveToStream(FStream);

    //FStreamの内容をRichEdit2に読み込む
    FStream.Position := 0;
    RichEdit2.Lines.LoadFromStream(FStream);
  finally
    FreeAndNil(FStream);
  end;
end;


図4
設計時画面
  • 上が送信元の RichEdit
  • 下が受信側の RichEdit
図5
[読み込み] ボタンをクリック
  • RTF ファイルを選択して表示
  • 図は添付の RTF ファイルを開いたところ
図6
[転送] ボタンをクリック
  • 下の RichEdit に全て転送される
  • [挿入で受信] をチェックすると,受信側では,受信した RTF 文書を現在のカーソル位置に挿入することができる
  • 送信側の選択部分だけを挿入することもできる
図7
選択範囲の転送
  • 任意の範囲を選択
  • [転送] ボタンをクリック
  • 選択範囲だけが,下の RichEdit に表示

このサンプルでは,複数のリッチテキストを結合するテストもできます.
通常,リッチテキストの文書は,単純にストリームに続けて格納しただけでは,複数のリッチテキストをまとめて 1 つの文書にはできません.
  • (1) [読み込み] ボタンでリッチテキストの文書を読み込む
  • (2) [転送] ボタンをクリックすると,下の TRichEdit にその文書が表示される
  • (3) [読み込み] ボタンで,別のリッチテキスト文書を読み込む
  • (4) [挿入で受信] をチェックして,[転送] ボタンをクリックする
の手順で操作します.
下の TRichEdit の文書が 1 つの文書となっていることが確認できます.もちろん,選択部分だけを挿入することもできます.
最初にリッチテキスト文書を送信すると,受信側の入力位置,つまりカーソルは,文書の最後尾になります.ここが挿入位置となります.


リスト3
EM_STREAMOUT と EM_STREAMIN メッセージを使用したリッチテキストの送受信
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls, StdActns, Buttons;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    RichEdit1: TRichEdit;
    Splitter1: TSplitter;
    RichEdit2: TRichEdit;
    OpenDialog1: TOpenDialog;
    SpeedButton1: TSpeedButton;
    CheckBox1: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
  private
    { Private 宣言 }
    InitDir : String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses RichEdit;

{$R *.dfm}

var
  FStream    : TMemoryStream;
  FStreamRec : TEditStream;

//-----------------------------------------------------------------------------
//  EM_STREAMINメッセージで使用するコールバック関数
//  FStreamからリッチテキストのデータを取得
//  このコールバック関数は,複数回実行されることがある(データサイズによる)
//-----------------------------------------------------------------------------
function EditStreamCallBackStreamIn(dwCookie: Longint; pbBuff: PByte;
    cb: Longint; var pcb: Longint): Longint; stdcall;
begin
  try
    pcb := FStream.Read(pbBuff^, cb);
    Result:= 0;
  except
    Result := 1;
  end;
end;

//-----------------------------------------------------------------------------
//  EM_STREAMOUTメッセージで使用するコールバック関数
//  FStreamにリッチテキストのデータが格納される
//  このコールバック関数は,複数回実行されることがある(データサイズによる)
//-----------------------------------------------------------------------------
function EditStreamCallBackStreamOut(dwCookie: Longint; pbBuff: PByte;
    cb: Longint; var pcb: Longint): Longint; stdcall;
begin
  try
    pcb := FStream.Write(pbBuff^, cb);
    Result:= 0;
  except
    Result := 1;
  end;
end;

//=============================================================================
//  フォーム生成時の処理
//  リッチテキストデータ格納用のTMemoryStreamを作成
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  RichEdit1.ScrollBars    := ssBoth;
  RichEdit2.ScrollBars    := ssBoth;
  RichEdit1.HideSelection := False;
  RichEdit2.HideSelection := False;

  FStream := TMemoryStream.Create;
end;

//=============================================================================
//  フォームのOnDestroyイベント処理
//  リッチテキストデータ格納用のTMemoryStreamを破棄
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FStream);
end;

//=============================================================================
//  [読み込み]ボタンクリック
//  リッチテキスト形式のテキストをRichEdit1に読み込んで表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AFilePath : String;
begin
  OpenDialog1.Filter := 'RTFファイル|*.RTF';
  if InitDir = '' then begin
    OpenDialog1.InitialDir := ExtractFileDir(Application.ExeName);
  end;
  if OpenDialog1.Execute then begin
    AFilePath := OpenDialog1.FileName;
    RichEdit1.Lines.LoadFromFile(AFilePath);
    RichEdit1.SetFocus;
  end;
end;

//=============================================================================
//  [転送]ボタン
//
//  RichEdit1の内容をRichEdit2へ送信
//  受信側でEM_STREAMINメッセージを使用してFStreamから取得するコード
//  FStreamRec(TEditStream)の使用には,usesにRichEditが必要
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  Flags : Integer;
begin
  //選択中のテキストがあれば,選択部分のみ送信
  if RichEdit1.SelText = '' then begin
    Flags := SF_RTF;
  end else begin
    Flags := SF_RTF or SFF_SELECTION;
  end;

  //送り側のリッチテキストをEM_STREAMOUTメッセージを使用してFStreamに格納
  FStream.Clear;
  FStreamRec.pfnCallback := @EditStreamCallBackStreamOut;
  //RichEdit1.Perform(EM_STREAMOUT, Flags, LPARAM(@FStreamRec));でも同じ
  SendMessage(RichEdit1.Handle, EM_STREAMOUT, Flags, LPARAM(@FStreamRec));


  //受信側のリッチテキストでEM_STREAMINメッセージを使用してFStreamから取得
  //メモリストリームの位置を先頭に
  FStream.Position := 0;

  //フラグの設定
  //SFF_SELECTIONは挿入モードで受け取る設定
  if CheckBox1.Checked then begin
    Flags := SF_RTF or SFF_SELECTION;
  end else begin
    Flags := SF_RTF;
  end;
  FStreamRec.pfnCallback := @EditStreamCallBackStreamIn;
  //RichEdit2.Perform(EM_STREAMIN, Flags, LPARAM(@FStreamRec));でも同じ
  SendMessage(RichEdit2.Handle, EM_STREAMIN, Flags, LPARAM(@FStreamRec));
end;

end.


もし,送信側のリッチテキストの全てを受信側に送るのであれば,EM_STREAMOUT メッセージを使用する必要はありません.リスト2 のように単にメモリストリームに格納するだけです
次のコードは,送信側の全てのリッチテキスト文書を,受信側で挿入モードで受け取ります.したがって,メモリストリームに格納する送信側のリッチテキストを変更して,ループ処理すれば,複数のリッチテキスト文書を結合することができます.FStream (TMemoryStream) はグローバル変数にしておく必要があります.

リスト4
送信側の全てのリッチテキストを送信し,受信側では挿入モードで受け取る場合
procedure TForm1.SpeedButton1Click(Sender: TObject);
var
  Flags : Integer;
begin
  //送り側のリッチテキストをFStreamに格納
  FStream.Clear;
  RichEdit1.Lines.SaveToStream(FStream);


  //受信側のリッチテキストでEM_STREAMINメッセージを使用してFStreamから取得
  //メモリストリームの位置を先頭に
  FStream.Position := 0;

  //受信側では常に挿入モードで受信する場合のフラグ
  Flags := SF_RTF or SFF_SELECTION;

  FStreamRec.pfnCallback := @EditStreamCallBackStreamIn;
  //RichEdit2.Perform(EM_STREAMIN, Flags, LPARAM(@FStreamRec));でも同じ
  SendMessage(RichEdit2.Handle, EM_STREAMIN, Flags, LPARAM(@FStreamRec));
end;




03_ [ コピー ] [ 貼り付け ] [元に戻す] 機能の実装  -  TActionManager


RichEdit 間の送受信は,クリップボードを使用した,コピー,貼り付け操作でも可能です.
例えば次のコードは,RichEdit1 の全てを,RichEdit2 に転送します.選択範囲だけをコピーするのであれば,SelectAll は要りません.

リスト5
クリップボードを使用したリッチテキストの送信
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
  RichEdit1.SelectAll;
  RichEdit1.CopyToClipboard;
  RichEdit2.PasteFromClipboard;
end;

本題からはそれますが,TRichEdit のような入力関係コントロールに [コピー] や [貼り付け] 機能を実装するには,TActionListやTActionManager コンポーネントを使用すると便利です.

次の図は,TActionManager の設定画面です.[標準アクションの新規追加]で,[編集] で項目を選択すると,フォームに配置したボタン類,あるいは,TPopupMenu の Action プロパティに,該当する Action を設定するだけで,それらの機能が使用可能となります.


  • 図8
  • アプリケーションに [コピー] や [貼り付け] 機能を実装する

TActionMamager コンポーネントを使用した,[コピー] と [貼り付け] 機能のテストです.
設計時には,SpeedButton の Caption は設定していません.実行時に TActionManager コンポが自動で設定します.もちろん,変更も可能です.また,ボタンクリックのイベントは必要ありません.


図9
設計時画面
図10
[読み込み] ボタンをクリック
  • RTFファイルを選択して表示
図11
RichEdit1 にフォーカス移動
  • RichEdit11 にフォーカス移動してから
  • [全て選択] をクリック
  • [コピー] をクリック
図12
RichEdit2 にフォーカス移動
  • RichEdit2 (下) にフォーカスを移動
  • [貼り付け] をクリック

リスト6
[コピー] や [貼り付け] [元に戻す] 機能のテスト
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls, StdActns, ActnMan, Buttons,
  ActnList, PlatformDefaultStyleActnCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    RichEdit1: TRichEdit;
    Splitter1: TSplitter;
    RichEdit2: TRichEdit;
    OpenDialog1: TOpenDialog;
    ActionManager1: TActionManager;
    EditCut1: TEditCut;
    EditCopy1: TEditCopy;
    EditPaste1: TEditPaste;
    EditSelectAll1: TEditSelectAll;
    SpeedButton1: TSpeedButton;
    SpeedButton2: TSpeedButton;
    SpeedButton3: TSpeedButton;
    EditUndo1: TEditUndo;
    SpeedButton4: TSpeedButton;
    FileOpen1: TFileOpen;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    InitDir : String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  RichEditにフォーカスを持たせたいのでTSpeedButtonを使用
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //実際にはオブジェクト・インスペクタで設定できる
  RichEdit1.ScrollBars    := ssBoth;
  RichEdit2.ScrollBars    := ssBoth;
  RichEdit1.HideSelection := False;
  RichEdit2.HideSelection := False;

  //各スピードボタンにActionを割当て
  //実際にはオブジェクト・インスペクタで設定できる
  SpeedButton1.Action := EditSelectAll1;
  SpeedButton2.Action := EditCopy1;
  SpeedButton3.Action := EditPaste1;
  SpeedButton4.Action := EditUndo1;
end;

//=============================================================================
//  [読み込み]ボタンクリック
//  リッチテキスト形式のテキストをRichEdit1に読み込んで表示
//
//  ファイルを開くも標準アクション[ファイル][TFileOpne]で設定可能
//  それを使用すれば以下のコードも不要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AFilePath : String;
begin
  OpenDialog1.Filter := 'RTFファイル|*.RTF';
  if InitDir = '' then begin
    OpenDialog1.InitialDir := ExtractFileDir(Application.ExeName);
  end;
  if OpenDialog1.Execute then begin
    AFilePath := OpenDialog1.FileName;
    RichEdit1.Lines.LoadFromFile(AFilePath);
    RichEdit1.SetFocus;
  end;
end;

end.