我一直在使用Ruby库" shoes"。基本上,您可以通过以下方式编写GUI应用程序:
1 2 3 4 5 6 7
| 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 *库那样package的常规绑定(对于GTK,Tk,wx,QT等)
鞋子从Web开发(如#f0c2f0样式颜色符号,CSS布局技术(如:margin => 10))和ruby(从以明智的方式广泛使用块)中获取收益。
Python缺少" ruby??ish块"使得不可能(隐喻地)直接使用端口:
1 2 3 4 5 6 7 8
| 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) |
没有什么地方比它干净,也没有那么灵活,我什至不确定它是否可以实现。
使用装饰器似乎是一种将代码块映射到特定动作的有趣方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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会提供一系列文章,这些文章设法介绍这些想法而又不费吹灰之力。
来自ORM的源代码(例如SQLObject)也可能会有所帮助,因为它使用了相同的声明性语法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| ## 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。我也有。有想法吗?
这是非常人为设计的,根本不是pythonic,但这是我尝试使用新的" with \\"语句进行半文字翻译的原因。
1 2 3 4 5
| with Shoes():
t = Para("Not clicked!")
with Button("The Label"):
Alert("You clicked the button!")
t.replace("Clicked!") |
最难的部分是处理以下事实:python不会给我们匿名函数包含多个语句。为了解决这个问题,我们可以创建命令列表并通过这些命令运行...
无论如何,这是我使用以下代码运行的后端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| 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 |
我从未对IBM的David Mertz关于metaclsses的文章感到满意,所以最近我写了自己的metaclass文章。享受。
如果将PyGTK与林间空地和此林间外package一起使用,则PyGTK实际上会变得有些Python。至少一点。
基本上,您可以在Glade中创建GUI布局。您还可以在Glade中指定事件回调。然后,为窗口编写一个类,如下所示:
1 2 3 4 5 6
| 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指定为单击的回调。沼气package程序需要花费大量精力来进行事件映射。
如果我要设计Pythonic GUI库,我会支持类似的东西,以帮助快速开发。唯一的区别是,我将确保小部件也具有更多的pythonic接口。当前的PyGTK类对我来说似乎是C语言,只是我使用foo.bar(...)而不是bar(foo,...),尽管我不确定我到底会做些什么。也许允许使用Django模型样式的声明性方法,在代码中指定小部件和事件,并允许您通过迭代器访问数据(在这种情况下有意义,例如,小部件列表),尽管我还没有真正考虑过。
最接近ruby块的是pep343中的with语句:
http://www.python.org/dev/peps/pep-0343/
我知道的唯一尝试这样做的是汉斯·诺瓦克(Hans Nowak)的蜡(不幸地死了)。
使用一些Metaclass魔术来保持顺序,我可以进行以下工作。我不确定它是pythonic的什么,但是创建简单的东西很有趣。
1 2 3 4 5 6 7 8 9 10 11
| 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() |
这可能是一个过分的简化,我认为尝试以这种方式创建通用ui库不是一个好主意。另一方面,您可以使用这种方法(元类和朋友)简化对现有ui库的某些类的用户界面的定义,并取决于实际可以节省大量时间和代码行的应用程序(具体取决于应用程序)。 >
也许不像Ruby版本那么漂亮,但是这样的事情呢:
1 2 3 4 5 6 7 8 9
| 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中,由于您以python dict的形式接收到类定义的内容,因此无法知道t应该在b之上还是相反。
但是,在Python 3.0中,元类以两种(次要)方式进行了更改。其中之一是__prepare__方法,该方法允许您提供自己的自定义字典式对象来代替使用-这意味着您将能够跟踪项定义的顺序,并相应地定位它们在窗口中。
这是一种使用基于类的元编程而不是继承来处理GUI定义的方法。
这是largley Django / SQLAlchemy的启发,它很大程度上基于元编程,并将GUI代码与"代码"分开。我还认为它应该像Java一样大量使用布局管理器,因为当您删除代码时,没有人愿意不断调整像素对齐方式。我也认为,如果我们可以拥有类似CSS的属性,那就太酷了。
这是一个集思广益的粗糙示例,该示例将在顶部显示一个带有标签的列,然后是一个文本框,然后在底部单击一个按钮以显示一条消息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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中随意命名控件很重要。
这里是仅使用绝对定位的同一应用程序(控件将出现在不同的位置,因为我们没有使用精美的布局管理器):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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) |
我尚不确定这是否是一个很棒的方法,但我绝对认为这是正确的方法。我没有时间进一步探索这个想法,但是如果有人将其作为一个项目,我会喜欢他们的。
我个人将尝试在GUI框架中实现类似API的JQuery。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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' |
如果您真的想对UI进行编码,则可以尝试获取类似于django的ORM的代码;像这样获得一个简单的帮助浏览器:
1 2 3 4 5 6 7
| 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的任何GUI工具包创建一个package器,该package器易于使用,并受Shoes的启发,但需要是一种面向对象的方法(针对ruby块)。
更多信息,请访问:http://wiki.alcidesfonseca.com/blog/python-universal-gui-revisited
欢迎任何人加入该项目。
声明性不一定比功能恕我直言更多(或更少)的Python语言。我认为分层方法是最好的(从下到上):
接受并返回python数据类型的本机层。
功能动态层。
一个或多个声明性/面向对象层。
类似于Elixir SQLAlchemy。