質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.35%
Python 3.x

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

Tkinter

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

Q&A

解決済

1回答

4393閲覧

Python3 Tkinter クラスの継承で意図した動作ができない

person

総合スコア224

Python 3.x

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

Tkinter

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

0グッド

1クリップ

投稿2021/01/13 00:15

編集2021/01/18 05:41

Toplevelを継承したときに、意図した動作をしません。
Frameを継承したら、意図した動作になりました。
Toplevelを継承した際の書き方の問題だと思いますが、どう直せばいいか教えてください。
意図した動作としては、tk.Tk()とtk.Toplevel()のウィンドウを1つずつ表示することです。
実際の動作としては、小さいウィンドウが2つ余計に出てきます。title()が自分では設定していない"tk"となっていて原因が分かりません。

回答よろしくお願いします。

意図した動作をしない

Python

1from tkinter import ttk 2import tkinter as tk 3 4class Main(tk.Tk): 5 def __init__(self): 6 self.sub = Sub() 7 self.open() 8 def open(self): 9 super().__init__() 10 self.geometry("400x300+0+0") 11 self.title("main") 12 self.sub.open(self) 13 14class Sub(tk.Toplevel): 15 def open(self, master): 16 super().__init__() 17 self.geometry("200x200+25+25") 18 self.title("sub") 19 self.attributes("-topmost", True) 20 self.grab_set() 21 self.transient(master) 22 self.protocol("WM_DELETE_WINDOW", self.close) 23 24 def close(self): 25 self.grab_release() 26 self.destroy() 27 28def main(): 29 app = Main() 30 app.mainloop() 31 32if __name__ == "__main__": 33 main()

Frame継承だと意図した動作

Python:

1from tkinter import ttk 2import tkinter as tk 3 4class Main(tk.Tk): 5 def __init__(self): 6 super().__init__() 7 self.geometry("400x300+0+0") 8 self.title("main") 9 sub = Sub() 10 sub.open(self) 11 12class Sub(tk.Frame): 13 def open(self, master): 14 self.tl = tk.Toplevel() 15 super().__init__(self.tl) 16 self.tl.geometry("200x200+25+25") 17 self.tl.title("sub") 18 self.tl.attributes("-topmost", True) 19 self.tl.grab_set() 20 self.tl.transient(master) 21 self.tl.protocol("WM_DELETE_WINDOW", self.close) 22 23 def close(self): 24 self.tl.grab_release() 25 self.tl.destroy() 26 27def main(): 28 app = Main() 29 app.mainloop() 30 31if __name__ == "__main__": 32 main()

追記

teamikl様より修正して意図した結果になったソースコード

Python:

1from tkinter import ttk 2import tkinter as tk 3 4class Main(tk.Tk): 5 def __init__(self): 6 super().__init__() 7 self.sub = Sub() 8 self.geometry("400x300+0+0") 9 self.title("main") 10 self.sub.open(self) # ← 書かなくてもToplevelウィンドウが表示される(?) 11 12class Sub(tk.Toplevel): 13 def open(self, master): 14 self.geometry("200x200+25+25") 15 self.title("sub") 16 self.attributes("-topmost", True) 17 self.grab_set() 18 self.transient(master) 19 self.protocol("WM_DELETE_WINDOW", self.close) 20 21 def close(self): 22 self.grab_release() 23 self.destroy() 24 25def main(): 26 app = Main() 27 app.mainloop() 28 29if __name__ == "__main__": 30 main()

ボタン押下でToplevelを開くように個人的に変えたプログラム

Python:

