🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

6568閲覧

tkinterのToplevelを用いて増やしたウインドウ上で選択したボタンを表示させたい

sobagome

総合スコア9

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

0クリップ

投稿2020/12/16 00:38

前提・実現したいこと

tkinterのToplevelを用いて増やしたウインドウ上で、
表示されたボタンを選択すると、選択したボタンの名称を表示するGUIを作成したい。

困っていること

  • Toplevelを用いないウインドウ上では、上記目的のGUIを作成できました。

⇒選択したボタン名称を表示する。

  • Toplevelを用いて増やしたウインドウ上で同様の動作をするGUIが作成できません。

⇒選択するボタンがすべて「Python」と表示される。

該当のソースコード

Python

1# Toplevelを用いないコード 2import tkinter as tk 3 4root = tk.Tk() 5v = tk.IntVar() 6v.set(1) 7languages = [("Python",1), ("Perl",2), ("Java",3), ("C++",4), ("C",5)] 8def ShowChoice(): 9 for language, val in languages: 10 if val == v.get(): 11 print(language) 12 break 13tk.Label(root, text="""好みのプログラミング言語を\n  \ 14一つ選択していください。:""", justify = tk.LEFT, padx = 20).grid() 15for language, val in languages: 16 tk.Radiobutton(root, 17 text=language, 18 indicatoron = 0, #0はインジケーター、1はラジオボタン 19 width = 20, 20 padx = 20, 21 variable=v, 22 command=ShowChoice, 23 value=val).grid(sticky=tk.W) 24# mainloop 25root.mainloop() 26 27 28 29

Python

1# Toplevelを用いるコード 2import tkinter as tk 3 4# 子ウインドウ 5class ChildWindow: 6 def __init__(self, parent): 7 self.parent = parent 8 self.window = None 9 def init(self): 10 if not self.window: 11 self.window = tk.Toplevel(self.parent) 12 self.root = tk.Tk() 13 self.root.title(u'ChildWindow)') 14 self.root.geometry('600x400') 15 v = tk.IntVar() 16 v.set(1) 17 languages = [("Python",1), ("Perl",2), ("Java",3), ("C++",4), ("C",5)] 18 def ShowChoice(): 19 for language, val in languages: 20 if val == v.get(): 21 print(language) 22 break 23 tk.Label(self.root, text="""好みのプログラミング言語を\n  \ 24 一つ選択していください。:""", justify = tk.LEFT, padx = 20).grid() 25 for language, val in languages: 26 tk.Radiobutton(self.root, 27 text=language, 28 indicatoron = 0, #0はインジケーター、1はラジオボタン 29 width = 20, 30 padx = 20, 31 variable=v, 32 command=ShowChoice, 33 value=val).grid(sticky=tk.W) 34 self.root.mainloop() 35 def this_window_close(self): 36 self.window.withdraw() 37 self.window = None 38 return "break" 39 40 41# 親ウィンドウ メイン画面 42class MainWindow: 43 cap, w, h, img = None, None, None, None 44 def __init__(self): 45 self.root = tk.Tk() 46 self.root.geometry("800x600+10+10") 47 self.root.title("MainWindow") 48 self.root.resizable(width=False, height=False) 49 self.variety_root = ChildWindow(self.root) 50 self.btn_child = tk.Button(text="子ウインドウ", command=self.variety_root.init) 51 self.btn_child.grid(row=1, column=0) 52 self.root.mainloop() 53def main(): 54 MainWindow() 55if __name__ == "__main__": 56 main()

試したこと

Toplevelを用いてウインドウを増やしていくと、Classを用いて管理した方がコードとして綺麗になると思い、
色々とネット上の情報を参考にして上記のようにコードを作成しましたが、動作の挙動が異なり困っています。

補足情報(FW/ツールのバージョンなど)

Python 3.8.3

teamikl👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

Python

1import tkinter as tk 2 3# 子ウインドウ 4class ChildWindow: 5 def __init__(self, parent): 6 self.parent = parent 7 self.window = None 8 def init(self): 9 if not self.window: 10 self.window = tk.Toplevel(self.parent) 11 # self.root = tk.Tk() 12 self.root = tk.Toplevel(self.window) 13 self.root.title(u'ChildWindow)') 14 self.root.geometry('600x400') 15 # v = tk.IntVar() 16 # v.set(1) 17 v = tk.IntVar(master=self.window) 18 self.v = v 19 self.v.set(1) 20 languages = [("Python",1), ("Perl",2), ("Java",3), ("C++",4), ("C",5)] 21 def ShowChoice(): 22 for language, val in languages: 23 if val == self.v.get(): 24 print(language) 25 break 26 tk.Label(self.root, text="""好みのプログラミング言語を\n  \ 27 一つ選択していください。:""", justify = tk.LEFT, padx = 20).grid() 28 for language, val in languages: 29 tk.Radiobutton(self.root, 30 text=language, 31 indicatoron = 0, #0はインジケーター、1はラジオボタン 32 width = 20, 33 padx = 20, 34 variable=self.v, 35 command=ShowChoice, 36 value=val).grid(sticky=tk.W) 37 # self.root.mainloop() 38 39 def this_window_close(self): 40 self.window.withdraw() 41 self.window = None 42 return "break" 43

投稿2020/12/16 06:03

sobagome

