【TkEasyGUI】ラジオボタンの選択制御を実装する方法
PySimpleGUI
のようにグループ化したラジオボタンをボタンなどで切り替えするものをTkEasyGUI
で実装する方法。
PySimpleGUI
ボタンを押すとラジオボタンの選択が変更されています。
TkEasyGUI
コードを変更する前
ボタンを押しても、ラジオボタンの選択が変更されずテキストが数字に更新されています。
コードを変更する後
PySimpleGUIのように、ボタンを押すとラジオボタンの選択が変更されています。
実験コード
に同じものが試せるものを置いています。
実行環境
- os Windows
- TkEasyGUI 0.2.73
- Python 3.10.11
TkEasyGUIバージョン0.2.73のRadio
クラスの update
メソッドにはラジオボタンのテキスト変更しか備わっておらず、PySimpleGUIのようにボタンや設定ファイルの情報を読み取り、切り替えするにはコードの書き換えが必要です。
コードだけほしい方は目次からRadioクラス全体のコード
に飛んでください。
コードの修正、追加の解説
TkEasyGUIのフォルダの widgets.py
ファイルを開きます。
Radio
クラスを探してください、1724行あたりにあると思います。
-
変更するメソッド
- update
- select
- create
-
新たに加えるメソッド
- deselect
update メソッドの修正
def update(self, value=None, text: Union[str, None] = None, **kw) -> None:
"""Update the widget."""
if text is not None:
self.set_text(text)
if value is not None:
self.select()
update メソッドを使ってラジオボタンの状態(選択されているかどうか)を変更した場合にも、グループ内で他のボタンが自動的に非選択状態になるようにします。
- 変更点
-
value
が指定された場合、self.select()
メソッドを呼び出しています。これにより、選択されたラジオボタンだけが有効になり、他のグループ内のボタンは解除されます。 - もともとは
value
の更新のみが行われていましたが、select()
を通してグループ内の一貫性を保つ処理を行うようにしました。
-
select メソッドの修正
def select(self) -> None:
"""Select the radio button and unselect others in the group."""
for radio in self.window.radio_group_dict[self.group_id][1]:
if radio != self:
radio.deselect() # Unselect other radio buttons in the same group
self.window.radio_group_dict[self.group_id][0].set(self.value)
ラジオボタンのグループ内で一度に1つのボタンだけが選択されるようにするための処理です。
- 変更点
- ラジオボタンのグループ(
self.group_id
)に属している他のラジオボタンをすべて非選択deselect()
)状態にします。 - 具体的には、グループ内のラジオボタンオブジェクトをループで回し、
self
以外のものにはdeselect()
を呼び出すことで、グループ内で他のボタンを非選択にします。 - 最終的に、現在のラジオボタンの値を
set(self.value)
で選択状態にします。
- ラジオボタンのグループ(
create メソッドの修正
def create(self, win: Window, parent: tk.Widget) -> tk.Widget:
# create radio group
if self.group_id not in win.radio_group_dict:
win.radio_group_dict[self.group_id] = [tk.IntVar(value=0), []] # Second element will store radio button objects
win.radio_group_dict[self.group_id][0].trace_add("write", lambda *args: self.disptach_event({"event_type": "change", "event": args}))
win.radio_group_dict[self.group_id][1].append(self) # Store the radio button object itself
self.value = len(win.radio_group_dict[self.group_id][1]) # Value is the number of radio buttons in the group
# create radiobutton
self.widget = ttk.Radiobutton(
parent,
value=self.value,
variable=win.radio_group_dict[self.group_id][0],
style=self.style_name,
**self.props)
if self.default_value:
self.select()
return self.widget
- ラジオボタングループの作成・管理部分
if self.group_id not in win.radio_group_dict:
win.radio_group_dict[self.group_id] = [tk.IntVar(value=0), []]
すべてのラジオボタンをグループで管理するため、win.radio_group_dict という辞書にグループIDをキーとして格納します。
- 変更点
-
win.radio_group_dict[self.group_id]
に、2つの要素を持つリストを作成しています。- 1つ目の要素は tk.IntVar(value=0) です。これは、グループ内のラジオボタンの選択状態を管理する変数です。0 はデフォルトの未選択状態を意味します。
- 2つ目の要素は空のリスト [] で、これは後ほど各ラジオボタンのオブジェクトを格納するためのリストです。
- trace_add で変更検知を追加
win.radio_group_dict[self.group_id][0].trace_add("write", lambda *args:self.disptach_event({"event_type": "change", "event": args}))
ラジオボタンの選択状態が変更された際にイベントを検知し、反応できるようにするためのコードです。
- 変更点:
-
trace_add("write")
は、IntVar
が書き換えられた(つまり選択が変更された)ときに自動でコールバックを呼び出します。 - ここでは
self.disptach_event({"event_type": "change", "event": args})
を使ってイベントを発火させています。これにより、ラジオボタンの選択が変更されると、外部からその変更に応じた処理が実行できるようになります。
-
- ラジオボタンオブジェクトをグループに追加
win.radio_group_dict[self.group_id][1].append(self)
グループ内のラジオボタンオブジェクトそのものを保存し、他のボタンを非選択にするために参照できるようにします。
- 変更点
- self(現在のラジオボタンオブジェクト)を、グループに対応するリスト(2番目の要素)に追加しています。
- これにより、グループ内のすべてのラジオボタンオブジェクトがリスト内で管理され、後から他のボタンの選択状態を変更する際に利用できます。
-
value
の設定
self.value = len(win.radio_group_dict[self.group_id][1])
ラジオボタンの value
属性を設定します。value
は、このラジオボタンがグループ内で何番目のボタンかを示すために使われます。
- 変更点
-
len(win.radio_group_dict[self.group_id][1])
により、現在のグループ内のボタン数を取得し、それをself.value
に割り当てています。 - つまり、グループ内で新しく追加されたラジオボタンは、ボタン数に応じた値を持ちます。これにより、選択状態を管理しやすくなります。
-
deselect メソッドの追加
def deselect(self) -> None:
"""Deselect the radio button."""
self.window.radio_group_dict[self.group_id][0].set(0)
ラジオボタンを非選択状態にするためのメソッドです。
-
self.window.radio_group_dict[self.group_id][0].set(0)
を使って、現在のラジオボタンの変数に0
を設定し、非選択状態にします。
Radioクラス全体のコード
class Radio(Element):
"""Checkbox element."""
def __init__(
self, text: str="",
group_id: Union[int, str] = "group",
default: bool = False,
key: Union[str, None] = None,
enable_events: bool = False,
# other
metadata: Union[dict[str, Any], None] = None,
**kw) -> None:
if key is None or key == "":
key = text
super().__init__("Radio", "TRadiobutton", key, True, metadata, **kw)
self.use_ttk = True
self.default_value = default
self.value: int = 0
self.props["text"] = text
self.group_id: str = str(group_id)
if enable_events:
self.bind_events({
"<Button-3>": "right_click"
}, "system")
def create(self, win: Window, parent: tk.Widget) -> tk.Widget:
# create radio group
if self.group_id not in win.radio_group_dict:
win.radio_group_dict[self.group_id] = [tk.IntVar(value=0), []] # Second element will store radio button objects
win.radio_group_dict[self.group_id][0].trace_add("write", lambda *args: self.disptach_event({"event_type": "change", "event": args}))
win.radio_group_dict[self.group_id][1].append(self) # Store the radio button object itself
self.value = len(win.radio_group_dict[self.group_id][1]) # Value is the number of radio buttons in the group
# create radiobutton
self.widget = ttk.Radiobutton(
parent,
value=self.value,
variable=win.radio_group_dict[self.group_id][0],
style=self.style_name,
**self.props)
if self.default_value:
self.select()
return self.widget
def deselect(self) -> None:
"""Deselect the radio button."""
self.window.radio_group_dict[self.group_id][0].set(0)
def select(self) -> None:
"""Select the radio button and unselect others in the group."""
for radio in self.window.radio_group_dict[self.group_id][1]:
if radio != self:
radio.deselect() # Unselect other radio buttons in the same group
self.window.radio_group_dict[self.group_id][0].set(self.value)
def is_selected(self) -> bool:
"""Check if the radio button is selected."""
return self.window.radio_group_dict[self.group_id][0].get() == self.value
def get_value(self) -> bool:
"""Get the value of the widget."""
return self.value
def get(self) -> Any:
"""Get the value of the widget."""
return self.is_selected()
def set_text(self, text: str) -> None:
"""Set the text of the widget."""
self.props["text"] = text
self._widget_update(text=text)
def set_value(self, b: bool) -> None:
"""Set the value of the widget."""
self.widget.set(b)
def update(self, value=None, text: Union[str, None] = None, **kw) -> None:
"""Update the widget."""
if text is not None:
self.set_text(text)
if value is not None:
self.select()
self._widget_update(**kw)
実験コード
import TkEasyGUI as eg
# import PySimpleGUI as eg
# レイアウトの作成
layout = [
[eg.Radio('選択肢1', group_id=0, key="RADIO1", default=True), eg.Radio('選択肢2', group_id=0, key="RADIO2")],
[eg.Button('選択肢2に変える', key='-CHANGE1-'), eg.Button('選択肢1に変える', key='-CHANGE2-')],
[eg.Button('交互に変える', key='-CHANGE3-')]
]
# ウィンドウの作成
window = eg.Window('ラジオボタン切り替え', layout)
counter = 0
# イベントループ
while True:
event, values = window.read()
counter += 1
if event == eg.WINDOW_CLOSED:
break
if event == '-CHANGE1-':
# ラジオボタンの値を切り替える
window['RADIO2'].update(True)
if event == '-CHANGE2-':
# ラジオボタンの値を切り替える
window['RADIO1'].update(True)
if event == '-CHANGE3-':
if counter % 2 == 0:
window['RADIO1'].update(True)
else:
window['RADIO2'].update(True)
# ウィンドウを閉じる
window.close()
Discussion