ウィンドウコントロールのクライアント領域をスクロールする - C#

ウィンドウコントロールのクライアント領域をスクロールするコードを紹介します。

概要

ウィンドウコントロールで描画された内容をスクロールさせたい場合があります。 クライアント領域の描画内容をスクロールするには、Windows APIのScrollWindowEx()を用います。 ScrollWindowExはRECT構造体を引数にとるため、RECT構造体も合わせて定義します。
Windows APIへ構造体のポインタを渡すコードについてはこちらの記事も参照してください。

書式

DllImport

DllImportは以下のコードを記述します。
[DllImport("user32.dll")]
static extern int ScrollWindowEx(IntPtr hWnd, int dx, int dy, IntPtr prcScroll, IntPtr prcClip,
  IntPtr hrgnUpdate, IntPtr prcUpdate, uint flags);
または
[DllImport("user32.dll")]
static extern int ScrollWindowEx(IntPtr hWnd, int dx, int dy, ref RECT prcScroll, ref RECT prcClip,
  IntPtr hrgnUpdate, out RECT prcUpdate, uint flags);

パラメーターの値は次の通りです。
パラメーター名 意味
hWnd クライアント領域をスクロールするウィンドウのハンドル
dx 水平方向のスクロールの量
dy 垂直方向のスクロール量
prcScroll クライアント領域でスクロールする範囲のRECT構造体
prcClip クリッピング領域のRECT構造体
hrgnUpdate スクロールにより無効になった領域のリージョンのハンドル
prcUpdate スクロールにより無効になった領域のRECT構造体
flags スクロールを制御するフラグ


flagsは以下のフラグを指定できます。
int値意味
SW_ERASE 0x0001 SW_INVALIDATE フラグと共にこのフラグを指定すると、スクロール後、WM_ERASEBKGND メッセージをウィンドウへ送信し、新たに無効になったリージョンを消去します。
SW_INVALIDATE 0x0002 スクロール後、hrgnUpdate パラメータが識別しているリージョンを無効にします。
SW_SCROLLCHILDREN 0x0004 prcScroll パラメータが指す長方形と重なり合う、すべての子ウィンドウをスクロールします。dx と dy の各パラメータで指定したピクセル数だけ、子ウィンドウをスクロールします。システムは、prcScroll パラメータが指す長方形と重なる子ウィンドウへ、WM_MOVE メッセージを送信します。
SW_SMOOTHSCROLL 0x0010 スムーズスクロールを行います。flags パラメータの HIWORD 部分で、スムーズスクロール操作を行う回数を指定します。

RECT構造体

RECT構造体の定義は次の通りです。
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
  public int _Left;
  public int _Top;
  public int _Right;
  public int _Bottom;
}

プログラム例1

UI

下図のフォームを作成します。Panelコントロールを1つ、Buttonを4つ配置します。

コード

