はじめに
何らかのイベントが発生したとき,途中でウィジェットを削除したくなるときがあります.例えば,グリッド状に表示したウィジェットを画面のリサイズに合わせて変更したい,というときです.消した分だけストレッチしたいということもあるでしょう.
常識的な思考でしたら,QBoxLayout.removeWidget
を呼び出すなどで登録されたウィジェットを削除しますが,どうやらremoveWidget
を呼び出してもレイアウトから削除されずに残ったままになります.
それだけでなく,ウィジェットが削除されたままレンダリングされる,という状態が発生するため問題だらけです.不具合にしか思えません.
今回はウィジェットを削除する方法を説明します.
回避策
removeWidget
を呼び出しても内部的にレンダリングされたままになる現象は今のところ対応されてないらしいです.対症療法的ですが,以下の方法で削除できます.
# ラベルだけ削除したい
layout = QVBoxLayout()
label = QLabel("I need to remove a label.")
btn = QPushButton("test")
layout.addWidget(label)
layout.addWidget(btn)
# 気持ち的に完全に削除したい
label.hide()
layout.removeWidget(label)
del label # 変数の中身自体を削除する
QWidget.hide()
を呼び出せば画面から消えてくれるだけでなく,消えた分だけストレッチもされます.あまりにも対症療法的とも思えますし,そもそもメモリリークが起こり得るのではないかと思うのですが,消す方法がないですし,どうやら内部的にはQWidget.show()
を呼び出せる状態のままになっているらしいです.
そのため,del文を使って参照自体を切り離すということをやっていますが,これがまた内部的に問題を起こすかどうかは未知数です.かなり危険な方法だと思います.
Flyweightパターンで回避する
このような状態では,かなり不安が残ります.特に,Qt内部で削除したウィジェットが参照されたときに落ちる可能性も拭えないからです.
(Pythonでは削除した変数はNone型ではなく例外が送出されます)
これを回避するには,到るところで例外キャッチしないといけないわけですがどこで発生するか特定できない例外のためにコードを割くのは無謀だと思います.
そこで回避策の一つとしてFlyweightパターンを使って使用できる想定分だけ大量のウィジェットを作成します.
# とりあえず大量のオブジェクトを作成しておく
layout = QVBoxLayout()
labelList = []
for _ in range(100):
label = QLabel("hogehoge")
label.hide()
layout.addWidget(label) # 表示はしないけど追加はしておく
labelList.append(label)
# 今は5個だけ表示する
for label in rangee(5):
label.show()
あらかじめ大量のオブジェクトを確保しておき,使い回すような方法をメモリプールと呼ぶことがあります.内容が同じ(もしくは継承先が同じ)であれば,本質的に使い回せることを示唆するデザインパターンです.
使わなくなればQWidget.hide()
を呼び出すことも,使いたくなればQWidget.show()
で呼び出すことができます.
考察
レイアウトから削除してウィジェットを切り離しても,どうやらQtのどこかに登録されたままになっている可能性が考えられます.レンダリングされたまま残るのですから,座標値なども親子関係を解消した上で解決しているように思われます.
そのため,QWidget.removeWidget
を呼び出してもレンダリングとして削除されず,再描画されても残ったままになる,という挙動を説明できます.恐らく,レンダリング周りでウィジェットもしくは描画情報が生き残っている可能性が考えられます.
del文で削除しても動いているには動いているのですが,基本的にQWidget.hide()
を呼び出しましょう,ということなので,下手するとdel文で削除したウィジェットを参照する可能性があります.del文の削除を行うと意図しない動作が発現することも予想できるため,ウィジェットを増減させるケースはFlyweightな設計を心がけるべきです.
おわりに
根本的な対応策になっていない気がするのですが,いずれにせよ大量のウィジェットを表示するのであれば,最悪想定分だけオブジェクトを確保するのは理にかなっています.
また,急な操作が発生するとウィジェットのために大量のメモリ確保が必要になり,非常に操作が重くなります.これを何度も繰り返すのはあまり良い設計ではありません.
この回避策だとそもそも少ない数だったら実装するコスト考えるとあまり良いとは言えないのではないか,と言われればその通りです.しかし,レンダリングの情報まで削除しに行くか? と言われると面倒くさいです.カプセル化されてたら調べる意味もありませんし,どのような副作用があるのかもわかりません.私ならウィジェットを増減させるならば,Qtの動作を考えてFlyweightにします.