.NETプログラミング研究 第64号 †
.NET質問箱 †
「.NET質問箱」では、「どぼん!のプログラミング掲示板」に書き込まれた.NETプログラミングに関する投稿を基に、さらに考察を加え、Q&A形式にまとめて紹介します。
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
| | Public Class MyDataGrid
Inherits DataGrid
Public Sub New()
AddHandler Me.Scroll, AddressOf MyDataGrid_Scroll
End Sub
Private _syncScrollGrid As MyDataGrid = Nothing
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
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
| | public class MyDataGrid : DataGrid
{
public MyDataGrid() : base()
{
this.Scroll += new EventHandler(MyDataGrid_Scroll);
}
private MyDataGrid _syncScrollGrid = null;
public MyDataGrid SyncScrollGrid
{
get
{
return _syncScrollGrid;
}
set
{
if (_syncScrollGrid != this)
{
_syncScrollGrid = value;
}
else
{
throw new ApplicationException("自分自身に設定できません。");
}
}
}
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を印刷する †
【質問】
System.Windows.Forms.DataGridコントロールに表示されているデータを印刷することはできますか?
【回答】
MSDNにそのものズバリの「コード : DataGrid の印刷」という例が紹介されています。しかしこれらは残念ながら表示されている範囲のみしか印刷できず、それ以外を印刷するには、適当な位置までスクロールして印刷するという処理を繰り返す必要があります。しかしそれも正確に行うのは難いため、この方法は実用的とは言えないでしょう。
実際に良く使われている方法は、DataGridで表示しているデータを自分で描画して印刷する方法です。この方法はかなり手間がかかりますが、自由度が高く、融通が利きます。
ここではその方法を具体的には説明しません。しかし、このようにしてDataGridを印刷するためのクラスは数多く公開されています。以下にその幾つかを紹介させていただきますので、参考にしてください。
○この記事の基になった掲示板のスレッド
単位を変更して描画する †
【質問】
メートルやインチ単位の長さを指定して描画や印刷を行いたいのですが、どのようにすればよいのでしょうか?
【回答】
一番簡単な方法は、描画先のGraphicsオブジェクトのPageUnitプロパティを変更してから描画するという方法でしょう。
PageUnitプロパティはページ座標で使用する長さの単位を指定するためのプロパティで、ページ変換(ページ座標からデバイス座標への変換)で使用されます。GDI+の座標系について詳しくは、MSDNの「座標系の種類」をご覧ください。
PageUnitプロパティにはGraphicsUnit列挙体を指定しますが、GraphicsUnit列挙体には、以下のようなメンバーがあります(MSDNからの引用です)。つまり、これらの単位を指定できるわけです。
Display | 1/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)
Dim redPen As New Pen(Color.Red, 0.1F)
e.Graphics.PageUnit = GraphicsUnit.Inch
e.Graphics.DrawRectangle(redPen, 0.2F, 0.4F, 4, 2)
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 redPen = new Pen(Color.Red, 0.1f);
e.Graphics.PageUnit = GraphicsUnit.Inch;
e.Graphics.DrawRectangle(redPen, 0.2f, 0.4f, 4, 2);
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)
Dim redPen As New Pen(Color.Red, 0.1F * e.Graphics.DpiX)
e.Graphics.DrawRectangle(redPen, _
0.2F * e.Graphics.DpiX, 0.4F * e.Graphics.DpiX, _
4 * e.Graphics.DpiX, 2 * e.Graphics.DpiX)
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 redPen = new Pen(Color.Red, 0.1f * e.Graphics.DpiX);
e.Graphics.DrawRectangle(redPen,
0.2f * e.Graphics.DpiX, 0.4f * e.Graphics.DpiX,
4 * e.Graphics.DpiX, 2 * e.Graphics.DpiX);
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)
Dim redPen As New Pen(Color.Red, 0.1F)
e.Graphics.ScaleTransform(e.Graphics.DpiX, e.Graphics.DpiY)
e.Graphics.DrawRectangle(redPen, 0.2F, 0.4F, 4, 2)
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 redPen = new Pen(Color.Red, 0.1f);
e.Graphics.ScaleTransform(e.Graphics.DpiX, e.Graphics.DpiY);
e.Graphics.DrawRectangle(redPen, 0.2f, 0.4f, 4, 2);
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)
Dim redPen As New Pen(Color.Red, 0.1F)
e.Graphics.PageScale = e.Graphics.DpiX / 0.8382F
e.Graphics.DrawRectangle(redPen, 0.2F, 0.4F, 4, 2)
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 redPen = new Pen(Color.Red, 0.1f);
e.Graphics.PageScale = e.Graphics.DpiX / 0.8382f;
e.Graphics.DrawRectangle(redPen, 0.2f, 0.4f, 4, 2);
redPen.Dispose();
}
|
○この記事の基になった掲示板のスレッド
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はできるだけ使わない方が良いでしょう。
○この記事の基になった掲示板のスレッド
コメント †