以下のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ScrollWindowDemo
{
  public partial class FormSimpleScroll : Form
  {
    /// <summary>
    /// Scroll children within *lprcScroll.
    /// </summary>
    public const uint SW_SCROLLCHILDREN = 0x0001;
    /// <summary>
    /// Invalidate after scrolling.
    /// </summary>
    public const uint SW_INVALIDATE = 0x0002;
    /// <summary>
    /// If SW_INVALIDATE, don't send WM_ERASEBACKGROUND.
    /// </summary>
    public const uint SW_ERASE = 0x0004;
    /// <summary>
    /// Use smooth scrolling.
    /// </summary>
    public const uint SW_SMOOTHSCROLL = 0x0010;


    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
      public int _Left;
      public int _Top;
      public int _Right;
      public int _Bottom;
    }

   //[DllImport("user32.dll")]
   //static extern int ScrollWindowEx(IntPtr hWnd, int dx, int dy, IntPtr prcScroll, IntPtr prcClip, IntPtr hrgnUpdate, IntPtr prcUpdate, uint flags);

    [DllImport("user32.dll")]
    static extern int ScrollWindowEx(IntPtr hWnd, int dx, int dy, ref RECT prcScroll, ref RECT prcClip, IntPtr hrgnUpdate, out RECT prcUpdate, uint flags);


    public FormSimpleScroll()
    {
      InitializeComponent();
    }

    private void panel1_Paint(object sender, PaintEventArgs e)
    {
      int rWidth = 64;
      int rHeiht = 64;

      Pen p = new Pen(Color.FromArgb(0x5d, 0x86, 0xba), 1);
      SolidBrush b = new SolidBrush(Color.FromArgb(0xcd, 0xdc, 0xef));

      Font f = new Font("MS UI Gothic", 10);
      SolidBrush bs = new SolidBrush(Color.FromArgb(0x00, 0x44, 0x99));

      int idx = 0;
      for (int y = 0; y < 12; y++) {
        for (int x = 0; x < 12; x++) {
          Rectangle drawRect = new Rectangle(x * rWidth, y * rHeiht, rWidth, rHeiht);
          e.Graphics.FillRectangle(b, drawRect);
          e.Graphics.DrawRectangle(p, drawRect);

          e.Graphics.DrawString(Convert.ToString(idx), f, bs, new Point(x * rWidth + 2, y * rHeiht + 2));
          idx++;
        }
      }

    }

    private void button1_Click(object sender, EventArgs e)
    {
      RECT rect = new RECT();
      rect._Left = 0;
      rect._Top = 0;
      rect._Right = panel1.Width;
      rect._Bottom= panel1.Height;
      RECT clip = new RECT();
      clip._Left = 0;
      clip._Top = 0;
      clip._Right = panel1.Width;
      clip._Bottom = panel1.Height;

      RECT udaterect = new RECT();

      ScrollWindowEx(panel1.Handle, 0, -16, ref rect, ref clip, (IntPtr)null, out udaterect, SW_SCROLLCHILDREN);
    }

    private void button4_Click(object sender, EventArgs e)
    {
      RECT rect = new RECT();
      rect._Left = 0;
      rect._Top = 0;
      rect._Right = panel1.Width;
      rect._Bottom = panel1.Height;
      RECT clip = new RECT();
      clip._Left = 0;
      clip._Top = 0;
      clip._Right = panel1.Width;
      clip._Bottom = panel1.Height;

      RECT udaterect = new RECT();

      ScrollWindowEx(panel1.Handle, 0, 16, ref rect, ref clip, (IntPtr)null, out udaterect, SW_SCROLLCHILDREN);
    }

    private void button2_Click(object sender, EventArgs e)
    {
      RECT rect = new RECT();
      rect._Left = 0;
      rect._Top = 0;
      rect._Right = panel1.Width;
      rect._Bottom = panel1.Height;
      RECT clip = new RECT();
      clip._Left = 0;
      clip._Top = 0;
      clip._Right = panel1.Width;
      clip._Bottom = panel1.Height;

      RECT udaterect = new RECT();

      ScrollWindowEx(panel1.Handle, -16, 0, ref rect, ref clip, (IntPtr)null, out udaterect, SW_SCROLLCHILDREN);
    }

    private void button3_Click(object sender, EventArgs e)
    {
      RECT rect = new RECT();
      rect._Left = 0;
      rect._Top = 0;
      rect._Right = panel1.Width;
      rect._Bottom = panel1.Height;
      RECT clip = new RECT();
      clip._Left = 0;
      clip._Top = 0;
      clip._Right = panel1.Width;
      clip._Bottom = panel1.Height;

      RECT udaterect = new RECT();

      ScrollWindowEx(panel1.Handle, 16, 0, ref rect, ref clip, (IntPtr)null, out udaterect, SW_SCROLLCHILDREN);
    }
  }
}

解説

Paintイベント

正方形のマスを描画します。幅と高さは64ピクセルで描画します。12x12個のマスを描画します。
  int rWidth = 64;
  int rHeiht = 64;

  Pen p = new Pen(Color.FromArgb(0x5d, 0x86, 0xba), 1);
  SolidBrush b = new SolidBrush(Color.FromArgb(0xcd, 0xdc, 0xef));

  Font f = new Font("MS UI Gothic", 10);
  SolidBrush bs = new SolidBrush(Color.FromArgb(0x00, 0x44, 0x99));

  int idx = 0;
  for (int y = 0; y < 12; y++) {
    for (int x = 0; x < 12; x++) {
      Rectangle drawRect = new Rectangle(x * rWidth, y * rHeiht, rWidth, rHeiht);
      e.Graphics.FillRectangle(b, drawRect);
      e.Graphics.DrawRectangle(p, drawRect);

      e.Graphics.DrawString(Convert.ToString(idx), f, bs, new Point(x * rWidth + 2, y * rHeiht + 2));
      idx++;
    }
  }

Buttonのクリックイベント

