.NETプログラミング研究 第64号

.NET質問箱

「.NET質問箱」では、「どぼん!のプログラミング掲示板」に書き込まれた.NETプログラミングに関する投稿を基に、さらに考察を加え、Q&A形式にまとめて紹介します。

DataGridコントロールをスクロールさせた時に、別のDataGridを同時にスクロールさせる

注意

この記事の最新版は「DataGridをスクロールさせた時に、別のDataGridを同時にスクロールさせる」で公開しています。

【質問】

System.Windows.Forms.DataGridコントロールを垂直方向にスクロールした時に、別のDataGridも同時にスクロールされるようにしたいのですが、どのようにすればよいのでしょうか?

【回答】

まずDataGridコントロールが垂直方向にスクロールされたことを知るには、Scrollイベントを捕捉したり、プロテクトメンバのGridVScrolledメソッドをオーバーライドしたり、同じくプロテクトメンバのVertScrollBarプロパティのScrollイベントを捕捉したりする方法があります。

GridVScrolledメソッドやVertScrollBarプロパティのScrollイベントを捕捉する場合は、注意が必要です。これらは、マウスのホイールを使ったスクロールや、カーソルキーによるスクロールには反応しません。よって、これらによるスクロールにも対応するためには、MouseWheelイベントなどの別の方法も併用する必要があります。

また、DataGridを指定した行までスクロールさせるには、「DataGrid内の指定された行までスクロールする」で紹介している方法が使えます。

以下に同時スクロールを可能にするDataGridの例を示します。ここではScrollイベントによりDataGridがスクロールされたことを感知しています。GridVScrolledメソッドを使った例に関しては、この記事の最後に示す掲示板のログをご覧ください。

  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
'/ <summary>
'/ 同時スクロールを可能にするDataGrid
'/ </summary>
Public Class MyDataGrid
    Inherits DataGrid
 
    Public Sub New()
        AddHandler Me.Scroll, AddressOf MyDataGrid_Scroll
    End Sub
 
    Private _syncScrollGrid As MyDataGrid = Nothing
    '/ <summary>
    '/ 同時にスクロールさせるDataGrid
    '/ </summary>
 
    Public Property SyncScrollGrid() As MyDataGrid
        Get
            Return _syncScrollGrid
        End Get
        Set(ByVal Value As MyDataGrid)
            If Not _syncScrollGrid Is Me Then
                _syncScrollGrid = Value
            Else
                Throw New ApplicationException("自分自身に設定できません。")
            End If
        End Set
    End Property
 
    '/ <summary>
    '/ 指定した位置までスクロールさせる
    '/ </summary>
    '/ <param name="rowNum">この行までスクロールする</param>
    Public Sub SetTopRow(ByVal rowNum As Integer)
        Dim args As New ScrollEventArgs(ScrollEventType.LargeIncrement, rowNum)
        MyBase.GridVScrolled(Me, args)
    End Sub
 
    Private Sub MyDataGrid_Scroll(ByVal sender As Object, ByVal e As EventArgs)
        If Not (_syncScrollGrid Is Nothing) Then
            '指定位置までスクロール
            _syncScrollGrid.SetTopRow(VertScrollBar.Value)
        End If
        'フォーカスが別のコントロールに移動しないようにする
        Me.Focus()
    End Sub
End Class
  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
 49
 50
 51
 52
 53
 54
 55
/// <summary>
/// 同時スクロールを可能にするDataGrid
/// </summary>
public class MyDataGrid : DataGrid
{
    public MyDataGrid() : base()
    {
        this.Scroll += new EventHandler(MyDataGrid_Scroll);
    }
 
    private MyDataGrid _syncScrollGrid = null;
    /// <summary>
    /// 同時にスクロールさせるDataGrid
    /// </summary>
    public MyDataGrid SyncScrollGrid
    {
        get
        {
            return _syncScrollGrid;
        }
        set
        {
            if (_syncScrollGrid != this)
            {
                _syncScrollGrid = value;
            }
            else
            {
                throw new ApplicationException("自分自身に設定できません。");
            }
        }
    }
 
