#title(.NETプログラミング研究 第70号)

#navi(.NET プログラミング研究)

#contents

*.NETプログラミング研究 第70号 [#fa28542b]

**.NET Tips [#zb701478]

今回は前回の続きです。.NET Framework 2.0から追加されたWindowsアプリケーションのコントロール、TableLayoutPanelコントロールについて説明します(最後にFlowLayoutPanelも)。

前回紹介した、TableLayoutPanelを使ったサンプル、「TableLayoutPanel1.exe」を今回も使用しています。

-[[TableLayoutPanelコントロールの基本的な使い方>http://dobon.net/vb/dotnet/control/tlbeginning.html]]

***セルの行または列を拡大する [#ibca28df]

#column(注意){{
この記事の最新版は「[[TableLayoutPanelのセルの行または列を拡大する>http://dobon.net/vb/dotnet/control/tlrowspan.html]]」で公開しています。
}}

フォームデザイナによる方法が、MSDNの次のページで説明されています。

-[[方法 : TableLayoutPanel コントロールの行と列を拡大する>http://msdn2.microsoft.com/ja-jp/library/ms171687.aspx]]

これによると、TableLayoutPanelコントロールのセルの行または列を拡大するには、セルにあるコントロールのRowSpanやColumnSpanプロパティの値を変更するとされています。しかしこの方法はフォームデザイナを使った時の方法であり、実際のコントロールのクラスにRowSpanやColumnSpanプロパティは存在しません。

実際には、TableLayoutPanel.SetRowSpanとSetColumnSpanメソッドを使って行と列を拡大(あるいは縮小)します。また、GetRowSpanとGetColumnSpanメソッドにより、コントロールが占有している行と列の数を取得できます。

次のコードでは、tableLayoutPanel1に配置されたcurrentControlが占有する列を一つ増やしています。ここでは、現在のtableLayoutPanel1の列の数を超えないようにしていますが、これを超えた数値を設定しても問題ないため、こうする必要はありません。

#code(vbnet){{
Dim newColSpan As Integer = _
    TableLayoutPanel1.GetColumnSpan(currentControl) + 1
'テーブルの列の数より大きくなることをさける
'SetColumnSpanでテーブルの列の数より大きい値を設定できるので、チェックしなくても問題ない
If TableLayoutPanel1.ColumnCount >= _
    TableLayoutPanel1.GetColumn(currentControl) + newColSpan Then
    TableLayoutPanel1.SetColumnSpan(currentControl, newColSpan)
End If
}}

#code(csharp){{
int newColSpan = tableLayoutPanel1.GetColumnSpan(currentControl) + 1;
//テーブルの列の数より大きくなることをさける
//SetColumnSpanでテーブルの列の数より大きい値を設定できるので、チェックしなくても問題ない
if (tableLayoutPanel1.ColumnCount >=
    tableLayoutPanel1.GetColumn(currentControl) + newColSpan)
{
    tableLayoutPanel1.SetColumnSpan(currentControl, newColSpan);
}
}}

サンプル「TableLayoutPanel1.exe」では、コントロールを右クリックして表示されるメニューから、列と行を1つ拡大できるようになっています。

***AnchorとDockプロパティ [#d63de17c]

TableLayoutPanelもFlowLayoutPanelと同様に、配置された子コントロールのAnchorとDockプロパティに、通常とは少し異なる特別な役割が与えられています。MSDNでは、次のページで説明されています。

-[[方法 : TableLayoutPanel コントロールで子コントロールを固定およびドッキングする>http://msdn2.microsoft.com/ja-jp/library/ms171691.aspx]]

サンプル「TableLayoutPanel1.exe」では、コントロールを右クリックして表示されるメニューから、コントロールのAnchorとDockプロパティを変更することができますので、いろいろと試してみてください。

***行と列のスタイルを変更する [#b9777a46]

#column(注意){{
この記事の最新版は「[[TableLayoutPanelの行と列のスタイルを変更する>http://dobon.net/vb/dotnet/control/tlrowstyles.html]]」で公開しています。
}}

MSDNでは、「方法 : TableLayoutPanel コントロールの列と行を編集する」で説明されています。

-[[方法 : TableLayoutPanel コントロールの列と行を編集する>http://msdn2.microsoft.com/ja-jp/library/ms171686.aspx]]

フォームデザイナを使った方法では、TableLayoutPanelのRowsやColumnsプロパティを変更しようとすると表示される「列と行のスタイル」ダイアログにより、行と列の挿入や、削除、スタイル(サイズの型)の変更を行うことができます。

しかし実際にはTableLayoutPanelクラスにRowsやColumnsプロパティが存在する訳ではなく、実体は、RowStylesプロパティとColumnStylesプロパティです。

例えば、一行目のサイズの型を絶対サイズの50ピクセルとするには、次のようにします。

#code(vbnet){{
TableLayoutPanel1.RowStyles(0) = New RowStyle(SizeType.Absolute, 50.0F)
}}

#code(csharp){{
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]が存在するか分かりませんので、正しいやり方とは言えません。つまり、ある行のスタイルを変更するコードは、ちょっと面倒ですが、次のようになりそうです。

#code(vbnet){{
'スタイルを変更する行数
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
}}

#code(csharp){{
//スタイルを変更する行数
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);
}
}}

