我们将如何设计一个非常" Pythonic"的UI框架?
我一直在使用Ruby库" shoes"。基本上,我们可以通过以下方式编写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
这使我想到如何在Python中设计一个类似的易于使用的GUI框架?基本上没有像C *库那样包装的常规绑定(对于GTK,Tk,wx,QT等)
鞋子从Web开发(例如#f0c2f0样式颜色符号,CSS布局技术,例如:margin => 10
)和红宝石(从以明智的方式广泛使用块)中获取灵感
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"标签)可能相当复杂,但似乎可以用一种相当简洁的方式来实现。
解决方案
回答
我们实际上可以做到这一点,但是这将需要使用元类,它们是深奥的魔术(有龙)。如果我们想向元类做一个介绍,那么IBM会提供一系列文章,这些文章设法介绍这些想法而不会引起注意。
像SQLObject这样的ORM的源代码也可能会有所帮助,因为它使用了相同的声明性语法。
回答
也许不如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!'
就像Justin所说的那样,要实现此目的,我们需要在类App上使用自定义元类,并在Para和Button上使用一堆属性。这实际上并不难。
接下来遇到的问题是:如何跟踪事物在类定义中出现的顺序?在Python 2.x中,由于我们将类定义的内容作为python dict接收,因此无法知道t是否应该在b之上或者相反。
但是,在Python 3.0中,元类以两种(次要)方式进行了更改。其中一种方法是__prepare__方法,它允许我们提供自己的自定义字典式对象来代替它-这意味着我们将能够跟踪项目定义的顺序,并将它们相应地放置在窗户。
回答
这可能是一个过分的简化,我认为尝试以这种方式创建通用ui库并不是一个好主意。另一方面,我们可以使用这种方法(元类和朋友)简化对现有ui库的某些类的用户界面的定义,并取决于实际可以节省大量时间和代码行的应用程序(取决于应用程序)。
回答
通过使用一些Metaclass魔术来保持顺序,我可以进行以下工作。我不确定它是pythonic的什么,但是创建简单的东西很有趣。
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()
回答
我有着同样的问题。我希望围绕适用于Python的任何GUI工具包创建一个包装器,该包装器易于使用,并且受Shoes的启发,但需要采用OOP方法(针对ruby块)。
有关更多信息,请访问:http://wiki.alcidesfonseca.com/blog/python-universal-gui-revisited
欢迎任何人加入该项目。
回答
我知道的唯一尝试这样做的是汉斯·诺瓦克(Hans Nowak)的蜡(不幸地死了)。
回答
最接近红宝石块的是pep343的with语句:
http://www.python.org/dev/peps/pep-0343/
回答
我从不满意David Mertz在IBM关于metaclsses的文章,所以我最近写了我自己的metaclass文章。享受。
回答
如果我们真的想对UI进行编码,则可以尝试获取类似于django的ORM的代码;像这样获得一个简单的帮助浏览器:
class MyWindow(Window): class VBox: entry = Entry() bigtext = TextView() def on_entry_accepted(text): bigtext.value = eval(text).__doc__
想法是将一些容器(如Windows)解释为简单类,将某些容器(如表,v / hboxes)通过对象名称识别,并将简单的小部件解释为对象。
我认为不必在窗口内命名所有容器,因此希望使用一些快捷方式(例如将旧类通过名称识别为小部件)。
关于元素的顺序:在上面的MyWindow中,我们不必跟踪它(从概念上讲,窗口是一个一槽容器)。在其他容器中,我们可以尝试跟踪顺序,假设每个小部件构造函数都可以访问某些全局小部件列表。这就是在django(AFAIK)中完成的方式。
这里很少有骇客,那里很少有调整...仍然没有什么值得思考的,但是我相信这是可能的...并且可用,只要我们不构建复杂的UI。
但是我对PyGTK + Glade感到非常满意。 UI对我来说只是一种数据,应该将其视为数据。有太多需要调整的参数(例如不同位置的间距),最好使用GUI工具进行管理。因此,我在林间空地中构建UI,另存为xml并使用gtk.glade.XML()进行解析。
回答
声明式不一定比功能恕我直言更多(或者更少)的Python风格。我认为分层的方法是最好的(从下到上):
- 接受并返回python数据类型的本机层。
- 功能动态层。
- 一个或者多个声明性/面向对象层。
类似于Elixir + SQLAlchemy。
回答
我个人将尝试在GUI框架中实现类似于API的JQuery。
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'
回答
这是非常人为设计的,根本不是pythonic,但这是我尝试使用新的" 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
回答
这是一种使用基于类的元编程而不是继承来进行GUI定义的方法。
这是largley Django / SQLAlchemy的启发,它很大程度上基于元编程,并将GUI代码与"代码"分开。我还认为它应该像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)
要注意的一件很酷的事情是,我们可以通过说" MAIN_WINDOW.my_layout.my_edit.text"来引用my_edit的输入。在窗口的声明中,我认为能够在函数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)
我还不确定这是否是一个很好的方法,但是我绝对认为这是正确的方法。我没有时间去探索这个想法,但是如果有人将其作为一个项目,我会喜欢的。
回答
## 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。我也有。有想法吗?
回答
如果我们将PyGTK与林间空地和此林间空包装一起使用,则PyGTK实际上会变得有些蟒蛇。至少一点。
基本上,我们可以在Glade中创建GUI布局。我们还可以在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")
在这里,我假设我在某个地方有一个名为button1的GTK Button,并指定了button_click_event作为单击的回调。沼气包装程序需要花费大量的精力来进行事件映射。
如果我要设计一个Pythonic GUI库,我将支持类似的东西,以帮助快速开发。唯一的区别是,我将确保小部件也具有更多的pythonic接口。当前的PyGTK类对我来说似乎非常C,只是我使用foo.bar(...)而不是bar(foo,...),尽管我不确定我到底会做些什么。也许允许使用Django模型样式的声明性方法,在代码中指定小部件和事件,并允许我们通过迭代器(有意义的地方,例如小部件列表)访问数据,尽管我并未真正考虑过。