wxPythonのユーティリティ・プログラム 2020春

2024/MAR/20

ちょいちょいwxPythonのプログラムを作るときに、 毎回似たようなコードを書いてたので、自分用にまとめてみました。

更新履歴
日付 変更内容
2020/MAY/24 新規作成
2020/MAY/29 トグルボタンとメニュー 追加
2020/MAY/30 ボタンとチェックボックスと不具合修正と 追加
2020/MAY/31 wrap()の更新と関連の補助的な関数の追加 追加
2020/JUN/02 未公開部分バグ修正 追加
2020/JUL/18 引数args追加 追加
2020/OCT/01 引数on_init追加 追加
2022/MAY/02 フレームの大きさの調整など 追加
2024/MAR/20 細かな色々な修正 追加

目次


とりあえず初版

wx_ut.py

pythonのユーティリティ・プログラム 2020冬 を使ってます。

kon_pageのpythonモジュールのインストール の手順でどこかに置くと、kon_page.pth が生成されてパスが設定されます。;-p)

サンプル

ソースだけ見ても「何のこっちゃ?」ですね。

作った本人が見てもそうです。

簡単なサンプルを作ってみます。

wx_smp1.py
#!/usr/bin/env python

import wx

import empty
import wx_ut
import dbg

def btn_go_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( inf.label )

def run():
	def init(wxo):
		tc = wxo.wx_new( wx.TextCtrl, '', min_h=80 )

		btn_ok = wxo.wx_new( wx.Button, 'OK' )
		btn_cancel = wxo.wx_new( wx.Button, 'Cancel' )
		btn_go = wxo.wx_new( wx.Button, 'Go' )

		def btn_hdl(inf):
			tc.SetValue( inf.label )

		wxo.bind( [ btn_ok, btn_cancel ], btn_hdl )
		wxo.bind( btn_go, btn_go_hdl )

		wp = wxo.wp
		lsts = [
			wp( [ wp( tc, prop=1,flag=wx.EXPAND ) ], prop=1, flag=wx.EXPAND ),
			wp( [ wp( btn_ok ), wp( btn_cancel ), wp( btn_go, prop=1 ) ], flag=wx.EXPAND )
		]
		wxo.wrap( wxo.frame, lsts )

		wxo.L = empty.new( locals() )

	wxo = wx_ut.new( 'wx sample 1', init )
	wxo.main_loop()

if __name__ == "__main__":
	run()
# EOF

new(title, init, fini=None)

wxo = wx_ut.new( 'wx sample 1', init )

titleのフレーム(ウィンドウ)を生成して、 引数で指定する初期化関数init()を呼び出します。

その際、 new()自身が返すはずの オブジェクトを、 init()の引数に指定して呼び出します。

(ちょっとだけ未来を先取りです ;-p)

引数finiに関数を指定すると、 フレームをクローズボックスクリックするなどで終了した時に、呼び出します。

init()同様、new()が返したオブジェクトをfini()の引数に指定して呼び出します。

main_loop(poll_func=None, sec=None, hz=None, gc=None)

wxo.main_loop()

new()が返すオブジェクトのメソッドです。

基本的にwxPythonのメインループを呼び出します。

poll_funcに関数を指定すると、デフォルトは1秒間隔で、引数なしで呼び出します。

1秒間隔は引数secで変更可能です。

hzで周波数で指定する事もできます。

両方指定すると、secの方が優先されます。

gcには可能ならば、thr.gc_new()で生成したオブジェクトを指定します。 (得に指定しなくても動作はします)

wx_new(cls, *args, **kwds)

new()が返すオブジェクトのメソッドです。

init(wxo)の中でwxPythonの部品を生成するときに使用します。

生成されたwxPythonの部品そのものが返ります。

テキストコントロールを生成する場合の例

tc = wxo.wx_new( wx.TextCtrl, '', min_h=80 )

ボタンを生成する場合の例

btn_ok = wxo.wx_new( wx.Button, 'OK' )

先頭の引数で、生成したい部品のクラスを指定します。

以降の引数は基本的にクラス生成時の引数として指定されます。

ただし、min_w, min_h, parentの引数名については、特別扱いします。

parentを指定しないと、デフォルト指定は内部で生成した、 トップレベルのフレームを指定した事になります。

wxPythonのラクスのコンストラクタは、parentは第1引数に固定なので、 内部で部品を生成する時は、このparentが引数の先頭で指定されます。

また、wxPythonのラクスのコンストラクタは、第2引数はidですが、 割り切ってwx.ID_ANYに固定にしてます。

min_w, min_h を指定すると、wxPythonの部品を生成した後、 SetMinSize()メソッドを呼び出して、最小サイズを設定してから、その部品を返します。

bind(o, hdl, evt=None)

new()が返すオブジェクトのメソッドです。

部品にイベントが発生したときに呼び出すハンドラ関数を結びつけます。

wxo.bind( [ btn_ok, btn_cancel ], btn_hdl )
wxo.bind( btn_go, btn_go_hdl )

引数oはボタンなどのwxPythonの部品を指定します。

複数の部品に同じハンドラ関数を設定したい場合、 引数oに部品のリストを指定できます。

引数hdlは指定した部品でイベントが発生したときに呼び出す関数を指定します。

引数hdlの関数は、引数にオブジェクトが1つ与えられて呼び出されます。

与えられるオブジェクトは、次の属性が設定されています。

属性 内容
ev 内部で設定したイベントハンドラに渡されるイベント
o イベントの発生したwxPythonの部品
typeId 発生したイベントのtypeId
evt typeIdに対応するイベント
本bind()メソッドやwxPythonの部品のBind()メソッドの引数で指定する
wx.EVT_BUTTONなどのイベント
wxo new()で返されるオブジェクト
v イベントが発生した部品の値 (oにGetValueメソッドがあれば)
label イベントが発生した部品のラベル (oにGetLabelメソドがあれば)
x イベントが発生したx座標 (evにGetPositionメソッドがあれば)
y イベントが発生したy座標 (evにGetPositionメソッドがあれば)

引数evtは、対象のイベントをwx.EVT_BUTTONなどで指定します。

引数oで指定する部品が次の場合は、 引数evtを省略すると次のデフォルト値として扱います。

部品のクラス デフォルトのイベント
wx.Button wx.EVT_BUTTON
wx.ToggleButton wx.EVT_TOGGLEBUTTON
wx.CheckBox wx.EVT_CHECKBOX
wx.Choice wx.EVT_CHOICE

wrap(win, lsts)

new()が返すオブジェクトのメソッドです。

wxo.wrap( wxo.frame, lsts )

winで指定したフレームやパネルなどのウィンドウに、 lstsで指定した部品を配置します。

lstsは配置したい部品を、wp()メソッドを使って配置情報を指定し、 横方向、縦方向の順にレイアウトしたリストを指定します。

wp = wxo.wp