***行や列を挿入する [#ue3c2bba]

#column(注意){{
この記事の最新版は「[[TableLayoutPanelの行や列を挿入する>http://dobon.net/vb/dotnet/control/tlrowinsert.html]]」で公開しています。
}}

「列と行のスタイル」ダイアログを使えば行や列の挿入は簡単ですが、これを実行時に行ううまい方法はありません。RowStyles.Insertでうまくいかないのは、今まで読んでいただけたのであれば、明白です。結局は、行や列を拡張して、TableLayoutPanelに配置されたコントロールを一つずつ順番に移動されるという地味なやり方になりそうです。

しかし単純にそのようなコードを書いたとしてもうまくいくとは限りません。なぜならば、先に述べたように、実行時にTableLayoutPanelに配置されたコントロールはどこに配置されるか予測が付きにくく、新しく挿入した行のセルに入ってきてしまう可能性があるからです(それでいいと言うのであれば、かまわないでしょうが)。

よってここでは、先に紹介したように、TableLayoutPanelに配置されたコントロールの位置を実際に現在ある位置に設定してから、列を挿入する処理を行うことにします。

以下の例では、insertRowの位置に行を挿入しています。サンプル「TableLayoutPanel1.exe」からの抜粋です。

#code(vbnet){{
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()
}}

#code(csharp){{
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();
}}

***行や列を削除する [#ja25e47e]

#column(注意){{
この記事の最新版は「[[TableLayoutPanelの行や列を削除する>http://dobon.net/vb/dotnet/control/tlrowremove.html]]」で公開しています。
}}

これもやはりうまい方法がありませんが、前と同じ方針で行ってみましょう。

以下の例では、removeRowの位置の行を削除しています。サンプル「TableLayoutPanel1.exe」からの抜粋です。

#code(vbnet){{
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()
}}

#code(csharp){{
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に行や列を挿入、削除するのは好ましくないということかもしれません。

***セルを独自に描画する [#i0b8cefe]

#column(注意){{
この記事の最新版は「[[TableLayoutPanelのセルを独自に描画する>http://dobon.net/vb/dotnet/control/tlcellpaint.html]]」で公開しています。
}}

TableLayoutPanelのCellPaintイベントにより、セルを独自に描画することができます。

次の例では、一つおきのセルに色をつけています。サンプル「TableLayoutPanel1.exe」からの抜粋です。

#code(vbnet){{
'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
}}

#code(csharp){{
//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);
    }
}
}}

***継承したコントロールでデザイナを使ってFlowLayoutPanelのプロパティを変更できない問題 [#k36503a6]

#column(注意){{
この記事の最新版は「[[継承したコントロールでデザイナを使ってFlowLayoutPanelのプロパティを変更できない問題>http://dobon.net/vb/dotnet/control/tlinheritable.html]]」で公開しています。
}}

FlowLayoutPanelを配置したコントロール(フォームを含む)を継承して新たなコントロールを作成した時、継承基に配置されたFlowLayoutPanelオブジェクトのModifiersがPublicであったとしても、継承したコントロールでそのFlowLayoutPanelオブジェクトのプロパティを変更できないという問題があります。これはFlowLayoutPanelだけでなく、TableLayoutPanelやToolStrip、MenuStrip、ContextMenuStrip、StatusStrip、DataGridView、BindingNavigatorコントロールでも起こります。このバグについて詳しくは次のページで説明されています。

-[[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についての解決法が次のページで紹介されています。

-[[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の代わりに継承基のコントロールで使用するということです。

つまり、例えばまず次のようなクラスを作成し、

#code(vbnet){{
<System.ComponentModel.Designer( _
"System.Windows.Forms.Design.PanelDesigner, System.Design")> _
Public Class InheritableFlowLayoutPanel
    Inherits FlowLayoutPanel

    Public Sub New()
    End Sub
End Class
}}

#code(csharp){{
[Designer("System.Windows.Forms.Design.PanelDesigner, System.Design")]
public class InheritableFlowLayoutPanel : FlowLayoutPanel
{
    public InheritableFlowLayoutPanel()
    {
    }
}
}}

これをFlowLayoutPanelの代わりに使うようにします。

残念ながらこれはFlowLayoutPanelだけの解決法で、TableLayoutPanelをはじめ、他のコントロールには使えません。

**コメント [#r099905e]
#comment

//これより下は編集しないでください
#pageinfo([[:Category/.NET]],2006-06-05 (月) 06:00:00,DOBON!,2010-03-24 (水) 00:30:54,DOBON!)
[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]