10
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Tkinter マウス操作でスクロール移動をする

Last updated at Posted at 2019-12-05

事の発端

私はtkinterでcanvasにスクロールバーを追加して動作させることに成功しました。しかし、新たにマウスで右ボタンを押しながら画像を上下左右に動かしたいという要望が発生しました。また、マウスのスクロールによってズームインとズームアウトをしたいという要望も発生しました。

動作環境

windows 10
PyCharm Community 2019.2
python3.7.5

参考元サイト

https://codeday.me/jp/qa/20190625/1100104.html
このサイトのコードを自分にカスタマイズしました。

関連ページ

tkinterでスクロールを表示するサンプル
↑サンプルコード置いています。

コード

import tkinter as tk
import random


class Example(tk.Frame):
    def __init__(self, root):
        tk.Frame.__init__(self, root)
        self.canvas = tk.Canvas(self, width=640, height=480, bg="white")
        self.bar_x = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview)
        self.bar_y = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.bar_y.set, xscrollcommand=self.bar_x.set)
        self.canvas.configure(scrollregion=(0, 0, 1000, 1000))

        self.bar_x.grid(row=1, column=0, sticky="ew")
        self.bar_y.grid(row=0, column=1, sticky="ns")
        self.canvas.grid(row=0, column=0, sticky="nsew")
        # ウィンドウの枠を広げたときに、Frameが拡張するようにする
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        for n in range(50):
            x0 = random.randint(0, 900)
            y0 = random.randint(50, 900)
            x1 = x0 + random.randint(50, 100)
            y1 = y0 + random.randint(50, 100)
            color = ("red", "orange", "yellow", "green", "blue")[random.randint(0, 4)]
            self.canvas.create_rectangle(x0, y0, x1, y1, outline="black", fill=color, activefill="black", tags=n)

        self.canvas.bind("<ButtonPress-1>", self.move_start)
        self.canvas.bind("<B1-Motion>", self.move_move)
        self.canvas.bind("<MouseWheel>", self.zoom_in_out)

    def move_start(self, event):
        self.canvas.scan_mark(event.x, event.y)

    def move_move(self, event):
        self.canvas.scan_dragto(event.x, event.y, gain=1)

    def zoom_in_out(self, event):
        if event.delta > 0:
            self.canvas.scale("all", event.x, event.y, 1.05, 1.05)
        elif event.delta < 0:
            self.canvas.scale("all", event.x, event.y, 0.95, 0.95)
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))


if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()

実行結果

canvas_scroll_move_1.png ・マウスをクリックしたまま上下左右に動かすと、画像も移動します。 ・ウィンドウの枠を広げると画像のフレームも一緒に拡大します。 ・不満な点として、Exampleクラスは、canvasの機能をもっているので、フォーム全体の機能と分離したいと思いました。その方が、プログラムが大きくなったときに見やすくなると思うためです。

さらに改修したコードの例

import tkinter as tk
import random


class ClassCanvas(tk.Canvas):
    def __init__(self, master, img_width, img_height, bg):
        super().__init__(master, bg=bg, height=img_height, width=img_width)
        self.bar_x = tk.Scrollbar(self, orient="horizontal", command=self.xview)
        self.bar_y = tk.Scrollbar(self, orient="vertical", command=self.yview)
        self.configure(yscrollcommand=self.bar_y.set, xscrollcommand=self.bar_x.set)
        self.configure(scrollregion=(0, 0, 1000, 1000))

        self.bar_y.pack(side=tk.RIGHT, fill=tk.Y)
        self.bar_x.pack(side=tk.BOTTOM, fill=tk.X)
        self.place(x=0, y=0, relheight=1, relwidth=1)

        for n in range(50):
            x0 = random.randint(0, 900)
            y0 = random.randint(50, 900)
            x1 = x0 + random.randint(50, 100)
            y1 = y0 + random.randint(50, 100)
            color = ("red", "orange", "yellow", "green", "blue")[random.randint(0, 4)]
            self.create_rectangle(x0, y0, x1, y1, outline="black", fill=color, activefill="black", tags=n)