lsts = [
	wp( [ wp( tc, prop=1,flag=wx.EXPAND ) ], prop=1, flag=wx.EXPAND ),
	wp( [ wp( btn_ok ), wp( btn_cancel ), wp( btn_go, prop=1 ) ], flag=wx.EXPAND )
]

オブジェクトを横方向に配置する順にリストにして、 そのリストを、縦方向に配置する順にさらにリストにします。

レイアウト情報の指定を削除したときの例

lsts = [
	wp( [ wp( tc ) ] ),
	wp( [ wp( btn_ok ), wp( btn_cancel ), wp( btn_go ) ] ),
]

wp(o, **kwds)

new()が返すオブジェクトのメソッドです。

wrap()メソッドで部品を配置するときに、 部品や、部品を横方向に並べた「行」に、 配置の情報を設定します。

wrap()で部品群を配置するときは、 内部でwxPythonのBoxSizerを使用します。

配置情報は、BoxSizerのAdd()メソッドの引数を次の名前で、 配置情報として設定します。

名前 内容 デフォルト値
prop プロポーション 0
flag フラグ 0
border 余白 4
wp = wxo.wp

lsts = [
	wp( [ wp( tc, prop=1,flag=wx.EXPAND ) ], prop=1, flag=wx.EXPAND ),
	wp( [ wp( btn_ok ), wp( btn_cancel ), wp( btn_go, prop=1 ) ], flag=wx.EXPAND )
]

縦に2行のレイアウトです。

1行目はテキストコントロールtcを配置

2行目は、ボタンok, ボタンcancel, ボタンgoを配置

1行目、2行目とも、行にフラグwx.EXPANDを設定しているので、 行は横方向に伸長します。

1行目の行のプロポーションを1に設定してるので、 フレームを縦方向に伸ばしたときは、1行目が伸長します。

テキストコントロールtcのフラグにwx.EXPANDを設定しているので、 tcは縦方向に伸長します。

テキストコントロールtcのプロポーションを1に設定しているので、 tcは横方向に伸長します。

ボタンgoのプロポーションを1に設定しているので、 ボタンは横方向に伸長します。

フラグのwx.EXPAND指定と、プロポーション指定はややこしいですが、 wxPythonのBoxSizerのAdd()メソッドの仕様に従ってます。

init関数の処理結果の保存

init関数の中で部品を生成した後、 必要であれば次のようにlocals()関数で、 ローカル変数や内部関数の情報をまとめて、 引数wxoの属性に記録しておくと便利です。

wxo.L = empty.new( locals() )

new()関数から返されたwxoは、 イベントハンドラの引数infからもinf.wxoで参照できます。

wxo.Lで、init関数中のローカル変数やメソッドを参照できます。 (変数のバインドは変更できませんが...)

def btn_go_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( inf.label )


トグルボタンとメニュー

v2.patch
diff -urN v1/wx_smp2.py v2/wx_smp2.py
--- v1/wx_smp2.py	1970-01-01 09:00:00.000000000 +0900
+++ v2/wx_smp2.py	2020-05-30 01:04:29.000000000 +0900
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+import wx
+
+import empty
+import wx_ut
+import dbg
+
+def tbtn_1_hdl(inf):
+	tc = inf.wxo.L.tc
+	tc.SetValue( 'tbtn_1={}'.format( inf.v ) )
+
+def menu_hdl(inf):
+	tc = inf.wxo.L.tc
+	tc.SetValue( '{} {} {} Hz'.format( inf.menu.i, inf.menu.lbl, inf.menu.v ) )
+
+def init(wxo):
+	tc = wxo.wx_new( wx.TextCtrl, '', min_h=80 )
+
+	tbtn_1 = wxo.toggle_new( 'ON', tbtn_1_hdl )
+
+	tbtn_2 = wxo.toggle_new( [ 'ON', 'OFF' ] )
+
+	def tbtn_2_hdl(inf):
+		tc.SetValue( 'tbtn_2={} lbl={}'.format( inf.v, inf.label ) )
+
+	wxo.toggle_bind( tbtn_2, tbtn_2_hdl )
+
+	lb = wxo.label_new( 'radio' )
+	menu = wxo.menu_new( [ 'MBS', 'ABC', 'OBC' ], menu_hdl, 'ABC', [ 1179, 1008, 1314 ] )
+
+	wp = wxo.wp
+	lsts = [
+		wp( [ wp( tc, prop=1, flag=wx.EXPAND ) ], prop=1, flag=wx.EXPAND ),
+		wp( [ wp( tbtn_1 ), wp( tbtn_2, prop=1 ), wp( lb ), wp( menu ) ], flag=wx.EXPAND ),
+	]
+	wxo.wrap( wxo.frame, lsts )
+	wxo.L = empty.new( locals() )
+
+def run():
+	wxo = wx_ut.new( 'wx sample 2', init )
+	wxo.main_loop()
+
+if __name__ == "__main__":
+	run()
+# EOF
diff -urN v1/wx_ut.py v2/wx_ut.py
--- v1/wx_ut.py	2020-05-25 00:37:33.000000000 +0900
+++ v2/wx_ut.py	2020-05-30 01:03:28.000000000 +0900
@@ -192,44 +192,85 @@
 		return wx_new( wx.StaticText, s, style=wx.ALIGN_CENTER, parent=parent )


-	drags = {}
+	obj_dic = {}
+
+	def bind_obj_dic_hdl(o, hdl):
+		p = obj_dic.get( o )
+		if p:
+			p.hdl = hdl
+
+

 	def drag_hdl(inf):
-		(drag, cb) = drags.get( inf.o )
+		p = obj_dic.get( inf.o )
+		(drag, hdl) = ( p.drag, p.hdl )
 		dr = drag.update( inf )
-		if dr and cb:
-			cb( dr )
+		if dr and hdl:
+			inf.dr = dr
+			hdl( inf )

-	def set_drag_cb(o, cb):
+	def set_drag_hdl(o, hdl):
 		if type( o ) == list:
 			f = lambda o: set_drag_cb( o, cb )
 			return list( map( f, o ) )

-		drags[ o ] = ( drag_new( o ), cb )
+		obj_dic[ o ] = empty.new( drag=draw_new( o ), hdl=hdl )
 		bind( o, drag_hdl, mouse_evts )


-	menus = {}

 	def menu_hdl(inf):
 		menu = inf.o
-		(lbls, cb, vs) = menus.get( menu )
-		if cb:
+		p = obj_dic.get( menu )
+		(lbls, hdl, vs) = ( p.lbls, p.hdl, p.vs )
+		if hdl:
 			i = menu.GetSelection()
 			lbl = lbls[ i ]
 			v = vs[ i ] if vs and i < len( vs ) else None
 			inf.menu = empty.new( i=i, lbl=lbl, v=v )
-			cb( inf )
+			hdl( inf )

-	def menu_new(lbls, cb=None, init_str='', vs=None, parent=None):
+	def menu_new(lbls, hdl=None, init_str='', vs=None, parent=None):
 		menu = wx_new( wx.Choice, choices=lbls, parent=parent )
 		if not init_str:
 			init_str = lbls[ 0 ]
 		menu.SetStringSelection( init_str )
