DataGridView コントロール上でのタッチ操作について

こんにちは、Visual Studio サポートチームです。

今回は、Windows フォーム アプリケーション上でタッチ操作をした場合に、意図せぬ動作となる現象とその回避方法をご紹介します。

Windows フォーム アプリケーションでよく使われるコントロールの 1 つに、DataGridView コントロールがあります。.NET Framework 2.0 から登場したこのコントロールは、データを表形式で表示でき、セル内の値の編集、行や列の追加や削除が簡単にできる非常に柔軟性の高いコントロールのため、アプリケーションの規模を問わず幅広く利用されています。

今回は、この DataGridView コントロール上でフリック操作※ 1 によるスクロールをした場合の現象です。タッチ ジェスチャには様々なものがありますが、フリックとは 1 本または複数の指で移動したい方向に画面を素早くなぞるジェスチャのことで、マウスでのスクロールと似ています。下図のような動作です。

image

DataGridView コントロールに複数のデータを表示させるとき、スクロール バーを用いることができますが、フリック操作により単一ビュー内で移動することができ、たとえば表内に収まらないセル等を表示できます。

しかしながら、このフリック操作による縦方向のスクロールで、スクロール バーと表示内容が一致しない場合があります。

たとえば、要素数が 30 個の場合、スクロール バーが下端まで移動すれば、30 個目の最下端セルが表示されますが、本現象の場合、スクロール バーが下端まで移動しても最下端のセルが表示されません。上端に移動させた場合も同様に、最上端のセルが表示されません。

- 上方向にスクロールした場合
-------------------------------
正常時は「1」が見えていますが、現象発生時は「3」までしか見えません。

・正常時                                           ・現象発生時

OK1 NG1

 - 下方向にスクロールした場合
-------------------------------
正常時は「30」が見えていますが、現象発生時は「29」までしか見えません。

・正常時                                           ・現象発生時 OK2 NG2

 

 - 原因
====
この現象は、DataGridView コントロールにおける不具合によって必要なメッセージが送信されないため、表示領域とコントロールの座標との整合性が取れなくなり発生します。タッチ操作に起因するので、マウスによるスクロールでは発生しません。

- 回避方法
======
アプリケーション側でウィンドウ メッセージを取得し、操作することによって回避することが可能です。

具体的には、縦スクロールした場合に発生する WM_VSCROLL メッセージを捕捉し、その通知コードが SB_THUMBPOSITION であれば、SB_THUMBTRACK の通知コードをもつ WM_VSCROLL メッセージをポストします。

WM_VSCROLL message
https://msdn.microsoft.com/en-us/library/windows/desktop/bb787577.aspx

以下手順となります。

1. ウィンドウ メッセージを受信するために、DataGridView コントロールを継承した独自の DataGridView コントロールを作成します。 (下記回避コード例では
MyDataGridView に相当)
2. 独自の DataGridView コントロールにてウィンドウ プロシージャをオーバーライドし、WM_VSCROLL メッセージを取得します。
3. WM_VSCROLL メッセージ取得後、SB_THUMBTRACK の通知コードをもつWM_VSCROLL メッセージをポストするデリゲートを非同期で実行します。
4. 標準の DataGridView コントロールの代わりに、独自の DataGridView コントロールを使用します。

 

- 回避コード例 (C#)
============
private const int WM_VSCROLL = 0x0115;
private const int SB_THUMBPOSITION = 0x0004;
private const int SB_THUMBTRACK = 0x0005;
[System.Runtime.InteropServices.DllImport("user32")]
private static extern int PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

public class MyDataGridView : DataGridView
{
// メッセージを処理します。
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);

if (m.Msg == WM_VSCROLL)
{
if (LoWord((long)m.WParam) == LoWord((long)SB_THUMBPOSITION))
{
BeginInvoke((Action<IntPtr, IntPtr>)((WParam, LParam) =>
{
// SB_THUMBPOSITION を SB_THUMBTRACK に変更します。
IntPtr testWParam = new IntPtr(SB_THUMBTRACK);
// WM_VSCROLL メッセージを再送します。
PostMessage(this.Handle, WM_VSCROLL, testWParam, LParam);
}), m.WParam, m.LParam);
}
}
}
}

===========

上記回避方法については、お客様において十分にご確認、ご検証くださいますようお願いします。なお、DataGridView コントロールの仕様変更および .NET Framework のバージョン アップ等に伴い、動作が変更される可能性がありますので、ご注意ください。

注釈
※ 1 フリックという用語は、Windows 8 よりスワイプという用語に統一されています。