1from tkinter import ttk 2import tkinter as tk 3 4""" 5 Frame を継承するのが一般的(?)だが、 6 Frame を生成・配置する必要はないので、Tk を継承する 7""" 8class Main(tk.Tk): 9 def __init__(self): 10 # Tk のコンストラクタ呼び出し 11 super().__init__() 12 # Sub のインスタンス生成(ここではToplevelウィンドウは表示しない) 13 self.sub = Sub() 14 # Tk の設定 15 self.geometry("400x300+0+0") 16 self.title("main") 17 # Tk にウィジェット配置 18 self.button = ttk.Button(self, text="open", command=self.pushed) 19 self.button.grid() 20 21 def pushed(self): 22 # open 呼び出しにより、Toplevelウィンドウを生成 23 self.sub.open(self) 24 25class Sub(tk.Toplevel): 26 """ 27 継承先クラス Sub のコンストラクタ __init__() の定義が無いと、 28 Sub() インスタンス生成時に継承元クラス Toplevel のコンストラクタ __init__() が呼ばれる。 29 Sub() インスタンス生成時に継承元のコンストラクタが呼ばれると困るので、 30 コンストラクタ(処理としては「何もしない」)を定義する。 31 32 上記対応により継承元のコンストラクタを呼ばなくなったため、 33 super().__init__() を別の関数(open)に書くことで、 34 そのタイミングで継承元のコンストラクタを呼び出す。 35 """ 36 def __init__(self): 37 pass 38 def open(self, master): 39 # Toplevel のコンストラクタ呼び出し(ここでToplevelウィンドウを表示する) 40 super().__init__() 41 # Toplevel の設定 42 self.geometry("200x200+25+25") 43 self.title("sub") 44 self.attributes("-topmost", True) 45 self.grab_set() 46 self.transient(master) 47 self.focus_set() 48 self.protocol("WM_DELETE_WINDOW", self.close) 49 50 def close(self): 51 self.grab_release() 52 self.destroy() 53 54def main(): 55 app = Main() 56 app.mainloop() 57 58if __name__ == "__main__": 59 main()

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

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

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

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

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

guest

回答1

0

ベストアンサー

問題点: Frame のコードもおかしな点があるのですが、
open() メソッド内で 親クラスの __init__() を呼び出してます。

修正方法

  • Main クラスの open() 内の super().__init__() は、コンストラクタ(__init__)に移動する。
  • Sub クラスの open() 内の super().__init__() は削除

下のコードも、結果的に目に見える問題にはなってないようですが、
open() 内での super().__init__() は誤りです。


tkinter のプログラムでは、
ウィンドウを持つ Tk や Toplevel と中身の Frame は分離したほうが良いです。
Toplevel の継承は、サブウィンドウを単体でテストしたいときに、クラスの再利用が少し手間になります。

投稿2021/01/13 02:35

teamikl

総合スコア8760

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

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

person

2021/01/13 04:29

回答ありがとうございます。 > Frame のコードもおかしな点があるのですが、 open() メソッド内で 親クラスの __init__() を呼び出してます。 コンストラクタで呼び出した方がいいんですね。 > Main クラスの open() 内の super().__init__() は、コンストラクタ(__init__)に移動する。 > Sub クラスの open() 内の super().__init__() は削除 Toplevelの継承では、上記の変更をしても、3つ目のウィンドウが出てきました。 from tkinter import ttk import tkinter as tk class Main(tk.Tk): def __init__(self): self.sub = Sub() #self.open() super().__init__() self.geometry("400x300+0+0") self.title("main") self.sub.open(self) class Sub(tk.Toplevel): def open(self, master): #super().__init__() self.geometry("200x200+25+25") self.title("sub") self.attributes("-topmost", True) self.grab_set() self.transient(master) self.protocol("WM_DELETE_WINDOW", self.close) def close(self): self.grab_release() self.destroy() def main(): app = Main() app.mainloop() if __name__ == "__main__": main()
teamikl

2021/01/13 04:50 編集

>Toplevelの継承では、上記の変更をしても、3つ目のウィンドウが出てきました。 インデントのないコメントのコードは実行できないので、質問のコードに反映できますか。 見た感じでは、Tkinter の初期化前に Sub (Toplevel) が呼び出されてます。 self.sub = Sub() # tk.Toplevel #self.open() super().__init__() # tk.Tk 初期化前のToplevel 呼び出しでは tk.Tk、初期化+ウィンドウとなるので デフォルトのウィンドウとToplevel のウィンドウの2枚が開きます。 例えば、以下のコードも同様にウィンドウを3つ開きます。 import tkinter as tk top = tk.Toplevel() root = tk.Tk() root.mainloop()
person