-		menus[ menu ] = ( lbls, cb, vs )
+		obj_dic[ menu ] = empty.new( lbls=lbls, hdl=hdl, vs=vs )
 		bind( menu, menu_hdl )
 		return menu

+	def menu_bind(menu, hdl):
+		bind_obj_dic_hdl( menu, hdl )
+
+
+
+	def toggle_lbl(lbls, v):
+		if type( lbls ) == list:
+			i = 1 if len( lbls ) >= 2 and v else 0
+			return lbls[ i ]
+		return lbls # str
+
+	def toggle_hdl(inf):
+		tbtn = inf.o
+		p = obj_dic.get( tbtn )
+		lbl = toggle_lbl( p.lbls, inf.v )
+		tbtn.SetLabel( lbl )
+		inf.label = lbl
+		if p.hdl:
+			p.hdl( inf )
+
+	def toggle_new(lbls, hdl=None, init_stat=False, parent=None):
+		tbtn = wx_new( wx.ToggleButton, toggle_lbl( lbls, init_stat ) )
+		if init_stat:
+			tbln.SetValue( init_state )
+		obj_dic[ tbtn ] = empty.new( lbls=lbls, hdl=hdl )
+		bind( tbtn, toggle_hdl )
+		return tbtn
+
+	def toggle_bind(tbtn, hdl):
+		bind_obj_dic_hdl( tbtn, hdl )
+
+

 	class MyApp(wx.App):
 		def OnInit(self):

追加してみました。

実は初版から、それらしきコードは入ってましたが、仕上がってない状態でした。

今回で体裁を整えて正式デビューです。

最新版を pythonのユーティリティ・プログラム 2020冬 に追加しておきます。

kon_pageのpythonモジュールのインストール からダウンロード、インストールできます。

toggle_new(lbls, hdl=None, init_stat=False, **kwds)

toggle_new(lbls, hdl=None, init_stat=False, parent=None) v3.patch で修正

トグルボタン部品を生成して返します。

wxo.wx_new( wx.ToggleButton, ... ) よりも少しだけ高機能です。

lblsにはラベル文字列か、リストを指定します。

リストの場合は、OFF時のラベル文字列と、ON時のラベル文字列のリストを指定します。

hdlはイベントが発生したときに呼び出すハンドラ関数を指定します。

この段階では指定せずに、toggle_bind()で指定することも出来ます。

init_statは初期状態をONにしたい場合にTrueを指定します。

toggle_bind(tbtn, hdl)

toggle_new()で生成したトグルボタン部品 tbtn に、 イベントが発生したときに呼び出すハンドラ関数 hdl を結びつけます。

ハンドラ関数の引数は、基本的にwxo.bind()と同じ仕様です。

menu_new(lbls, hdl=None, init_str='', vs=None, **kwds)

menu_new(lbls, hdl=None, init_str='', vs=None, parent=None) v3.patch で修正

メニュー部品を生成して返します。

wxo.wx_new( wx.Choice, ... ) よりも少しだけ高機能です。

lblsにはメニューラベル文字列のリストを指定します。

hdlはイベントが発生したときに呼び出すハンドラ関数を指定します。

この段階では指定せずに、menu_bind()で指定することも出来ます。

init_strは選択状態にするメニューラベル文字列を指定します。

vsはlblsのメニューラベルに対応して保持させたい値がある場合に指定します。

menu_bind(menu, hdl)

menu_new()で生成したメニュー部品 menu に、 イベントが発生したときに呼び出すハンドラ関数 hdl を結びつけます。

ハンドラ関数の引数は、基本的にwxo.bind()と同じ仕様ですが、 メニュー部品の場合は、ハンドラ関数の引数のオブジェクトにmenu属性が追加されます。

menu属性には、次の属性を持つオブジェクがセットされます。

属性 内容
i 選択したメニューのインデックス
lbl 選択したメニューのラベル文字列
v 選択したメニューに対応する値 (menu_newの引数vsで指定していた場合)
指定がなければ、vの値にはNoneがセットされます。

サンプル2

例によってサンプルプログラムを用意してみました。

wx_smp2.py
#!/usr/bin/env python

import wx

import empty
import wx_ut
import dbg

def tbtn_1_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( 'tbtn_1={}'.format( inf.v ) )

def menu_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( '{} {} {} Hz'.format( inf.menu.i, inf.menu.lbl, inf.menu.v ) )

def init(wxo):
	tc = wxo.wx_new( wx.TextCtrl, '', min_h=80 )

	tbtn_1 = wxo.toggle_new( 'ON', tbtn_1_hdl )

	tbtn_2 = wxo.toggle_new( [ 'ON', 'OFF' ] )

	def tbtn_2_hdl(inf):
		tc.SetValue( 'tbtn_2={} lbl={}'.format( inf.v, inf.label ) )

	wxo.toggle_bind( tbtn_2, tbtn_2_hdl )

	lb = wxo.label_new( 'radio' )
	menu = wxo.menu_new( [ 'MBS', 'ABC', 'OBC' ], menu_hdl, 'ABC', [ 1179, 1008, 1314 ] )

	wp = wxo.wp
	lsts = [
		wp( [ wp( tc, prop=1, flag=wx.EXPAND ) ], prop=1, flag=wx.EXPAND ),
		wp( [ wp( tbtn_1 ), wp( tbtn_2, prop=1 ), wp( lb ), wp( menu ) ], flag=wx.EXPAND ),
	]
	wxo.wrap( wxo.frame, lsts )
	wxo.L = empty.new( locals() )

def run():
	wxo = wx_ut.new( 'wx sample 2', init )
	wxo.main_loop()

if __name__ == "__main__":
	run()
# EOF


ボタンとチェックボックスと不具合修正と

