ボタンを disable に
File メニューに Show アイテムを
役に立つ(?)テクニック
Show アイテムも enable/disable にする
ここまでのまとめ
大事なことを忘れてました。Speak ボタンについてです。このボタンはデフォルトに設定されていますが、入力フィールドに何も入力されていないのに押せるというのはちょっと変でした。何も入力されていない場合はボタンを disable(実行不可能)にしましょう。
コールバックでよく出てくることなのに、忘れてました。
speakwin.py 変更後:3- ---------------------------------------------- class Win(W.Window): #メインウィンドウのクラス def __init__(self, arg): self.parent = arg #ウィンドウを表示 W.Window.__init__(self, (10, 40, 250, 180), 'SpeakText', fontsettings = ('Osaka',0,12,(0,0,0))) self.prompttext = W.TextBox((10,10, 50, 22), text = 'Input:') self.text = W.EditText((60,10,140, 22), callback = self.do_key) # callback を追加 self.speakbutton = W.Button((90, 60, 60, 22), title = 'Speak', callback = self.speak) self.open() self.setdefaultbutton(self.speakbutton) #self.bind("<close>", self.domenu_close) #self.bind("cmdw", self.close) ・ ・ ・ def do_key(self): # 追加 self.speakbutton.enable( self.text.get() ) # 追加 ----------------------------------------------
入力フィールド self.text にコールバック self.do_key を加えました。W.EditText の callback は、何か入力されたり変化があると self.do_key が呼び出されます。self.do_key は、入力フィールが空欄(self.text.get() が "" ->false)なら Speak ボタン(self.speakbutton)を disable(実行不可能)にします。空欄でないなら enable(実行可能)にします。
self.speakbutton.enable(1) -> Speak ボタンがenableに、
self.speakbutton.enable(0) -> Speak ボタンがdisableになります。
こうして実行してみると、何故かアプリケーションの起動時に入力フィールドに何も表示されていないのに、ボタンが enable になっています。一度 delete キーを押したり、いくつか文字を入力したあとに文字を全て delete すると、disable になってくれます。
対処法は分かりません。self.open() のあとに
self.text.set("a")
self.text.set("")
などを入れても、アプリケーションの起動時にボタンは enable になります。「入力フィールドに初期値として文字列を入れておく」というのが、対処法、というか、不具合を隠すテクニックかもしれません。
対処法わかりました。
speakwin.py ---------------------------------------------- class Win(W.Window): #メインウィンドウのクラス def __init__(self, arg): ・ ・ ・ self.open() self.setdefaultbutton(self.speakbutton) self.speakbutton.enable(0) #<-ここ ・ ・ ・ ----------------------------------------------
こんなふうにself.speakbutton.enable(0)を書いておけばいいんでした。当たり前です。基礎の基礎でした。
この文書を書いてから2年目にして気が付くなんて恥ずかしさの極み(@^ ^@)。(2004/01/28)
前回、File メニューに Close アイテムを作りました。Close アイテムを選んだり、クローズボックスをクリックしてウィンドウを閉じただけでは、アプリケーションは終了しないようになっています。
ウィンドウを閉じたら Quit しかできないのは面白くないので、もう一度ウィンドウを表示する Show アイテムを作ってみます。MyApp.py に追記します。
MyApp.py 変更後:112-118 ---------------------------------------------- #newitem = FrameWork.MenuItem(m, "New", "N", 'new') #openitem = FrameWork.MenuItem(m, "Openノ", "O", 'open') #FrameWork.Separator(m) closeitem = FrameWork.MenuItem(m, "Close", "W", 'close') showitem = FrameWork.MenuItem(m, "Show", "S", 'show') # 追加 #saveitem = FrameWork.MenuItem(m, "Save", "S", 'save') #saveasitem = FrameWork.MenuItem(m, "Save asノ", None, 'save_as') #FrameWork.Separator(m) quititem = FrameWork.MenuItem(m, "Quit", "Q", 'quit') ----------------------------------------------
と、気軽に書きましたが、domenu_show メソッドを作らなければなりません。これは、難しいです。私にとっては。
何故かというと、ウィンドウを表示するのには self.main = speakwin.Win(self) を実行すればいいんですが、ウィンドウがすでにある場合にはウィンドウを作らないようにしないと、たくさんウィンドウが作れてしまいます。
クローズされているかどうか判断して、クローズされていたら self.main = speakwin.Win(self) を実行する、ということにしなければなりません。
if で条件分けして判断するために『ウィンドウが閉じていると、hasattr(self, "main") や callable(self.main.open) は false かな』と思って調べたら、ウィンドウが開いていてもクローズされていても true でした。途中で print させたりして調べました。
クローズそのものをやめて、Close アイテムは self.main.show(0) を実行するようにして、Show アイテムは self.main.show(1) を実行することにしてみました。これはいいかなと思ったんですが、ウィンドウをクローズボックスのクリックでクローズすると domenu_close メソッドを実行してしまいます。これを補足するように self.bind("<close>", self.my_close) を設定しようか、と考えたり...
ウィンドウそのものを Python:Mac:Tools:IDE:PyConsole.py の PyConsole のようにしっかり作って、show() の呼び出しで表示/非表示切り替える、という手もありますが、ここでやっている程度のためにたくさんフラグを設定したりして頭を悩ませられても、それで完成するならいいけど、私には絶対できません。
PyConsole クラスを継承したりメソッドをコピーしたりして speakwin.py の Win クラスを作ると、割と簡単にできるのかもしれません。
で、結論だけ書きます(以上のいくつかのパラグラフは悩んだ私の愚痴でした)。MyApp.py の MyApp クラスの最後に、以下のメソッドを書きます。
MyApp.py :MyApp クラスのメソッド ---------------------------------------------- def domenu_show(self, *args): if self.main.wid: pass else: import speakwin self.main = speakwin.Win(self) ----------------------------------------------
self.main.wid というもので判断します。それが true ならウィンドウが表示されているということなので pass、そうでないなら self.main = speakwin.Win(self) で新たにウィンドウを作ります。
これだと、Show アイテムが選ばれた場合、ウィンドウがすでに表示されている時は何もしません。ウィンドウが閉じられている時にはあらたにウィンドウが表示されます。
self.main.wid (つまりWinインスタンスのwid)は何かと言うと、self.main.wid には Window オブジェクトが代入されていて、ウィンドウが閉じられると self.main.wid は None になります。これを利用したものです。エライ!よくみつけた。どうやってみつけたかというと...
上の方法は、_show、_activate などの Win のいろんな変数がウィンドウの表示/非表示でどう変わるのかを調べていてみつけました。調べる方法は簡単です。デバッグためのメニューアイテムを作ります。
---------------------------------------------- ・ ・ # File menu # 109行目 m = Wapplication.Menu(self.menubar, "File") #newitem = FrameWork.MenuItem(m, "New", "N", 'new') debugitem = FrameWork.MenuItem(m, "Debug", "O", 'debug') # ここ ・ ・ def domenu_debug(self): raise "debug" ----------------------------------------------
上のように、openitem を debugitem に変更して、MyApp クラスのメソッドとして domenu_debug を加えます。こうしてアプリケーションを実行し、メニューから debug を選び、エラーを発生させると、何故か PythonIDE でお馴染みの Traceback が開きます。
Wウィジェットはもともと PythonIDE を構成している部品を利用しているので、Wウィジェットでアプリケーションを作るときには、このように PythonIDE のデバッガも利用できるというわけです。
まるで、Smalltalk でオブジェクトブラウザ/デバッグブラウザを使うみたいに便利ですねぇ。(マイナーな比喩ですみません)
PythonIDE のデバッグと同様にブラウズできるので、ウィンドウが開いている時、閉じてる時に Debug メニューを選んで Traceback ウィンドウでオブジェクトの状態を調べてみました。Traceback ウィンドウの『Browse locals...』ボタンを押します。すると『Object browser』ウィンドウが開きます。その中の『self』の項目をダブルクリックすると、さらに『Object browser』が開くので、その中の『main』をダブルクリックします。すると、また『Object browser』が開いてmainのメンバ変数の状態などがわかります。mainの変数を調べると、ウィンドウが閉じているとwid が None になるので、wid で判断するといいかも、と考えました。
下図は、ウィンドウが開いている時の self.main オブジェクトをObject browserで見たもの。
こちらはウィンドウが閉じてる時の self.main オブジェクト。
これはさらに難しいです。ウィンドウが開いているなら Show アイテムを disableに、閉じているなら enable にしなければなりません。
self._menustocheck に showitem を加えて、can_callback を書いて、それが true を返せば enable に、false なら disable になるようですが...
speak.Win クラスに can_show を書いても、Win のウィンドウ自身が閉じているので can_show を参照できず disable のままになります。MyApp クラスに can_show を書いて、ウィンドウが閉じている時( if self.main.wid == None )に true を返すようにしても Show アイテムは enable になりません。おかしいなぁ。
とりあえず、私の考えた解決策を書いておきます。もっといい方法があるんだと思いますが...
MyApp.py 変更後:137-139 ---------------------------------------------- self._menustocheck = [closeitem, showitem, # showitem 追加 undoitem, cutitem, copyitem, pasteitem, clearitem, selallitem] ---------------------------------------------- MyApp クラスのメソッドとして追加 ---------------------------------------------- def checkmenus(self, window): Wapplication.Application.checkmenus(self, window) for item in self._menustocheck: callback = item.menu.items[item.item-1][2] if callback == 'show': if self.main.wid: item.enable(0) else: item.enable(1) ----------------------------------------------
この変更前までは MyApp のスーパークラス Wapplication.Application の checkmenus メソッドがメニューアイテムの enable/disable をやっていました。どういうふうにやっていたかというと...
Python 2.0:Mac:Tools:IDE:Wapplication.py 219-234 ---------------------------------------------- def checkmenus(self, window): for item in self._menustocheck: callback = item.menu.items[item.item-1][2] if type(callback) >< StringType: item.enable(1) elif hasattr(window, "domenu_" + callback): if hasattr(window, "can_" + callback): canhandler = getattr(window, "can_" + callback) if canhandler(item): item.enable(1) else: item.enable(0) else: item.enable(1) else: item.enable(0) ----------------------------------------------
self._menustocheck の中のアイテムについて、domenu_callback、can_callback が true かどうかで enable/disable にしています。
can_show は MyApp.py や speakwin.py に書いても働かないようなので、showアイテムについては self.main.wid で判断すればいいと考えました。それで、同じ checkmenus メソッドをオーバーライドして、まず Wapplication.Application の checkmenus を実行します。さらに 'show' についてはウィンドウが開いていたら(self.main.wid が true なら)disable、そうじゃないなら enable にする、ということにしました。
上のように書き加えた MyApp.py を実行すると、ウィンドウが開いている時の Show アイテムは disable に、閉じている時には enable になり、Show アイテムを選ぶとウィンドウが表示されます。
ウィンドウが開いている時。
ウィンドウが閉じている時。
ここまでの MyApp.py と speakwin.py、そして MyApp.rsrc を一つのフォルダに入れた speak3.sit.bin(約 30k)を作りました。