class ClassForm:
    def __init__(self, width, height):
        self.root = tk.Tk()
        # windowサイズの初期化
        self.window_width = width
        self.window_height = height
        self.root.geometry(str(self.window_width) + "x" + str(self.window_height) + "+100+100")
        self.bg_color = "snow"

        self.canvas = ClassCanvas(self.root, 1000, 1000, bg="white")
        self.canvas.bind("<ButtonPress-1>", self.move_start)
        self.canvas.bind("<B1-Motion>", self.move_move)
        self.canvas.bind("<MouseWheel>", self.zoom_in_out)

        self.root.mainloop()

    def move_start(self, event):
        self.canvas.scan_mark(event.x, event.y)

    def move_move(self, event):
        self.canvas.scan_dragto(event.x, event.y, gain=1)

    def zoom_in_out(self, event):
        if event.delta > 0:
            self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
        elif event.delta < 0:
            self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))


def main():
    ClassForm(640, 480)


if __name__ == "__main__":
    main()

実行結果

canvas_scroll_move_2.png ・私はフォームのクラスとCanvasのクラスに分けました。 ・canvasの配置の方法をgridではなくて、packに変更しました。 ・マウス動作処理の関数はFormクラスに所属させました。 ・これはとても素晴らしいサンプルだと思います。みなさんの参考になりますか?

以下、2020年2月追記

マウスホイールでcanvasがy方向にスクロールさせる

ズームの代わりに、マウスホイールで縦スクロールするように修正しました。

import tkinter as tk
import random


class ExampleApp:
    def __init__(self):
        self.root = tk.Tk()
        self.canvas = tk.Canvas(self.root, width=640, height=480, bg="white")
        self.bar_x = tk.Scrollbar(self.root, orient="horizontal", command=self.canvas.xview)
        self.bar_y = tk.Scrollbar(self.root, orient="vertical", command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.bar_y.set, xscrollcommand=self.bar_x.set)
        self.canvas.configure(scrollregion=(0, 0, 1000, 1000))

        self.bar_y.pack(fill=tk.Y, side=tk.RIGHT, expand=False)
        self.bar_x.pack(fill=tk.X, side=tk.BOTTOM, expand=False)

        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # ウィンドウの枠を広げたときに、Frameが拡張するようにする
        self.root.grid_rowconfigure(0, weight=1)
        self.root.grid_columnconfigure(0, weight=1)

        for n in range(50):
            x0 = random.randint(0, 900)
            y0 = random.randint(50, 900)
            x1 = x0 + random.randint(50, 100)
            y1 = y0 + random.randint(50, 100)
            color = ("red", "orange", "yellow", "green", "blue")[random.randint(0, 4)]
            self.canvas.create_rectangle(x0, y0, x1, y1, outline="black", fill=color, activefill="black", tags=n)

        self.canvas.bind("<ButtonPress-1>", self.move_start)
        self.canvas.bind("<B1-Motion>", self.move_move)
        self.canvas.bind("<MouseWheel>", self.mouse_y_scroll)

        self.root.mainloop()

    def move_start(self, event):
        self.canvas.scan_mark(event.x, event.y)

    def move_move(self, event):
        self.canvas.scan_dragto(event.x, event.y, gain=1)

    def mouse_y_scroll(self, event):
        if event.delta > 0:
            self.canvas.yview_scroll(-1, 'units')
        elif event.delta < 0:
            self.canvas.yview_scroll(1, 'units')


def main():
    ExampleApp()


if __name__ == "__main__":
    main()

実行結果

コピペで動作しますので、確認して下さい。

canvasの中にボタンを置いてマウスホイールでスクロールさせるサンプル

上のサンプルでは、canvasの上にボタンを置いても、canvasしかスライドしてくれません。
canvasと一緒にボタンもスクロールさせるのは、簡単に見えてなかなか思うようにいきませんでした。
マウスホイールのスクロールでこんなに時間がかかるとは思いませんでした。自分が悩んだので、コードを公開します。

import tkinter as tk


class ClassFrame(tk.Frame):
    def __init__(self, master, bg=None, width=None, height=None):
        super().__init__(master, bg=bg, width=width, height=height)