v3.patch
diff -urN v2/wx_smp3.py v3/wx_smp3.py
--- v2/wx_smp3.py	1970-01-01 09:00:00.000000000 +0900
+++ v3/wx_smp3.py	2020-05-30 19:58:52.000000000 +0900
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+import wx
+
+import empty
+import wx_ut
+import dbg
+
+def btn_hdl(inf):
+	tc = inf.wxo.L.tc
+	tc.SetValue( inf.label )
+
+def tbtn_1_hdl(inf):
+	tc = inf.wxo.L.tc
+	tc.SetValue( 'tbtn_1={}'.format( inf.v ) )
+
+def menu_hdl(inf):
+	tc = inf.wxo.L.tc
+	tc.SetValue( '{} {} {} Hz'.format( inf.menu.i, inf.menu.lbl, inf.menu.v ) )
+
+def init(wxo):
+	tc = wxo.wx_new( wx.TextCtrl, '', min_h=80 )
+
+	(btn_ok, btn_cancel, btn_go) = wxo.button_new( [ 'OK', 'Cancel', 'Go' ], btn_hdl )
+
+	def ckb_hdl(inf):
+		btn_go.Enable( inf.v )
+
+	ckb = wxo.checkbox_new( 'enable', ckb_hdl, init_stat=True )
+
+	tbtn_1 = wxo.toggle_new( 'ON', tbtn_1_hdl )
+
+	tbtn_2 = wxo.toggle_new( [ 'ON', 'OFF' ] )
+
+	def tbtn_2_hdl(inf):
+		tc.SetValue( 'tbtn_2={} lbl={}'.format( inf.v, inf.label ) )
+
+	wxo.toggle_bind( tbtn_2, tbtn_2_hdl )
+
+	lb = wxo.label_new( 'radio' )
+	menu = wxo.menu_new( [ 'MBS', 'ABC', 'OBC' ], menu_hdl, 'ABC', [ 1179, 1008, 1314 ] )
+
+	wp = wxo.wp
+	lsts = [
+		wp( [ wp( tc, prop=1, flag=wx.EXPAND ) ], prop=1, flag=wx.EXPAND ),
+		wp( [ wp( tbtn_1 ), wp( tbtn_2, prop=1 ), wp( lb ), wp( menu ) ], flag=wx.EXPAND ),
+		wp( [ wp( btn_ok ), wp( btn_cancel ), wp( ckb ), wp( btn_go, prop=1 ) ], flag=wx.EXPAND ),
+	]
+	wxo.wrap_frame( lsts )
+	wxo.L = empty.new( locals() )
+
+def run():
+	wxo = wx_ut.new( 'wx sample 3', init )
+	wxo.main_loop()
+
+if __name__ == "__main__":
+	run()
+# EOF
diff -urN v2/wx_ut.py v3/wx_ut.py
--- v2/wx_ut.py	2020-05-30 01:03:28.000000000 +0900
+++ v3/wx_ut.py	2020-05-30 19:58:22.000000000 +0900
@@ -133,6 +133,9 @@

 		win.SetSizer( vsz )

+	def wrap_frame(lsts):
+		wrap( e.frame, lsts )
+

 	obj_cls_dic = {}

@@ -184,13 +187,6 @@
 		return o.Bind( evt, bind_hdl )


-	def label_new(s, parent=None):
-		if type( s ) == list:
-			f = lambda s: label_new( s, parent )
-			return list( map( f, s ) )
-
-		return wx_new( wx.StaticText, s, style=wx.ALIGN_CENTER, parent=parent )
-

 	obj_dic = {}

@@ -219,19 +215,59 @@



+	def label_new(s, **kwds):
+		if type( s ) == list:
+			f = lambda s: label_new( s, **kwds )
+			return list( map( f, s ) )
+
+		return wx_new( wx.StaticText, s, style=wx.ALIGN_CENTER, **kwds )
+
+
+
+	def button_new(lbl, hdl=None, **kwds):
+		if type( lbl ) == list:
+			f = lambda lbl: button_new( lbl, hdl, **kwds )
+			return list( map( f, lbl ) )
+
+		btn = wx_new( wx.Button, lbl, **kwds )
+		if hdl:
+			bind( btn, hdl )
+		return btn
+
+
+
+	def checkbox_new(lbl, hdl=None, init_stat=False, **kwds):
+		if type( lbl ) == list:
+			f = lambda lbl: checkbox_new( lbl, hdl, init_stat, **kwds )
+			return list( map( f, lbl ) )
+
+		ckb = wx_new( wx.CheckBox, lbl, **kwds )
+		if init_stat:
+			ckb.SetValue( init_stat )
+		if hdl:
+			bind( ckb, hdl )
+		return ckb
+
+
+
+	def menu_get(menu):
+		p = obj_dic.get( menu )
+		(lbls, hdl, vs) = ( p.lbls, p.hdl, p.vs )
+
+		i = menu.GetSelection()
+		lbl = p.lbls[ i ]
+		v = vs[ i ] if vs and i < len( vs ) else None
+		return empty.new( i=i, lbl=lbl, v=v )
+
 	def menu_hdl(inf):
 		menu = inf.o
 		p = obj_dic.get( menu )
-		(lbls, hdl, vs) = ( p.lbls, p.hdl, p.vs )
-		if hdl:
-			i = menu.GetSelection()
-			lbl = lbls[ i ]
-			v = vs[ i ] if vs and i < len( vs ) else None
-			inf.menu = empty.new( i=i, lbl=lbl, v=v )
-			hdl( inf )
+		if p.hdl:
+			inf.menu = menu_get( menu )
+			p.hdl( inf )

-	def menu_new(lbls, hdl=None, init_str='', vs=None, parent=None):
-		menu = wx_new( wx.Choice, choices=lbls, parent=parent )
+	def menu_new(lbls, hdl=None, init_str='', vs=None, **kwds):
+		menu = wx_new( wx.Choice, choices=lbls, **kwds )
 		if not init_str:
 			init_str = lbls[ 0 ]
 		menu.SetStringSelection( init_str )
@@ -259,8 +295,8 @@
 		if p.hdl:
 			p.hdl( inf )

-	def toggle_new(lbls, hdl=None, init_stat=False, parent=None):
-		tbtn = wx_new( wx.ToggleButton, toggle_lbl( lbls, init_stat ) )
+	def toggle_new(lbls, hdl=None, init_stat=False, **kwds):
+		tbtn = wx_new( wx.ToggleButton, toggle_lbl( lbls, init_stat ), **kwds )
 		if init_stat:
 			tbln.SetValue( init_state )
 		obj_dic[ tbtn ] = empty.new( lbls=lbls, hdl=hdl )

色々と不具合があり修正しました。

wrap_frame(lsts)

1つめ、2つめのサンプルを見直すと、

wxo.wrap( wxo.frame, lsts )

の箇所が、かなり違和感があったので追加してみました。

wxo.wrap_frame( lsts )

に置き換え可能です。

label_new(s, **kwds)

以前から既に入っていましたが、ラベル部品を生成して返します。

sにラベル文字列を指定します。

sに文字列のリストを指定すると、文字列ごとにラベル部品を生成して、 部品のリストを返します。

button_new(lbl, hdl=None, **kwds)

ボタン部品を生成して返します。

lblにラベル文字列を指定します。

lblに文字列のリストを指定すると、文字列ごとにボタン部品を生成して、 部品のリストを返します。

hdlはイベントが発生したときに呼び出すハンドラ関数を指定します。

checkbox_new(lbl, hdl=None, init_stat=False, **kwds)

チェックボックス部品を生成して返します。

lblにラベル文字列を指定します。

lblに文字列のリストを指定すると、文字列ごとにチェックボックス部品を生成して、 部品のリストを返します。

hdlはイベントが発生したときに呼び出すハンドラ関数を指定します。

init_statは初期状態をONにしたい場合にTrueを指定します。

menu_get(menu)

menu_new()で生成したメニュー部品の状態を返します。

menuにはmenu_new()で生成したメニュー部品を指定します。

次の属性を持つオブジェクトを返します。

