.NETプログラミング研究 第70号 †
.NET Tips †
今回は前回の続きです。.NET Framework 2.0から追加されたWindowsアプリケーションのコントロール、TableLayoutPanelコントロールについて説明します(最後にFlowLayoutPanelも)。
前回紹介した、TableLayoutPanelを使ったサンプル、「TableLayoutPanel1.exe」を今回も使用しています。
セルの行または列を拡大する †
フォームデザイナによる方法が、MSDNの次のページで説明されています。
これによると、TableLayoutPanelコントロールのセルの行または列を拡大するには、セルにあるコントロールのRowSpanやColumnSpanプロパティの値を変更するとされています。しかしこの方法はフォームデザイナを使った時の方法であり、実際のコントロールのクラスにRowSpanやColumnSpanプロパティは存在しません。
実際には、TableLayoutPanel.SetRowSpanとSetColumnSpanメソッドを使って行と列を拡大(あるいは縮小)します。また、GetRowSpanとGetColumnSpanメソッドにより、コントロールが占有している行と列の数を取得できます。
次のコードでは、tableLayoutPanel1に配置されたcurrentControlが占有する列を一つ増やしています。ここでは、現在のtableLayoutPanel1の列の数を超えないようにしていますが、これを超えた数値を設定しても問題ないため、こうする必要はありません。
1
2
3
4
5
6
7
8
| | Dim newColSpan As Integer = _
TableLayoutPanel1.GetColumnSpan(currentControl) + 1
If TableLayoutPanel1.ColumnCount >= _
TableLayoutPanel1.GetColumn(currentControl) + newColSpan Then
TableLayoutPanel1.SetColumnSpan(currentControl, newColSpan)
End If
|
1
2
3
4
5
6
7
8
| | int newColSpan = tableLayoutPanel1.GetColumnSpan(currentControl) + 1;
if (tableLayoutPanel1.ColumnCount >=
tableLayoutPanel1.GetColumn(currentControl) + newColSpan)
{
tableLayoutPanel1.SetColumnSpan(currentControl, newColSpan);
}
|
サンプル「TableLayoutPanel1.exe」では、コントロールを右クリックして表示されるメニューから、列と行を1つ拡大できるようになっています。
AnchorとDockプロパティ †
TableLayoutPanelもFlowLayoutPanelと同様に、配置された子コントロールのAnchorとDockプロパティに、通常とは少し異なる特別な役割が与えられています。MSDNでは、次のページで説明されています。
サンプル「TableLayoutPanel1.exe」では、コントロールを右クリックして表示されるメニューから、コントロールのAnchorとDockプロパティを変更することができますので、いろいろと試してみてください。
行と列のスタイルを変更する †
MSDNでは、「方法 : TableLayoutPanel コントロールの列と行を編集する」で説明されています。
フォームデザイナを使った方法では、TableLayoutPanelのRowsやColumnsプロパティを変更しようとすると表示される「列と行のスタイル」ダイアログにより、行と列の挿入や、削除、スタイル(サイズの型)の変更を行うことができます。
しかし実際にはTableLayoutPanelクラスにRowsやColumnsプロパティが存在する訳ではなく、実体は、RowStylesプロパティとColumnStylesプロパティです。
例えば、一行目のサイズの型を絶対サイズの50ピクセルとするには、次のようにします。
1
| | TableLayoutPanel1.RowStyles(0) = New RowStyle(SizeType.Absolute, 50.0F)
|
1
| | tableLayoutPanel1.RowStyles[0] = new RowStyle(SizeType.Absolute, 50F);
|
ただし、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]が存在するか分かりませんので、正しいやり方とは言えません。つまり、ある行のスタイルを変更するコードは、ちょっと面倒ですが、次のようになりそうです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| | 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
While TableLayoutPanel1.RowStyles.Count < rowIndex
TableLayoutPanel1.RowStyles.Add( _
New RowStyle(SizeType.AutoSize))
End While
TableLayoutPanel1.RowStyles.Add(rs)
End If
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| | int rowIndex = 4;
RowStyle rs = new RowStyle(SizeType.Absolute, 50F);
if (tableLayoutPanel1.RowStyles.Count > rowIndex)
{
tableLayoutPanel1.RowStyles[rowIndex] = rs;
}
else
{
while (tableLayoutPanel1.RowStyles.Count < rowIndex)
{
tableLayoutPanel1.RowStyles.Add(
new RowStyle(SizeType.AutoSize));
}
tableLayoutPanel1.RowStyles.Add(rs);
}
|
行や列を挿入する †
「列と行のスタイル」ダイアログを使えば行や列の挿入は簡単ですが、これを実行時に行ううまい方法はありません。RowStyles.Insertでうまくいかないのは、今まで読んでいただけたのであれば、明白です。結局は、行や列を拡張して、TableLayoutPanelに配置されたコントロールを一つずつ順番に移動されるという地味なやり方になりそうです。
しかし単純にそのようなコードを書いたとしてもうまくいくとは限りません。なぜならば、先に述べたように、実行時にTableLayoutPanelに配置されたコントロールはどこに配置されるか予測が付きにくく、新しく挿入した行のセルに入ってきてしまう可能性があるからです(それでいいと言うのであれば、かまわないでしょうが)。
よってここでは、先に紹介したように、TableLayoutPanelに配置されたコントロールの位置を実際に現在ある位置に設定してから、列を挿入する処理を行うことにします。
以下の例では、insertRowの位置に行を挿入しています。サンプル「TableLayoutPanel1.exe」からの抜粋です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| | 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()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| | 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();
|
行や列を削除する †
これもやはりうまい方法がありませんが、前と同じ方針で行ってみましょう。
以下の例では、removeRowの位置の行を削除しています。サンプル「TableLayoutPanel1.exe」からの抜粋です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| | 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()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| | 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();
|
このように、実行時にTableLayoutPanelに行や列を挿入あるいは削除するのは簡単ではありません。.NET Frameworkにこのような機能が用意されていないということは、もしかしたら実行時にTableLayoutPanelに行や列を挿入、削除するのは好ましくないということかもしれません。
セルを独自に描画する †
TableLayoutPanelのCellPaintイベントにより、セルを独自に描画することができます。
次の例では、一つおきのセルに色をつけています。サンプル「TableLayoutPanel1.exe」からの抜粋です。
1
2
3
4
5
6
7
8
9
10
11
| | 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
|
1
2
3
4
5
6
7
8
9
10
| | void tableLayoutPanel1_CellPaint(object sender,
TableLayoutCellPaintEventArgs e)
{
if (e.Column % 2 == 1 ^ e.Row % 2 == 1)
{
e.Graphics.FillRectangle(Brushes.LightSkyBlue, e.CellBounds);
}
}
|
継承したコントロールでデザイナを使ってFlowLayoutPanelのプロパティを変更できない問題 †
FlowLayoutPanelを配置したコントロール(フォームを含む)を継承して新たなコントロールを作成した時、継承基に配置されたFlowLayoutPanelオブジェクトのModifiersがPublicであったとしても、継承したコントロールでそのFlowLayoutPanelオブジェクトのプロパティを変更できないという問題があります。これはFlowLayoutPanelだけでなく、TableLayoutPanelやToolStrip、MenuStrip、ContextMenuStrip、StatusStrip、DataGridView、BindingNavigatorコントロールでも起こります。このバグについて詳しくは次のページで説明されています。
FlowLayoutPanelについての解決法が次のページで紹介されています。
これによると、FlowLayoutPanelを継承したクラスを作成し、これにdesignerTypeにPanelDesignerを指定したDesigner属性をつけ、これをFlowLayoutPanelの代わりに継承基のコントロールで使用するということです。
つまり、例えばまず次のようなクラスを作成し、
1
2
3
4
5
6
7
8
| | <System.ComponentModel.Designer( _
"System.Windows.Forms.Design.PanelDesigner, System.Design")> _
Public Class InheritableFlowLayoutPanel
Inherits FlowLayoutPanel
Public Sub New()
End Sub
End Class
|
1
2
3
4
5
6
7
| | [Designer("System.Windows.Forms.Design.PanelDesigner, System.Design")]
public class InheritableFlowLayoutPanel : FlowLayoutPanel
{
public InheritableFlowLayoutPanel()
{
}
}
|
これをFlowLayoutPanelの代わりに使うようにします。
残念ながらこれはFlowLayoutPanelだけの解決法で、TableLayoutPanelをはじめ、他のコントロールには使えません。
コメント †