2021/01/17 07:51

super().__init__() より下に self.sub = Sub() を書いたら2枚になりました。 ありがとうございます。
person

2021/01/18 04:47

質問ではToplevelをTkとほぼ同時に表示していますが、 ボタンを押したときなど特定のタイミングでToplevelを表示することを考えたときに不都合が生じたため、少しソースコードを変えました。 その際に > Sub クラスの open() 内の super().__init__() は削除 とあったのですが、必要そうだと思ったので書きました。 質問文にソースコードを追記したので、そちらもよろしければ確認お願いします。
teamikl

2021/01/18 05:18 編集

__init__ は通常、インスタンス生成時に一度しか呼び出されない為 初期化時のみ必要な処理を書きます。 ウィンドウの表示・非表示は、後からwithdraw/iconifyで変更できるので 必要な初期設定は __init__ 内で済ませて、 openでは表示のみにした方が良いです。 open内で親クラスのコンストラクタを呼び出すのは、(open内でのsuper().__init__) そもそも一度しか呼ばれることを想定していない関数を、 複数回呼べる事になるので、使い方によっては誤動作の原因になったり 一度しか使えない再利用性の低いクラスになってしまいます。 (※例えば、open()を複数回呼び出したときにどのような挙動になるか、等)
teamikl

2021/01/19 01:18

- ボタンを押したとき、必要になってからインスタンス生成 - 非表示のままインスタンス生成しておき、ボタンを押したとき表示 解決策としてはどちらでもよいですが、上で説明したのは後者の方法です。 URLの質問は、super().__init__()に self を渡している点が問題なので、 エラーになる為、問題の焦点が違います。 今回の問題点は「メソッド内での親クラスのコンストラクタ呼び出し」 通常は一回しか呼び出さないメソッドを、同インスタンスに対して 何度も呼び出せる設計になっている点です。 このコード内では、モーダルダイアログなので、 アプリケーション内で実際にそのような操作はできませんが、 実際にバグを再現した方が解りやすいと思います、 コード中のgrab_setの行をコメントして、 ボタンを何度も押してみて挙動を確かめて見て下さい。 例: 複数subウィンドウを開きます。 最初に開いたウィンドウを閉じようとすると、 必ず最後に開いたウィンドウのみが閉じる。他のウィンドウは閉じられない Mainクラスから参照されるインスタンス(self.sub)と、 実際のインスタンス(super().__init__()で生成される) が 1対1に結び付いてないのが解ると思います。 「モーダルダイアログにしていて、複数回呼び出す事が出来ないから問題ないのでは?」 という、とりあえず意図通りに動けばよいみたいな観点もありますが、 問題が再現しない等に注意して使わないといけないという事なので、 これはそのままクラスの再利用性が低いことに繋がってます。 ==== コードの意図としては、実際のインスタンス化のタイミングを 必要になる時点まで遅らせるというのは理解できます。 (インスタンス化のコストが重たいクラスを使う時等で、 必要になるまで処理を遅らせる、遅延ロード(lazy loading)といった手法もあります)
teamikl

2021/01/19 01:55 編集

多分、この場合(インスタンス生成を遅らせる)は、SubクラスはToplevelを継承せずに 普通に open 内で Toplevel を作るのが良いと思います。
person

2021/01/19 05:22

継承することによるデメリットもあるんですね。 継承しないほうも試してみます。 ありがとうございました。
teamikl

2021/01/19 07:33

継承自体のデメリットではなく、 インスタンス化の時点でウィンドウが表示されるという、 Toplevelクラスの仕様が、 要求(インスタンス化とは別でウィンドウは後から表示したい) と噛み合ってない点ですね。 継承で実装するなら、表示・非表示の制御は先述した通り withdraw/iconifyで制御できます。 継承によるのデメリットの側面は、例えば 親クラスから引き継いだ属性と名前空間が混同してしまう事により 自分で定義したメソッドとToplevelから継承したメソッドが混ざってしまう、 IDE等で入力候補をだすと大量にメソッドのリストが表示されてしまうなどがあります。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問