非常に「Python っぽい」UI フレームワークを設計するにはどうすればよいでしょうか?
-
09-06-2019 - |
質問
Rubyのライブラリ「靴」をいじってみました。基本的に、GUI アプリケーションは次の方法で作成できます。
Shoes.app do
t = para "Not clicked!"
button "The Label" do
alert "You clicked the button!" # when clicked, make an alert
t.replace "Clicked!" # ..and replace the label's text
end
end
これをきっかけに、同様に使いやすい GUI フレームワークを Python でどのように設計できるだろうかと考えました。基本的に C* ライブラリのラッパーであるという通常の結びつきを持たないもの (GTK、Tk、wx、QT などの場合)
シューズはウェブ開発からのものを取り入れています (例: #f0c2f0
スタイルカラー表記、CSSレイアウトテクニックなど :margin => 10
)、および Ruby から (賢明な方法でブロックを広範囲に使用)
Python には「rubyish ブロック」がないため、(比喩的に) 直接移植は不可能です。
def Shoeless(Shoes.app):
self.t = para("Not clicked!")
def on_click_func(self):
alert("You clicked the button!")
self.t.replace("clicked!")
b = button("The label", click=self.on_click_func)
これほどきれいなところはありませんし、そうでもないでしょう ほぼ 非常に柔軟ですが、それが実装可能かどうかさえわかりません。
デコレータの使用は、コードのブロックを特定のアクションにマップする興味深い方法のように思えます。
class BaseControl:
def __init__(self):
self.func = None
def clicked(self, func):
self.func = func
def __call__(self):
if self.func is not None:
self.func()
class Button(BaseControl):
pass
class Label(BaseControl):
pass
# The actual applications code (that the end-user would write)
class MyApp:
ok = Button()
la = Label()
@ok.clicked
def clickeryHappened():
print "OK Clicked!"
if __name__ == '__main__':
a = MyApp()
a.ok() # trigger the clicked action
基本的に、デコレーター関数は関数を保存し、アクション (クリックなど) が発生すると、適切な関数が実行されます。
さまざまなものの範囲 (たとえば、 la
上記の例の label) はかなり複雑になる可能性がありますが、かなりきちんとした方法で実行できるようです。
解決
実際にこれを実行することもできますが、メタクラスを使用する必要があります。 深い 魔法(ドラゴンがいます)。メタクラスの概要を知りたい場合は、次のシリーズがあります。 IBMの記事 あなたの脳を溶かすことなくアイデアを導入することができます。
SQLObject などの ORM のソース コードも、これと同じ種類の宣言構文を使用しているため、役立つ場合があります。
他のヒント
## All you need is this class:
class MainWindow(Window):
my_button = Button('Click Me')
my_paragraph = Text('This is the text you wish to place')
my_alert = AlertBox('What what what!!!')
@my_button.clicked
def my_button_clicked(self, button, event):
self.my_paragraph.text.append('And now you clicked on it, the button that is.')
@my_paragraph.text.changed
def my_paragraph_text_changed(self, text, event):
self.button.text = 'No more clicks!'
@my_button.text.changed
def my_button_text_changed(self, text, event):
self.my_alert.show()
## The Style class is automatically gnerated by the framework
## but you can override it by defining it in the class:
##
## class MainWindow(Window):
## class Style:
## my_blah = {'style-info': 'value'}
##
## or like you see below:
class Style:
my_button = {
'background-color': '#ccc',
'font-size': '14px'}
my_paragraph = {
'background-color': '#fff',
'color': '#000',
'font-size': '14px',
'border': '1px solid black',
'border-radius': '3px'}
MainWindow.Style = Style
## The layout class is automatically generated
## by the framework but you can override it by defining it
## in the class, same as the Style class above, or by
## defining it like this:
class MainLayout(Layout):
def __init__(self, style):
# It takes the custom or automatically generated style class upon instantiation
style.window.pack(HBox().pack(style.my_paragraph, style.my_button))
MainWindow.Layout = MainLayout
if __name__ == '__main__':
run(App(main=MainWindow))
メタクラス Python の魔法のノウハウを少し使えば、Python で行うのは比較的簡単です。私が持っているもの。そして PyGTK の知識。私も持っています。アイデアはありますか?
IBM でのメタクラスに関する David Mertz の記事には決して満足できなかったので、最近自分で記事を書きました。 メタクラス記事. 。楽しむ。
これは非常に不自然で、Python 的ではありませんが、新しい "with" ステートメントを使用して、半直訳を試みてみました。
with Shoes():
t = Para("Not clicked!")
with Button("The Label"):
Alert("You clicked the button!")
t.replace("Clicked!")
最も難しい部分は、Python では複数のステートメントを含む匿名関数が提供されないという事実に対処することです。これを回避するには、コマンドのリストを作成し、それらを実行することができます...
とにかく、これを実行したバックエンドコードは次のとおりです。
context = None
class Nestable(object):
def __init__(self,caption=None):
self.caption = caption
self.things = []
global context
if context:
context.add(self)
def __enter__(self):
global context
self.parent = context
context = self
def __exit__(self, type, value, traceback):
global context
context = self.parent
def add(self,thing):
self.things.append(thing)
print "Adding a %s to %s" % (thing,self)
def __str__(self):
return "%s(%s)" % (self.__class__.__name__, self.caption)
class Shoes(Nestable):
pass
class Button(Nestable):
pass
class Alert(Nestable):
pass
class Para(Nestable):
def replace(self,caption):
Command(self,"replace",caption)
class Command(Nestable):
def __init__(self, target, command, caption):
self.command = command
self.target = target
Nestable.__init__(self,caption)
def __str__(self):
return "Command(%s text of %s with \"%s\")" % (self.command, self.target, self.caption)
def execute(self):
self.target.caption = self.caption
順序を維持するためのメタクラスの魔法を使用すると、次のことが機能します。どの程度 Python っぽいのかはわかりませんが、単純なものを作成するのにはとても楽しいです。
class w(Wndw):
title='Hello World'
class txt(Txt): # either a new class
text='Insert name here'
lbl=Lbl(text='Hello') # or an instance
class greet(Bbt):
text='Greet'
def click(self): #on_click method
self.frame.lbl.text='Hello %s.'%self.frame.txt.text
app=w()
私が知っているこれを行う唯一の試みは ハンス・ノワクのワックス (残念ながら亡くなってしまいました)。
Ruby っぽいブロックに最も近いのは、pep343 の with ステートメントです。
使用する場合 PyGTK と 空き地 そして この空き地ラッパー, その場合、PyGTK は実際にはいくらか Python っぽくなります。少なくとも少しは。
基本的に、GUI レイアウトは Glade で作成します。Glade ではイベント コールバックも指定します。次に、次のようにウィンドウのクラスを作成します。
class MyWindow(GladeWrapper):
GladeWrapper.__init__(self, "my_glade_file.xml", "mainWindow")
self.GtkWindow.show()
def button_click_event (self, *args):
self.button1.set_label("CLICKED")
ここでは、GTK ボタンがどこかにあると仮定します。 ボタン1 そして私が指定したこと ボタンクリックイベント として クリックした 折り返し電話。Glade ラッパーはイベント マッピングに多くの労力を費やします。
私が Pythonic GUI ライブラリを設計するとしたら、迅速な開発を支援するために、同様のものをサポートするでしょう。唯一の違いは、ウィジェットにもより Python 的なインターフェイスが備わっていることを確認することです。現在の PyGTK クラスは、bar(foo, ...) の代わりに foo.bar(...) を使用することを除けば、私にとっては非常に C に見えますが、どう違うのかは正確にはわかりません。おそらく、コード内でウィジェットとイベントを指定し、イテレータ (意味のある場合、たとえばウィジェット リストなど) を介してデータにアクセスできるようにする、Django モデル スタイルの宣言的手段を許可します。ただし、私はそれについて実際には考えていません。
Ruby バージョンほど滑らかではないかもしれませんが、次のようなものはどうでしょうか。
from Boots import App, Para, Button, alert
def Shoeless(App):
t = Para(text = 'Not Clicked')
b = Button(label = 'The label')
def on_b_clicked(self):
alert('You clicked the button!')
self.t.text = 'Clicked!'
ジャスティンが言ったように, 、これを実装するには、クラスでカスタム メタクラスを使用する必要があります。 App
, 、および多数のプロパティ Para
そして Button
. 。これは実際にはそれほど難しいことではありません。
次に遭遇する問題は次のとおりです。どうやって追跡していますか 注文 クラス定義に現れるものですか?Python 2.x では、 t
上にあるはずです b
またはその逆、クラス定義の内容を Python として受け取るため dict
.
ただし、Python 3.0 では メタクラスが変更されています いくつかの(マイナーな)方法で。そのうちの 1 つは、 __prepare__
このメソッドを使用すると、代わりに使用する独自のカスタム辞書のようなオブジェクトを提供できます。これは、項目が定義されている順序を追跡し、それに応じてウィンドウ内に配置できることを意味します。
これは単純化しすぎている可能性がありますが、この方法で汎用 UI ライブラリを作成しようとするのは得策ではないと思います。一方、このアプローチ (メタクラスとそのフレンド) を使用すると、既存の ui ライブラリやアプリケーションによってはユーザー インターフェイスの特定のクラスの定義を簡素化し、実際に大幅な時間とコード行を節約できます。
私もこれと同じ問題を抱えています。私は、使いやすく、Shoes からインスピレーションを得た、Python 用の GUI ツールキットのラッパーを作成したいと考えていますが、(Ruby ブロックに対して) OOP アプローチである必要があります。
詳細については、次をご覧ください。 http://wiki.alcidesfonseca.com/blog/python-universal-gui-revisited
どなたでもプロジェクトに参加していただけます。
本当に UI をコーディングしたい場合は、django の ORM に似たものを入手してみるとよいでしょう。単純なヘルプ ブラウザを取得するには、次のようにします。
class MyWindow(Window):
class VBox:
entry = Entry()
bigtext = TextView()
def on_entry_accepted(text):
bigtext.value = eval(text).__doc__
このアイデアは、一部のコンテナー (ウィンドウなど) を単純なクラスとして解釈し、一部のコンテナー (テーブル、v/hbox など) をオブジェクト名によって認識し、単純なウィジェットをオブジェクトとして解釈することです。
ウィンドウ内のすべてのコンテナに名前を付ける必要はないと思うので、いくつかのショートカット (名前によってウィジェットとして認識される古いスタイルのクラスなど) が望ましいでしょう。
要素の順序について:上記の MyWindow では、これを追跡する必要はありません (ウィンドウは概念的には 1 スロットのコンテナです)。他のコンテナでは、各ウィジェット コンストラクターが何らかのグローバル ウィジェット リストにアクセスできると仮定して、順序を追跡することができます。これは、ジャンゴでそれが行われる方法です(私の知る限り)。
ハックはほとんどなく、微調整もほとんどありません...まだ考えるべきことは少ないですが、可能性はあると信じています...複雑な UI を構築しない限り、使用可能です。
しかし、私は PyGTK+Glade にかなり満足しています。私にとって UI は単なるデータであり、データとして扱う必要があります。調整するにはパラメータが多すぎるため (さまざまな場所の間隔など)、GUI ツールを使用して管理する方が良いでしょう。したがって、私は Glade で UI を構築し、xml として保存し、gtk.glade.XML() を使用して解析します。
個人的には実装してみようと思います JQuery GUI フレームワークの API のようなものです。
class MyWindow(Window):
contents = (
para('Hello World!'),
button('Click Me', id='ok'),
para('Epilog'),
)
def __init__(self):
self['#ok'].click(self.message)
self['para'].hover(self.blend_in, self.blend_out)
def message(self):
print 'You clicked!'
def blend_in(self, object):
object.background = '#333333'
def blend_out(self, object):
object.background = 'WindowBackground'
ここでは、継承ではなくクラスベースのメタプログラミングを使用して、GUI 定義を少し異なる方法で行うアプローチを示します。
これは、メタプログラミングに大きく基づいており、GUI コードを「コード コード」から分離しているという点で、主に Django/SQLAlchemy に影響を受けています。また、Java のようにレイアウト マネージャーを多用するべきだとも思います。コードを削除するときにピクセルの位置合わせを常に微調整する人はいないからです。また、CSS のようなプロパティがあれば素晴らしいと思います。
これはブレインストーミングによる大まかな例です。上部にラベルの付いた列、次にテキスト ボックス、そして下部をクリックしてメッセージを表示するボタンが表示されます。
from happygui.controls import * MAIN_WINDOW = Window(width="500px", height="350px", my_layout=ColumnLayout(padding="10px", my_label=Label(text="What's your name kiddo?", bold=True, align="center"), my_edit=EditBox(placeholder=""), my_btn=Button(text="CLICK ME!", on_click=Handler('module.file.btn_clicked')), ), ) MAIN_WINDOW.show() def btn_clicked(sender): # could easily be in a handlers.py file name = MAIN_WINDOW.my_layout.my_edit.text # same thing: name = sender.parent.my_edit.text # best practice, immune to structure change: MAIN_WINDOW.find('my_edit').text MessageBox("Your name is '%s'" % ()).show(modal=True)
注目すべき素晴らしい点の 1 つは、次のようにして my_edit の入力を参照できることです。 MAIN_WINDOW.my_layout.my_edit.text
. 。ウィンドウの宣言では、関数 kwargs でコントロールに任意の名前を付けられることが重要だと思います。
以下は、絶対位置のみを使用する同じアプリです (派手なレイアウト マネージャーを使用していないため、コントロールは別の場所に表示されます)。
from happygui.controls import * MAIN_WINDOW = Window(width="500px", height="350px", my_label=Label(text="What's your name kiddo?", bold=True, align="center", x="10px", y="10px", width="300px", height="100px"), my_edit=EditBox(placeholder="", x="10px", y="110px", width="300px", height="100px"), my_btn=Button(text="CLICK ME!", on_click=Handler('module.file.btn_clicked'), x="10px", y="210px", width="300px", height="100px"), ) MAIN_WINDOW.show() def btn_clicked(sender): # could easily be in a handlers.py file name = MAIN_WINDOW.my_edit.text # same thing: name = sender.parent.my_edit.text # best practice, immune to structure change: MAIN_WINDOW.find('my_edit').text MessageBox("Your name is '%s'" % ()).show(modal=True)
これが非常に素晴らしいアプローチであるかどうかはまだ完全にはわかりませんが、間違いなく正しい道を進んでいると思います。このアイデアを詳しく検討する時間はありませんが、もし誰かがこれをプロジェクトとして取り上げてくれたら、ぜひ喜んでもらいたいと思います。