你会如何设计一个非常“Pythonic”的UI框架?

时间:2008-09-12 11:18:04

标签: python user-interface frameworks

我一直在玩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

这让我想到 - 我如何在Python中设计一个同样易于使用的GUI框架?一个没有通常的基本上是C *库包装的东西(在GTK,Tk,wx,QT等的情况下)

鞋子从web开发中获取内容(如#f0c2f0样式颜色表示法,CSS布局技术,如:margin => 10),以及ruby(以明智的方式广泛使用块)

Python缺乏“rubyish blocks”使得(隐喻)直接端口变得不可能:

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标签)可能相当复杂,但似乎可以以相当简洁的方式进行。

15 个答案:

答案 0 :(得分:7)

你实际上可以关闭它,但它需要使用元类,它们是魔法(有龙)。如果你想要一个介绍元类,有一系列articles from IBM能够在不融化你的大脑的情况下引入这些想法。

来自ORM的源代码(如SQLObject)也可能有所帮助,因为它使用了同样的声明性语法。

答案 1 :(得分:5)

## 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的知识。我也有。获取想法?

答案 2 :(得分:4)

我从未对David Mertz在IBM关于元数据的文章感到满意,所以我最近编写了自己的metaclass article。享受。

答案 3 :(得分:4)

这是非常人为的,而不是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

答案 4 :(得分:3)

有了一些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()

答案 5 :(得分:3)

我所知道的唯一尝试是Hans Nowak's Wax(不幸的是死了)。

答案 6 :(得分:3)

最接近红宝石块的是来自pep343的声明:

http://www.python.org/dev/peps/pep-0343/

答案 7 :(得分:3)

如果您将PyGTKgladethis glade wrapper一起使用,那么PyGTK实际上会变得有点pythonic。至少一点点。

基本上,您在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_click_event 作为点击的回调。林地包装从事件映射中花费了很多精力。

如果我要设计一个Pythonic GUI库,我会支持类似的东西,以帮助快速开发。唯一的区别是我会确保小部件也有更多的pythonic界面。当前的PyGTK类对我来说似乎很C,除了我使用foo.bar(...)而不是bar(foo,...),虽然我不确定我的确会做些什么。可能允许Django模型样式声明方法在代码中指定小部件和事件,并允许您通过迭代器访问数据(它可能有意义,例如小部件列表),尽管我还没有真正考虑过它。

答案 8 :(得分:2)

也许不像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!'

Like Justin said,要实现此功能,您需要在类App上使用自定义元类,并在ParaButton上使用一组属性。这实际上不会太难。

您接下来遇到的问题是:如何跟踪事物在类定义中出现的顺序?在Python 2.x中,无法知道t是否应该高于b或者相反,因为您将类定义的内容作为python dict接收。

然而,在Python 3.0 metaclasses are being changed中有几种(次要的)方式。其中之一是__prepare__方法,它允许您提供自己的自定义字典对象 - 这意味着您将能够跟踪项目的定义顺序,并定位它们因此在窗口中。

答案 9 :(得分:2)

这可能过于简单了,我认为尝试以这种方式创建通用ui库并不是一个好主意。另一方面,您可以使用此方法(元类和朋友)来简化现有ui库的某些类用户界面的定义,并根据实际上可以为您节省大量时间和代码行的应用程序。

答案 10 :(得分:1)

我有同样的问题。我想围绕任何易于使用的GUI工具包创建一个包装器,并且受到Shoes的启发,但需要采用OOP方法(针对ruby块)。

更多信息请参阅:http://wiki.alcidesfonseca.com/blog/python-universal-gui-revisited

欢迎任何人加入该项目。

答案 11 :(得分:1)

如果你真的想要编写UI代码,你可以尝试获得与django的ORM类似的东西;像这样得到一个简单的帮助浏览器:

class MyWindow(Window):
    class VBox:
        entry = Entry()
        bigtext = TextView()

        def on_entry_accepted(text):
            bigtext.value = eval(text).__doc__

这个想法是将一些容器(如windows)解释为简单的类,一些容器(如表,v / hbox)被对象名识别,简单的小部件作为对象。

我不认为人们必须在窗口内命名所有容器,因此需要一些快捷方式(比如旧式类被名称识别为小部件)。

关于元素的顺序:在上面的MyWindow中,您不必跟踪它(窗口在概念上是一个单槽容器)。在其他容器中,您可以尝试跟踪订单,假设每个窗口小部件构造函数都可以访问某些全局窗口小部件列表。这是在django(AFAIK)中完成的。

这里几乎没有什么黑客攻击,但很少有人调整......仍有一些事情需要考虑,但我相信只要你不构建复杂的用户界面就可以......并且可用。

然而,我对PyGTK + Glade非常满意。 UI对我来说只是一种数据,它应该被视为数据。调整的参数太多(比如不同位置的间距),最好使用GUI工具进行管理。因此,我在glade中构建我的UI,保存为xml并使用gtk.glade.XML()进行解析。

答案 12 :(得分:1)

声明不一定比函数恕我直言更多(或更少)pythonic。我认为分层方法是最好的(来自buttom):

  1. 接受并返回python数据类型的本机层。
  2. 功能性动态图层。
  3. 一个或多个声明/面向对象的层。
  4. Elixir + SQLAlchemy类似。

答案 13 :(得分:1)

就个人而言,我会尝试在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'

答案 14 :(得分:1)

这是一种使用基于类的元编程而不是继承来略微改变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)

我不完全确定这是否是一个非常好的方法,但我绝对认为它是在正确的道路上。我没有时间更多地探索这个想法,但如果有人把它作为一个项目,我会很喜欢它们。