スクロールビューのスクロール量をSet/Getする(Windows Forms)

以前、WPFのカテゴリの記事でスクロールビューのスクロール量を Set/Get について解説をしました。本記事は Windows Forms で同様のことを行います。

Fig. 1 に示す幅4096ピクセル、高さ4096ピクセルの画像を、Panel コンテナの中に配置した PictureBox に描画します。(外縁の黒い部分は描画しません)

Panel の AutoScroll プロパティを True にして、Fig. 2, 3, 4, 5 のようにコードから任意のスクロール量を指令します。

Fig. 1 サイズ4096×4096の大きな画像
Fig. 2 左上隅(0,0)の位置にスクロール
Fig. 3 左右上下 1/4 の位置にスクロール
Fig. 4 左右上下 2/4 の位置にスクロール
Fig. 5 左右上下 3/4 の位置にスクロール

コードの解説をします。

25~35行目、同じようなコードを書くのが面倒なので共通のイベントハンドラを登録し、Sender を判断することでイベントハンドラの中で条件分岐するようにしました。

38~39行目、PictureBox を配置するコンテナの Panel のプロパティを設定します。

AutoScroll を True にしておけば、Panel より大きな PictureBox が内包されると自動的にスクロールバーが出現します。Panel より小さい PictureBox が内包されるとスクロールバーが消えます。実行時に PictureBox のサイズを動的に変更しても、スクロールバーの表示/非表示が連動します。

42行目、PictureBox の位置とサイズを設定します。

pictureBox1.Left = 0;
pictureBox1.Top = 0;
pictureBox1.Width = INI_W;
pictureBox1.Height = INI_H;

のように4行に分けて書いてもいいですが、Left と Top の設定を忘れがちなので、SetBounds() メソッドを使うのがいいでしょう。

51~134行目、ウインドウのペイントのコードです。ここ以外に描画に関するコードを記述してはいけません。

114,115行目、スクロールバーのスクロール量を取得しています。実行すればわかりますが、取得される値はどちらも負の値です。

136~170行目、スクロール量を指令しているイベントハンドラです。

141~145行目、このイベントハンドラの呼び出し元( Sender )を評価して、どの位置に移動するか条件分岐します。ここは25~35行目のイベントハンドラの登録と関連する部分です。

164,165行目、画面に見えている領域のサイズを取得しており、そのサイズの半分だけスクロール量をオフセットします。オフセットを含めなければ注目点は画面の左上隅に表示されてしまいます。その動作はユーザーが意図するものではないと思います。

int sx = tmp_x;
int sy = tmp_y;

のように、そのまま代入して試してみることをお勧めします。

168行目が、スクロール量を指令しているところです。AutoScrollPosition というプロパティに new Point(sx, sy ) でイッキに設定するというところが初心者泣かせです。

SetHorzVert(x, y) みたいなメソッドがあるとか、HorizontalPosition とか VerticalPosition などのプロパティがありそうな感じがしますが、そういうものは存在しません。

