Edited at

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

More than 3 years have passed since last update.


はじめに

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