【Python/tkinter】マウスでスクロールを行う

マウスでスクロールを行う方法の解説ページアイキャッチ

このページにはプロモーションが含まれています

下記ページでスクロールバーの作り方を紹介しました。下記ページではスクロールをスライダーの移動で行いましたが、今回は「スクロールを “マウスで” 行う」方法について解説していきます。

スクロールバーの作成方法解説ページアイキャッチ Tkinterの使い方:スクロールバー(Scrollbar)の使い方

より具体的には、「マウスでスクロール可能なキャンバス」を作成し、その中で「スクロールを “マウスで” 行う」方法を解説していきたいと思います。

「マウスでスクロール可能なキャンバス」は、上記ページで紹介している「スクロールバー付きのキャンバス」を応用することで作成していきます。

「スクロールバー付きのキャンバス」の作り方を理解している前提で解説させていただきますので、この作り方をご存知ない方は、まずは上記のページを読んでいただけると今後の解説が理解しやすくなると思います。

マウスでスクロール可能なキャンバスの作り方

今回は、クリックした状態でマウスを移動させた際に、その移動に合わせてスクロールされるようなキャンバスを作成したいと思います。

出来上がりは下のアニメーションのようになります。

マウスでスクロール可能なキャンバスの出来上がりイメージ

このような「マウスでスクロール可能なキャンバス」を作るために、下記のスクロールバーのメソッド(Scrollbar クラスのメソッド)と、

  • delta
  • get

下記のキャンバスのメソッド(Canvas クラスのメソッド)とを利用します。

  • xview_moveto
  • yview_moveto

Scrollbar クラスの get メソッド

Scrollbar クラスの get メソッドは、現在のスライダーの開始位置(下の図の start)と終了位置(下の図の end)を取得するメソッドです。

setメソッドの説明図

この「開始位置」と「終了位置」は 01 の値であり、スクロールバーの左端(上端)を 0、右端(下端)を 1 とした時の、それぞれの相対的な位置になります。この相対的な位置を、このページでは “スクロールバー上の位置” と呼ばせていただきます。

スライダーの位置はキャンバスのスクロール位置に応じて連動して変化しますので、get メソッドでは、”キャンバスの現在のスクロール位置” を取得することができると考えることができます。

スポンサーリンク

Scrollbar クラスの delta メソッド

さらに Scrollbar クラスの delta メソッドでは、キャンバス上の「2つの座標の差分」から、”スクロールバー上の位置” としての差分量を取得することが可能です。

したがって、その2つの座標の差分を「移動前のマウスの座標」と「移動後のマウスの座標」の差分とすれば、”スクロールバー上の位置” としての「マウスの移動量」を取得することができます。

deletaメソッドの説明図

Canvas クラスの xview_moveto メソッドと yview_moveto メソッド

また、Canvas クラスの xview_movetoyview_moveto は、それぞれ「キャンバス(の表示領域)を水平方向にスクロールするメソッド」と「キャンバス(の表示領域)を垂直方向にスクロール」するメソッドになります。

xview_movetoとyview_movetoの説明図

引数には、ピクセル単位の座標ではなく、スクロール先の “スクロールバー上の位置” を指定します。より具体的に言うと、スクロールバー上の “開始位置” の方を指定します。

ですので、xview_moveto を実行するときに 0 を指定した場合は表示領域が左端へ、yview_moveto を実行するときに 0 を指定した場合は表示領域が上端へ移動することになります。

もちろん 1 を指定した場合は表示領域が右端や下端へ移動することにもなるのですが、あくまでも指定するのは開始位置の方ですので、1 に近い値の指定により表示領域が右端や下端へ移動することもあるはずです(終了位置が 1 となった時に表示領域は右端 or 下端に移動していることになる)。

マウスでスクロールを行う流れ

つまり、これらのメソッドを利用して下記のような処理を行えば、マウスの移動に合わせてキャンバスのスクロールを行うことができることになります(扱う値は全て “スクロールバー上の位置” を表す値となります)。

  • delta メソッドで「マウスの移動量」を取得
  • get メソッドで「現在のキャンバスのスクロール位置」を取得
  • 「現在のキャンバスのスクロール位置(開始位置)」+「マウスの移動量」を計算して「移動後のキャンバスのスクロール位置(開始位置)」を取得
  • xview_movetoyview_moveto でキャンバスを「移動後のキャンバスのスクロール位置(開始位置)」にスクロール