    /// <summary>
    /// 指定した位置までスクロールさせる
    /// </summary>
    /// <param name="rowNum">この行までスクロールする</param>
    public void SetTopRow(int rowNum)
    {
        ScrollEventArgs args = 
            new ScrollEventArgs(ScrollEventType.LargeIncrement, rowNum);
        base.GridVScrolled(this, args);
    }
 
    private void MyDataGrid_Scroll(object sender, EventArgs e)
    {
        if (_syncScrollGrid != null)
        {
            //指定位置までスクロール
            _syncScrollGrid.SetTopRow(VertScrollBar.Value);
        }
        //フォーカスが別のコントロールに移動しないようにする
        this.Focus();
    }
}

このクラスを使用するには、DataGridコントロールをこのMyDataGridに置き換えます。ユーザーがスクロールするDataGridと、そのスクロールと同時にスクロールさせたいDataGridの両方にMyDataGridを使用します。そして、ユーザーがスクロールするMyDataGridのSyncScrollGridプロパティに、同時にスクロールさせたいMyDataGridを指定します。なお両者のMyDataGridの行数は必ず同じにしてください。

○この記事の基になった掲示板のスレッド

DataGridを印刷する

注意

この記事の最新版は「DataGridを印刷する」で公開しています。

【質問】

System.Windows.Forms.DataGridコントロールに表示されているデータを印刷することはできますか?

【回答】

MSDNにそのものズバリの「コード : DataGrid の印刷」という例が紹介されています。しかしこれらは残念ながら表示されている範囲のみしか印刷できず、それ以外を印刷するには、適当な位置までスクロールして印刷するという処理を繰り返す必要があります。しかしそれも正確に行うのは難いため、この方法は実用的とは言えないでしょう。

実際に良く使われている方法は、DataGridで表示しているデータを自分で描画して印刷する方法です。この方法はかなり手間がかかりますが、自由度が高く、融通が利きます。

ここではその方法を具体的には説明しません。しかし、このようにしてDataGridを印刷するためのクラスは数多く公開されています。以下にその幾つかを紹介させていただきますので、参考にしてください。

○この記事の基になった掲示板のスレッド

単位を変更して描画する

注意

この記事の最新版は「単位を変更して描画する」で公開しています。

【質問】

メートルやインチ単位の長さを指定して描画や印刷を行いたいのですが、どのようにすればよいのでしょうか?

【回答】

一番簡単な方法は、描画先のGraphicsオブジェクトのPageUnitプロパティを変更してから描画するという方法でしょう。

PageUnitプロパティはページ座標で使用する長さの単位を指定するためのプロパティで、ページ変換(ページ座標からデバイス座標への変換)で使用されます。GDI+の座標系について詳しくは、MSDNの「座標系の種類」をご覧ください。

PageUnitプロパティにはGraphicsUnit列挙体を指定しますが、GraphicsUnit列挙体には、以下のようなメンバーがあります(MSDNからの引用です)。つまり、これらの単位を指定できるわけです。

Display1/75インチを長さの単位に指定します。
Documentドキュメント単位(1/300インチ)を長さの単位に指定します。
Inchインチを長さの単位に指定します。
Millimeterミリメートルを長さの単位に指定します。
Pixelデバイスピクセルを長さの単位に指定します。
Pointプリンタポイント(1/72インチ)を長さの単位に指定します。
Worldワールド単位を長さの単位に指定します。