ボタンのクリックにより以下のコードが実行されます。
ScrollWindowEx 関数を呼び出しスクロールをします。下記のコードでは、コントロールの領域の全体をスクロール領域とし、 クリッピングする領域もコントロールの領域の全体とします。
private void button1_Click(object sender, EventArgs e)
{
  RECT rect = new RECT();
  rect._Left = 0;
  rect._Top = 0;
  rect._Right = panel1.Width;
  rect._Bottom= panel1.Height;
  RECT clip = new RECT();
  clip._Left = 0;
  clip._Top = 0;
  clip._Right = panel1.Width;
  clip._Bottom = panel1.Height;

  RECT udaterect = new RECT();

  ScrollWindowEx(panel1.Handle, 0, -16, ref rect, ref clip, (IntPtr)null, out udaterect, SW_SCROLLCHILDREN);
}

実行結果

プロジェクトを実行します。ウィンドウが表示され、下図の画面が表示されます。


[button1]をクリックします。描画されたPanel1の領域が上方向にスクロールします。


[button1]をさらにクリックします。クリックするたびに領域が上方向にスクロールします。


[button4]をクリックします。描画されたPanel1の領域が下方向にスクロールします。


[button4]をさらにクリックします。クリックするたびに領域が下方向にスクロールします。


[button2]をクリックします。描画されたPanel1の内容が左方向にスクロールします。



[button3]をクリックします。描画された内容が右方向にスクロールします。


補足
このプログラムでは、描画内容をスクロールしますが、画面外からスクロールインする領域は描画されません。 画面外からスクロールインする領域を描画する方法はこちらの記事を参照してください。

プログラム例2

UI

下図のフォームを作成します。

コード

VisualComponent.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Security.Permissions;
using System.Drawing;
using System.Runtime.InteropServices;

namespace WinformVisualComponent
{
  public partial class VisualComponent : Control
  {
    private static int counter = 0;

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
      public int _Left;
      public int _Top;
      public int _Right;
      public int _Bottom;
    }
    
    [DllImport("user32.dll")]
    static extern int ScrollWindowEx(IntPtr hWnd, int dx, int dy, IntPtr prcScroll,
       IntPtr prcClip, IntPtr hrgnUpdate, IntPtr prcUpdate, uint flags);
    
    public enum ScrollBarsMode { None, Vertical, Horizontal, Both }

    private ScrollBarsMode scrollBarsMode;
    private TextBox debugTextBox;
    
    public ScrollBarsMode ScrollBars
    {
      set
      {
        scrollBarsMode = value;
        this.UpdateStyles();
      }
      get
      {
        return scrollBarsMode;
      }
    }

    public TextBox DebugTextBox
    {
      set
      {
        debugTextBox = value;
      }

      get
      {
        return debugTextBox;
      }
    }

    public VisualComponent()
    {
      InitializeComponent();
    }

    public VisualComponent(IContainer container)
    {
      container.Add(this);
      InitializeComponent();
    }

    protected override void WndProc(ref Message message)
    {
      base.WndProc(ref message);

      RECT rect = new RECT();
      rect._Left = 0;
      rect._Top = 0;
      rect._Right = this.Width;
      rect._Bottom = this.Height;
      IntPtr prect = Marshal.AllocCoTaskMem(Marshal.SizeOf(rect));
      Marshal.StructureToPtr(rect, prect, true);

      RECT cliprect = new RECT();
      cliprect._Left = 0;
      cliprect._Top = 0;
      cliprect._Right = this.Width;
      cliprect._Bottom = this.Height;
      IntPtr pcliprect = Marshal.AllocCoTaskMem(Marshal.SizeOf(cliprect));
      Marshal.StructureToPtr(cliprect, pcliprect, true);

      RECT updateRect = new RECT();
      IntPtr pupdateRect = Marshal.AllocCoTaskMem(Marshal.SizeOf(updateRect));

      if (message.Msg == WindowsConst.WM_VSCROLL){
        short lo = LoWord((long)message.WParam);
        if (lo == WindowsConst.SB_LINEDOWN) {
          ScrollWindowEx(this.Handle, 0, 16, prect, IntPtr.Zero, 
            (IntPtr)null, pupdateRect, WindowsConst.SW_SCROLLCHILDREN);
        }
        else if (lo == WindowsConst.SB_LINEUP) {
          ScrollWindowEx(this.Handle, 0,-16, prect, IntPtr.Zero,
            (IntPtr)null, pupdateRect, WindowsConst.SW_SCROLLCHILDREN);      
        }
      }
      else if (message.Msg == WindowsConst.WM_HSCROLL) {
        short lo = LoWord((long)message.WParam);
        if (lo == WindowsConst.SB_LINELEFT) {
          ScrollWindowEx(this.Handle, -16, 0, prect, IntPtr.Zero, 
            (IntPtr)null, pupdateRect, WindowsConst.SW_SCROLLCHILDREN);             
        }
        else if (lo == WindowsConst.SB_LINERIGHT) {
          ScrollWindowEx(this.Handle, 16, 0, prect, IntPtr.Zero,
            (IntPtr)null, pupdateRect, WindowsConst.SW_SCROLLCHILDREN);
        }
      }
    }

