背景
前稿迄でカラムの直接編集ちっくは実現できました。でも、全てのカラムが編集対象になるとは限りません。
現状それをコントロールするには、どのカラムを編集対象とするかを外部に保持して、外部からコントロールする必要があります。
許せんっ!
美しくないっ!
きちんと役割毎に分離しなくては…
方針
前稿からの流れで行くと…
- ColumnHeaderをサブクラス化する
- サブクラスに
Editable
プロパティを設ける - (一応確認まで)
Editable
プロパティがtrue
のカラムだけの編集を可能とする
と云う様な事がしたいんですよね。
んー、プロパティ追加するだけなら何てこたないんです。
public class ColumnHeaderEx : ColumnHeader {
public ColumnHeaderEx() : base() {
}
[Browsable(true)]
public bool Editable { get; set; }
}
ってなサブクラスを定義して、Form1.cs
の中に以下の様なコードを追加しておけばオッケー。
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
って記述しないといけないし…
と云う事で今回の目的は以下とします。
この右側にカスタムプロパティが出て欲しい訳です。
…頑張りましょっ!
実験
まず、ネットを漁りました。
イイ感じの日本語の情報には余り行き当たりません…
断片的な話と、ColumnHeader
をサブクラス化した場合は、カスタムデザイナ(?)だかカスタムエディタ(?)を指定しないといけないだら、良く分かりません。おまけに、カスタム何ちゃらを作ってしまったら、標準の「列の編集」が効かなくなるんじゃないの?
とか、悶々、悶々。。。
そうこうのた打っているうちに、ふと…
ColumnHeader コレクションエディター
なんてエラそーな名前が付いてるけど、所詮はプロパティエディタじゃないの?
だったら、ListView.Columns
の中身にColumnHeaderEx
型のインスタンスが入っていれば、件のエディタ上にカスタムプロパティも一緒に表示されるのではっ?
と思ったら、後は実験あるのみっ!
取り敢えず、ColumnHeader
のサブクラスを宣言。
public class ColumnHeaderEx : ColumnHeader {
public ColumnHeaderEx() : base() {
}
[Browsable(true)]
public bool Editable { get; set; }
}
で、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
よしよし、方向性は間違っていないと見た!
問題
すっごい前が開けた様な気はするんですが、実はまだまだ問題は山積しています。
-
Designer.cs
を直接編集しちゃうのはダメでしょう - 上のエディタ画面でメンバーを追加すると
ColumnHeader
クラスが追加されちゃいます - 相変わらず
Editable
プロパティの参照にキャストが必要です
でも実はこれら、全部根っこは一緒です。
つまりListView.Columns
の型がColumnHeaderExCollecction
であれば、キャストはいらなくなります。こうなればきっと、プロパティエディタ君はよしなに取り計らってくれて、ジェネレータ氏もDesigner.cs
のコード生成をうまい事処理してくれるんじゃないかなぁ?(希望的観測)
となれば、コードの直接編集もいらない筈です。
後は、参考になりそうな情報を探すだけ…(何か振出しに戻ってるし)
で、ついに見つけました。
残念ながら日本語でも英語でもないので、どんなやり取りがなされているのか(私には)微塵も分かりませんが、サンプルコードは(一揃い纏まってるという意味で)完璧です。
上のページの一番最後のスレッドにダウンロードリンクがあります。そのzipファイルの中にListViewExBarebone.zip
ってなファイルがあって、それが名前の通りColumnHeader
のサブクラス化の基本骨格だけにシェイプしたコード群です。でもやっぱり若干の贅肉は付いてますが…(多分骨格に対する視点が違うんだと思います)
ただ一つ問題が解決されていません。
このBarebone
、やっぱりカスタムデザイナを利用しています。多分この辺りかと…
:
[Designer(typeof(ListViewExDesigner))] <== ここ!
public partial class ListViewEx : ListView {
:
では、実際に確認してみましょう。
危惧した通り、「列の編集」選択肢が出てきませんね。
列を追加するには、
おー、見慣れた奴だ。Test
カスタムプロパティも追加されてますね。
でもでも、ColumnsNew
だとぉ?
なんじゃそら。
構造
取り敢えずBarebone
から以下を読み取りました。(多分、こんな感じです)
-
ListView
のサブクラスとしてListViewEx
が被さっています -
ListViewEx
はListView
のColumns
プロパティをオーバライドしてIDEから表面上消します - 新たなプロパティとして
ColumnsNew
を定義しています
こっちがデフォルトプロパティになっています - (上の絵では書ききれていませんが)プロパティ
Columns
とColumnsNew
は同時に更新が行なわれ、内容を同期させています -
ListViewEx
にはカスタムデザイナListViewExDesigner
が関連付けられています
ソースで云うと重要なのはここら辺。
:
[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
のサブクラスを格納するコレクションである限り、何も破綻しないのではないだろうか?
どう?
そんな気がしませんか?
となれば、前出の二重プロパティは次の様な形でも大丈夫な筈です。
:
[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
指定にして、公開プロパティにならない様に気を使っています。
まぁこの入り口だけ修正してもダメで、同期更新の部分もそれなりに手を加えないといけません。
えー、例えばこんな感じ…
:
protected override void OnClearComplete() {
Validate("OnClearComplete");
base.OnClearComplete();
this.listviewex.ColumnsOrg.Clear();
}
:
結果
取り敢えず一通り修正して、検証。
お、出た。
勝った…
仕上
さて、目的を達成するための部品は揃った様です。最後の仕上げとしてBarebone
から贅肉落とした上で本稿の目的であるEditable
を実装しましょう。
更に前稿の成果である直接編集ちっくListViewEx
とも合体させちゃうと、サブクラス化した甲斐があると云うものです。
では―
まずは、ヘッダのサブクラス周りから…
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; }
}
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
はもう一回全体を載せちゃいます。差分だけ説明しながら…
とも思ったんですが、まどろっこしいので。
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();
}
}
で、こんな感じになりました。
ごく一般的な操作で表示されたColumnHeaderEx コレクション エディター
で、Editable
プロパティが増えています。columnHeaderEx3
のEditable
をtrue
にしてみます。
使い方としてはListViewEx
のMouseDoubleClick
イベントハンドラに以下を記述するだけです。
private void listViewEx1_MouseDoubleClick(object sender, MouseEventArgs e) {
listViewEx1.EditColumn();
}
ダブルクリックされたらとにかくListViewEx.EditColumn()
を呼んじゃって下さい。編集対象かどうかはListViewEx
が判断してくれます。(ま、このインタフェースが良いかどうかは議論の余地があると思いますが…)
ColumnHeaderEx3
のカラムはダブルクリックで直接編集ちっくモードに入りました。
でも、ColumnHeaderEx2
やColumnHeaderEx4
のカラムはダブルクリックしても反応しません。
ぐー
完成!
後は、実用に耐えられる様にプロパティとか公開メソッドとか真面目に考えないといけないなぁ。
ま、技術的な面では一通り検証できたという事で…
どっとはらい