PageUnitプロパティを使った例を以下に示します。ここでは、フォームのOnPaintメソッドをオーバーライドすることにより、フォームに太さ0.1インチで4X2インチの長方形を描画しています。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    MyBase.OnPaint(e)
 
    'Penを用意する
    Dim redPen As New Pen(Color.Red, 0.1F)
 
    'インチ単位にする
    e.Graphics.PageUnit = GraphicsUnit.Inch
    '太さ0.1インチで4X2インチの長方形を描画
    e.Graphics.DrawRectangle(redPen, 0.2F, 0.4F, 4, 2)
 
    'Penを破棄
    redPen.Dispose()
End Sub
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint (e);
 
    //Penを用意する
    Pen redPen = new Pen(Color.Red, 0.1f);
    
    //インチ単位にする
    e.Graphics.PageUnit = GraphicsUnit.Inch;
    //太さ0.1インチで4X2インチの長方形を描画
    e.Graphics.DrawRectangle(redPen, 0.2f, 0.4f, 4, 2);
    
    //Penを破棄
    redPen.Dispose();
}

もし自分で描画する画面のdpi(dots per inch)を取得して、それを元にピクセル単位の長さを計算できるのであれば、そうすることもできます。dpiはその名の通り、1インチ内のピクセル数を表し、水平方向および垂直方向のdpiは、GraphicsクラスのDpiXとDpiYプロパティで取得することができます。

補足:「HOWTO: How to Make an Application Display Real Units of Measurement」では、GetDeviceCaps関数を使ってdpiを取得する方法が紹介されています。ちなみにDpiXプロパティはGdipGetDpiX関数を使っているようです。

このような方法により、先ほどと同じようにインチ単位で描画する例を示します。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    MyBase.OnPaint(e)
 
    'Penを用意する
    Dim redPen As New Pen(Color.Red, 0.1F * e.Graphics.DpiX)
 
    '太さ0.1インチで4X2インチの長方形を描画
    e.Graphics.DrawRectangle(redPen, _
        0.2F * e.Graphics.DpiX, 0.4F * e.Graphics.DpiX, _
        4 * e.Graphics.DpiX, 2 * e.Graphics.DpiX)
 
    'Penを破棄
    redPen.Dispose()
End Sub
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint (e);
    
    //Penを用意する
    Pen redPen = new Pen(Color.Red, 0.1f * e.Graphics.DpiX);
 
    //太さ0.1インチで4X2インチの長方形を描画
    e.Graphics.DrawRectangle(redPen,
        0.2f * e.Graphics.DpiX, 0.4f * e.Graphics.DpiX,
        4 * e.Graphics.DpiX, 2 * e.Graphics.DpiX);
 
    //Penを破棄
    redPen.Dispose();
}

このように長さをいちいち計算するのが面倒であれば、ScaleTransformメソッドにより、ワールド変換を使って解決することもできます。水平方向と垂直方向のdpiが同じであれば、PageScaleプロパティにより、ページ変換で行うこともできます。

ScaleTransformメソッドを使った例を以下に示します。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    MyBase.OnPaint(e)
 
    'Penを用意する
    Dim redPen As New Pen(Color.Red, 0.1F)
 
    'インチ単位にする
    e.Graphics.ScaleTransform(e.Graphics.DpiX, e.Graphics.DpiY)
    '太さ0.1インチで4X2インチの長方形を描画
    e.Graphics.DrawRectangle(redPen, 0.2F, 0.4F, 4, 2)
 
    'Penを破棄
    redPen.Dispose()
End Sub
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint (e);
    
    //Penを用意する
    Pen redPen = new Pen(Color.Red, 0.1f);
 
    //インチ単位にする
    e.Graphics.ScaleTransform(e.Graphics.DpiX, e.Graphics.DpiY);
    //太さ0.1インチで4X2インチの長方形を描画
    e.Graphics.DrawRectangle(redPen, 0.2f, 0.4f, 4, 2);
 
    //Penを破棄
    redPen.Dispose();
}

