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

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

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

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

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

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

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

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

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

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

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

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

  • xview_moveto
  • yview_moveto

下記のスクロールバーのメソッド(Scrollbar クラスのメソッド)を利用します。

  • delta
  • get

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

xview_movetoとyview_movetoの説明図

引数には、ピクセル単位の座標ではなく、スクロール先の “スクロールバー上の位置” を指定します。要はスクロールバーの左端(上端)を 0、右端(下端)を 1 として考えた時のスクロール先の相対位置になります。

また、Scrollbar クラスの get メソッドでは、現在のスライダーの位置を取得することが可能です。この位置も、上記の通り “スクロールバー上の位置” となります。

setメソッドの説明図

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

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

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

deletaメソッドの説明図

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

  • 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 画像オブジェクトの相互変換

まとめ

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

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

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

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です