LoginSignup
5
4

More than 5 years have passed since last update.

PropertyGridにコピペ機能を追加する(1/2)

Last updated at Posted at 2016-07-18

はじめに

PropertyGridにいろいろ出力して値が見れて便利!→でも上手くコピペできないじゃん。
値のところを範囲選択して何とかコピペしてみるけど、やっぱりプロパティ名+値でツリーっぽくなってほしい。

ここでは、複数回に分けて、PropertyGridのコピペ機能拡張に挑んでみます。
本記事では、比較的容易で効果の高い方法で拡張を行っていきます。
大まかに分けて以下の機能を追加します。
1. プロパティ名+値のコピー
2. ツリー構造でコピー
3. LINQPad連携 (HTML表示)
Figure1.png

第2回はこちら http://qiita.com/longlongago_k/items/a58e7b5e57e02d90e544

プロパティ名+値のコピー 機能の実装

プロジェクトの作成

  1. クラスライブラリプロジェクトを作成します。デフォルトで作成されるClass1.csはいらないので削除してください。
  2. プロジェクトを右クリック→追加→新しい項目→カスタムコントロール でカスタムコントロールを追加してください。ここではPropertyGridPlusという名前で作成しました。
  3. 親クラスはPropertyGridへ変更してください。 この時点でのコードは以下です。
//PropertyGridPlus.cs
namespace PropertyGridPlus
{
  public partial class PropertyGridPlus : PropertyGrid //ControlからPropertyGridに変更する
  {
    public PropertyGridPlus()
    {
      InitializeComponent();
    }

    protected override void OnPaint(PaintEventArgs pe)
    {
      base.OnPaint(pe);
    }
  }
}

コピー機能の実装

まずはUIから作成していきます。右クリメニューを作成します。
1. デザイナを開き、ツールボックスからContexMenuStripをD&Dして追加します。
2. カスタムコントロールの時はなぜかグラフィカルに編集できないので、Itemsプロパティを直接編集して"選択した項目をコピー"と言うようなメニューを追加します。
3. デザイナの何もないところをクリックするかプロパティビューの上の欄から選ぶかしてPropertyGridPlus本体を選択します。ContexMenuStripに2.で作成したContexMenuStripを設定します。この時点で適当なフォームにPropertyGridPlusを張り付けると以下のようになっています。細かいところは適当にチューニングしてください。
Figure2.png
4. 3.で作成したメニューのClickイベントとコピー用テキスト生成関数を作成します。Clickイベントはデザイナから作れなかったのでコードで入れています。PropertyGridで現在選択している項目はGridItem型のSelectedGridItemで取得できます。SelectedGridItemを入力として、コピー用テキストを生成するCreateTextを作成します。今後色々機能を追加するので引数は少し賑やかになっています。詳しくはコメントを見てください。

  public partial class PropertyGridPlus : PropertyGrid
  {
    public PropertyGridPlus()
    {
      InitializeComponent();
      toolStripMenuItemCopySelected.Click += ToolStripMenuItemCopySelected_Click;
    }

    private void ToolStripMenuItemCopySelected_Click(object sender, EventArgs e)
    {
      string text = CreateText(SelectedGridItem, false, 1);
      Clipboard.SetText(text);
    }
    /// <summary>
    /// 選択した項目の内容をテキストに出力します。
    /// </summary>
    /// <param name="item">選択した項目</param>
    /// <param name="useIndent">インデント処理するか</param>
    /// <param name="limitDepth">子アイテムを読み込む深さ</param>
    /// <param name="depth">再帰処理用のカウンタ(内部用)</param>
    /// <returns></returns>
    public string CreateText(GridItem item, bool useIndent, int limitDepth = int.MaxValue, int depth = 0)
    {
      string ret = "";
      ret = $"{item.Label}\t{item.Value}";
      return ret;
    }
  }

この時点で選択した項目がいい感じにコピーできるようになっています。

ツリー構造でコピーする

ツリー構造でコピーする際は、以下について考慮する必要があります。
1. どの深さまでツリーを展開するかを設定できるようにする必要がある
2. ”①前方のスペース”+”②プロパティ名”+"③後方のスペース"+"値"の構成でテキストを生成する必要がある。①についてはツリーの深さ分タブを入れればよいです。③については、一番深いアイテムの深さを1.も考慮した上でカウントする必要があります。

一番深いアイテムの深さを取得する

  • PropertyGridから渡されるGridItem
  • 読み込む最大の深さ

を入力として一番深いアイテムの深さを取得します。再帰処理で一段ツリーを展開するごとに深さ+1をするような処理になっています。

 /// <summary>
 /// 指定した深さ以内で最も深い深さを取得します。
 /// </summary>
 /// <param name="item">PropertyGridのアイテム</param>
 /// <param name="limitDepth">最大深さ</param>
 /// <param name="currentDepth">再帰処理用の内部カウンタ。呼び出す際は指定しない。</param>
 /// <returns></returns>
 private int getDepthFromGridItem(GridItem item, int limitDepth, int currentDepth = 0)
 {
   //指定した深さ以上になっていればツリーの展開を中断し、現在の深さをそのまま返す。
   if (currentDepth >= limitDepth)
   {
     return currentDepth;
   }
   //サブツリーの展開
   int retDepth = currentDepth;
   foreach (var child in item.GridItems)
   {
     int childDepth = getDepthFromGridItem(child as GridItem, limitDepth, currentDepth + 1);
     //サブツリーの中で最も深い深さを取得する。
     if (retDepth < childDepth)
       retDepth = childDepth;
   }
   return retDepth;
 }

