CodeZine(コードジン)

特集ページ一覧

フォームに合わせてDataGridViewを自動的にサイズ変更する

フォームのサイズ変更時に水平スクロールバーが表示されないようにするアイデア

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/07/21 12:00

本稿では、フォームのサイズを変更した後に、常にそのサイズに合わせてDataGridViewのサイズを変更し、水平スクロールバーが表示されないようにする方法を、細かいテクニックを交えて解説します。

はじめに

 この記事では、フォームのサイズを変更した後に、常にそのサイズに合わせて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に依存します。BorderStyleFixedStyleに設定されている場合は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のサブクラスに含めるか、または派生させた新しいコンポーネントに含めることもできます。

注意
 動作確認はVS 2005 Beta 2で行いました。

 このコードをプロジェクト内で使用する場合は、1回だけ記述すれば済みます(たとえばstaticクラス内に記述します)。各フォームにはprevSize変数を用意する必要があります。また、最大化できるフォームの場合は、prevWindowState変数も必要です。



  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

バックナンバー

連載:japan.internet.com翻訳記事

もっと読む

All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5