Python

[Tkinter] 矢印キーでウィジェットの選択(フォーカス)を変更/移動させる方法!

通常、Tkinterで作成したGUI上のウィジェットは矢印キー(↑→↓←)で選択位置を変更することはできません。

ですが、Tkinterには下図のような矢印キーでフォーカス(選択)位置を変えられるようにするメソッドがあります!

今回は、選択位置をキー入力で変えられるようにするために方法を分かりやすく説明していきます!

すらいむ管理人

意外とキー入力で操作したい場面って多々ありますよね。。。

矢印キーでボタン選択させる方法

では実際にプログラムを解説していきます。

選択したいウィジェットにフォーカスを合わせるためのメソッドは、focus_set() メソッドになります。

ウィジェット名.focus_set()

この .focus_set() を用いて「」「」キーで選択位置を変えていくプログラムを書いてみました!

完成したプログラムは以下のような形になりました。

import tkinter as tk

class FOCUS_WIDGET():
    def __init__(self):
        self.num = 0
        self.button_num = 5
        self.widget_list = []
    
    def gen_window(self):
        root=tk.Tk()
        root.geometry("400x200")
        root.title("Python")
        for i in range(self.button_num):
            #ボタンウィジェット作成
            button=tk.Button(root,text="ボタン%s" %i)
            #ボタンとキーをバインド
            button.bind("<KeyPress>", self.press_key)
            #ボタンウィジェット配置
            button.pack()
            #リストに格納
            self.widget_list.append(button)
        #フォーカスの初期位置を指定
        self.widget_list[self.num].focus_set()
        root.mainloop()
        
    def press_key(self, event):
        # 上下キーで要素番号を変動
        if event.keysym == "Up":
           self.num = self.num - 1 
        elif event.keysym == "Down":
           self.num = self.num + 1
        # 指定要素へフォーカスを切り替え
        self.widget_list[self.num].focus_set()

ins = FOCUS_WIDGET()
ins.gen_window()

ここでは、まず初めに作成したボタンウィジェットを .append() で widget_list[] に格納しています。そうすることで各ボタンをリスト番号で指定できるようになります!

また、ボタンのクリックイベントとボタンウィジェットを紐づけるために .bind() メソッドを使用しています。
詳しくはボタンクリック時に複数処理を行う方法でも少し解説しているので確認してみてください!

なのでリスト番号を、矢印キーを押すたびに増減させてあげればボタンの選択位置を変えていくことができるという訳です!

また、フォーカスの初期位置を指定してあげないと矢印キーで位置を変更することができませんでした。。。なので、gen_window() 関数内で初期位置を指定してあげています。

本記事では他にも下記の事柄を解説しています!

クリックでフォーカスする機能を加える

しかし、上記のプログラムでは矢印キーで選択位置を変更することはできましたが実際にボタンをクリックした際に選択位置が変更されません

すらいむ管理人

ボタンクリック時にも選択位置が変えられた方が使いやすいですよね。

実際にボタンクリック時にも選択位置を変更したい場合は、下記のようにボタンクリックイベントをもう一つ定義して、「クリックされたボタンをフォーカスする」という関数を作ってあげます

(追記した部分のみ説明分を書いています)

import tkinter as tk

class FOCUS_WIDGET():
    def __init__(self):
        self.num = 0
        self.button_num = 5
        self.widget_list = []
    
    def gen_window(self):
        root=tk.Tk()
        root.geometry("400x200")
        root.title("Python")
        for i in range(self.button_num):
            button=tk.Button(root,text="ボタン%s" %i)
            button.bind("<KeyPress>", self.press_key)
            #ボタンとクリックイベントをバインド
            button.bind("<ButtonPress>", self.click_button)
            button.pack()
            self.widget_list.append(button)
        self.widget_list[0].focus_set()
        root.mainloop()
        
    def press_key(self, event):
        if event.keysym == "Up":
           self.num = self.num - 1 
        elif event.keysym == "Down":
           self.num = self.num + 1
        self.widget_list[self.num].focus_set()

    def click_button(self, event):
        #クリックしたボタンのテキストを取得
        button_text = event.widget.cget("text")
        #クリックしたボタンの番号を現在位置に更新
        self.num = int(button_text[-1])
        #クリックしたボタン番号の位置をフォーカス
        self.widget_list[int(button_text[-1])].focus_set()