大雑把にいうと、スクロールバー付きのキャンバスに対して、クリックした状態でマウスが移動された時に上記の処理を行うようにすれば「マウスでスクロール可能なキャンバス」を作成することができます。

スポンサーリンク

マウスでスクロール可能なキャンバスのスクリプト

それでは実際のスクリプトを紹介していきたいと思います。

サンプルスクリプト

マウスでスクロール可能なキャンバスのスクリプト例は下記のようになります。

マウスでスクロール可能なキャンバス
# -*- coding:utf-8 -*-
import tkinter

is_clicking = False
before_x = 0
before_y = 0
	
def start_scroll(event):
	global is_clicking
	global before_x, before_y
	global canvas

	# クリック中フラグを立てる
	is_clicking = True

	# 現在のマウスの位置を記憶
	before_x = event.x
	before_y = event.y

def scroll(event):
	global canvas
	global is_clicking
	global xbar
	global ybar
	global before_x, before_y

	
	if is_clicking:
		# クリック中であれば表示領域を変更

		# 前回からのマウスのピクセル数を取得
		dx = event.x - before_x
		dy = event.y - before_y

		# 「マウスの移動量」を取得
		h = xbar.delta(dx, dy)
		v = ybar.delta(dx, dy)

		# 水平方向の「現在のキャンバスのスクロール位置」を取得
		start_x, end_x = xbar.get()

		# 水平方向の「移動後のキャンバスのスクロール位置」を取得
		after_x = max(min(start_x + h, 1.0), 0.0) # 値は0〜1に丸める
			
		# 水平方向に対して「移動後のキャンバスのスクロール位置」にスクロール
		canvas.xview_moveto(after_x)

		# 現在のマウスの位置を記憶
		before_x = event.x

		# 水平方向の「現在のキャンバスのスクロール位置」を取得
		start_y, end_y = ybar.get()

		# 水平方向の「移動後のキャンバスのスクロール位置」を取得
		after_y = max(min(start_y + v, 1.0), 0.0) # 値は0〜1に丸める
		
		# 垂直方向に対して「移動後のキャンバスのスクロール位置」にスクロール
		canvas.yview_moveto(after_y)

		# 現在のマウスの位置を記憶
		before_y = event.y

def end_scroll(event):
	global is_clicking

	# クリック中フラグを下ろす
	is_clicking = False


# メインウィンドウの作成
app = tkinter.Tk()

# キャンバスの作成と配置
canvas = tkinter.Canvas(
	app,
	width=400,
	height=300,
	scrollregion=(-200, -100, 800, 600),
	cursor="hand" # エラーになる場合は消してOK
)
canvas.grid(row=0, column=0)

# 楕円をキャンバスからはみ出るように描画
canvas.create_oval(
	300, 250,
	500, 350,
	fill="blue"
)

# 楕円をキャンバスの外に描画
canvas.create_oval(
	700, 0,
	800, 200,
	fill="red"
)

# 長方形をキャンバスの外に描画
canvas.create_rectangle(
	600, 400,
	800, 500,
	fill="green"
)

# 長方形をキャンバスの外に描画
canvas.create_rectangle(
	-200, -100,
	0, 0,
	fill="purple"
)


# 水平方向のスクロールバーを作成
xbar = tkinter.Scrollbar(
	app,  # 親ウィジェット
	orient=tkinter.HORIZONTAL,  # バーの方向
)

# 垂直方向のスクロールバーを作成
ybar = tkinter.Scrollbar(
	app,  # 親ウィジェット
	orient=tkinter.VERTICAL,  # バーの方向
)

# キャンバスの下に水平方向のスクロールバーを配置
xbar.grid(
	row=1, column=0,  # キャンバスの下の位置を指定
	sticky=tkinter.W + tkinter.E  # 左右いっぱいに引き伸ばす
)

# キャンバスの右に垂直方向のスクロールバーを配置
ybar.grid(
	row=0, column=1,  # キャンバスの右の位置を指定
	sticky=tkinter.N + tkinter.S  # 上下いっぱいに引き伸ばす
)


# キャンバスをスクロールするように設定

# スクロールバーのスライダーが動かされた時に実行する処理を設定
xbar.config(
	command=canvas.xview
)
ybar.config(
	command=canvas.yview
)


