ColumnHeaderにプロパティを追加したいっ！


# 背景
[前稿](http://qiita.com/Toraja/items/7b840099b15ede85490a)迄でカラムの直接編集ちっくは実現できました。でも、全てのカラムが編集対象になるとは限りません。
現状それをコントロールするには、どのカラムを編集対象とするかを外部に保持して、外部からコントロールする必要があります。

許せんっ！
美しくないっ！

きちんと役割毎に分離しなくては…

# 方針
前稿からの流れで行くと…

* ColumnHeaderをサブクラス化する
* サブクラスに`Editable`プロパティを設ける
* （一応確認まで）`Editable`プロパティが`true`のカラムだけの編集を可能とする

と云う様な事がしたいんですよね。
<br/>
んー、プロパティ追加するだけなら何てこたないんです。

```csharp
public class ColumnHeaderEx : ColumnHeader {
	public ColumnHeaderEx() : base() {
	}

	[Browsable(true)]
	public bool Editable { get; set; }
}
```

ってなサブクラスを定義して、`Form1.cs` の中に以下の様なコードを追加しておけばオッケー。

```csharp:あくまでもイメージです
private ColumnHeaderEx columnHeaderEx1;
private ColumnHeaderEx columnHeaderEx2;
private ColumnHeaderEx columnHeaderEx3;

this.columnHeaderEx1 = new ColumnHeaderEx();
this.columnHeaderEx2 = new ColumnHeaderEx();
this.columnHeaderEx3 = new ColumnHeaderEx();

this.columnHeaderEx1.Text = "カラム 1";
this.columnHeaderEx2.Text = "カラム 2";
this.columnHeaderEx3.Text = "カラム 3";

this.listView1.Columns.AddRange(new ColumnHeader[] { this.columnHeaderEx1, this.columnHeaderEx2, this.columnHeaderEx3});
```

ただ残念なのは、これではデザイナで列の定義が出来ないんですよ。
また折角定義した`Editable`プロパティを参照する際には、 `(ColumnHeaderEx)(this.listView1.Columns[i]).Editable`って記述しないといけないし…

と云う事で今回の目的は以下とします。

* ColumnHeaderExサブクラスを実現します
* デザイナ画面でカスタムプロパティ含めて列定義が出来るようにします
* 勿論、デザイナ画面ってのはここの事　↓<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/3991ae23-1593-c100-73e6-1e57d2b5d356.png)ここを選んで<br/><br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/579a160f-93e4-684d-f4a4-ed713e361882.png)からの<br/><br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/ae21fa6f-14e1-f3c3-0df8-7e9b38d5867c.png)こいつ

この右側にカスタムプロパティが出て欲しい訳です。
…頑張りましょっ！

# 実験
まず、ネットを漁りました。
イイ感じの日本語の情報には余り行き当たりません…

断片的な話と、`ColumnHeader`をサブクラス化した場合は、カスタムデザイナ(?)だかカスタムエディタ(?)を指定しないといけないだら、良く分かりません。おまけに、カスタム何ちゃらを作ってしまったら、標準の「列の編集」が効かなくなるんじゃないの？
とか、悶々、悶々。。。
<br/>
そうこうのた打っているうちに、ふと…
`ColumnHeader コレクションエディター`なんてエラそーな名前が付いてるけど、所詮はプロパティエディタじゃないの？
だったら、`ListView.Columns`の中身に`ColumnHeaderEx`型のインスタンスが入っていれば、件のエディタ上にカスタムプロパティも一緒に表示されるのではっ？

と思ったら、後は実験あるのみっ！
<br/>
取り敢えず、`ColumnHeader`のサブクラスを宣言。

```csharp:前にも出た奴
public class ColumnHeaderEx : ColumnHeader {
	public ColumnHeaderEx() : base() {
	}

	[Browsable(true)]
	public bool Editable { get; set; }
}
```

で、`Form1.Designer.cs`を強引に編集。