属性 内容
i 選択されてるメニューのインデックス
lbl 選択されてるメニューのラベル文字列
v 選択されたメニューに対応する値 (menu_newの引数vsで指定していた場合)
指定がなければ、vの値にはNoneがセットされます。

サンプル3

既出の2つのサンプルを合わせたような感じにした上に、 チェックボックスを追加してみました。

wx_smp3.py
#!/usr/bin/env python

import wx

import empty
import wx_ut
import dbg

def btn_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( inf.label )

def tbtn_1_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( 'tbtn_1={}'.format( inf.v ) )

def menu_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( '{} {} {} Hz'.format( inf.menu.i, inf.menu.lbl, inf.menu.v ) )

def init(wxo):
	tc = wxo.wx_new( wx.TextCtrl, '', min_h=80 )

	(btn_ok, btn_cancel, btn_go) = wxo.button_new( [ 'OK', 'Cancel', 'Go' ], btn_hdl )

	def ckb_hdl(inf):
		btn_go.Enable( inf.v )

	ckb = wxo.checkbox_new( 'enable', ckb_hdl, init_stat=True )

	tbtn_1 = wxo.toggle_new( 'ON', tbtn_1_hdl )

	tbtn_2 = wxo.toggle_new( [ 'ON', 'OFF' ] )

	def tbtn_2_hdl(inf):
		tc.SetValue( 'tbtn_2={} lbl={}'.format( inf.v, inf.label ) )

	wxo.toggle_bind( tbtn_2, tbtn_2_hdl )

	lb = wxo.label_new( 'radio' )
	menu = wxo.menu_new( [ 'MBS', 'ABC', 'OBC' ], menu_hdl, 'ABC', [ 1179, 1008, 1314 ] )

	wp = wxo.wp
	lsts = [
		wp( [ wp( tc, prop=1, flag=wx.EXPAND ) ], prop=1, flag=wx.EXPAND ),
		wp( [ wp( tbtn_1 ), wp( tbtn_2, prop=1 ), wp( lb ), wp( menu ) ], flag=wx.EXPAND ),
		wp( [ wp( btn_ok ), wp( btn_cancel ), wp( ckb ), wp( btn_go, prop=1 ) ], flag=wx.EXPAND ),
	]
	wxo.wrap_frame( lsts )
	wxo.L = empty.new( locals() )

def run():
	wxo = wx_ut.new( 'wx sample 3', init )
	wxo.main_loop()

if __name__ == "__main__":
	run()
# EOF


wrap()の更新と関連の補助的な関数の追加

v4.patch
diff -urN v3/wx_smp4.py v4/wx_smp4.py
--- v3/wx_smp4.py	1970-01-01 09:00:00.000000000 +0900
+++ v4/wx_smp4.py	2020-05-31 20:52:44.000000000 +0900
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+import wx
+
+import empty
+import wx_ut
+import base
+import dbg
+
+def get_name(inf):
+	return inf.wxo.o_names.get( inf.o, '?' )
+
+def btn_hdl(inf):
+	tc = inf.wxo.L.tc
+	tc.SetValue( inf.label )
+
+def tbtn_hdl(inf):
+	tc = inf.wxo.L.tc
+	tc.SetValue( '{}={} label={}'.format( get_name( inf ), inf.v, inf.label ) )
+
+def menu_hdl(inf):
+	tc = inf.wxo.L.tc
+	tc.SetValue( '{} {} {} Hz'.format( inf.menu.i, inf.menu.lbl, inf.menu.v ) )
+
+def init(wxo):
+	tc = wxo.wx_new( wx.TextCtrl, '', min_h=80 )
+
+	(btn_ok, btn_cancel, btn_go) = wxo.button_new( [ 'OK', 'Cancel', 'Go' ], btn_hdl )
+
+	def ckb_hdl(inf):
+		btn_go.Enable( inf.v )
+
+	ckb = wxo.checkbox_new( 'enable', ckb_hdl, init_stat=True )
+
+	tbtn_1 = wxo.toggle_new( 'ON', tbtn_hdl )
+
+	tbtn_2 = wxo.toggle_new( [ 'ON', 'OFF' ], tbtn_hdl )
+
+	menu = wxo.menu_new( [ 'MBS', 'ABC', 'OBC' ], menu_hdl, 'ABC', [ 1179, 1008, 1314 ] )
+
+	wp = wxo.wp
+	wp_exp = wxo.wp_exp
+	lsts = [
+		wp_exp( [ wp_exp( tc, prop=1 ) ], prop=1 ),
+		wp_exp( [ tbtn_1, wp( tbtn_2, prop=1 ), 'radio', menu ] ),
+		wp_exp( [ btn_ok, btn_cancel, ckb, wp( btn_go, prop=1 ) ] ),
+	]
+	wxo.wrap_frame( lsts )
+	wxo.L = empty.new( locals() )
+	wxo.o_names = base.rev_dic( locals() )
+
+def run():
+	wxo = wx_ut.new( 'wx sample 4', init )
+	wxo.main_loop()
+
+if __name__ == "__main__":
+	run()
+# EOF
diff -urN v3/wx_ut.py v4/wx_ut.py
--- v3/wx_ut.py	2020-05-30 19:58:22.000000000 +0900
+++ v4/wx_ut.py	2020-05-31 20:04:25.000000000 +0900
@@ -107,6 +107,13 @@
 def new(title, init, fini=None):
 	e = empty.new( app=None, frame=None )

+	newed_objs = {}
+
+	def newed_obj_set(k, o):
+		newed_objs[ k ] = o
+		return o
+
+
 	def wp(o, **kwds):
 		dv = empty.new( border=4, prop=0, flag=0 )
 		for (k, v) in vars( dv ).items():
@@ -115,19 +122,32 @@
 		kwds[ 'o' ] = o
 		return empty.new( kwds )

+	def wp_exp(o, **kwds):
+		kwds[ 'flag' ] = kwds.get( 'flag', 0 ) | wx.EXPAND
+		return wp( o, **kwds )
+
+	def to_wp(o):
+		if empty.is_empty( o ):
+			return o
+		if type( o ) == str:
+			o = newed_obj_set( o, label_new( o ) )
+		return wp( o )
+
 	def wrap(win, lsts):
 		vsz = wx.BoxSizer( wx.VERTICAL )
-		for wp_v in lsts:
+		for o_v in lsts:
 			hsz = wx.BoxSizer( wx.HORIZONTAL )
+			wp_v = to_wp( o_v )
 			lst = wp_v.o
-			for wp_h in lst:
+			for o_h in lst:
+				wp_h = to_wp( o_h )
 				wp_h.flag |= wx.LEFT | wx.ALIGN_CENTER_VERTICAL
-				if wp_h == lst[ -1 ]:
+				if o_h == lst[ -1 ]:
 					wp_h.flag |= wx.RIGHT
 				hsz.Add( wp_h.o, wp_h.prop, wp_h.flag, wp_h.border )

 			wp_v.flag |= wx.TOP