総合スコア9

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

teamikl

2020/12/16 06:21 編集

モーダルダイアログを別途すれば、回答で指摘した点についてはほぼ問題ありません。 改善可能な点、Toplevel を2つ作っている → 余分な Toplevel を削除 root (MainWindow)   self.window (ChildWindow)     self.root self.window = tk.Toplevel(self.parent) self.root = tk.Toplevel(self.window) は、ひとつにまとめます。 self.window = tk.Toplevel(self.parent) root は Tk() のインスタンスに使われるため、(プログラム内で唯一) サブウィンドウ等は、self.window に統一したほうが良いと思います。 ChildWindow の self.root の部分は合わせて self.window に変更
teamikl

2020/12/16 06:35

もう一点、 self.window.withdraw() は非表示なので、 ウィンドウを閉じる、破棄する場合は destroy()
sobagome

2020/12/16 06:45

ご確認ありがとうございます。  if not self.window:   self.window = tk.Toplevel(self.parent)  self.root = tk.Toplevel(self.window) 上記で2種類Toplabelを作っているので、  self.winodw = tk.Toplevel(self.parent) に置き換えた上でself.root→self.windowに合わせるということですね。 動作も想定の動作になり、コードもスッキリしました。
teamikl

2020/12/16 09:05 編集

後々気がついたのですが、小ウィンドウは__init__ でウィジェット生成ではなく インスタンスは予め作っておいて、ボタンを押した時に (initメソッドで) 生成してるのですね。 予め小ウィンドウの画面を作っておくなら、 deiconify() / withdraw() で表示・非表示を切り替えて、 ウィンドウ自体は再利用するアプローチもありです。 ※ モーダルではない場合は対策をしないと、 複数回ボタンを押したときの挙動が問題になることがあります。 ---- 質問には影響ない部分なので余談ですが、IntVar -> StringVar StringVar を使うとラジオボタンで選択された値を直接、変数に入れられるので、 ShowChoice 内で for 文で探索してる部分は不要になり、print(self.v.get()) のみにできます。 StringVar の値を変更すると、その値を持つラジオボタンが自動的に選択状態になります。
sobagome

2020/12/16 09:22

ウインドウを破棄せずに、表示非表示の切替で組むという方法もあるのですね。 余談の件も、StringVarに置き換えて実装を試してみましたが上手くいきました。 また一つコードがシンプルになりました。 全てを理解は出来た訳ではありませんが、大変勉強になりました。
guest

0

ベストアンサー

問題点: tkinter.Tk を複数回呼び出している

tkinter.Tk は、ウィンドウを持つ他に、
tcl/tk ライブラリとの初期化等の処理も受け持つため、
プログラムの途中で複数回呼び出すと、おかしな挙動をすることがあります。

解決策:

  • tkinter.Tk() と mainloop() は、基本一つのプログラムで一度のみにします。

 2つ目のウィンドウでは、Tk の かわりに Toplevel
モーダルダイアログの場合は、grab_set と wait_window メソッドを使う
ウィンドウのサイズやタイトルは、Toplevel に対しても変更可能なので、複数の root は不要です。

  • tkinter.IntVar() のインスタンスは、ローカル変数 (v) ではなく、インスタンス変数 (self.v) に保持する。

 tkinter の非ウィジェットのオブジェクト (PhotoImage や IntVar) は、
デストラクタで破棄されるので、何処かに保存する必要があります。self.v = v
これは、一見うまく動いてるようでプログラムが複雑になって発現するケースもあるので注意。
また、サブウィンドウの場合は、親オブジェクトを指定した方が良いです
v = tk.IntVar(master=self.window) 省略時は root が親になる。


不完全な対策ですが、最小の手順でボタンの挙動のみ直したい場合

diff

1# ChildWindow 内 2- self.root = tk.Tk() 3+ self.root = self.window

投稿2020/12/16 04:25

編集2020/12/16 04:26
teamikl

総合スコア8729

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sobagome

2020/12/16 06:03

ご丁寧な解説ありがとうございます。思った通りの動作をするようになりました。 知見が無かったのですが、調べてみると2つ目のウインドウを作成する際に親ウインドウを操作させないのならばToplevelではなく、grab_set と wait_window メソッドを使用した方が良さそうですね。別途勉強いたします。 ところで「ウィンドウのサイズやタイトルは、Toplevel に対しても変更可能なので、複数の root は不要です。」のコメントで、複数のrootが何を指すのか理解できず、宜しければもう少し解説頂けますでしょうか。 また、解決方法欄に子ウインドウのコードのみ記載しておりますが、頂いた解決策の考えが反映されたコードにしたつもりですが如何でしょうか。
teamikl

2020/12/16 06:14

複数のrootは ChildWindow と MainWindow の self.root = tk.Tk() を指します。 root は単に慣習的につけられる変数名ですが、 (tkではウィジェットは木構造を持ち、その一番根本となる部分 root ) ChildWindow でも tk.Tk() を呼んでしまっている点が問題です。 サンプルコードの中には、tk.Tk() を複数回使うものもあるかもしれませんが、 基本的には避けたほうが無難です。 (正しく運用するには、Tk() 内部の理解が必要なので複雑になってくる)
sobagome

2020/12/16 06:52

理解できました。今後tk.Tk() の複数回使用は避けたいと思います。 ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問