naritoブログ

このブログはDjangoとBootstrap4で作成されました
ソースコード

Tkinter、NoteBookを使う

プログラミング関連 Python Tkinter 約7日前
2017年10月11日13:57
今回はTkinterでNoteBookを使います。
ブラウザのタブのようなものが作れる、とイメージするとわかりやすいです。

まずはちょっとしたサンプルです。起動すると、こんな感じ。Textウィジェットを複数タブで管理します。


Ctrl+Nを押すと、タブが増えていきます。



ソースコード。非常にシンプルです。
import tkinter as tk
import tkinter.ttk as ttk


class NoteBook(ttk.Notebook):
    """Textを管理するのNoteBookウィジェット."""

    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)
        self.add_tab()

    def add_tab(self, event=None):
        """新しいタブを追加する."""
        # Textウィジェットの作成
        text_widget = tk.Text(self)

        # NoteBookにTextを追加
        self.add(text_widget, text='new')

        # 今開いたタブを選択する
        now_open_tab = self.tabs()[-1]
        self.select(now_open_tab)


if __name__ == '__main__':
    root = tk.Tk()
    root.title('NoteBook')
    app = NoteBook(root)
    app.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W))
    root.bind('<Control-KeyPress-n>', app.add_tab)
    root.mainloop()




Ctrl+Nで、add_tabメソッドが呼ばれます。これが新規タブの追加処理です。
event=Noneとしていますが、これはbindでのイベント以外に呼び出せるようにするためです。
__init__内で行ってるように、tkinterのイベントループではなく、自分で明示的に呼び出す場合にevent引数の扱いに困りますからね。
その場合に複数の引数を扱う場合は、event以外の引数をキーワード引数として呼ぶことを心がけましょう。
def add_tab(self, event=None):
       """新しいタブを追加する."""
...
...
    root.bind('<Control-KeyPress-n>', app.add_tab)


add_tabメソッドは、今のところシンプルです。タブとして追加したい場合は、addメソッドを呼びます。
text='new'のように、表示名を指定できます。
        # Textウィジェットの作成
        text_widget = tk.Text(self)

        # NoteBookにTextを追加
        self.add(text_widget, text='new')


作成されたタブを選択状態にするのが一般的です。なので、以下のようにします。
        # 今開いたタブを選択する
        now_open_tab = self.tabs()[-1]
        self.select(now_open_tab)


self.tabs()で、現在のタブ一覧をタプルで返します。中身はウィジェット名で、ユニークな文字列です。
[-1]とすることで、最後に追加されたタブ、つまり今作成したものが得られるというわけです。
print(self.tabs())
('.!notebook.!text', '.!notebook.!text2', '.!notebook.!text3')


タブを選択状態にする場合は、selectメソッドを呼びます。位置で指定してもいいですし、上で取得した文字列を指定してもよいです。
# 位置
self.select(1)

# ウィジェット名
now_open_tab = self.tabs()[-1]  # '.!notebook.!text' 等
self.select(now_open_tab)


タブを開いているか否か、は簡単に判別できます。タブがなければ、self.tabs()は空タプルです。
以下のやり方以外でも、とりあえず処理して例外をキャッチする、等もできるでしょう。
if not self.tabs():
    # タブが一つもない場合の処理...



Ctrl+Dで、現在選択しているタブを削除できるようにしてみましょう。
まず、以下のようにイベントを紐付けて...
root.bind('<Control-KeyPress-d>', app.delete_tab)



削除用の処理を書きます。
    def delete_tab(self, event=None):
        """タブを削除する"""
        if not self.tabs():
            return 'break'
        current = self.select()
        self.forget(current)



タブがない時に削除をしようとすると、エラーになります。
なので、タブがない場合は処理をしないようにしましょう。
return 'break'とすると、他に紐付いたイベントがあっても全て無視してくれます。
今回は別に'break'しなくてもよかったです。Ctrl+Dに紐付いた他のイベントはないですから。
        if not self.tabs():
            return 'break'



selectに引数を指定しないと、現在選択中のタブウィジェット名が返されます。
forgetにこのウィジェット名か、位置を渡すと削除されます。
        current = self.select()
        self.forget(current_text)



もう一つよく使うのは、tabメソッドです。(tabsじゃないよ)
下の例は、タブ名を「hello」に変えています。
self.tab(now_open_tab, text='hello')





タブで管理されている各ウィジェット...今回であればtk.Textにアクセスしたいこともあるはずです。
とりあえずCtrl+Sを押すと、現在のTextウィジェットに文字を挿入してみましょう。まずはイベントの定義...
root.bind('<Control-KeyPress-s>', app.insert_text)



そして、処理です。
    def insert_text(self, event=None):
        """選択しているタブ内のTextウィジェットに文字を入れる."""
        current_widget_name = self.select().split('.')[2]
        text_widget = self.children[current_widget_name]
        text_widget.insert('0.0', 'Hello world')



一つ重要なこととして、self.select()で帰るウィジェット名と、children()に指定するウィジェット名は違う、ということです。
# self.select()で帰る
.!notebook.!text6

# children()に指定するウィジェット名
!text6



しかし、現在アクティブなタブを取得するにはselect()を使うのが手っ取り早いです。
なので、select()で帰る文字列を整形し、children()に渡せる形に変換します。
current_widget_name = self.select().split('.')[2]


その後は、children()で取得し、Textを単純に操作できます。
        text_widget = self.children[current_widget_name]
        text_widget.insert('0.0', 'Hello world')



もし全てのタブ内のTextに統一して操作するならば、以下のように簡単にできます。
        for text in self.children.values():
            text.insert('0.0', 'Hello world')