tkinterでスクロールを表示するサンプル

## 概要
Tkinterを使ったスクロールバーの簡単なサンプルを作ろうとしたら、結局手の込んだサンプルまで作成することになったので、「もういいや」と思い全部公開します。最後の方はタイトルを逸脱して複数のframeを継承したクラスを利用したTkinterのフォームの作成方法という感じの内容になっています。

### 動作環境
Windows10
PyCharm
python3

### 関連ページ
[Tkinter マウス操作でスクロール移動をする](https://qiita.com/hatorijobs/items/9cad567a01511166e95b)
↑サンプルコード置いています。

##　コード
・まずは簡単なサンプルで、動作するtkiterのスクロールバーを表示します。

``` python3
import tkinter as tk

def main():
    root = tk.Tk()
    root.geometry("400x300")  # 横400x縦300

    # Canvasを生成
    canvas = tk.Canvas(root, bg="white")
    canvas.place(x=0, y=0, width=200, height=300)   # 処理位置的にここが良さそう

    # Scrollbarを生成してCanvasに配置処理
    bar_y = tk.Scrollbar(canvas, orient=tk.VERTICAL)
    bar_x = tk.Scrollbar(canvas, orient=tk.HORIZONTAL)
    bar_y.pack(side=tk.RIGHT, fill=tk.Y)
    bar_x.pack(side=tk.BOTTOM, fill=tk.X)
    bar_y.config(command=canvas.yview)
    bar_x.config(command=canvas.xview)
    canvas.config(yscrollcommand=bar_y.set, xscrollcommand=bar_x.set)
    # Canvasのスクロール範囲を設定
    canvas.config(scrollregion=(0, 0, 300, 500))
    # self.canvas.pack(anchor=tk.NW, side=tk.LEFT)

    # 確認用の矩形表示
    canvas.create_rectangle(10, 10, 100, 100, fill='green')
    root.mainloop()


if __name__ == "__main__":
    main()
``` 

### 実行結果
・ウィンドウの大きさを変えてもスクロールが消えたりしません。
<img width="539" alt="scroll2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262477/5f6a23fe-8e7f-2127-d2e9-66a1f7c985ca.png">
・スクロールバーが付いてるのは、ウィンドウ枠ではなく、canvasであることが確認出来ます。これが、ウィンドウ枠にスクロールバーが付いていると意図した動作と異なってしまいます。

## クラス化した場合のコード（完成形ではない）
・(追記)但し、ここではtkinterのwidgetを継承してないので、後々コードが(特にコンストラクタで)長ったらしくなってしまうと考えられます。これを回避するためにはtkinterのwidgetを継承してFrameなどのwidgetごとにクラス化して、各クラスのコンストラクタで処理を実装するのがわかりやすいです。

```python3
import tkinter as tk


class ClassScroll:
    def __init__(self):
        self.root = tk.Tk()
        self.root.geometry("400x300")  # 横400x縦300

        # Canvasを生成
        self.canvas = tk.Canvas(self.root, bg="white")
        self.canvas.place(x=0, y=0, width=200, height=300)   # 処理位置的にここが良さそう

        # Scrollbarを生成してCanvasに配置処理
        bar_y = tk.Scrollbar(self.canvas, orient=tk.VERTICAL)
        bar_x = tk.Scrollbar(self.canvas, orient=tk.HORIZONTAL)
        bar_y.pack(side=tk.RIGHT, fill=tk.Y)
        bar_x.pack(side=tk.BOTTOM, fill=tk.X)
        bar_y.config(command=self.canvas.yview)
        bar_x.config(command=self.canvas.xview)
        self.canvas.config(yscrollcommand=bar_y.set, xscrollcommand=bar_x.set)
        # Canvasのスクロール範囲を設定
        self.canvas.config(scrollregion=(0, 0, 300, 500))
        # self.canvas.pack(anchor=tk.NW, side=tk.LEFT)

        # 確認用の矩形表示
        self.canvas.create_rectangle(10, 10, 100, 100, fill='green')
        # ここでmainloop()をすることなどが、スクロール付きのcanvasのクラスとしておかしい
        # 動作はするが、理想的な構造ではないと思います。(自分で作っておきながら・・・)
        self.root.mainloop()


def main():
    ClassScroll()


if __name__ == "__main__":
    main()
```
・実行結果は上の画像と同じです。
・追記）自分用と書いていた部分を削除しました。
・追記）コメントで頂いたように継承したクラスを使った方が、後々使いやすいコードになると思います。ただ、段階を踏んでより良いクラス化の実装をするための考え方を残して置きたいので、この途中経過のコードは残しておきます。

## 継承を利用した場合のコード(上より良いと思う)
・当ページのタイトルからは逸脱した内容のサンプルコードです。
・複数のframeをクラス化して利用したサンプルコードを書いてみました。そうすることで継承したことの利点が分かりやすく見えると思ったためです。
・つまり「tkinterのwidgetを継承しなくても実装出来るのに、なんで継承をしているのか？」という自分の疑問があったのですが、それに対する答えが少し分かった気がしたので、以下のコードを例として作成しました。

``` python3
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 ClassLabelFrameTop(tk.LabelFrame):
    def __init__(self, master, text=None, pad_x=None, pad_y=None, bg=None):
        super().__init__(master, text=text, padx=pad_x, pady=pad_y, bg=bg)
        button1 = tk.Button(self, text="タブ1", bg=bg)
        button1.pack(side="left", padx=(10, 0))
        button2 = tk.Button(self, text="タブ2", bg=bg)
        button2.pack(side="left", padx=(10, 0))
        button3 = tk.Button(self, text="タブ3", bg=bg)
        button3.pack(side="left", padx=(10, 0))


class ClassLabelFrameLeft(tk.LabelFrame):
    def __init__(self, master, text=None, pad_x=None, pad_y=None, bg=None):
        super().__init__(master, text=text, padx=pad_x, pady=pad_y, bg=bg)
        left_button1 = tk.Button(self, text="アクション1", bg=bg)
        left_button1.pack(anchor=tk.NW, fill=tk.X, padx=(10, 10), pady=(0, 10))
        left_button2 = tk.Button(self, text="アクション2", width="50", bg=bg)
        left_button2.pack(anchor=tk.NW, fill=tk.X, padx=(10, 10), pady=(0, 10))
        left_button3 = tk.Button(self, text="アクション3", bg=bg)
        left_button3.pack(anchor=tk.NW, fill=tk.X, padx=(10, 10), pady=(0, 10))


class ClassCanvas(tk.Canvas):
    def __init__(self, master, scroll_width, scroll_height, bg):
        super().__init__(master, bg=bg)
        # Scrollbarを生成してCanvasに配置処理
        bar_y = tk.Scrollbar(self, orient=tk.VERTICAL)
        bar_x = tk.Scrollbar(self, orient=tk.HORIZONTAL)
        bar_y.pack(side=tk.RIGHT, fill=tk.Y)
        bar_x.pack(side=tk.BOTTOM, fill=tk.X)
        bar_y.config(command=self.yview)
        bar_x.config(command=self.xview)
        self.config(yscrollcommand=bar_y.set, xscrollcommand=bar_x.set)
        # Canvasのスクロール範囲を設定
        self.config(scrollregion=(0, 0, scroll_width, scroll_height))


def main():
    root = tk.Tk()
    root.geometry("500x300")  # 横400x縦300
    bg_color = "snow"

    # 最上位のフレーム
    frame_big_left = ClassFrame(root, bg="white", width=180)
    frame_big_right = ClassFrame(root, bg="red")
    frame_big_left.pack(side=tk.LEFT, expand=0, fill=tk.Y)
    frame_big_right.pack(side=tk.LEFT, expand=1, fill=tk.BOTH)

    # 左側の大フレームの中に設置するラベル付き縦フレームのテスト
    label_frame_left_menu = ClassLabelFrameLeft(frame_big_left, text="左縦メニュー", pad_y=7, bg=bg_color)
    label_frame_left_menu.place(x=8, y=4, width=165)

    # Canvasを生成
    scroll_max = {"width": 600, "height": 700}
    canvas = ClassCanvas(frame_big_right, scroll_width=scroll_max["width"],
                         scroll_height=scroll_max["height"], bg="white")
    canvas.place(x=0, y=0, relheight=1, relwidth=1)
    # 確認用の矩形表示
    canvas.create_rectangle(50, 50, scroll_max["width"] - 100, scroll_max["height"] - 100,
                            fill="green", outline="blue", width=3)

    # 右側の大フレームの中に設置するlabelFrame
    label_frame_top = ClassLabelFrameTop(frame_big_right, text="メニュー", pad_x=0, pad_y=5, bg=bg_color)
    label_frame_top.place(x=8, y=4, width=200, height=50)
    label_frame_top.lift(canvas)

    # 右側のFrameの中に設置するFrame
    frame_test = ClassFrame(frame_big_right, bg="grey", width=200, height=50)
    frame_test.pack(side=tk.RIGHT, anchor=tk.SW, expand=0, padx=(10, 17), pady=(0, 25))

    root.mainloop()


if __name__ == "__main__":
    main()

```
### 実行結果
<img width="598" alt="frame_class.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262477/290bba3a-ef1d-7f7a-7248-c05d8d8d0e8f.png">
・画面の枠を広げると、隠れている緑の矩形が見えます。そのときのスクロールバーも右下のframeも、期待通りの動作となっています。
・あくまでサンプルかつ初版なので、後から修正を加えると思います。