# キャンバススクロール時に実行する処理を設定
canvas.config(
	xscrollcommand=xbar.set
)
canvas.config(
	yscrollcommand=ybar.set
)

# イベントの設定
canvas.bind("<ButtonPress>", start_scroll)
canvas.bind("<Motion>", scroll)
canvas.bind("<ButtonRelease>", end_scroll)

# メインループ
app.mainloop()

スクリプトを実行すれば、このページの最初でお見せした下のアニメーションのようなアプリが起動します。

マウスでスクロール可能なキャンバスの出来上がりイメージ

スクリプトのベースは下記ページのスクロールバー付きのキャンバスで紹介しているスクリプトになります。

スクロールバーの作成方法解説ページアイキャッチ Tkinterの使い方:スクロールバー(Scrollbar)の使い方

変更点は下記の3つです。

  • マウス操作に対するイベントの設定を追加
  • イベントハンドラの定義を追加
  • キャンバス上でのマウスカーソルの形状変更

マウス操作に対するイベントの設定を追加

今回はマウス操作に応じてキャンバスのスクロールを行うようにするため、下記で「クリック時」「移動時」「リリース時(クリック終了時)」に対するイベントの設定を行うようにしています。

イベントの設定の追加
# イベントの設定
canvas.bind("<ButtonPress>", start_scroll)
canvas.bind("<Motion>", scroll)
canvas.bind("<ButtonRelease>", end_scroll)

イベントに関して詳しく知りたい方は下記ページを参考にしていただければと思います。

イベント処理解説ページのアイキャッチ Tkinterの使い方:イベント処理を行う

イベントハンドラの定義を追加

さらに、上記のイベントが発生したときに実行する下記の3つのイベントハンドラの定義を追加しています。

  • start_scroll
  • scroll
  • end_scroll

特に scroll 関数が長くて処理も難しそうに見えますが、結局はマウスでスクロール可能なキャンバスの作り方で解説した内容の処理を行なっているだけです。

scroll が何をやっているか分からない方はマウスでスクロール可能なキャンバスの作り方の内容と見比べながらスクリプトを読んでみていただければと思います。

補足ですが、scroll 関数ではキャンバスのスクロールは行なっていますが、スクロールバーのスライダーの位置を設定するような処理は実行していません。

ですが、それでもマウスでのキャンバスのスクロール時にスライダーの位置も自動的に移動してくれます。

これは xview_movetoyview_moveto によってウィジェットがスクロールされた際に、自動的にスクロールバーのスライダーの位置を設定する Scrollbar クラスの set メソッドが実行されるようになっているためです。

スクロールされた際に自動的に set メソッドが実行されるのは、下記で xscrollcommandyscrollcommand を設定しているためです。

setメソッドを自動実行するための設定
# キャンバススクロール時に実行する処理を設定
canvas.config(
	xscrollcommand=xbar.set
)
canvas.config(
	yscrollcommand=ybar.set
)

この辺りの詳細は下記ページで解説していますので、詳しく知りたい方は下記ページを読んでみてください。

スクロールバーの作成方法解説ページアイキャッチ Tkinterの使い方:スクロールバー(Scrollbar)の使い方

キャンバス上でのマウスカーソルの形状変更

また、キャンバス上でのマウスカーソルの形状を “手の形” にするように、下記の Canvas クラスのコンストラクタ実行時に cursor"hand" に指定するようにしています。

カーソルの変更
canvas = tkinter.Canvas(
	app,
	# 略
	cursor="hand" # エラーになる場合は消してOK
)

MacOSX では上記の cursor 指定を行なっても正常に動作することは確認できていますが、他の環境だともしかしたら実行時にエラーが発生するかもしれません。

エラーが発生した場合は、上記の cursor 指定の部分をコメントアウト or 削除していただければ正常に動作するようになると思います。

スクロール方向を逆にする

上記のスクリプトを実際に動作させてみて “マウスの移動方向に対してスクロール方向が逆” だと感じる場合、「マウスの移動量」の正負を逆にすることで解決できると思います。

より具体的には、scroll 関数でのマウスの移動量の取得を行なっている部分を、下記のように変更することでスクロールの方向を逆にすることができます。

スクロール方向を逆にする
# 「マウスの移動量」を取得
h = -xbar.delta(dx, dy)
v = -ybar.delta(dx, dy)

