はじめに
この記事では、フォームのサイズを変更した後に、常にそのサイズに合わせてDataGridView
のサイズを変更し、水平スクロールバーが表示されないようにする方法を解説します。
DataGridView
クラスは、Windows Form 2.0に含まれる新しいコントロールです。.NET Framework 1.1以前の環境では利用できません。解説
このコードで最も重要なのは、すべての列の幅を再計算する処理です(可視列がある場合)。しかし、最後の部分で丸め誤差が生じると、最終列の幅が1ピクセルから数ピクセル広くなってしまい、スクロールバーが表示されることがあります。それを避けるため、最後に、最終列に必要な幅を正確に計算します。
これについて、重要なコード部分を示しながら説明していきます。
int fixedWidth = SystemInformation.VerticalScrollBarWidth + dataGrid.RowHeadersWidth + 2; int mul = 100 * (dataGrid.Width - fixedWidth) / (prevWidth - fixedWidth);
まず、データグリッドの固定幅(fixedWidth
変数)を決定します。これは、スクロールバーの幅とデータグリッドの行ヘッダーの幅を足したものです。行ヘッダーの幅を含めるのは、行ヘッダーをサイズ変更すると非常に見苦しくなるからです。次に、サイズ変更に使用する係数(mul
変数)を決定します。ここで100を掛けている理由について考えてみましょう。ここでは、データグリッドの列の幅を、小数点を含む数値で乗算/除算することになります(たとえば、フォームを50%にする場合は0.5を掛けます)。しかし、浮動小数点数の処理は非常に低速です。この計算は、ほんの少し想像力を働かせれば不要になります。ここでは、100を掛けることにより、小数点の位置を2桁ずらしています。
fixedWidth
に2ピクセルを追加するという補正は重要です。この値は、グリッドのBorderStyle
に依存します。BorderStyle
がFixedStyle
に設定されている場合は2ピクセルが必要ですが、None
に設定されている場合は不要です。また、Fixed3D
に設定されている場合は、もちろん追加するピクセル数を増やす必要があります。
次に、データグリッドのすべての可視列を反復処理して、新しい幅を計算します。もちろん、MinimumWidth
プロパティがあることを考慮しなければなりません。
columnWidth = (dataGrid.Columns[i].Width * mul + 50) / 100; dataGrid.Columns[i].Width = Math.Max(columnWidth, dataGrid.Columns[i].MinimumWidth);
ここで+ 50
しているのはなぜでしょうか。mul
変数の値を決めるときに、浮動小数点数の代わりに整数を使用したことを思い出してください。ここでは、100で割る前に50を足すことで値を補正しています。こうしなければ、切り捨てが生じるため、サイズ変更のたびに各列の幅が少しずつ狭くなっていってしまいます。
最後にすべきことは、最終列の正確な幅を調べることです。これが必要なのは、丸め誤差が生じたり、一部の列のMinimumWidth
プロパティの影響を受けたりすることがあるからです。こうなると、最終列の後にスクロールバーか空白が含まれてしまい、見た目が非常に悪くなります。
public void ResizeGrid(DataGridView dataGrid, ref int prevWidth) { if (prevWidth == 0) prevWidth = dataGrid.Width; if (prevWidth == dataGrid.Width) return; int fixedWidth = SystemInformation.VerticalScrollBarWidth + dataGrid.RowHeadersWidth + 2; int mul = 100 * (dataGrid.Width - fixedWidth) / (prevWidth - fixedWidth); int columnWidth; int total = 0; DataGridViewColumn lastVisibleCol = null; for (int i = 0; i < dataGrid.ColumnCount; i++) if (dataGrid.Columns[i].Visible) { columnWidth = (dataGrid.Columns[i].Width * mul + 50) / 100; dataGrid.Columns[i].Width = Math.Max(columnWidth, dataGrid.Columns[i].MinimumWidth); total += dataGrid.Columns[i].Width; lastVisibleCol = dataGrid.Columns[i]; } if (lastVisibleCol == null) return; columnWidth = dataGrid.Width - total + lastVisibleCo .Width - fixedWidth; lastVisibleCol.Width = Math.Max(columnWidth, lastVisibleCol.MinimumWidth); prevWidth = dataGrid.Width; }
テストアプリケーション
これは、ResizeGrid
関数の使用方法を示すテストアプリケーションです。WindowState
の値とフォームのサイズ変更前の幅を記憶させるために、フォーム内に2つの変数を用意することに注目してください。
Resize
イベントでは再計算を行いません。Resize
イベントで再計算を行うと、非常に多くの計算処理が発生し(特に、ユーザーが非常にゆっくりとサイズ変更する場合)、丸め誤差のせいで均整が取れなくなってしまうからです。そのため、ResizeEnd
を使用して、サイズ変更の後に1回だけ再計算します。ただし、ユーザーがフォームを最大化するか、元のサイズに戻す場合にも、再計算する必要があります。これをResize
イベントで行います。
テストアプリケーションの完全なコードは次のとおりです。
namespace ResizeGridDemo { public partial class main: Form { int prevWidth; FormWindowState prevWindowState; public main() { InitializeComponent(); prevWidth = Width; prevWindowState = WindowState; } private void main_ResizeEnd(object sender, EventArgs e) { ResizeGrid(dataGrid, ref prevWidth); } private void main_Resize(object sender, EventArgs e) { if (WindowState != prevWindowState && WindowState != FormWindowState.Minimized) { prevWindowState = WindowState; ResizeGrid(dataGrid, ref prevWidth); } } } }
まとめ
これらすべてのコードをフォームに記述することもできますが、DataGridView
のサブクラスに含めるか、または派生させた新しいコンポーネントに含めることもできます。
このコードをプロジェクト内で使用する場合は、1回だけ記述すれば済みます(たとえばstatic
クラス内に記述します)。各フォームにはprevSize
変数を用意する必要があります。また、最大化できるフォームの場合は、prevWindowState
変数も必要です。