-			if wp_v == lsts[ -1 ]:
+			if o_v == lsts[ -1 ]:
 				wp_v.flag |= wx.BOTTOM
 			vsz.Add( hsz, wp_v.prop, wp_v.flag, wp_v.border )

wp_exp(o, **kwds)

wx_smp3.py を見直してみると、wp( ..., flag=wx.EXPAND ) 指定が多く、 それでごちゃごちゃ感が増してるようでした。

flagのデフォルトをwx.EXPANDにした版として、wp_exp() を追加してみました。

wrap(win, lsts) および wrap_frame(lsts) 更新

wrap(win, lsts)を更新しました。

wrap_frame()は内部的にwrap()を呼び出しているので、 wrap_frame()も更新となります。

lstsで指定する要素は、wp()やwp_exp()で返された値を前提としていますが、 次の場合も対応しました。

文字列の場合

内部でlabel_new()を呼び出し、ラベル部品を生成して使用します。

ラベル部品を引数として内部でwp()を呼び出して変換されます。

生成したラベル部品は、wxo.newed_objs という辞書に、指定文字列をキーとして登録されます。

文字列以外で、wp()やwp_exp()が返した値以外の場合

内部でwp()を呼び出して変換されます。

サンプル4

サンプル3と同機能ですが、ソースが少しすっきりしました。

wx_smp4.py
#!/usr/bin/env python

import wx

import empty
import wx_ut
import base
import dbg

def get_name(inf):
	return inf.wxo.o_names.get( inf.o, '?' )

def btn_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( inf.label )

def tbtn_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( '{}={} label={}'.format( get_name( inf ), inf.v, inf.label ) )

def menu_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( '{} {} {} Hz'.format( inf.menu.i, inf.menu.lbl, inf.menu.v ) )

def init(wxo):
	tc = wxo.wx_new( wx.TextCtrl, '', min_h=80 )

	(btn_ok, btn_cancel, btn_go) = wxo.button_new( [ 'OK', 'Cancel', 'Go' ], btn_hdl )

	def ckb_hdl(inf):
		btn_go.Enable( inf.v )

	ckb = wxo.checkbox_new( 'enable', ckb_hdl, init_stat=True )

	tbtn_1 = wxo.toggle_new( 'ON', tbtn_hdl )

	tbtn_2 = wxo.toggle_new( [ 'ON', 'OFF' ], tbtn_hdl )

	menu = wxo.menu_new( [ 'MBS', 'ABC', 'OBC' ], menu_hdl, 'ABC', [ 1179, 1008, 1314 ] )

	wp = wxo.wp
	wp_exp = wxo.wp_exp
	lsts = [
		wp_exp( [ wp_exp( tc, prop=1 ) ], prop=1 ),
		wp_exp( [ tbtn_1, wp( tbtn_2, prop=1 ), 'radio', menu ] ),
		wp_exp( [ btn_ok, btn_cancel, ckb, wp( btn_go, prop=1 ) ] ),
	]
	wxo.wrap_frame( lsts )
	wxo.L = empty.new( locals() )
	wxo.o_names = base.rev_dic( locals() )

def run():
	wxo = wx_ut.new( 'wx sample 4', init )
	wxo.main_loop()

if __name__ == "__main__":
	run()
# EOF


未公開部分バグ修正

v5.patch
diff -ur v4/wx_ut.py v5/wx_ut.py
--- v4/wx_ut.py	2020-05-31 20:04:25.000000000 +0900
+++ v5/wx_ut.py	2020-06-02 21:48:26.000000000 +0900
@@ -227,10 +227,10 @@

 	def set_drag_hdl(o, hdl):
 		if type( o ) == list:
-			f = lambda o: set_drag_cb( o, cb )
+			f = lambda o: set_drag_hdl( o, hdl )
 			return list( map( f, o ) )

-		obj_dic[ o ] = empty.new( drag=draw_new( o ), hdl=hdl )
+		obj_dic[ o ] = empty.new( drag=drag_new( o ), hdl=hdl )
 		bind( o, drag_hdl, mouse_evts )

まだ紹介してない部分で、不具合が見つかりましたので、修正を入れておきます。


引数args追加

ROSがらみのプログラムのGUIで、wx_ut.pyを少し試してみたのですが、、、

最初のwx_ut.new()呼び出しから、init()へ何も情報が渡せない仕様が、なかなか辛いなと。

渡せるようにnew()の引数に追加しておきます。

v6.patch
diff -urN v5/wx_smp5.py v6/wx_smp5.py
--- v5/wx_smp5.py	1970-01-01 09:00:00.000000000 +0900
+++ v6/wx_smp5.py	2020-07-18 23:48:51.000000000 +0900
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+import wx
+
+import empty
+import wx_ut
+import base
+import dbg
+
+def get_name(inf):
+	return inf.wxo.o_names.get( inf.o, '?' )
+
+def btn_hdl(inf):
+	tc = inf.wxo.L.tc
+	tc.SetValue( inf.label )
+
+def tbtn_hdl(inf):
+	tc = inf.wxo.L.tc
+	tc.SetValue( '{}={} label={}'.format( get_name( inf ), inf.v, inf.label ) )
+
+def menu_hdl(inf):
+	tc = inf.wxo.L.tc
+	tc.SetValue( '{} {} {} kHz'.format( inf.menu.i, inf.menu.lbl, inf.menu.v ) )
+
+def init(wxo):
+	tc = wxo.wx_new( wx.TextCtrl, '', min_h=80 )
+
+	(btn_ok, btn_cancel, btn_go) = wxo.button_new( [ 'OK', 'Cancel', 'Go' ], btn_hdl )
+
+	def ckb_hdl(inf):
+		btn_go.Enable( inf.v )
+
+	ckb = wxo.checkbox_new( 'enable', ckb_hdl, init_stat=True )
+
+	tbtn_1 = wxo.toggle_new( 'ON', tbtn_hdl )
+
+	tbtn_2 = wxo.toggle_new( [ 'ON', 'OFF' ], tbtn_hdl )
+
+	(lbls, vals) = zip( *wxo.args.kHz.items() )
+	menu = wxo.menu_new( lbls, menu_hdl, lbls[ 1 ], vals )
+
+	wp = wxo.wp
+	wp_exp = wxo.wp_exp
+	lsts = [
+		wp_exp( [ wp_exp( tc, prop=1 ) ], prop=1 ),
+		wp_exp( [ tbtn_1, wp( tbtn_2, prop=1 ), 'radio', menu ] ),
+		wp_exp( [ btn_ok, btn_cancel, ckb, wp( btn_go, prop=1 ) ] ),
+	]
+	wxo.wrap_frame( lsts )
+	wxo.L = empty.new( locals() )
+	wxo.o_names = base.rev_dic( locals() )
+
+def run():
+	args = empty.new( kHz={ 'MBS': 1179, 'ABC': 1008, 'OBC': 1314 } )
+	wxo = wx_ut.new( 'wx sample 4', init, args=args )
+	wxo.main_loop()
+
+if __name__ == "__main__":
+	run()
+# EOF
diff -urN v5/wx_ut.py v6/wx_ut.py
--- v5/wx_ut.py	2020-06-02 21:48:26.000000000 +0900
+++ v6/wx_ut.py	2020-07-18 23:26:30.000000000 +0900
@@ -104,8 +104,8 @@
 	wx.PostEvent( obj, ev )


