ListView(Details)のカラムを直接編集したいっ！　巻之弐


# 前口上
[前稿](http://qiita.com/Toraja/items/51dd3ec878c647583231)を受けて、サブクラス化にてカラム直接編集を実現してみたいと思います。

序なので、ふっかちゃんバージョンも紹介しときましょう。
ただ、実はふっかちゃん、MSのサイトからうまくコピーできないパターンがあります。（改行がぐちゃぐちゃで…）
copyしてくる時はちょっと古めの下記サイトを参照してください。（両方必要です）

* [WndProcHooker](https://msdn.microsoft.com/ja-jp/library/ms229658(v=vs.80).aspx)
* [Win32](https://msdn.microsoft.com/ja-jp/library/ms229683(v=vs.80).aspx)

あ、後 `Win32` の中に
`[DllImport("coredll.dll")]`
ってのが11ヶ所ありますが、何か宜しくないので、
`[DllImport("user32.dll")]`
に変えて下さい。問題ない筈です。


# ふっかちゃん
[前稿](http://qiita.com/Toraja/items/51dd3ec878c647583231)のコードに前出ふたつのコードを合わせた上で、以下の様にコードを追加すればOKです。

```csharp
public Form1() {
	InitializeComponent();

    //    次の２行を追加する
	WndProcHooker.HookWndProc(listView1, new WndProcHooker.WndProcCallback(WM_Hook_Handler), WM_HSCROLL);
	WndProcHooker.HookWndProc(listView1, new WndProcHooker.WndProcCallback(WM_Hook_Handler), WM_VSCROLL);
}

const int WM_HSCROLL = 0x114;
const int WM_VSCROLL = 0x115;

int WM_Hook_Handler(IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled) {
	Control.FromHandle(hwnd).Focus();
	return -1;
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
	WndProcHooker.UnhookWndProc(listView1, WM_VSCROLL);
	WndProcHooker.UnhookWndProc(listView1, WM_HSCROLL);
}
```

`ListView` のスクロールバーを触ると、直接編集ちっくモード抜けたでしょ？
ふっかちゃん、中々やるなー

# サブクラス化
いきなりソース！
のみっ！

```csharp
using System;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

public class ListViewEx : ListView {
	public ListViewEx() : base() {
		this.SetStyle(ControlStyles.EnableNotifyMessage, true);

		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;
	}

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

	TextBox EditBox;

	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;
		}
	}

	[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.GetItemAt(loc.X, loc.Y);
		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;

		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();
	}
}
```

いやいや、使用説明だけはしなくては…

使いたいプロジェクトの中に `ListVIewEx.cs` のクラスファイルを追加して、上記ソースをまるっと貼り付けちゃって下さい。
一旦全体ビルドしたら、ツールボックスに `ListViewEx` が増えている筈なのでデザイナ画面で使えます。（勿論ビルドエラーがあったらダメですよ）
<br/>
<dl>
<dt>プロパティ</dt>
<dd>CurrentRowとCurrentRowIndexプロパティが増えてます</dd>
<dd>CurrentColumnとCurrentColumnIndexプロパティも増えてます</dd>
<dt>メソッド</dt>
<dd>EditColumn()が定義されています</dd>
</dl>

機能は名前から推し量って下さい。（投げやり）

`EditColumn` メソッドはフォームに張り付けた `ListViewEx` の `MouseDoubleClick` イベント辺りで必要性を確認の上呼びだす仕様です。
<br/>
コードで説明すると、こんな感じ↓
これで前稿と同じく、カラム2だけが編集対象となります。

```csharp
private void listView1_MouseDoubleClick(object sender, MouseEventArgs e) {
	if(listView1.CurrentColumnIndex == 1)
        listView1.EditColumn();
}
```

一つ忘れものがありました。
カラムサイズを調整した時にもモード抜けないといけないですね。サブクラスバージョンでは織り込んであります。


# 次への布石
今回のサブクラス化では、どのカラムが編集対象であるかは、外部からコントロールしなければなりません。
カラム位置で判定していたりすると、コードの可読性は落ちますしメンテナンス性も悪くなります。

ここはひとつListViewItemのカラムヘッダ辺りに `Editable` みたいなプロパティがあると良いのになー

とか思っちゃったのが運の尽き。
更に泥沼にはまっていく…

ちゅー事で次はColumnHeaderのサブクラス対応かっ？
請うご期待！
<br/>
※ 一旦脳味噌クールダウン図って別ネタを挟むかも…