class ScrollFrame(ClassFrame):
    def __init__(self, master, rect_num, bg=None, width=None, height=None):
        super(ScrollFrame, self).__init__(master, bg=bg, width=width, height=height)

        # スクロールバーの作成
        self.scroll_bar = tk.Scrollbar(self, orient=tk.VERTICAL)
        self.scroll_bar.pack(fill=tk.Y, side=tk.RIGHT, expand=False)
        self.canvas = tk.Canvas(self, bg="blue", yscrollcommand=self.scroll_bar.set)
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.scroll_bar.config(command=self.canvas.yview)

        # ビューをリセット
        self.canvas.xview_moveto(0)
        self.canvas.yview_moveto(0)

        self.interior = tk.Frame(self.canvas, bg="gray90", borderwidth=10)
        self.interior_id = self.canvas.create_window(0, 0, window=self.interior, anchor=tk.NW)

        self.interior.bind('<Configure>', self.configure_interior)
        self.canvas.bind('<Configure>', self.configure_canvas)

        self.buttons = []
        for i in range(rect_num):
            self.buttons.append(tk.Button(self.interior, text="選択肢 " + str(i + 1)))
            self.buttons[i].pack(anchor=tk.NW, fill=tk.X, padx=(30, 30), pady=(0, 10))

    def configure_interior(self, event=None):
        size = (self.interior.winfo_reqwidth(), self.interior.winfo_reqheight())
        self.canvas.config(scrollregion="0 0 %s %s" % size)
        if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
            self.canvas.config(width=self.interior.winfo_reqwidth())

    def configure_canvas(self, event=None):
        if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
            self.canvas.itemconfigure(self.interior_id, width=self.canvas.winfo_width())


class ScrollForm:
    def __init__(self, width=300, height=400):
        self.root = tk.Tk()
        self.root.title("選択フォーム")
        self.window_width = width
        self.window_height = height
        self.window_pos_x = (self.root.winfo_screenwidth() - self.window_width) // 2
        self.window_pos_y = (self.root.winfo_screenheight() - self.window_height) // 2
        self.root.geometry(str(self.window_width) + "x" + str(self.window_height)
                           + "+" + str(self.window_pos_x) + "+" + str(self.window_pos_y))

        pos_y = 0
        height = 20
        self.label_top = tk.Label(text="マウスホイールでスクロールして下さい。")
        self.label_top.place(x=0, y=pos_y, relwidth=1.0, height=height)
        pos_y += height
        height = 20
        self.label_top_2 = tk.Label(text="スクロールバーをドラッグして下さい。")
        self.label_top_2.place(x=0, y=pos_y, relwidth=1.0, height=height)
        pos_y += height
        height = 300
        self.frame = ScrollFrame(master=self.root, rect_num=50, width=self.window_width, height=self.window_height)
        self.frame.place(x=0, y=pos_y, relwidth=1.0, height=height)
        pos_y += height
        height = 20
        self.label = tk.Label(text="確認が終わっから、決定ボタンを押してください。")
        self.label.place(x=0, y=pos_y, relwidth=1.0, height=height)
        pos_y += height
        height = 25
        width = 70
        self.decision_button = tk.Button(text="決定")
        self.decision_button.place(x=(self.window_width - width) // 2, y=pos_y, width=width, height=height)

        # self.frame.interior.bind("<ButtonPress-1>", self.move_start)
        # self.frame.interior.bind("<B1-Motion>", self.move_move)
        self.frame.interior.bind("<MouseWheel>", self.mouse_y_scroll)
        for i in range(50):
            self.frame.buttons[i].bind("<MouseWheel>", self.mouse_y_scroll)

        if __name__ == "__main__":
            self.root.mainloop()

    def move_start(self, event):
        self.frame.canvas.scan_mark(event.x, event.y)

    def move_move(self, event):
        self.frame.canvas.scan_dragto(event.x, event.y, gain=1)

    def mouse_y_scroll(self, event):
        if event.delta > 0:
            self.frame.canvas.yview_scroll(-1, 'units')
        elif event.delta < 0:
            self.frame.canvas.yview_scroll(1, 'units')


def main():
    ScrollForm()


if __name__ == "__main__":
    main()

実行結果

以下のような画面が現れて、スクロールでy軸にスライドさせることが出来ます。
scroll_2020_2.png

10
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?