Windows Phoneアプリケーション開発入門

第6回Windows phoneでテキストエディタを作ろう!(4)

はじめに

「Windows phoneでテキストエディタを作ろう!」の4回目では、⁠検索」⁠置換」を実装します。

(テキストエディタと呼ばないかもしれませんが)Windows付属のメモ帳を参考に、デスクトップで動作しているテキストエディタの検索・置換と同等の機能を、どのようにしてWindows phoneアプリケーションに組み込むかを検討しながら実装を進めて行きましょう。

実装後のサンプルプログラムを用意しています。UIに手を入れていますので実際にVisual Studioのフォームデザイナを確認しながら追っていただければ、より理解が早いかと思います。

検索機能のUIを検討する

メモ帳のメニューから検索を選択すると、検索専用のダイアログが表示されます。

図1 メモ帳の検索ダイアログ
図1 メモ帳の検索ダイアログ

テキストエディタに触ったことはありますか? 検索を行う時には、大抵文章が表示されているフォームとは別に小さなダイアログを表示し、文章を確認しながら検索ワードや置換ワードを入力していたのではないでしょうか。

今作っているアプリケーションでもメモ帳と同様に、小さなダイアログを表示して検索機能を設けたいところですが、Windows phoneアプリケーションのダイアログと言えば「Windows phoneでテキストエディタを作ろう!」3回目で使用したOpenFileDialogのように全画面表示されてしまいます。

前述の通り、検索と置換は文章を確認しながら行いたいのでダイアログは使用せずに、テキストボックスの上部にパネルを用意し、そのパネル上から検索と置換の操作を行うようにしましょう。

検索・置換用のパネルの作成

フォームデザイナのツールボックスからPanelコントロールを選択し、TextBox上にPanelを配置します。Panelの以下のプロパティを変更します。

NamepanelSearch
DockTop
VisibleFalse

Visibleプロパティは、コントロールの表示・非表示を設定するプロパティです。アプリケーションの起動時点にこのパネルを表示させる必要はありませんので、Falseに設定します。

また、TextBoxよりもPanelを後に追加しているため、Panelが最前面に表示されTextBoxが隠れてしまっています。TextBoxを右クリックしポップアップから「最前面へ移動」を選択すると、TextBoxが適切に表示されます。

図2 PanelがTextBoxより前面に表示されている状態
図2 PanelがTextBoxより前面に表示されている状態

Panel上に検索ワード・置換ワードの入力欄や、実際に検索処理を行う為のボタン等のコントロールを配置しましょう。

図3 検索パネルへのコントロールの配置図
図3 検索パネルへのコントロールの配置図

それぞれのコントロールの名前は以下のように設定しました。

textSearchWord検索ワードの入力欄
textReplaceWord置換ワードの入力欄
btnSearch検索・置換処理ボタン
btnPanelCloseパネル閉ボタン

パネルの表示・非表示処理の実装

検索パネルへの各コントロールの配置が完了しましたので実装を進めて行きます。ここではパネルの表示・非表示処理の実装を行います。

検索と置換で同じパネルを共有して使いたいので、現在のモードを持って処理する内容を切り分けたいと思います。検索か置換かのパネルの状態を定義したPanelModeを定義し、現在の状態を持つPanelMode型の変数nowPanelModeを宣言します。

private enum PanelMode
{
    Search, // 検索モード
    Replace // 置換モード
}
private PanelMode nowPanelMode;

このnowPanelModeの状態に応じて処理を切り分けます。

「検索」選択時のパネルの表示

メニューから「検索」が選択された場合の処理を記します。検索を行いますので、パネルのモードには、PanelMode.Searchを設定します。

private void menuItemSearch_Click(object sender, EventArgs e)
{
    // 検索パネルのモードを「検索」に設定
    nowPanelMode = PanelMode.Search;
    
    // ボタンのテキストを「検索」に設定
    btnSearch.Text = "検索";
    
    // 置換ワード欄への入力を無効にする
    lblReplaceWord.Enabled = false;
    textReplaceWord.Enabled = false;
    
    // 検索パネルを開く
    panelSearch.Visible = true;
}

使用しないtextReplaceWordのEnabledプロパティにfalseを設定し無効にしています。パネルに対する設定が完了後、検索パネルのVisibleプロパティにtrueを設定し、検索パネルを表示します。

図4 ⁠検索」選択時のパネルの状態
図4 「検索」選択時のパネルの状態

「置換」選択時のパネルの表示

メニューから「置換」が選択された場合の処理を記します。検索パネルの表示時と同様の処理を行います。置換ですのでパネルのモードはPanelMode.Replaceを設定します。

private void menuItemReplace_Click(object sender, EventArgs e)
{
    // 検索パネルのモードを「置換」に設定
    nowPanelMode = PanelMode.Replace;
    
    // ボタンのテキストを「置換」に設定
    btnSearch.Text = "置換";
    
    // 置換ワード欄への入力を有効にする
    lblReplaceWord.Enabled = true;
    textReplaceWord.Enabled = true;
    
    // 検索パネルを開く
    panelSearch.Visible = true;
}

置換するテキストを入力したいので、textReplaceWordを有効にしています。最後にパネルのVisibleプロパティをtrueに設定し置換パネルを表示します。

図5 ⁠置換」選択時のパネルの状態
図5 「置換」選択時のパネルの状態

検索パネルの非表示処理

「閉じる」ボタンを押下すると、検索パネルを非表示にしたいです。パネルの表示処理とは逆にpanelSearchのVisibleプロパティをfalseに設定することで、パネルを閉じることができます。