-def new(title, init, fini=None):
-	e = empty.new( app=None, frame=None )
+def new(title, init, fini=None, args=None):
+	e = empty.new( app=None, frame=None, args=args )

 	newed_objs = {}

サンプル5

サンプル4 wx_smp4.py を無理やり引数argsを使うように変更してみました。(^^;)

メニューのラベルと値の情報をnew()の引数に渡して、init()で使ってみました。

ついでに、永らく気になって直しそびれていた Hz を kHz に修正 ;-p)

wx_smp5.py
#!/usr/bin/env python

import wx

import empty
import wx_ut
import base
import dbg

def get_name(inf):
	return inf.wxo.o_names.get( inf.o, '?' )

def btn_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( inf.label )

def tbtn_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( '{}={} label={}'.format( get_name( inf ), inf.v, inf.label ) )

def menu_hdl(inf):
	tc = inf.wxo.L.tc
	tc.SetValue( '{} {} {} kHz'.format( inf.menu.i, inf.menu.lbl, inf.menu.v ) )

def init(wxo):
	tc = wxo.wx_new( wx.TextCtrl, '', min_h=80 )

	(btn_ok, btn_cancel, btn_go) = wxo.button_new( [ 'OK', 'Cancel', 'Go' ], btn_hdl )

	def ckb_hdl(inf):
		btn_go.Enable( inf.v )

	ckb = wxo.checkbox_new( 'enable', ckb_hdl, init_stat=True )

	tbtn_1 = wxo.toggle_new( 'ON', tbtn_hdl )

	tbtn_2 = wxo.toggle_new( [ 'ON', 'OFF' ], tbtn_hdl )

	(lbls, vals) = zip( *wxo.args.kHz.items() )
	menu = wxo.menu_new( lbls, menu_hdl, lbls[ 1 ], vals )

	wp = wxo.wp
	wp_exp = wxo.wp_exp
	lsts = [
		wp_exp( [ wp_exp( tc, prop=1 ) ], prop=1 ),
		wp_exp( [ tbtn_1, wp( tbtn_2, prop=1 ), 'radio', menu ] ),
		wp_exp( [ btn_ok, btn_cancel, ckb, wp( btn_go, prop=1 ) ] ),
	]
	wxo.wrap_frame( lsts )
	wxo.L = empty.new( locals() )
	wxo.o_names = base.rev_dic( locals() )

def run():
	args = empty.new( kHz={ 'MBS': 1179, 'ABC': 1008, 'OBC': 1314 } )
	wxo = wx_ut.new( 'wx sample 4', init, args=args )
	wxo.main_loop()

if __name__ == "__main__":
	run()
# EOF


引数on_init追加

frameを非表示のままで起動したい事があり、少し変更しました。

new()に引数on_initを追加し、関数を指定できるようにしました。

on_initに関数指定されているときは、 AppのOnInit()でframeをShow()してる箇所を、 on_init関数の呼び出しに置き換えるようにしました。

on_init関数は、引数としてnew()の返すオブジェクトが渡ります。

wx_ut.new( ... , on_init=lambda wxo: None )

などと、何も処理しない関数を渡すと、 結果OnInit()でframe.Show()が無くなるだけとなり、 frameが非表示のままになります。

v7.patch
diff -ur v6/wx_ut.py v7/wx_ut.py
--- v6/wx_ut.py	2020-07-18 23:26:30.000000000 +0900
+++ v7/wx_ut.py	2020-10-01 20:03:50.000000000 +0900
@@ -104,8 +104,8 @@
 	wx.PostEvent( obj, ev )


-def new(title, init, fini=None, args=None):
-	e = empty.new( app=None, frame=None, args=args )
+def new(title, init, fini=None, args=None, on_init=None):
+	e = empty.new( app=None, frame=None, args=args, on_init=on_init )

 	newed_objs = {}

@@ -339,7 +339,11 @@
 			e.frame.Layout()
 			e.frame.Fit()
 			e.app.SetTopWindow( e.frame )
-			e.frame.Show()
+
+			if e.on_init:
+				e.on_init( e )
+			else:
+				e.frame.Show()
 			return 1

 	def poll_start(poll_func, sec, hz, gc):


フレームの大きさの調整など

音楽ファイルの分割 2020冬

で、試してみて、Ubuntu 18.04 でフレームのサイズが しっくりこなくなっていたので修正しました。

v8.patch
diff -ur v7/wx_ut.py v8/wx_ut.py
--- v7/wx_ut.py	Thu Oct  1 20:03:50 2020
+++ v8/wx_ut.py	Mon May  2 19:11:52 2022
@@ -1,5 +1,6 @@
 #!/usr/bin/env python

+import os
 import wx

 import empty
@@ -333,18 +334,35 @@
 			e.app = self
 			e.frame = wx_new( wx.Frame, title, parent=None )
 			bind( e.frame, quit_hdl, wx.EVT_CLOSE )
+			set_icon()

 			init( e )

 			e.frame.Layout()
 			e.frame.Fit()
+			e.frame.FitInside()
 			e.app.SetTopWindow( e.frame )

+			( w, h ) = e.frame.GetSize()
+			e.frame.SetMinSize( ( w, h + 10 ) )  # title bar height ?
+
 			if e.on_init:
 				e.on_init( e )
 			else:
 				e.frame.Show()
 			return 1
+
+	def set_icon( path='hoge.png' ):
+		if not e.frame:
+			return
+
+		if not os.path.exists( path ):
+			return
+
+		bm = wx.Bitmap( path )
+		icon = wx.EmptyIcon()
+		icon.CopyFromBitmap( bm )
+		e.frame.SetIcon( icon )

 	def poll_start(poll_func, sec, hz, gc):
 		th = None


細かな色々な修正

お仕事とで使ってるうちに色々と修正が溜まっていたので反映します。

v9.patch
--- v8/wx_ut.py	2022-05-02 19:11:52.000000000 +0900
+++ v9/wx_ut.py	2024-03-20 14:00:00.717505639 +0900
@@ -2,6 +2,7 @@

 import os
 import wx
+import math

 import empty
 import thr
@@ -55,6 +56,8 @@
 	wx.ToggleButton: wx.EVT_TOGGLEBUTTON,
 	wx.CheckBox: wx.EVT_CHECKBOX,
 	wx.Choice: wx.EVT_CHOICE,
+	wx.TextCtrl: [ wx.EVT_TEXT_ENTER, wx.EVT_SET_FOCUS ],
+	wx.Slider: wx.EVT_SLIDER,
 }


@@ -105,6 +108,15 @@
 	wx.PostEvent( obj, ev )


