下記ページでスクロールバーの作り方を紹介しました。下記ページではスクロールをスライダーの移動で行いましたが、今回は「スクロールを “マウスで” 行う」方法について解説していきます。
Tkinterの使い方:スクロールバー(Scrollbar)の使い方より具体的には、「マウスでスクロール可能なキャンバス」を作成し、その中で「スクロールを “マウスで” 行う」方法を解説していきたいと思います。
「マウスでスクロール可能なキャンバス」は、上記ページで紹介している「スクロールバー付きのキャンバス」を応用することで作成していきます。
「スクロールバー付きのキャンバス」の作り方を理解している前提で解説させていただきますので、この作り方をご存知ない方は、まずは上記のページを読んでいただけると今後の解説が理解しやすくなると思います。
Contents
マウスでスクロール可能なキャンバスの作り方
今回は、クリックした状態でマウスを移動させた際に、その移動に合わせてスクロールされるようなキャンバスを作成したいと思います。
出来上がりは下のアニメーションのようになります。
このような「マウスでスクロール可能なキャンバス」を作るために、下記のスクロールバーのメソッド(Scrollbar
クラスのメソッド)と、
delta
get
下記のキャンバスのメソッド(Canvas
クラスのメソッド)とを利用します。
xview_moveto
yview_moveto
Scrollbar
クラスの get
メソッド
Scrollbar
クラスの get
メソッドは、現在のスライダーの開始位置(下の図の start
)と終了位置(下の図の end
)を取得するメソッドです。
この「開始位置」と「終了位置」は 0
〜 1
の値であり、スクロールバーの左端(上端)を 0
、右端(下端)を 1
とした時の、それぞれの相対的な位置になります。この相対的な位置を、このページでは “スクロールバー上の位置” と呼ばせていただきます。
スライダーの位置はキャンバスのスクロール位置に応じて連動して変化しますので、get
メソッドでは、”キャンバスの現在のスクロール位置” を取得することができると考えることができます。
スポンサーリンク
Scrollbar
クラスの delta
メソッド
さらに Scrollbar
クラスの delta
メソッドでは、キャンバス上の「2つの座標の差分」から、”スクロールバー上の位置” としての差分量を取得することが可能です。
したがって、その2つの座標の差分を「移動前のマウスの座標」と「移動後のマウスの座標」の差分とすれば、”スクロールバー上の位置” としての「マウスの移動量」を取得することができます。
Canvas
クラスの xview_moveto
メソッドと yview_moveto
メソッド
また、Canvas
クラスの xview_moveto
と yview_moveto
は、それぞれ「キャンバス(の表示領域)を水平方向にスクロールするメソッド」と「キャンバス(の表示領域)を垂直方向にスクロール」するメソッドになります。
引数には、ピクセル単位の座標ではなく、スクロール先の “スクロールバー上の位置” を指定します。より具体的に言うと、スクロールバー上の “開始位置” の方を指定します。
ですので、xview_moveto
を実行するときに 0
を指定した場合は表示領域が左端へ、yview_moveto
を実行するときに 0
を指定した場合は表示領域が上端へ移動することになります。
もちろん 1
を指定した場合は表示領域が右端や下端へ移動することにもなるのですが、あくまでも指定するのは開始位置の方ですので、1
に近い値の指定により表示領域が右端や下端へ移動することもあるはずです(終了位置が 1
となった時に表示領域は右端 or 下端に移動していることになる)。
マウスでスクロールを行う流れ
つまり、これらのメソッドを利用して下記のような処理を行えば、マウスの移動に合わせてキャンバスのスクロールを行うことができることになります(扱う値は全て “スクロールバー上の位置” を表す値となります)。
delta
メソッドで「マウスの移動量」を取得get
メソッドで「現在のキャンバスのスクロール位置」を取得- 「現在のキャンバスのスクロール位置(開始位置)」+「マウスの移動量」を計算して「移動後のキャンバスのスクロール位置(開始位置)」を取得
xview_moveto
・yview_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_moveto
や yview_moveto
によってウィジェットがスクロールされた際に、自動的にスクロールバーのスライダーの位置を設定する Scrollbar
クラスの set
メソッドが実行されるようになっているためです。
スクロールされた際に自動的に set
メソッドが実行されるのは、下記で xscrollcommand
と yscrollcommand
を設定しているためです。
# キャンバススクロール時に実行する処理を設定
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
クラスのコンストラクタ実行時に、下記のように width
を 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にする
)
一応 highlightthickness
と bd
も 0
にしていますが、おそらくこれらの指定をしなくてもスクロールバーは消えてくれると思います。
画像ビューワーへの応用
ここまでキャンバスには図形を描画していましたが、もちろん画像を描画した場合でもマウスでスクロールを行うことが可能です。
これにより、読み込んだ画像をマウスでスクロールしながら表示するような画像ビューワーアプリを作成するようなこともできます。
# -*- 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()
スクリプト実行後、下側のボタンから画像ファイルを選択すれば、その画像がキャンバス上に描画されます。
さらに、その画像上でマウスをクリックしながら移動させると、下のアニメーションのように表示領域を変化させることができます。
ここまでのスクリプトとの違いは、「画像を読み込み」ボタンを追加したことと、そのボタンが押された時に下記を行う read_image
関数の定義を追加したことの2点です。
- ファイル選択画面を表示
- 選択された画像を読み込み
- 読み込んだ画像をキャンバスに描画
- キャンバスのスクロール可能範囲(
scrollregion
)を画像のサイズに設定
ポイントはキャンバスのスクロール可能範囲(scrollregion
)を画像のサイズが分かった後に設定しているところです。
このように、スクロール可能範囲(scrollregion
)は config
メソッドを利用することで、キャンバスを作成した後からでも設定することが可能です。
また、画像の読み込みは tkinter.PhotoImage
で行なっており、下記ページで解説しているように読み込める画像フォーマットが限られています(.png
ファイルは読み込み可能)。
JPEG 画像などを読み込んで表示したい場合は、PIL や OpenCV を使って画像を読み込み、それを tkinter.PhotoImage
オブジェクトに変換するのが良いと思います。
このやり方の詳細は下記ページで解説していますので、詳しく知りたい方は下記ページを読んでみてください。
【Python】PIL ⇔ OpenCV2 ⇔ Tkinter 画像オブジェクトの相互変換まとめ
このページでは、「マウスでスクロール可能なキャンバス」を例にして「ウィジェットをマウスでスクロール」する方法の解説を行いました!
スクロールバーのメソッドをうまく利用することで、このマウスでのスクロールを実現することができます。
マウスでスクロールできるようにすることで、よりユーザーが操作しやすいアプリを作成することができるようになります!マウスでスクロール可能なアプリを作成したくなった際は、ぜひこのページで解説した内容を参考にしてください!