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

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

はじめに

「Windows phoneでテキストエディタを作ろう!」の3回目では、⁠保存」⁠開く」を実装しましょう。⁠保存」は入力した文章をテキストファイルに保存し、⁠開く」は保存したテキストファイルを開いて編集可能にします。

今回は、⁠保存」⁠開く」の実装のほかにも、ソフトキーボード(SIP:Soft Input Panel)を使った際の問題にも対応しましょう。ハードキーボードが搭載されていないWindows phoneで文章を入力するときにはソフトキーボードを使いますが、最前面に表示されるのでアプリケーションが隠れてしまう問題があります。

今回も実装後のサンプルプログラムを用意しています。

ファイルの書き込みと読み込みは簡単!

「Windows phoneでテキストエディタを作ろう!」1回目で、フォームデザイナを使って、SaveFileDialogとOpenFileDialogを配置しました。

ユーザーに対してどの場所にどんなファイル名でテキストファイルを保存するのか(開くのか)を選択させます。この2つのダイアログの動作にあまり違いはなく、選択後には対象のテキストファイルを絶対パスで取得できます。

「保存」「開く」の実装にはあまり差はありません。

「保存」の実装

入力した文章をテキストファイルへ書き込みます。プロパティの設定をソースコードで書いてもよいのですが煩雑になるので、プロパティウィンドウで以下の指定をします。

NamesaveFileDialog
Filterテキストファイル(*.txt)|*.txt
FilterIndex1

Filterプロパティは名前の通り、フィルタをかけ拡張子が「.txt」のファイルのみを表示します。SaveFileDialogの場合は、ユーザーが拡張子を入力しなくても自動的に補完されます。

メニューから「保存」が選択された時に、ファイル保存用のダイアログを表示し、ユーザーにどの場所に保存するのかを問い合わせます。ユーザーが指定した名前と同じファイル名のファイルが存在していた場合は注意を促してくれますので、勝手にデータを上書きしてしまうことはありません。

private void menuSave_Click(object sender, EventArgs e)
{
  // ファイルを保存するためのダイアログを表示する
  if (saveFileDialog.ShowDialog() != DialogResult.OK)
  {
    return;
  }
  
  // テキストボックスに入力されているテキストを
  // ユーザーが指定したファイルパスに書き出す
  string path = saveFileDialog.FileName;
  using (StreamWriter strm = new StreamWriter(path))
  {
    strm.Write(textEdit.Text);
  }
}

「開く」の実装

ファイルシステムにあるテキストファイルを読み込みます。SaveFileDialogと同様にプロパティウィンドウで以下の指定をします。

NameopenFileDialog
Filterテキストファイル(*.txt)|*.txt
FilterIndex1

SaveFileDialogで実施した方法と逆に、OpenFileDialogでユーザーに指定してもらったファイル名からテキストを読み込み、textEditのTextプロパティへ設定しています。

private void menuOpen_Click(object sender, EventArgs e)
{
  // ファイルを開くためのダイアログを表示する
  if (openFileDialog.ShowDialog() != DialogResult.OK)
  {
    return;
  }

  // ユーザーが指定したファイルパスから
  // テキストを読み込みTextBoxに表示させる
  string path = openFileDialog.FileName;
  using (StreamReader strm = new StreamReader(path))
  {
    textEdit.Text = strm.ReadToEnd();
  }
}

この方法だと読み込んだテキストを無条件にTextプロパティへ設定していますので、編集中の文章があっても関係なしに上書きしてしまいます。長時間掛けて編集したファイルが消えてしまうのは、とても悲しい気持ちになりますので対策をしましょう。

ファイルを開いたときの上書きを防止する

保存済みかどうかのフラグを用意します。このフラグがfalseの場合は、テキストを編集中として「開く」処理時にユーザーに注意を促しましょう。TextEditorクラスに以下のフラグを用意します。

/// <summary>
/// 現在のテキストが保存済みか判定用フラグ
/// </summary>
bool _isSavedFile = true;
bool isSavedFile
{
  get { return _isSavedFile; }
  set { _isSavedFile = value; } 
}

isSavedFileは保存済みかどうかのフラグですので、現在の文章を保存したらtrueを設定します。

private void menuSave_Click(object sender, EventArgs e)
{
  // ファイルを保存するためのダイアログを表示する
  if (saveFileDialog.ShowDialog() != DialogResult.OK)
  {
    return;
  }

  // TextBoxに表示しているテキストを
  // ユーザーが指定したファイルパスに書き出す
  using (StreamWriter strm = new StreamWriter(saveFileDialog.FileName))
  {
    strm.Write(textEdit.Text);
  }

  // 保存が完了したのでフラグにtrueを設定する
  this.isSavedFile = true;
}

テキストボックスのテキストに変更があった場合、TextChangeイベントが発行されます。このイベントの通知を受けると編集中に入ったとしてisSavedFileをfalseに設定します。

private void textEdit_TextChanged(object sender, EventArgs e)
{
  // テキストが変更されたのでフラグにfalseを設定する
  this.isSavedFile = false;
}

編集中(isSavedFileがfalse)の状態でテキストファイルを開こうとすると、注意を促しユーザーの了承を得てから上書きするようにします。

