はじめに
PropertyGridにいろいろ出力して値が見れて便利!→でも上手くコピペできないじゃん。
値のところを範囲選択して何とかコピペしてみるけど、やっぱりプロパティ名+値でツリーっぽくなってほしい。
ここでは、複数回に分けて、PropertyGridのコピペ機能拡張に挑んでみます。
本記事では、比較的容易で効果の高い方法で拡張を行っていきます。
大まかに分けて以下の機能を追加します。
第2回はこちら http://qiita.com/longlongago_k/items/a58e7b5e57e02d90e544
プロパティ名+値のコピー 機能の実装
プロジェクトの作成
- クラスライブラリプロジェクトを作成します。デフォルトで作成されるClass1.csはいらないので削除してください。
- プロジェクトを右クリック→追加→新しい項目→カスタムコントロール でカスタムコントロールを追加してください。ここではPropertyGridPlusという名前で作成しました。
- 親クラスは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から作成していきます。右クリメニューを作成します。
- デザイナを開き、ツールボックスからContexMenuStripをD&Dして追加します。
- カスタムコントロールの時はなぜかグラフィカルに編集できないので、Itemsプロパティを直接編集して"選択した項目をコピー"と言うようなメニューを追加します。
- デザイナの何もないところをクリックするかプロパティビューの上の欄から選ぶかしてPropertyGridPlus本体を選択します。ContexMenuStripに2.で作成したContexMenuStripを設定します。この時点で適当なフォームにPropertyGridPlusを張り付けると以下のようになっています。細かいところは適当にチューニングしてください。
- 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.も考慮した上でカウントする必要があります。
一番深いアイテムの深さを取得する
- 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 )個だけタブを入れて値を出力する。
コードは以下のようになります。インデント有り版と無し版で関数を分けました。
/// <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()を呼び出してください。
以下は実装例です。
サンプルプロジェクトファイルは下記になります。zipがアップロードできなかったので、拡張子をjpgに変えています。
右クリックから保存してjpg→zipにリネームしてください。