```csharp:Form1.Designer.csの一部（順不同）
            ：
private System.Windows.Forms.ListView listView1;
private ColumnHeaderEx columnHeader1;    <=    ここを修正
private ColumnHeaderEx columnHeader2;    <=    ここを修正
private ColumnHeaderEx columnHeader3;    <=    ここを修正
            ：
this.listView1 = new ListView();
this.columnHeader1 = new ColumnHeaderEx();    <=    ここを修正
this.columnHeader2 = new ColumnHeaderEx();    <=    ここを修正
this.columnHeader3 = new ColumnHeaderEx();    <=    ここを修正
            ：
```

一旦ビルドしたのち、デザイナから「列の編集」を選択。
__出たっ！　　Editable__

![image](https://qiita-image-store.s3.amazonaws.com/0/136132/fa47fd51-8c28-64f2-e527-c8ecdf472213.png)

よしよし、方向性は間違っていないと見た！

# 問題
すっごい前が開けた様な気はするんですが、実はまだまだ問題は山積しています。

* `Designer.cs`を直接編集しちゃうのはダメでしょう
* 上のエディタ画面でメンバーを追加すると`ColumnHeader`クラスが追加されちゃいます
* 相変わらず`Editable`プロパティの参照にキャストが必要です

でも実はこれら、全部根っこは一緒です。
つまり`ListView.Columns`の型が`ColumnHeaderExCollecction`であれば、キャストはいらなくなります。こうなればきっと、プロパティエディタ君はよしなに取り計らってくれて、ジェネレータ氏も`Designer.cs`のコード生成をうまい事処理してくれるんじゃないかなぁ？（希望的観測）
となれば、コードの直接編集もいらない筈です。

後は、参考になりそうな情報を探すだけ…（何か振出しに戻ってるし）
<br/><br/>
で、ついに[見つけました](http://www.mycsharp.de/wbb2/thread.php?postid=3523383)。

残念ながら日本語でも英語でもないので、どんなやり取りがなされているのか（私には）微塵も分かりませんが、サンプルコードは（一揃い纏まってるという意味で）完璧です。
上のページの一番最後のスレッドにダウンロードリンクがあります。そのzipファイルの中に`ListViewExBarebone.zip`ってなファイルがあって、それが名前の通り`ColumnHeader`のサブクラス化の基本骨格だけにシェイプしたコード群です。でもやっぱり若干の贅肉は付いてますが…（多分骨格に対する視点が違うんだと思います）

ただ一つ問題が解決されていません。
この`Barebone`、やっぱりカスタムデザイナを利用しています。多分この辺りかと…

```csharp:ListViewEx.cs
        ：
[Designer(typeof(ListViewExDesigner))]        <==    ここ！
public partial class ListViewEx : ListView {
        ：
```

では、実際に確認してみましょう。

![image](https://qiita-image-store.s3.amazonaws.com/0/136132/772a788d-9134-4526-92e8-c9e6d9b7ee8c.png)

危惧した通り、「列の編集」選択肢が出てきませんね。
列を追加するには、

![image](https://qiita-image-store.s3.amazonaws.com/0/136132/ad515657-cfaa-af6c-ccc1-d6b6f06fce7b.png)ここから「…」をクリックすれば<br/>
![image](https://qiita-image-store.s3.amazonaws.com/0/136132/a7c182b0-cc6b-bfc3-0454-45669b3adfc1.png)

おー、見慣れた奴だ。`Test`カスタムプロパティも追加されてますね。
でもでも、`ColumnsNew`だとぉ？
なんじゃそら。

# 構造
取り敢えず`Barebone`から以下を読み取りました。（多分、こんな感じです）

![IMG_0153.PNG](https://qiita-image-store.s3.amazonaws.com/0/136132/fa36b4b1-cf7a-3221-6238-3cee31623eec.png)

* `ListView`のサブクラスとして`ListViewEx`が被さっています
* `ListViewEx`は`ListView`の`Columns`プロパティをオーバライドしてIDEから表面上消します
* 新たなプロパティとして`ColumnsNew`を定義しています
こっちがデフォルトプロパティになっています
* （上の絵では書ききれていませんが）プロパティ`Columns`と`ColumnsNew`は同時に更新が行なわれ、内容を同期させています
* `ListViewEx`にはカスタムデザイナ`ListViewExDesigner`が関連付けられています

ソースで云うと重要なのはここら辺。

```csharp:ListViewEx.cs（部分）
        ：
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new ColumnHeaderCollection Columns { get { return base.Columns; } }

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ListViewExColumnHeaderCollection ColumnsNew { get { return this.columns; } }
        ：
```

何でここの二重管理をしているかと云うと、表向きは利用者に`ColumnsNew`でサブクラス側を公開して、内部向けには既存の`Columns`を操作して貰おう、と云う意図でしょうね。

# 対策
さて、ここで実験の成果が生きてきます。

Malfuncさんはあくまでも、内部的には`ColumnHeaderCollection`型の`Columns`を使って貰いたいと思っている様ですが、プロパティエディタとしてはそこの型は（余り）気にしていない模様。あくまでもそこに入っているオブジェクトが持っているプロパティを全部見せてくれるだけです。

だからきっと、`Columns`が`ColumnHeader`のサブクラスを格納するコレクションである限り、何も破綻しないのではないだろうか？
どう？
そんな気がしませんか？

となれば、前出の二重プロパティは次の様な形でも大丈夫な筈です。

```csharp:ListViewEx.cs（部分）
        ：
[Designer(typeof(ListViewExDesigner))]        <==    この行は削除して下さい
        ：
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
internal ColumnHeaderCollection ColumnsOrg { get { return base.Columns; } }

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new ListViewExColumnHeaderCollection Columns { get { return this.columns; } }
        ：
```

ここでは、表向きはサブクラス化された`Columns`、内部で使って貰う方は`base.Columns`=`ColumnsOrg`に分かれています。で、内部向けの方は`internal`指定にして、公開プロパティにならない様に気を使っています。

まぁこの入り口だけ修正してもダメで、同期更新の部分もそれなりに手を加えないといけません。
えー、例えばこんな感じ…

```csharp:ListViewExColumnHeaderCollection.cs（部分）　
        ：
protected override void OnClearComplete() {
	Validate("OnClearComplete");
	base.OnClearComplete();
	this.listviewex.ColumnsOrg.Clear();
}
        ：
```

# 結果
取り敢えず一通り修正して、検証。
お、出た。

![image](https://qiita-image-store.s3.amazonaws.com/0/136132/278be296-516c-ec80-0c3f-9814556d3040.png)

![image](https://qiita-image-store.s3.amazonaws.com/0/136132/183d61c8-7ef1-84ac-fd72-0c4eac639dca.png)

勝った…

# 仕上
さて、目的を達成するための部品は揃った様です。最後の仕上げとして`Barebone`から贅肉落とした上で本稿の目的である`Editable`を実装しましょう。
更に[前稿](http://qiita.com/Toraja/items/7b840099b15ede85490a)の成果である直接編集ちっく`ListViewEx`とも合体させちゃうと、サブクラス化した甲斐があると云うものです。

では―
まずは、ヘッダのサブクラス周りから…

```csharp:ColumnHeaderEx.cs
using System.ComponentModel;
using System.Windows.Forms;

public class ColumnHeaderEx : ColumnHeader {
	public ColumnHeaderEx() : base() {
		this.Editable = false;
	}

	[Category("Behavior")]
	[Browsable(true)]
	[EditorBrowsable(EditorBrowsableState.Always)]
	[DefaultValue(false)]
	public bool Editable { get; set; }
}
```

```csharp:ColumnHeaderExCollection.cs
using System;
using System.Collections;
using System.Windows.Forms;

public class ColumnHeaderExCollection : CollectionBase {
	private ListViewEx owner;

	public ColumnHeaderExCollection(ListViewEx owner) {
		this.owner = owner;
	}

	public void Add(ColumnHeaderEx header) {
		owner.ColumnsOrg.Add((ColumnHeaderEx)header.Clone());
		this.InnerList.Add(header);
	}

	public void AddRange(ColumnHeaderEx[] headers) {
		for(int i = 0; i < headers.Length; i++)
			Add((ColumnHeaderEx)headers[i]);
	}

	public ColumnHeaderEx this[int i] {
		get {
			if(i < 0 || i >= this.InnerList.Count)
				return null;
			return (ColumnHeaderEx)this.InnerList[i];
		}
		set {
			this.InnerList[i] = value;
		}
	}

	protected override void OnInsertComplete(int index, object value) {
		this.owner.ColumnsOrg.Insert(index, (ColumnHeader)((ICloneable)value).Clone());
	}

	protected override void OnRemoveComplete(int index, object value) {
		base.OnRemoveComplete(index, value);
		this.owner.ColumnsOrg.RemoveAt(index);
	}

	protected override void OnClearComplete() {
		base.OnClearComplete();
		this.owner.ColumnsOrg.Clear();
	}
}
```

んー、`ListViewEx.cs`はもう一回全体を載せちゃいます。差分だけ説明しながら…
とも思ったんですが、まどろっこしいので。

```csharp:ListViewEx.cs
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

public partial class ListViewEx : ListView {
	public ListViewEx() : base() {
		this.View = View.Details;
		this.FullRowSelect = true;
		this.columns = new ColumnHeaderExCollection(this);

		this.EditBox = new TextBox();
		this.EditBox.Parent = this;
		this.EditBox.Visible = false;
		this.EditBox.BorderStyle = BorderStyle.FixedSingle;
		this.EditBox.Leave += EditBox_Leave;
		this.EditBox.KeyPress += EditBox_KeyPress;

		this.SetStyle(ControlStyles.EnableNotifyMessage, true);
	}

	protected override void Dispose(bool disposing) {
		if(disposing) {
			this.EditBox.KeyPress -= EditBox_KeyPress;
			this.EditBox.Leave -= EditBox_Leave;
			this.EditBox.Dispose();
		}
		base.Dispose(disposing);
	}

	private void EditBox_Leave(object sender, EventArgs e) {
		CurrentColumn.Text = EditBox.Text;
		EditBox.Visible = false;
	}

	private void EditBox_KeyPress(object sender, KeyPressEventArgs e) {
		switch(e.KeyChar) {
		case (char)Keys.Enter:
			this.Focus();
			e.Handled = true;
			break;

		case (char)Keys.Escape:
			EditBox.Text = CurrentColumn.Text;
			this.Focus();
			e.Handled = true;
			break;
		}
	}



	ColumnHeaderExCollection columns;
	TextBox EditBox;



	[Browsable(false)]
	[EditorBrowsable(EditorBrowsableState.Never)]
	public new View View {
		get { return base.View; }
		set { base.View = View.Details; }
	}

	[Browsable(false)]
	[EditorBrowsable(EditorBrowsableState.Never)]
	public new bool FullRowSelect {
		get { return base.FullRowSelect; }
		set { base.FullRowSelect = true; }
	}

	[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
	[Browsable(false)]
	[EditorBrowsable(EditorBrowsableState.Never)]
	internal ColumnHeaderCollection ColumnsOrg { get { return base.Columns; } }

	[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
	[Browsable(false)]
	[EditorBrowsable(EditorBrowsableState.Never)]
	public new ColumnHeaderExCollection Columns { get { return this.columns; } }

	[Browsable(false)]
	public ListViewItem CurrentRow { get; set; }

	[Browsable(false)]
	public ListViewItem.ListViewSubItem CurrentColumn { get; set; }

	[Browsable(false)]
	public int CurrentRowIndex { get { return (CurrentRow == null) ? -1 : CurrentRow.Index; } }

	[Browsable(false)]
	public int CurrentColumnIndex { get { return (CurrentColumn == null) ? -1 : CurrentRow.SubItems.IndexOf(CurrentColumn); } }
	
	protected override void OnItemCheck(ItemCheckEventArgs ice) {
		if(CurrentColumn != null && CurrentColumnIndex != 0)
			ice.NewValue = ice.CurrentValue;

		base.OnItemCheck(ice);
	}

	protected override void OnMouseDown(MouseEventArgs e) {
		Point loc = this.PointToClient(Cursor.Position);

		CurrentRow = null;
		CurrentColumn = null;

		CurrentRow = this.HitTest(loc).Item;
		if(CurrentRow == null || !CurrentRow.Bounds.Contains(loc))
			CurrentRow = null;
		else {
			CurrentColumn = CurrentRow.GetSubItemAt(loc.X, loc.Y);
			if(CurrentColumn == null || !CurrentColumn.Bounds.Contains(loc))
				CurrentColumn = null;
		}

		base.OnMouseDown(e);
	}

	protected override void OnResize(EventArgs e) {
		this.Focus();
		base.OnResize(e);
	}

	protected override void OnColumnWidthChanging(ColumnWidthChangingEventArgs e) {
		this.Focus();
		base.OnColumnWidthChanging(e);
	}

	protected override void OnNotifyMessage(Message m) {
		switch(m.Msg) {
		case WM_HSCROLL:
		case WM_VSCROLL:
			this.Focus();
			break;
		}
		base.OnNotifyMessage(m);
	}
	
	const int WM_HSCROLL = 0x114;
	const int WM_VSCROLL = 0x115;
	
	public void EditColumn() {
		if(CurrentColumn == null || CurrentColumnIndex == 0) return;
		if(!this.columns[CurrentColumnIndex].Editable) return;

		Rectangle rect = CurrentColumn.Bounds;
		rect.Intersect(this.ClientRectangle);
		rect.Y -= 1;

		EditBox.Bounds = rect;
		EditBox.Text = CurrentColumn.Text;
		EditBox.Visible = true;
		EditBox.BringToFront();
		EditBox.Focus();
	}
}
```

で、こんな感じになりました。

![image](https://qiita-image-store.s3.amazonaws.com/0/136132/b51c66b9-3598-a0af-bc30-90dc817a8529.png)

![image](https://qiita-image-store.s3.amazonaws.com/0/136132/b3514343-7419-570c-5a3f-84086d7d509a.png)

ごく一般的な操作で表示された`ColumnHeaderEx コレクション エディター`で、`Editable`プロパティが増えています。`columnHeaderEx3`の`Editable`を`true`にしてみます。
<br/>
使い方としては`ListViewEx`の`MouseDoubleClick`イベントハンドラに以下を記述するだけです。

```csharp
private void listViewEx1_MouseDoubleClick(object sender, MouseEventArgs e) {
	listViewEx1.EditColumn();
}
```

ダブルクリックされたらとにかく`ListViewEx.EditColumn()`を呼んじゃって下さい。編集対象かどうかは`ListViewEx`が判断してくれます。（ま、このインタフェースが良いかどうかは議論の余地があると思いますが…）
<br/>
`ColumnHeaderEx3`のカラムはダブルクリックで直接編集ちっくモードに入りました。

![image](https://qiita-image-store.s3.amazonaws.com/0/136132/7133aeab-e8cc-9945-91a5-1b3daf07fa73.png)

でも、`ColumnHeaderEx2`や`ColumnHeaderEx4`のカラムはダブルクリックしても反応しません。

![image](https://qiita-image-store.s3.amazonaws.com/0/136132/1b4ab3bf-c6e8-0203-744b-2e69b125a88a.png)

ぐー
完成！
<br/>
後は、実用に耐えられる様にプロパティとか公開メソッドとか真面目に考えないといけないなぁ。
ま、技術的な面では一通り検証できたという事で…

どっとはらい