もちろんPageUnitプロパティが使えるならばこのような方法を使う必要はないと思いますが、PageUnitプロパティで指定できない単位を使いたい場合はこのような方法が必要になるでしょう。最後に蛇足ですが、寸単位で描画する例を示します。ここでは、DpiXとDpiYが同じものとし、PageScaleプロパティを使っています。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    MyBase.OnPaint(e)
 
    'Penを用意する
    Dim redPen As New Pen(Color.Red, 0.1F)
 
    '寸単位にする(1インチを0.8382寸とする)
    e.Graphics.PageScale = e.Graphics.DpiX / 0.8382F
    '太さ0.1寸で4X2寸の長方形を描画
    e.Graphics.DrawRectangle(redPen, 0.2F, 0.4F, 4, 2)
 
    'Penを破棄
    redPen.Dispose()
End Sub
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint (e);
    
    //Penを用意する
    Pen redPen = new Pen(Color.Red, 0.1f);
 
    //寸単位にする(1インチを0.8382寸とする)
    e.Graphics.PageScale = e.Graphics.DpiX / 0.8382f;
    //太さ0.1寸で4X2寸の長方形を描画
    e.Graphics.DrawRectangle(redPen, 0.2f, 0.4f, 4, 2);
 
    //Penを破棄
    redPen.Dispose();
}

○この記事の基になった掲示板のスレッド

VB6のScaleHeight、ScaleWidthプロパティに代わるものは?

注意

この記事の最新版は「VB6のScaleHeight、ScaleWidthプロパティに代わるものは?」で公開しています。

【質問】

VB6のFormオブジェクトのScaleHeight、ScaleWidthプロパティに代わるものは、C#やVB.NETでは何ですか?

【回答】

MSDNの「Visual Basic .NET における Form オブジェクトの変更点」によると、これらに代わるものはありません。ただし、座標の単位がピクセルの場合は、FormクラスのClientRectangleプロパティやClientSizeプロパティのHeight、Widthプロパティがその代わりとして使えます。

ただし、コントロールの大きさをフォームの大きさに合わせて変える目的でScaleHeightとScaleWidthプロパティを使用するのであれば、DockやAnchorプロパティを使うのがよいでしょう。

MSDNの「ScaleMode がサポートされていない」によると、VB6のコードをVS.NETによりアップグレードすると、VB6.TwipsToPixelsXやTwipsToPixelsYを使って単位をtwipからピクセルに変換するようです。

なおC#でTwipsToPixelsXやTwipsToPixelsYを使うには、「参照設定」に「Microsoft.VisualBasic.Compatibility」を追加し、

  1
  2
Microsoft.VisualBasic.Compatibility.VB6.Support.TwipsToPixelsX(500)
Microsoft.VisualBasic.Compatibility.VB6.Support.TwipsToPixelsY(800)

のようにして呼び出します。もしこれらのメソッドを使いたくないのであれば、次のようにしてTwipsPerPixelXとTwipsPerPixelYの値を計算することもできます。なおここではフォームクラス内に記述するものとし、フォームのある画面のTwipsPerPixelXとTwipsPerPixelYの値を計算しています。

  1
  2
  3
  4
Dim g As Graphics = Me.CreateGraphics()
Dim TwipsPerPixelX As Single = 1440.0F / g.DpiX
Dim TwipsPerPixelY As Single = 1440.0F / g.DpiY
g.Dispose()
  1
  2
  3
  4
Graphics g = this.CreateGraphics();
float TwipsPerPixelX = 1440f / g.DpiX;
float TwipsPerPixelY = 1440f / g.DpiY;
g.Dispose();

しかしながら、TwipsToPixelsXやTwipsToPixelsYはできるだけ使わない方が良いでしょう。

○この記事の基になった掲示板のスレッド

コメント



ページ情報
  • カテゴリ : .NET
  • 作成日 : 2006-01-28 (土) 18:00:00
  • 作成者 : DOBON!
  • 最終編集日 : 2010-03-22 (月) 03:06:23
  • 最終編集者 : DOBON!
[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]