ステータスバーには、スクロール量の値、ビューポートの幅と高さ、ピクチャボックスの幅と高さをリアルタイム表示しています。スクロールバーのX方向(左右方向)を一番右に移動し、Y方向(上下方向)を一番下に移動したときに、これら3つの値がどのような関係を示すか確認してみてください。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace aaa
{

	public partial class Form1 : Form
	{

		const int INI_W = 4096;
		const int INI_H = 4096;

		public Form1()
		{

			InitializeComponent();

			// メニューのコマンドを同じイベントハンドラで登録する.
			this.menuSetScrollPos0.Click += new System.EventHandler(this.menuSetScrollPosN_Click);
			this.menuSetScrollPos1.Click += new System.EventHandler(this.menuSetScrollPosN_Click);
			this.menuSetScrollPos2.Click += new System.EventHandler(this.menuSetScrollPosN_Click);
			this.menuSetScrollPos3.Click += new System.EventHandler(this.menuSetScrollPosN_Click);

			// ツールボタンのコマンドを同じイベントハンドラで登録する.
			this.tbnSetScrollPos0.Click += new System.EventHandler(this.tbnSetScrollPosN_Click);
			this.tbnSetScrollPos1.Click += new System.EventHandler(this.tbnSetScrollPosN_Click);
			this.tbnSetScrollPos2.Click += new System.EventHandler(this.tbnSetScrollPosN_Click);
			this.tbnSetScrollPos3.Click += new System.EventHandler(this.tbnSetScrollPosN_Click);

			// パネルを自動スクロールにしてフィル配置する.
			panel1.AutoScroll = true;
			panel1.Dock = DockStyle.Fill;

			// ピクチャボックスの位置とサイズを決定する.
			pictureBox1.SetBounds( 0, 0, INI_W, INI_H );

		}

		private void menuApplicationQuit_Click( object sender, EventArgs e )
		{
			this.Close();
		}

		private void pictureBox1_Paint( object sender, PaintEventArgs e )
		{

			Graphics grph = e.Graphics;

			// ピクチャボックスのサイズを取得する.
			int cw = pictureBox1.ClientRectangle.Width;
			int ch = pictureBox1.ClientRectangle.Height;

			// 描画のためのアイテムを生成する.
			SolidBrush sb_bg = new SolidBrush( Color.Navy );
			Pen pen = new Pen( Color.White, 1.0f );
			SolidBrush sb_mkr = new SolidBrush( Color.Yellow );
			SolidBrush sb_txt = new SolidBrush( Color.Red );
			Font font = new Font( "Courier New", 24.0f );

			// ピクチャボックス全体を塗りつぶす.
			grph.FillRectangle( sb_bg, new Rectangle( 0, 0, cw, ch ));

			// ビットシフト2で4分割.
			int skip_x = cw >> 2;
			int skip_y = ch >> 2;

			for ( int k = 0; k < 4; k++ )
			{

				// 垂直線をひく.
				int xs0 = skip_x * k;
				int ys0 = 0;
				int xe0 = xs0;
				int ye0 = ch;
				grph.DrawLine( pen, xs0, ys0, xe0, ye0 );

				// 水平線をひく.
				int xs1 = 0;
				int ys1 = skip_y * k;
				int xe1 = cw;
				int ye1 = ys1;
				grph.DrawLine( pen, xs1, ys1, xe1, ye1 );

				// 正方形のマーカーを描画する.
				const int MARKER_RECT_W = 32;
				const int MARKER_RECT_H = 32;
				int x = ( skip_x * k ) - ( MARKER_RECT_W >> 1 );
				int y = ( skip_y * k ) - ( MARKER_RECT_H >> 1 );
				grph.FillRectangle( sb_mkr, new Rectangle( x, y, MARKER_RECT_W, MARKER_RECT_H ));

				// 正方形マーカーの上に数字を描画する.
				String s = k.ToString();
				float txt_out_x = x;
				float txt_out_y = y;
				grph.DrawString( s, font, sb_txt, txt_out_x, txt_out_y );

			}

			// 描画のために生成したアイテムはすべて廃棄する.
			if ( sb_bg  != null ) { sb_bg.Dispose();  }
			if ( pen    != null ) { pen.Dispose();    }
			if ( sb_mkr != null ) { sb_mkr.Dispose(); }
			if ( sb_txt != null ) { sb_txt.Dispose(); }
			if ( font   != null ) { font.Dispose();   }

			// スクロール量を取得する.
			int sx = panel1.AutoScrollPosition.X;
			int sy = panel1.AutoScrollPosition.Y;

			// ビューポートのサイズを取得する.
			int vpt_w = panel1.ClientRectangle.Width;
			int vpt_h = panel1.ClientRectangle.Height;

			// ピクチャボックスのサイズを取得する.
			int pbx_w = pictureBox1.Width;
			int pbx_h = pictureBox1.Height;

			// ステータスバーに表示する.
			String s0 = String.Format( "sx({0})", sx );
			String s1 = String.Format( "sy({0})", sy );
			String s2 = String.Format( "vpt_w({0})", vpt_w );
			String s3 = String.Format( "vpt_h({0})", vpt_h );
			String s4 = String.Format( "pbx_w({0})", pbx_w );
			String s5 = String.Format( "pbx_h({0})", pbx_h );
			toolStripStatusLabel1.Text = String.Format( "{0} {1} {2} {3} {4} {5}", s0, s1, s2, s3, s4, s5 );

		}

		private void menuSetScrollPosN_Click( object sender, EventArgs e )
		{

			int mul = 0;

			if      ( sender.Equals( menuSetScrollPos0 ) || sender.Equals( tbnSetScrollPos0 )){ mul = 0; }
			else if ( sender.Equals( menuSetScrollPos1 ) || sender.Equals( tbnSetScrollPos1 )){ mul = 1; }
			else if ( sender.Equals( menuSetScrollPos2 ) || sender.Equals( tbnSetScrollPos2 )){ mul = 2; }
			else if ( sender.Equals( menuSetScrollPos3 ) || sender.Equals( tbnSetScrollPos3 )){ mul = 3; }
			else                                                                              { return; }

			// ピクチャボックスのサイズを取得する.
			int cw = pictureBox1.ClientRectangle.Width;
			int ch = pictureBox1.ClientRectangle.Height;

			// ピクチャボックスのサイズを4分割する.
			int unit_w = cw >> 2;
			int unit_h = ch >> 2;

			// ビューポートのサイズを取得する.
			int vpt_w = panel1.ClientRectangle.Width;
			int vpt_h = panel1.ClientRectangle.Height;

			// スクロールさせたい仮のXY値.
			int tmp_x = unit_w * mul; 
			int tmp_y = unit_h * mul; 

			// ビューポートの半分だけオフセットさせると注目点がまんなかにくる.
			int sx = tmp_x - ( vpt_w >> 1 );
			int sy = tmp_y - ( vpt_h >> 1 );

			// スクロールさせる.
			panel1.AutoScrollPosition = new Point( sx, sy );

		}

		private void tbnSetScrollPosN_Click( object sender, EventArgs e )
		{
			menuSetScrollPosN_Click( sender, e );
		}

	}

}

上記のコードはイベントハンドラをまとめているので、コピーペーストではうまくビルドできないと思います。プロジェクトをアーカイブしたものを用意しましたので、よろしければご活用ください。

本記事は Windows Forms の実施例でしたが、WPF における実施例は下記の記事で紹介しています。

スクロールビューのスクロール量をSet/Getする

画像のサイズが大きい場合スクロールビューを使って表示します。それに付随した水平垂直スクロールバーのスクロール量をSet/Getする方法を紹介します。