インデント付きコピーを実装する

  1. 現在の深さ分タブを入れる。(インデント)
  2. プロパティ名を出力する。もし、子要素を持っていれば先頭に"+"や"▼"等の記号を入れる。
  3. (最大深さ - 現在の深さ + 1 )個だけタブを入れて値を出力する。 Figure3.png

コードは以下のようになります。インデント有り版と無し版で関数を分けました。

    /// <summary>
    /// 選択した項目の内容をテキストに出力します。
    /// </summary>
    /// <param name="item">選択した項目</param>
    /// <param name="useIndent">インデント処理するか</param>
    /// <param name="limitDepth">子アイテムを読み込む深さ</param>
    /// <param name="depth">再起処理用のカウンタ(内部用)</param>
    /// <returns></returns>
    public string CreateText(GridItem item, bool useIndent, int limitDepth = int.MaxValue, int depth = 0)
    {
      string ret = "";
      if (useIndent)
      {
        int depth_max;
        depth_max = getDepthFromGridItem(item, limitDepth);
        ret = createTextWithIndent(item, limitDepth, depth_max);
      }
      else
      {
        ret = createTextWithoutIndent(item, limitDepth);
      }

      return ret;
    }

    /// <summary>
    /// GridItemからツリーのコピーテキストを取得します。
    /// </summary>
    /// <param name="item">PropertyGridのアイテム</param>
    /// <param name="limitDepth">最大の深さ</param>
    /// <param name="maxDepth">最も深いノードの深さ(getDepthFromGridItemで取得する)</param>
    /// <param name="currentDepth">再帰処理用の内部カウンタ。呼び出す際は指定しない。</param>
    /// <returns></returns>
    private string createTextWithIndent(GridItem item, int limitDepth, int maxDepth, int currentDepth = 0)
    {
      StringBuilder ret = new StringBuilder();
      //インデントのため、タブを現在の深さ分挿入する。
      ret.Append(new string('\t', currentDepth));
      //子アイテムがあるときの記号。自由に設定してください。
      string expandSign = "";
      if (item.GridItems.Count > 0)
        expandSign = "▼";
      //プロパティ名の追加
      ret.Append($"{expandSign}{item.Label.TrimStart('\t')}");
      //プロパティ名と値の間のスペースを追加
      ret.Append($"{new string('\t', maxDepth - currentDepth + 1)}");
      //値の出力
      ret.AppendLine($"{item.Value}");
      //サブツリーの展開
      if (currentDepth < limitDepth)
      {
        foreach (var child in item.GridItems)
        {
          ret.Append(createTextWithIndent(child as GridItem, limitDepth, maxDepth, currentDepth + 1));
        }
      }
      return ret.ToString();
    }

    /// <summary>
    /// GridItemからツリーのコピーテキストを取得します。(インデント無し版)
    /// </summary>
    /// <param name="item">PropertyGridのアイテム</param>
    /// <param name="limitDepth">最大の深さ</param>
    /// <param name="maxDepth">最も深いノードの深さ(getDepthFromGridItemで取得する)</param>
    /// <param name="currentDepth">再帰処理用の内部カウンタ。呼び出す際は指定しない。</param>
    /// <returns></returns>
    private string createTextWithoutIndent(GridItem item, int limitDepth, int currentDepth = 0)
    {
      StringBuilder ret = new StringBuilder();
      ret.AppendLine($"{item.Label}\t{item.Value}");
      if (currentDepth < limitDepth)
      {
        foreach (var child in item.GridItems)
        {
          ret.Append(createTextWithoutIndent(child as GridItem, limitDepth, currentDepth + 1));
        }
      }
      return ret.ToString();
    }

ここでのポイントは、stringのコンストラクタでn個の文字を生成するすることができる事です。

new string('\t', 3); //タブを3個生成

クリップボードへテキストをコピーする

これまで組んだコードで生成したテキストをクリップボードへコピーします。これは簡単で、Clipboard.SetText()するだけです。

    //コンテキストメニューのClickイベントハンドラ
    private void ToolStripMenuItemCopyAllWithIndent_Click(object sender, EventArgs e)
    {
      string text = CreateText(SelectedGridItem, true);
      Clipboard.SetText(text);
    }

完成

コンテクストメニューに対して適当にコピー設定のバリエーションを作るか、コピー設定ダイアログ等を作ってCreateText()を呼び出してください。
以下は実装例です。
Figure4.png

サンプルプロジェクトファイルは下記になります。zipがアップロードできなかったので、拡張子をjpgに変えています。
右クリックから保存してjpg→zipにリネームしてください。
PropertyGridPlus.zip.jpg

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4