┏第70号━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃         .NETプログラミング研究         ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ──<メニュー>─────────────────────── ■.NET Tips ・TableLayoutPanelコントロール -セルの行または列を拡大する -AnchorとDockプロパティ -行と列のスタイルを変更する -行や列を挿入する -行や列を削除する -セルを独自に描画する ・継承したコントロールでデザイナを使ってFlowLayoutPanelのプロ  パティを変更できない問題 ─────────────────────────────── ─────────────────────────────── ■.NET Tips ─────────────────────────────── 今回は前回の続きです。.NET Framework 2.0から追加されたWindows アプリケーションのコントロール、TableLayoutPanelコントロールに ついて説明します(最後にFlowLayoutPanelも)。 前回紹介した、TableLayoutPanelを使ったサンプル、「 TableLayoutPanel1.exe」を今回も使用しています。 [URL]サンプル「TableLayoutPanel1.exe」 http://dobon.net/vb/dotnet/control/files/TableLayoutPanel1.exe [URL]ソース http://dobon.net/vb/dotnet/control/files/TableLayoutPanel1.zip ─────────────────────────────── ★セルの行または列を拡大する フォームデザイナによる方法が、MSDNの次のページで説明されていま す。 [URL]方法 : TableLayoutPanel コントロールの行と列を拡大する http://msdn2.microsoft.com/ja-jp/library/ms171687.aspx これによると、TableLayoutPanelコントロールのセルの行または列を 拡大するには、セルにあるコントロールのRowSpanやColumnSpanプロ パティの値を変更するとされています。しかしこの方法はフォームデ ザイナを使った時の方法であり、実際のコントロールのクラスに RowSpanやColumnSpanプロパティは存在しません。 実際には、TableLayoutPanel.SetRowSpanとSetColumnSpanメソッドを 使って行と列を拡大(あるいは縮小)します。また、GetRowSpanと GetColumnSpanメソッドにより、コントロールが占有している行と列 の数を取得できます。 次のコードでは、tableLayoutPanel1に配置されたcurrentControlが 占有する列を一つ増やしています。ここでは、現在の tableLayoutPanel1の列の数を超えないようにしていますが、これを 超えた数値を設定しても問題ないため、こうする必要はありません。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ Dim newColSpan As Integer = _ TableLayoutPanel1.GetColumnSpan(currentControl) + 1 'テーブルの列の数より大きくなることをさける 'SetColumnSpanでテーブルの列の数より大きい値を設定できるので、チェックしなくても問題ない If TableLayoutPanel1.ColumnCount >= _ TableLayoutPanel1.GetColumn(currentControl) + newColSpan Then TableLayoutPanel1.SetColumnSpan(currentControl, newColSpan) End If ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ int newColSpan = tableLayoutPanel1.GetColumnSpan(currentControl) + 1; //テーブルの列の数より大きくなることをさける //SetColumnSpanでテーブルの列の数より大きい値を設定できるので、チェックしなくても問題ない if (tableLayoutPanel1.ColumnCount >= tableLayoutPanel1.GetColumn(currentControl) + newColSpan) { tableLayoutPanel1.SetColumnSpan(currentControl, newColSpan); } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ サンプル「TableLayoutPanel1.exe」では、コントロールを右クリッ クして表示されるメニューから、列と行を1つ拡大できるようになっ ています。 ★AnchorとDockプロパティ TableLayoutPanelもFlowLayoutPanelと同様に、配置された子コント ロールのAnchorとDockプロパティに、通常とは少し異なる特別な役割 が与えられています。MSDNでは、次のページで説明されています。 [URL]方法 : TableLayoutPanel コントロールで子コントロールを固 定およびドッキングする http://msdn2.microsoft.com/ja-jp/library/ms171691.aspx サンプル「TableLayoutPanel1.exe」では、コントロールを右クリッ クして表示されるメニューから、コントロールのAnchorとDockプロパ ティを変更することができますので、いろいろと試してみてください。 ★行と列のスタイルを変更する MSDNでは、「方法 : TableLayoutPanel コントロールの列と行を編集 する」で説明されています。 [URL]方法 : TableLayoutPanel コントロールの列と行を編集する http://msdn2.microsoft.com/ja-jp/library/ms171686.aspx フォームデザイナを使った方法では、TableLayoutPanelのRowsや Columnsプロパティを変更しようとすると表示される「列と行のスタ イル」ダイアログにより、行と列の挿入や、削除、スタイル(サイズ の型)の変更を行うことができます。 しかし実際にはTableLayoutPanelクラスにRowsやColumnsプロパティ が存在する訳ではなく、実体は、RowStylesプロパティと ColumnStylesプロパティです。 例えば、一行目のサイズの型を絶対サイズの50ピクセルとするには、 次のようにします。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ TableLayoutPanel1.RowStyles(0) = New RowStyle(SizeType.Absolute, 50.0F) ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ tableLayoutPanel1.RowStyles[0] = new RowStyle(SizeType.Absolute, 50F); ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ただし、RowStylesとColumnStylesはあくまでスタイル(サイズの型) のコレクションであって、行と列そのものを表しているわけではあり ません。 「列と行のスタイル」ダイアログのみで列(あるいは行)を管理して いる場合は、通常TableLayoutPanelの列の数とスタイルの数は一致し ます。つまり、TableLayoutPanelクラスのRowCountとRowStyles. Count(あるいはColumnCountとColumnStyles.Count)の数値は一致し ます。フォームデザイナを使ってTableLayoutPanelのRowCountや ColumnCountを変更して列や行の数を増やした場合でも、それに応じ てRowStylesやColumnStylesにスタイルが追加され、RowCountと RowStyles.Count(あるいはColumnCountとColumnStyles.Count)の数 は通常同じになります。 注意:デザイナで列や行を増やした時は問題ありませんが、減らした ときは、前のスタイルが削除されずに残り、列や行数とスタイルの数 が同じにならないことが多々(というよりほとんど)あります。この 場合でもデザイナで見る限りでは分からず、コードを見なければ分か りません。 しかし、デザイナを使わないで列や行の数を変更した場合は、そうは なりません。RowCountやColumnCountプロパティを変更しても、 RowStylesやColumnStylesに自動的にスタイルが追加されることはあ りません。先に示したように、Controls.Addで子コントロールを追加 して行や列が拡張した場合も同じく、スタイルは追加されません。つ まり、必要に応じて自分で追加しなければならないということになり ます。 その意味で、先ほど示した例も、tableLayoutPanel1.RowStyles[0]が 存在するか分かりませんので、正しいやり方とは言えません。つまり、 ある行のスタイルを変更するコードは、ちょっと面倒ですが、次のよ うになりそうです。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 'スタイルを変更する行数 Dim rowIndex As Integer = 4 'スタイル Dim rs As New RowStyle(SizeType.Absolute, 50.0F) If TableLayoutPanel1.RowStyles.Count > rowIndex Then TableLayoutPanel1.RowStyles(rowIndex) = rs Else 'とりあえずRowStyleを埋める While TableLayoutPanel1.RowStyles.Count < rowIndex TableLayoutPanel1.RowStyles.Add( _ New RowStyle(SizeType.AutoSize)) End While TableLayoutPanel1.RowStyles.Add(rs) End If ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //スタイルを変更する行数 int rowIndex = 4; //スタイル RowStyle rs = new RowStyle(SizeType.Absolute, 50F); if (tableLayoutPanel1.RowStyles.Count > rowIndex) { tableLayoutPanel1.RowStyles[rowIndex] = rs; } else { //とりあえずRowStyleを埋める while (tableLayoutPanel1.RowStyles.Count < rowIndex) { tableLayoutPanel1.RowStyles.Add( new RowStyle(SizeType.AutoSize)); } tableLayoutPanel1.RowStyles.Add(rs); } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ★行や列を挿入する 「列と行のスタイル」ダイアログを使えば行や列の挿入は簡単ですが、 これを実行時に行ううまい方法はありません。RowStyles.Insertでう まくいかないのは、今まで読んでいただけたのであれば、明白です。 結局は、行や列を拡張して、TableLayoutPanelに配置されたコントロー ルを一つずつ順番に移動されるという地味なやり方になりそうです。 しかし単純にそのようなコードを書いたとしてもうまくいくとは限り ません。なぜならば、先に述べたように、実行時にTableLayoutPanel に配置されたコントロールはどこに配置されるか予測が付きにくく、 新しく挿入した行のセルに入ってきてしまう可能性があるからです(そ れでいいと言うのであれば、かまわないでしょうが)。 よってここでは、先に紹介したように、TableLayoutPanelに配置され たコントロールの位置を実際に現在ある位置に設定してから、列を挿 入する処理を行うことにします。 以下の例では、insertRowの位置に行を挿入しています。サンプル「 TableLayoutPanel1.exe」からの抜粋です。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ TableLayoutPanel1.SuspendLayout() Dim c As Control For Each c In TableLayoutPanel1.Controls Dim pos As TableLayoutPanelCellPosition = _ TableLayoutPanel1.GetPositionFromControl(c) TableLayoutPanel1.SetCellPosition(c, pos) If TableLayoutPanel1.RowCount <= pos.Row Then TableLayoutPanel1.RowCount = pos.Row + 1 End If If TableLayoutPanel1.ColumnCount <= pos.Column Then TableLayoutPanel1.ColumnCount = pos.Column + 1 End If Next c '列を増やす TableLayoutPanel1.RowCount += 1 'コントロールを移動 Dim y As Integer For y = TableLayoutPanel1.RowCount - 1 To insertRow Step -1 Dim x As Integer For x = 0 To TableLayoutPanel1.ColumnCount - 1 c = TableLayoutPanel1.GetControlFromPosition(x, y) If Not (c Is Nothing) Then TableLayoutPanel1.SetCellPosition( _ c, New TableLayoutPanelCellPosition(x, y + 1)) End If Next x Next y 'スタイルを挿入 If TableLayoutPanel1.RowStyles.Count > insertRow Then TableLayoutPanel1.RowStyles.Insert( _ insertRow, New RowStyle(SizeType.AutoSize)) End If TableLayoutPanel1.ResumeLayout() ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ tableLayoutPanel1.SuspendLayout(); foreach (Control c in tableLayoutPanel1.Controls) { TableLayoutPanelCellPosition pos = tableLayoutPanel1.GetPositionFromControl(c); tableLayoutPanel1.SetCellPosition(c, pos); if (tableLayoutPanel1.RowCount <= pos.Row) tableLayoutPanel1.RowCount = pos.Row + 1; if (tableLayoutPanel1.ColumnCount <= pos.Column) tableLayoutPanel1.ColumnCount = pos.Column + 1; } //列を増やす tableLayoutPanel1.RowCount++; //コントロールを移動 for (int y = tableLayoutPanel1.RowCount - 1; y >= insertRow; y--) { for (int x = 0; x < tableLayoutPanel1.ColumnCount; x++) { Control c = tableLayoutPanel1.GetControlFromPosition(x, y); if (c != null) { tableLayoutPanel1.SetCellPosition( c, new TableLayoutPanelCellPosition(x, y + 1)); } } } //スタイルを挿入 if (tableLayoutPanel1.RowStyles.Count > insertRow) { tableLayoutPanel1.RowStyles.Insert( insertRow, new RowStyle(SizeType.AutoSize)); } tableLayoutPanel1.ResumeLayout(); ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ★行や列を削除する これもやはりうまい方法がありませんが、前と同じ方針で行ってみま しょう。 以下の例では、removeRowの位置の行を削除しています。サンプル「 TableLayoutPanel1.exe」からの抜粋です。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ TableLayoutPanel1.SuspendLayout() Dim c As Control For Each c In TableLayoutPanel1.Controls Dim pos As TableLayoutPanelCellPosition = _ TableLayoutPanel1.GetPositionFromControl(c) TableLayoutPanel1.SetCellPosition(c, pos) If TableLayoutPanel1.RowCount <= pos.Row Then TableLayoutPanel1.RowCount = pos.Row + 1 End If If TableLayoutPanel1.ColumnCount <= pos.Column Then TableLayoutPanel1.ColumnCount = pos.Column + 1 End If Next c '削除する列にあるコントロールを削除 Dim x As Integer For x = 0 To TableLayoutPanel1.ColumnCount - 1 c = TableLayoutPanel1.GetControlFromPosition(x, removeRow) If Not (c Is Nothing) Then TableLayoutPanel1.Controls.Remove(c) End If Next x 'コントロールを移動 Dim y As Integer For y = removeRow + 1 To TableLayoutPanel1.RowCount - 1 For x = 0 To TableLayoutPanel1.ColumnCount - 1 c = TableLayoutPanel1.GetControlFromPosition(x, y) If Not (c Is Nothing) Then TableLayoutPanel1.SetCellPosition( _ c, New TableLayoutPanelCellPosition(x, y - 1)) End If Next x Next y '列を減らす If TableLayoutPanel1.RowCount > 0 Then TableLayoutPanel1.RowCount -= 1 End If 'スタイルを削除 If TableLayoutPanel1.RowStyles.Count > removeRow Then TableLayoutPanel1.RowStyles.RemoveAt(removeRow) End If TableLayoutPanel1.ResumeLayout() ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ tableLayoutPanel1.SuspendLayout(); foreach (Control c in tableLayoutPanel1.Controls) { TableLayoutPanelCellPosition pos = tableLayoutPanel1.GetPositionFromControl(c); tableLayoutPanel1.SetCellPosition(c, pos); if (tableLayoutPanel1.RowCount <= pos.Row) tableLayoutPanel1.RowCount = pos.Row + 1; if (tableLayoutPanel1.ColumnCount <= pos.Column) tableLayoutPanel1.ColumnCount = pos.Column + 1; } //削除する列にあるコントロールを削除 for (int x = 0; x < tableLayoutPanel1.ColumnCount; x++) { Control c = tableLayoutPanel1.GetControlFromPosition(x, removeRow); if (c != null) { tableLayoutPanel1.Controls.Remove(c); } } //コントロールを移動 for (int y = removeRow + 1; y < tableLayoutPanel1.RowCount; y++) { for (int x = 0; x < tableLayoutPanel1.ColumnCount; x++) { Control c = tableLayoutPanel1.GetControlFromPosition(x, y); if (c != null) { tableLayoutPanel1.SetCellPosition( c, new TableLayoutPanelCellPosition(x, y - 1)); } } } //列を減らす if (tableLayoutPanel1.RowCount > 0) tableLayoutPanel1.RowCount--; //スタイルを削除 if (tableLayoutPanel1.RowStyles.Count > removeRow) { tableLayoutPanel1.RowStyles.RemoveAt(removeRow); } tableLayoutPanel1.ResumeLayout(); ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ このように、実行時にTableLayoutPanelに行や列を挿入あるいは削除 するのは簡単ではありません。.NET Frameworkにこのような機能が用 意されていないということは、もしかしたら実行時に TableLayoutPanelに行や列を挿入、削除するのは好ましくないという ことかもしれません。 ★セルを独自に描画する TableLayoutPanelのCellPaintイベントにより、セルを独自に描画す ることができます。 次の例では、一つおきのセルに色をつけています。サンプル「 TableLayoutPanel1.exe」からの抜粋です。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 'TableLayoutPanel1のCellPaintイベントハンドラ Private Sub TableLayoutPanel1_CellPaint( _ ByVal sender As System.Object, _ ByVal e As System.Windows.Forms.TableLayoutCellPaintEventArgs) _ Handles TableLayoutPanel1.CellPaint '一つおきにセルの背景色を変更する If (e.Column Mod 2 = 1) Xor (e.Row Mod 2 = 1) Then e.Graphics.FillRectangle(Brushes.LightSkyBlue, e.CellBounds) End If End Sub ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //tableLayoutPanel1のCellPaintイベントハンドラ void tableLayoutPanel1_CellPaint(object sender, TableLayoutCellPaintEventArgs e) { //一つおきにセルの背景色を変更する if (e.Column % 2 == 1 ^ e.Row % 2 == 1) { e.Graphics.FillRectangle(Brushes.LightSkyBlue, e.CellBounds); } } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ─────────────────────────────── ●継承したコントロールでデザイナを使ってFlowLayoutPanelのプロパ ティを変更できない問題 FlowLayoutPanelを配置したコントロール(フォームを含む)を継承 して新たなコントロールを作成した時、継承基に配置された FlowLayoutPanelオブジェクトのModifiersがPublicであったとしても、 継承したコントロールでそのFlowLayoutPanelオブジェクトのプロパ ティを変更できないという問題があります。これはFlowLayoutPanel だけでなく、TableLayoutPanelやToolStrip、MenuStrip、 ContextMenuStrip、StatusStrip、DataGridView、BindingNavigator コントロールでも起こります。このバグについて詳しくは次のページ で説明されています。 [URL]Bug Details: Form designer doesn’t allow to modify properties of all new for .Net Framework 2.0 controls http://lab.msdn.microsoft.com/productfeedback/viewfeedback.aspx?feedbackid=02f9cd99-08a7-4efa-92d0-99a53b91d302 FlowLayoutPanelについての解決法が次のページで紹介されています。 [URL]FDBK37485#1: Can manage to use FlowLayoutPanel with some work. http://lab.msdn.microsoft.com/productfeedback/ViewWorkaround.aspx?FeedbackID=FDBK37485#1 これによると、FlowLayoutPanelを継承したクラスを作成し、これに designerTypeにPanelDesignerを指定したDesigner属性をつけ、これ をFlowLayoutPanelの代わりに継承基のコントロールで使用するとい うことです。 つまり、例えばまず次のようなクラスを作成し、 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ _ Public Class InheritableFlowLayoutPanel Inherits FlowLayoutPanel Public Sub New() End Sub End Class ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ [Designer("System.Windows.Forms.Design.PanelDesigner, System.Design")] public class InheritableFlowLayoutPanel : FlowLayoutPanel { public InheritableFlowLayoutPanel() { } } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ これをFlowLayoutPanelの代わりに使うようにします。 残念ながらこれはFlowLayoutPanelだけの解決法で、 TableLayoutPanelをはじめ、他のコントロールには使えません。 =============================== ■ここで示したコードの多くはまずC#で書き、それを「C# to VB.NET Translator」でVB.NETのコードに変換し、修正を加えたものです。 [URL]C# to VB.NET Translator http://authors.aspalliance.com/aldotnet/examples/translate.aspx ■このマガジンの購読、購読中止、バックナンバー、説明に関しては  次のページをご覧ください。  http://www.mag2.com/m/0000104516.htm ■発行人・編集人:どぼん!  (Microsoft MVP for Visual Basic, Oct 2005-Oct 2006)  http://dobon.net  dobon_info@yahoo.co.jp ■ご質問等はメールではなく、掲示板へお願いいたします。  http://dobon.net/vb/bbs.html ■上記メールアドレスへのメールは確実に読まれる保障はありません  (スパム、ウィルス対策です)。メールは下記URLのフォームメール  から送信してください。  http://dobon.net/mail.html Copyright (c) 2003 - 2006 DOBON! All rights reserved. ===============================