private void btnPanelClose_Click(object sender, EventArgs e)
{
    // 検索パネルを閉じる
    panelSearch.Visible = false; 
}

「検索」「置換」の実装

検索ボタンが押下された時の処理を記します。検索と置換の共通処理部分のみ記載しています。非共通部分は後で書きます。

private void btnSearch_Click(object sender, EventArgs e)
{
    // 検索ワードの取得
    string searchWord = textSearchWord.Text;
    // 検索ワードが空文字列だった場合、処理を行わない
    if (searchWord == "") return;
    
    // 検索開始位置を現在のカーソル位置から算出
    int startIdx = textEdit.SelectionStart + textEdit.SelectionLength;
    // テキストの終端まで達した場合、処理を行わない
    if (startIdx > textEdit.Text.Length) return;
    
    // 検索開始位置から検索を開始して、
    // 最初に見つけた検索ワードの位置を取得
    int wordIdx = textEdit.Text.IndexOf(searchWord, startIdx);
    // 検索ワードを発見できなかった場合、処理を行わない
    if (wordIdx < 0) return;
    
    // 検索パネルのモードによって処理を変更する
    switch (nowPanelMode)
    {
        case PanelMode.Search:
            
            // 検索処理
            
            break;
        
        case PanelMode.Replace:
            
            // 置換処理
            
            break;
    }
    
    // カレット(カーソルの選択位置)が表示領域内に入るまでスクロールする
    textEdit.ScrollToCaret();
    
    // textEditにフォーカスを当てる
    textEdit.Focus();
}

長いコードなので特に注意すべき点のみを抜き出して説明します。

入力されたテキストから対象となるワードを検索するのに、String.IndexOf(string, int)メソッドを使用しています。このIndexOfメソッドは最初に発見した検索ワードの開始位置を返します。見つからなかった場合は-1を返しますので、wordIdxの位置をチェックし処理を行わずに終了します。

    // 検索開始位置から検索を開始して、
    // 最初に見つけた検索ワードの位置を取得
    int wordIdx = textEdit.Text.IndexOf(searchWord, startIdx);
    // 検索に掛からなかった
    if (wordIdx < 0) return;

パネルモードごとの処理を行った後、現在の表示領域外にカーソルが行ってしまうとユーザーから確認する手段がなくなってしまいます。TextBoxコントロールのScrollToCaretメソッドでカレットが表示領域に入るまでスクロールを行います。

    // カレット(カーソルの選択位置)が表示領域内に入るまでスクロールする
    textEdit.ScrollToCaret();

「検索」の実装

検索の実装は選択状態にするだけです。

// 検索ワードを選択状態に変更する
textEdit.Select(wordIdx, searchWord.Length);

「置換」の実装

次に置換の実装を行います。TextBoxコントロールには置換用のメソッドは無いため、Windowsメッセージを送り置換処理に対応します。

置換処理を行うWindows メッセージは、EM_REPLACESELhttp://msdn.microsoft.com/en-us/library/aa929135.aspxです。現在選択選択されているテキストに対して「置換」を行います。もし範囲選択されていなければカーソル位置に対しての「挿入」となります。ここでは、置換として扱いたいので実際に置き換える前に、検索ワードを選択状態にしてからEM_REPLACESELを送ることにします。

コピーやペーストの時に実装した処理と違い、EM_REPLACESELには引数を渡す必要があります。引数wParamは、アンドゥを許可するかどうかを指定します。1の場合はアンドゥを許可し、0の場合は許可しません。引数lParamには、置換ワードを指定します。

// 検索ワードを選択します
textEdit.Select(wordIdx, searchWord.Length);

// 置換ワードの取得
string word = textReplaceWord.Text;

// 現在選択選択されているテキストに対して置換処理を行う
// Windows メッセージのIDを定義
int EM_REPLACESEL = 0x00C2;

// 引数wParamは、0の場合アンドゥを不可に、1の場合は可能にする
IntPtr wParam = new IntPtr(1);
// 引数lParamは、置換を行うテキストを指定する。
// マネージstringの内容をネイティブ向けのBSTRにコピーする
IntPtr lParam
    = System.Runtime.InteropServices.Marshal.StringToBSTR(word);

Microsoft.WindowsCE.Forms.Message msg
    = Microsoft.WindowsCE.Forms.Message.Create(textEdit.Handle,
                                               EM_REPLACESEL,
                                               wParam,
                                               lParam);
Microsoft.WindowsCE.Forms.MessageWindow.SendMessage(ref msg);

// ネイティブ向けのBSTRは、解放が必要なので、
// 使用後は明示的に解放を行う。
System.Runtime.InteropServices.Marshal.FreeBSTR(lParam);

まとめ

今回は、検索・置換機能の実装を行いました。文字列を発見すればテキストを選択するだけの「検索」と違い、.NET Compact Frameworkでの「置換」処理ではWindowsメッセージを使う必要がありました。結果、コードを量が多くなってしまい少し複雑になってしましました。.NET Frameworkでの開発経験のある方からすると、思いのほかWindowsメッセージを使うと感じられたかもしれません。

さて、この「Windows phoneでテキストエディタを作ろう!」も予定していた機能の実装が完了しました。これで.NET Compact Frameworkで作るWindows phoneアプリケーション開発の一通りのエッセンスを経験したことになります。次回は、インストーラファイルを作成したいと思います。

以上で今回は終わりです。ありがとうございました。

おすすめ記事

記事・ニュース一覧