    protected override CreateParams CreateParams
    {
      get
      {
        new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();

        CreateParams cp = base.CreateParams;
       
        int ScrollBarFlag = 0;
        switch (scrollBarsMode) {
          case ScrollBarsMode.None:
            ScrollBarFlag = 0;
            break;
          case ScrollBarsMode.Horizontal:
            ScrollBarFlag = WindowsConst.WS_HSCROLL;
            break;
          case ScrollBarsMode.Vertical:
            ScrollBarFlag = WindowsConst.WS_VSCROLL;
            break;
          case ScrollBarsMode.Both:
            ScrollBarFlag = WindowsConst.WS_HSCROLL + WindowsConst.WS_VSCROLL;
            break;
        }

        cp.Style |= ScrollBarFlag;
        return cp;
      }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
      base.OnPaint(e);
    }

    public void Drawinit()
    {
      Graphics g = this.CreateGraphics();
      g.DrawRectangle(new Pen(Color.Red, 1.0f), new Rectangle(100, 100, 20, 20));
    }

    protected short LoWord(long input)
    {
      return (short)((int)input & 0xFFFF);
    }

    protected short HiWord(long input)
    {
      return (short)((int)input >> 16);
    }

    protected long MakeWParam(short l, short h){
      return MakeLong(l, h);
    }

    protected long MakeLong(short a, short b)
    {
      return a & 0xFFFF | b & 0XFFF << 16;
    }
  }
}
フォームのコード
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WinformVisualComponent
{
  public partial class SubFormScrollEvent : Form
  {
    public SubFormScrollEvent()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      SubFormLogView sflv = new SubFormLogView();
      visualComponent1.DebugTextBox = sflv.textBox1;
      sflv.Show();
    }

    private void button2_Click(object sender, EventArgs e)
    {
      visualComponent1.Drawinit();
    }
  }
}

解説

  • スクロールバーを表示するコードはこちらの記事を参照してください。
  • スクロールバーの操作を検出するコードはこちらの記事を参照してください。

以下のコードではRECT構造体の定義とScrollWindowExのインポートをします。
  [StructLayout(LayoutKind.Sequential)]
  public struct RECT
  {
    public int _Left;
    public int _Top;
    public int _Right;
    public int _Bottom;
  }
    
  [DllImport("user32.dll")]
  static extern int ScrollWindowEx(IntPtr hWnd, int dx, int dy, IntPtr prcScroll,
     IntPtr prcClip, IntPtr hrgnUpdate, IntPtr prcUpdate, uint flags);

RECT構造体とそのポインタを準備します。ポインタはMarshal.AllocCoTaskMemメソッドを用いてメモリを確保し、Marshal.StructureToPtrメソッドで構造体をポインタにします。
  RECT rect = new RECT();
  rect._Left = 0;
  rect._Top = 0;
  rect._Right = this.Width;
  rect._Bottom = this.Height;
  IntPtr prect = Marshal.AllocCoTaskMem(Marshal.SizeOf(rect));
  Marshal.StructureToPtr(rect, prect, true);

  RECT cliprect = new RECT();
  cliprect._Left = 0;
  cliprect._Top = 0;
  cliprect._Right = this.Width;
  cliprect._Bottom = this.Height;
  IntPtr pcliprect = Marshal.AllocCoTaskMem(Marshal.SizeOf(cliprect));
  Marshal.StructureToPtr(cliprect, pcliprect, true);

  RECT updateRect = new RECT();
  IntPtr pupdateRect = Marshal.AllocCoTaskMem(Marshal.SizeOf(updateRect));

ウィンドウコントロールをスクロールします。
ScrollWindowEx(this.Handle, 16, 0, prect, IntPtr.Zero,
  (IntPtr)null, pupdateRect, WindowsConst.SW_SCROLLCHILDREN);

実行結果

アプリケーションを実行すると下図のウィンドウが表示されます。


Button2をクリックします。赤い四角形が表示されます。(下図参照)


スクロールバーの矢印ボタンをクリックするとコントロールのクライアント領域がスクロールします。赤い四角の位置が変わることでスクロールの動作を確認できます。


著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2011-11-19
iPentec all rights reserverd.