+def exp_path( path='', DIR='${IINO_DIR}' ):
+	if '$' in path:
+		path = os.path.expandvars( path )
+	if path[ 0 ] != '/' and not os.path.exists( path ):
+		path = os.path.join( DIR, path )
+		path = os.path.expandvars( path )
+	return path
+
+
 def new(title, init, fini=None, args=None, on_init=None):
 	e = empty.new( app=None, frame=None, args=args, on_init=on_init )

@@ -142,7 +154,8 @@
 			lst = wp_v.o
 			for o_h in lst:
 				wp_h = to_wp( o_h )
-				wp_h.flag |= wx.LEFT | wx.ALIGN_CENTER_VERTICAL
+				#wp_h.flag |= wx.LEFT | wx.ALIGN_CENTER_VERTICAL
+				wp_h.flag |= wx.LEFT
 				if o_h == lst[ -1 ]:
 					wp_h.flag |= wx.RIGHT
 				hsz.Add( wp_h.o, wp_h.prop, wp_h.flag, wp_h.border )
@@ -188,9 +201,14 @@
 		if k in bind_dic:
 			(evt, hdl) = bind_dic.get( k )
 			inf = ev_inf_new( ev, o, typeId, evt, e ) # e as wxo
-			hdl( inf )
+			if hdl:
+				hdl( inf )

 	def bind(o, hdl, evt=None):
+		if evt == None:
+			cls = obj_cls_dic.get( o )
+			evt = cls_evt_dic.get( cls )
+
 		if type( o ) == list:
 			f = lambda o: bind( o, hdl, evt )
 			return list( map( f, o ) )
@@ -198,10 +216,6 @@
 			f = lambda evt: bind( o, hdl, evt )
 			return list( map( f, evt ) )

-		if evt == None:
-			cls = obj_cls_dic.get( o )
-			evt = cls_evt_dic.get( cls )
-
 		k = ( o, evt.typeId )
 		bind_dic[ k ] = ( evt, hdl )

@@ -280,6 +294,15 @@
 		v = vs[ i ] if vs and i < len( vs ) else None
 		return empty.new( i=i, lbl=lbl, v=v )

+	def menu_set( menu, lbls, init_str='', vs=None ):
+		menu.SetItems( lbls )
+		if not init_str:
+			init_str = lbls[ 0 ]
+		menu.SetStringSelection( init_str )
+		p = obj_dic.get( menu )
+		p.lbls = lbls
+		p.vs = vs
+
 	def menu_hdl(inf):
 		menu = inf.o
 		p = obj_dic.get( menu )
@@ -299,7 +322,14 @@
 	def menu_bind(menu, hdl):
 		bind_obj_dic_hdl( menu, hdl )

+	def menu_int_new( v_min, v_max, hdl=None, v_init=None, **kwds ):
+		vs = range( v_min, v_max + 1 )
+		lbls = list( map( str, vs ) )
+		init_str = ''
+		if v_init is not None:
+			init_str = str( v_init )

+		return menu_new( lbls, hdl, init_str, vs, **kwds )

 	def toggle_lbl(lbls, v):
 		if type( lbls ) == list:
@@ -327,6 +357,116 @@
 	def toggle_bind(tbtn, hdl):
 		bind_obj_dic_hdl( tbtn, hdl )

+	def tc_new( s, hdl=None ):
+		tc = wx_new( wx.TextCtrl, s, style=wx.TE_PROCESS_ENTER )
+		bind( tc, hdl )
+		return tc
+
+	def tc_num_new( v, qtz_exp='0.01', hdl=None ):
+		qtz = lambda v: dbg.quantize( v, qtz_exp )
+
+		def hdl_tc( inf ):
+			tc = inf.o
+			try:
+				inf.v = str( qtz( inf.v ) )
+				tc.SetValue( inf.v )
+			except:
+				return
+			if hdl:
+				hdl( inf )
+
+		return tc_new( str( qtz( v ) ), hdl_tc )
+
+	def tc_file_sel( init_path='', btn_lbl='sel' ):
+		tc = tc_new( init_path )
+
+		def hdl( inf ):
+			dlg = wx.FileDialog( e.frame )
+			if dlg.ShowModal() == wx.ID_OK:
+				tc.SetValue( dlg.GetPath() )
+
+		btn = button_new( btn_lbl, hdl )
+
+		def GetValue():
+			return tc.GetValue()
+
+		return empty.new( locals() )
+
+	def sld_new( v, v_min, v_max, hdl=None ):
+		sld = wx_new( wx.Slider, v, v_min, v_max )
+		bind( sld, hdl )
+		return sld
+
+	def sld_float_new( v, v_min, v_max, v_step=None, hdl=None ):
+		if v_step is None:
+			v_step = ( v_max - v_min ) / 100
+
+		n_max = math.ceil( ( v_max - v ) / v_step )
+		v_max = v + v_step * n_max
+
+		n_min = math.ceil( ( v - v_min ) / v_step )
+		v_min = v - v_step * n_min
+
+		n = n_min + n_max
+
+		def cnv( v_i ):
+			return v_min + ( v_max - v_min ) * v_i / n
+
+		def rcnv( v ):
+			return int( n * ( v - v_min ) / ( v_max - v_min ) )
+
+		def hdl_( inf ):
+			if hdl:
+				inf.v = cnv( inf.v )
+				hdl( inf )
+
+		sld = sld_new( n_min, 0, n, hdl_ )
+
+		def get():
+			return cnv( sld.GetValue() )
+
+		def set( v ):
+			sld.SetValue( rcnv( v ) )
+
+		obj_dic[ sld ] = empty.new( locals() ) # !
+
+		return sld
+
+	def sld_float_get( sld ):
+		return obj_dic.get( sld ).get()
+
+	def sld_float_set( sld, v ):
+		obj_dic.get( sld ).set( v )
+
+	def sld_float_tc_new( v, v_min, v_max, v_step=None, hdl=None ):
+
+		def hdl_sld( inf ):
+			tc.SetValue( str( inf.v ) )
+			if hdl:
+				hdl( inf )
+
+		sld = sld_float_new( v, v_min, v_max, v_step, hdl_sld )
+
+		def hdl_tc( inf ):
+			if inf.typeId == wx.EVT_TEXT_ENTER.typeId:
+				s = tc.GetValue()
+				try:
+				        v = float( s )
+				except:
+					return
+				bak = v
+				v = max( v, v_min )
+				v = min( v, v_max )
+				sld_float_set( sld, v )
+
+				if v != bak:
+					tc.SetValue( str( v ) )
+				if hdl:
+					hdl( inf )
+
+		tc = tc_new( str( v ), hdl_tc )
+
+		return ( sld, tc )  # !


 	class MyApp(wx.App):
@@ -352,10 +492,11 @@
 				e.frame.Show()
 			return 1

-	def set_icon( path='hoge.png' ):
+	def set_icon( path='logo_white_square_w1000.png' ):
 		if not e.frame:
 			return

+		path = exp_path( path, DIR='${IINO_DIR}/imgs' )
 		if not os.path.exists( path ):
 			return