スポンサーリンク

スクロールバーを消す

マウスでスクロールできるようになったので、もはやスクロールバーは無くても良いですね!

ただしスクロールバーの配置自体をしないようにすると(xbar.grid 実行部分と ybar.grid 実行部分を削除する)と、スクロール自体ができなくなるようですので、スクロールバーのサイズ(太さ)を 0 にしてしまうのが良さそうです。

具体的には Scrollbar クラスのコンストラクタ実行時に、下記のように width0 に指定してやれば良いです。

スクロールバーを消す
# 水平方向のスクロールバーを作成
xbar = tkinter.Scrollbar(
	app,  # 親ウィジェット
	orient=tkinter.HORIZONTAL,  # バーの方向
	width=0, # バーの幅を0にする
	highlightthickness=0, # フォーカス時の囲い線の太さを0にする
	bd=0, # バーの枠線の太さを0にする
)

# 垂直方向のスクロールバーを作成
ybar = tkinter.Scrollbar(
	app,  # 親ウィジェット
	orient=tkinter.VERTICAL,  # バーの方向
	width=0, # バーの幅を0にする
	highlightthickness=0, # フォーカス時の囲い線の太さを0にする
	bd=0, # バーの枠線の太さを0にする
)

一応 highlightthicknessbd0 にしていますが、おそらくこれらの指定をしなくてもスクロールバーは消えてくれると思います。

画像ビューワーへの応用

ここまでキャンバスには図形を描画していましたが、もちろん画像を描画した場合でもマウスでスクロールを行うことが可能です。

これにより、読み込んだ画像をマウスでスクロールしながら表示するような画像ビューワーアプリを作成するようなこともできます。

画像ビューワー
# -*- coding:utf-8 -*-
import tkinter
import tkinter.filedialog

image = None
is_clicking = False
before_x = 0
before_y = 0

def read_image():
	global app
	global image
	global canvas

	# ファイル選択ダイアログの表示
	path = tkinter.filedialog.askopenfilename()

	# 画像を読み込む
	image = tkinter.PhotoImage(
		file=path
	)

	# 画像を描画
	canvas.create_image(
		0, 0,
		anchor=tkinter.NW,
		image=image
	)
	canvas.config(
		scrollregion=(
			0, 0,
			image.width(), image.height()
		)
	)

def start_scroll(event):
	global is_clicking
	global before_x, before_y
	global canvas

	# クリック中フラグを立てる
	is_clicking = True

	# 現在のマウスの位置を記憶
	before_x = event.x
	before_y = event.y

def scroll(event):
	global canvas
	global is_clicking
	global xbar
	global ybar
	global before_x, before_y

	
	if is_clicking:
		# クリック中であれば表示領域を変更

		# 前回からのマウスのピクセル数を取得
		dx = event.x - before_x
		dy = event.y - before_y

		# 「マウスの移動量」を取得
		h = -xbar.delta(dx, dy)
		v = -ybar.delta(dx, dy)

		# 水平方向の「現在のキャンバスのスクロール位置」を取得
		start_x, end_x = xbar.get()

		# 水平方向の「移動後のキャンバスのスクロール位置」を取得
		after_x = max(min(start_x + h, 1.0), 0.0) # 値は0〜1に丸める
			
		# 水平方向に対して「移動後のキャンバスのスクロール位置」にスクロール
		canvas.xview_moveto(after_x)

		# 現在のマウスの位置を記憶
		before_x = event.x

		# 水平方向の「現在のキャンバスのスクロール位置」を取得
		start_y, end_y = ybar.get()

		# 水平方向の「移動後のキャンバスのスクロール位置」を取得
		after_y = max(min(start_y + v, 1.0), 0.0) # 値は0〜1に丸める
		
		# 垂直方向に対して「移動後のキャンバスのスクロール位置」にスクロール
		canvas.yview_moveto(after_y)

		# 現在のマウスの位置を記憶
		before_y = event.y

def end_scroll(event):
	global is_clicking

	# クリック中フラグを下ろす
	is_clicking = False


# メインウィンドウの作成
app = tkinter.Tk()

# キャンバスの作成と配置
canvas = tkinter.Canvas(
	app,
	width=400,
	height=300,
	# scrollregion=(-200, -100, 800, 600),
	cursor="hand" # エラーになる場合は消してOK
)
canvas.grid(row=0, column=0)