ins = FOCUS_WIDGET()
ins.gen_window()

(実行結果)

すらいむ管理人

click_button() 関数にどのような処理を追加したのか説明していきます!

 #クリックしたボタンのテキストを取得
button_text = event.widget.cget("text")

まず、この行ではクリックしたボタンのテキスト情報を取得しています。
例えば、GUI上の “ボタン1” をクリックした場合、event.widget.cget(“text”) で返される値は “ボタン1” という文字列になります。

        #クリックしたボタンの番号を現在位置に更新
        self.num = int(button_text[-1])
        #クリックしたボタン番号の位置をフォーカス
        self.widget_list[self.num].focus_set()

次に、先ほど取得した文字列の末尾(ボタン番号)を取得して現在位置を更新して、さらにその位置に .focus_set() しているという訳です!

Enterキーでボタンをクリックさせる方法

さらに、Enterキーを押下した際にフォーカスしているボタンをクリックしたい場合の処理について説明します!

Enterキーを押した際にクリックイベントを実行したい場合には、.bind() メソッドでEnterキー入力とクリックイベントを紐づけてあげる必要があります。

どのように紐づけるかというと、Enterキーは、Keysym上では<Return>として定義されているため下記のようにして紐づけます。

ウィジェット名.bind("<Return>", 実行したい関数名)

今回は、Enterキーを押した際にフォーカスされているボタンのテキストを出力させるプログラムを書いてみます!

(完成したプログラム:行を追加した部分のみコメント記載)

import tkinter as tk


class FOCUS_WIDGET():
    def __init__(self):
        self.num = 0
        self.button_num = 5
        self.widget_list = []
    
    def gen_window(self):
        root=tk.Tk()
        root.geometry("400x200")
        root.title("Python")
        for i in range(self.button_num):
            button=tk.Button(root,text="ボタン%s" %i)
            button.bind("<KeyPress>", self.press_key)
            button.bind("<ButtonPress>", self.click_button)
            #Enterキーとボタンクリックイベントをバインド
            button.bind("<Return>", self.press_key)
            button.pack()
            self.widget_list.append(button)
        self.widget_list[0].focus_set()
        root.mainloop()
        
    def press_key(self, event):
        # 上下キーで要素番号を変動
        if event.keysym == "Up":
           self.num = self.num - 1 
        elif event.keysym == "Down":
           self.num = self.num + 1
        #Enterキークリック時の処理を記載(下記は押したボタンテキストを表示)
        elif event.keysym == "Return":
           print(event.widget.cget("text"))
        self.widget_list[self.num].focus_set()

    def click_button(self, event):
        button_text = event.widget.cget("text")
        self.num = int(button_text[-1])
        self.widget_list[int(button_text[-1])].focus_set()

ins = FOCUS_WIDGET()
ins.gen_window()

すらいむ管理人

press_key() 関数に、event.keysym == “Return” を追加してボタンのテキストを表示させる処理を追加してみました!!!

ボタン以外のウィジェットにも対応

本プログラムは、ボタンウィジェット以外にもそのまま適用可能です。

例えば、gen_window() 関数において作成するウィジェットをEntry boxに変更してみます。すると同様に矢印キーやボタンクリックで位置を変更することができました!

Entry boxの作成方法は以下の通りです。

Entry = tk.Entry( parent, options )

(parent : 親ウィジェット、options : 幅、高さなど種々のオプション)

(gen_window() 関数を下記のように変更)

    def gen_window(self):
        root=tk.Tk()
        root.geometry("400x200")
        root.title("Python")
        for i in range(self.button_num):
            entry=tk.Entry(root,text="ボタン%s" %i)
            entry.bind("<KeyPress>", self.press_key)
            entry.bind("<ButtonPress>", self.click_button)
            entry.pack()
            self.widget_list.append(entry)
        self.widget_list[0].focus_set()
        root.mainloop()

以上となります。
最後まで見ていただきありがとうございました!