private void menuOpen_Click(object sender, EventArgs e)
{
  // ファイルを保存済みか
  if (!this.isSavedFile)
  {
    // 上書きの確認用の文言
    System.Text.StringBuilder sb = new StringBuilder();
    sb.Append("現在編集中のテキストが存在しています。\n");
    sb.Append("現在のテキストを破棄して、新しく開きますか?");

    // メッセージボックスの表示
    DialogResult result = DialogResult.None;
    result = MessageBox.Show(sb.ToString(),
                             "caption", 
                             MessageBoxButtons.YesNo, 
                             MessageBoxIcon.Asterisk, 
                             MessageBoxDefaultButton.Button2);

    if (result != DialogResult.Yes)
    {
      return;
    }
  }

  // ファイルを開くためのダイアログを表示する
  if (openFileDialog.ShowDialog() != DialogResult.OK)
  {
    return;
  }

  // ユーザーが指定したファイルパスから
  // テキストを読み込みTextBoxに表示させる
  using (StreamReader strm = new StreamReader(openFileDialog.FileName))
  {
    textEdit.Text = strm.ReadToEnd();
  }
}

編集中に「開く」を選択時してみました。

上書きの注意文を表示
上書きの注意文を表示

ソフトウェアキーボードを表示させた時の対応

Windows Mobile 6.5 Professionalエディションが搭載されたWindows phoneでは、ハードキーボードを搭載しない端末が増えています。その手の端末では、文字の入力をするためにソフトキーボード(SIP:Soft Input Panel)を使用します。

ソフトキーボードを有効にすると、ソフトキーボード部分が最前面に表示されます。アプリが隠れてしまい、場合によっては文字を入力しようとしている部分を隠してしまうことになります。

カーソルがソフトキーボードで隠れてしまう
カーソルがソフトキーボードで隠れてしまう

少し使い勝手が悪いですね。InputPanelコンポーネントを利用して改善しましょう。最終形としては、ソフトキーボードが表示されると、textEditの終端をソフトキーボードの上に移動させて被って表示されないようにします。

InputPanelは、マネージドコードからソフトキーボードの制御を行うことができるコンポーネントです。ツールボックスにありますので、フォームデザイナにてフォームにドラッグアンドドロップしてください。

InputPanelコンポーネントの追加
InputPanelコンポーネントの追加

追加したInputPanelをダブルクリックすると、EnabledChangedイベントのイベントハンドラがソースコードに追加されます。このEnabledChangedイベントは、ソフトキーボードが有効か無効に状態が変更されると通知されます。

private void inputPanel1_EnabledChanged(object sender, EventArgs e)
{
  if (inputPanel1.Enabled)
  {
    // ソフトキーボードが表示されている
  }
  else
  {
    // ソフトキーボードが表示されていない
  }
}

次に、ソフトキーボードの表示状態の変化に応じて、textEditの高さを変更します。

「Windows phoneでテキストエディタを作ろう!」1回目で、テキストボックスのDcokプロパティにはDockStyle.Fillを設定しました。Dockプロパティを設定すると、親コントロール(今回の場合はフォーム)のサイズ変更に合わせて、適切な配置でテキストのサイズを自動で変更します。

ソフトキーボードが非表示の場合は、今までどおりDockStyle.Fillを設定して、textEditをフォームの全部の端に合わせています。表示される場合は、DockプロパティにDockStyle.Topを設定して、フォームの上辺に揃えて配置した上で変更後の高さを設定します。

private void inputPanel1_EnabledChanged(object sender, EventArgs e)
{
  // 現在のテキストボックスの高さを取得
  int newHeight = textEdit.Height;

  if (inputPanel1.Enabled)
  {
    // ソフトキーボードが表示されている

    // テキストボックスの高さを設定するために
    // Dockプロパティを上辺にのみ揃える
    textEdit.Dock = DockStyle.Top;

    // ソフトキーボードの高さを考慮して、
    // 残った部分をテキストボックスの高さとする
    newHeight = newHeight - inputPanel1.Bounds.Height;
  }
  else
  {
    // ソフトキーボードが表示されていない

    // ソフトキーボードのことは考慮する必要がないので
    // フォームのすべての辺とドッキングさせる。
    textEdit.Dock = DockStyle.Fill;
  }

  // テキストボックスの高さを更新する
  textEdit.Height = newHeight;
}

ソフトキーボードの有効・無効の切り替え時に、テキストボックスのDockプロパティと高さを変更しました。これでソフトウェアキーボードとテキストボックスが干渉しなくなりました。

テキストボックスの高さ変更後
テキストボックスの高さ変更後

まとめ

開発するアプリケーションがテキストエディタだったので、テキストの書き込みと読み込みを行いました。デスクトップ版の.NET Frameworkで開発経験のある方には、⁠保存」⁠開く」の実装は特に問題ないと思います。

一方、InputPanelを使ってソフトキーボードとテキストボックスが重ならないようにする対応は、モバイル端末であるWindows phone独特の対応です。デスクトップと比較するとどうしてもソフトキーボードが占める領域が大きいので、開発者が干渉しないようにコントロールの配置を考慮する必要がありました。

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

おすすめ記事

記事・ニュース一覧