# 水平方向のスクロールバーを作成
xbar = tkinter.Scrollbar(
	app,  # 親ウィジェット
	orient=tkinter.HORIZONTAL,  # バーの方向
	width=0, # バーの幅を0にする
	highlightthickness=0, # フォーカス時の囲い線の太さを0にする
	bd=0, # バーの枠線の太さを0にする
)

# 垂直方向のスクロールバーを作成
ybar = tkinter.Scrollbar(
	app,  # 親ウィジェット
	orient=tkinter.VERTICAL,  # バーの方向
	width=0, # バーの幅を0にする
	highlightthickness=0, # フォーカス時の囲い線の太さを0にする
	bd=0, # バーの枠線の太さを0にする
)

# キャンバスの下に水平方向のスクロールバーを配置
xbar.grid(
	row=1, column=0,  # キャンバスの下の位置を指定
	sticky=tkinter.W + tkinter.E  # 左右いっぱいに引き伸ばす
)

# キャンバスの右に垂直方向のスクロールバーを配置
ybar.grid(
	row=0, column=1,  # キャンバスの右の位置を指定
	sticky=tkinter.N + tkinter.S  # 上下いっぱいに引き伸ばす
)

# キャンバスをスクロールするように設定

# スクロールバーのスライダーが動かされた時に実行する処理を設定
xbar.config(
	command=canvas.xview
)
ybar.config(
	command=canvas.yview
)


# キャンバススクロール時に実行する処理を設定
canvas.config(
	xscrollcommand=xbar.set
)
canvas.config(
	yscrollcommand=ybar.set
)

# 画像読み込みボタンの作成と配置
button = tkinter.Button(
	app,
	text="画像読み込み",
	command=read_image
)
button.grid(column=0,row=2,columnspan=2)

# イベントの設定
canvas.bind("<ButtonPress>", start_scroll)
canvas.bind("<Motion>", scroll)
canvas.bind("<ButtonRelease>", end_scroll)

# メインループ
app.mainloop()

スクリプト実行後、下側のボタンから画像ファイルを選択すれば、その画像がキャンバス上に描画されます。

さらに、その画像上でマウスをクリックしながら移動させると、下のアニメーションのように表示領域を変化させることができます。

画像をスクロールする様子

MEMO

表示している画像はリズム727さんによる写真ACからの写真を使用させていただいています

ここまでのスクリプトとの違いは、「画像を読み込み」ボタンを追加したことと、そのボタンが押された時に下記を行う read_image 関数の定義を追加したことの2点です。

  • ファイル選択画面を表示
  • 選択された画像を読み込み
  • 読み込んだ画像をキャンバスに描画
  • キャンバスのスクロール可能範囲(scrollregion)を画像のサイズに設定

ポイントはキャンバスのスクロール可能範囲(scrollregion)を画像のサイズが分かった後に設定しているところです。

このように、スクロール可能範囲(scrollregion)は config メソッドを利用することで、キャンバスを作成した後からでも設定することが可能です。

また、画像の読み込みは tkinter.PhotoImage で行なっており、下記ページで解説しているように読み込める画像フォーマットが限られています(.png ファイルは読み込み可能)。

Tkinterで画像を扱う方法の解説ページアイキャッチ Tkinter の使い方:PhotoImage・BitmapImageクラスで画像を扱う

JPEG 画像などを読み込んで表示したい場合は、PIL や OpenCV を使って画像を読み込み、それを tkinter.PhotoImage オブジェクトに変換するのが良いと思います。

このやり方の詳細は下記ページで解説していますので、詳しく知りたい方は下記ページを読んでみてください。

画像オブジェクト変換方法の解説ページアイキャッチ 【Python】PIL ⇔ OpenCV2 ⇔ Tkinter 画像オブジェクトの相互変換

まとめ

このページでは、「マウスでスクロール可能なキャンバス」を例にして「ウィジェットをマウスでスクロール」する方法の解説を行いました!

スクロールバーのメソッドをうまく利用することで、このマウスでのスクロールを実現することができます。

マウスでスクロールできるようにすることで、よりユーザーが操作しやすいアプリを作成することができるようになります!マウスでスクロール可能なアプリを作成したくなった際は、ぜひこのページで解説した内容を参考にしてください!

同じカテゴリのページ一覧を表示