Как бы вы разработали очень "Pythonic" UI Framework? - PullRequest
12 голосов
/ 12 сентября 2008

Я играл с рубиновой библиотекой "обувь". По сути, вы можете написать приложение с графическим интерфейсом следующим образом:

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? Тот, у которого нет обычной связи с тем, что он в основном является обёрткой для библиотеки C * (в случае GTK, Tk, wx, QT и т. Д.)

Обувь берет вещи из веб-разработки (например, цветовая нотация в стиле * 1006, методы CSS-разметки, например :margin => 10) и из рубина (интенсивное использование блоков разумным образом)

Отсутствие в Python «рубиновых блоков» делает (метафорически) прямой порт невозможным:

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 ]

7 голосов
/ 12 сентября 2008

Вы могли бы на самом деле справиться с этим, но это потребовало бы использования метаклассов, которые являются deep magic (там есть драконы). Если вы хотите познакомиться с метаклассами, есть серия статей от IBM , в которых можно воплотить идеи, не растопив мозги.

Исходный код из ORM, такого как SQLObject, также может помочь, поскольку он использует тот же тип декларативного синтаксиса.

5 голосов
/ 03 декабря 2008
## 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. Который у меня тоже есть. Получает идеи?

4 голосов
/ 03 декабря 2008

Это чрезвычайно надумано и совсем не пифонично, но вот моя попытка полу-буквального перевода с использованием нового выражения «с».

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 голосов
/ 02 декабря 2008

Меня никогда не устраивали статьи Дэвида Мерца в IBM о metaclsses, поэтому я недавно написал собственную статью metaclass . Наслаждайтесь.

3 голосов
/ 03 декабря 2008

Если вы используете PyGTK с поляна и эту оболочку поляны , то PyGTK на самом деле становится несколько pythonic. По крайней мере, немного.

По сути, вы создаете макет 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")

Здесь я предполагаю, что у меня есть кнопка GTK, называемая button1 , и что я указал button_click_event в качестве clicked callback. Обертка поляны отнимает много сил при составлении карт событий.

Если бы я спроектировал библиотеку Pythonic GUI, я бы поддержал нечто подобное, чтобы помочь быстрой разработке. Разница лишь в том, что я бы позаботился о том, чтобы у виджетов был более питонический интерфейс. Текущие классы PyGTK кажутся мне очень Си, за исключением того, что я использую foo.bar (...) вместо bar (foo, ...), хотя я не совсем уверен, что я буду делать по-другому. Вероятно, допускается использование декларативных средств в стиле моделей Django для определения виджетов и событий в коде и предоставления вам доступа к данным через итераторы (где это имеет смысл, например, списки виджетов), хотя я на самом деле не думал об этом.

3 голосов
/ 02 декабря 2008

Ближайшие к рубиновым блокам вы можете получить оператор with из pep343:

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

3 голосов
/ 02 декабря 2008

Единственная попытка сделать это, о которой я знаю, это Воск Ганса Новака (который, к сожалению, мертв).

3 голосов
/ 15 сентября 2008

С некоторой магией метаклассов, чтобы сохранить порядок, у меня работает следующее. Я не уверен, насколько она питонна, но это весело для создания простых вещей.

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()
2 голосов
/ 13 сентября 2008

Это может быть упрощением, я не думаю, что было бы хорошей идеей попытаться создать универсальную библиотеку пользовательского интерфейса таким образом. С другой стороны, вы можете использовать этот подход (метаклассы и друзья), чтобы упростить определение определенных классов пользовательских интерфейсов для существующей библиотеки пользовательского интерфейса и в зависимости от приложения, которое может реально сэкономить вам значительное количество времени и строк кода.

2 голосов
/ 12 сентября 2008

Может быть, не так гладко, как версия 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 метаклассы изменяются несколькими (второстепенными) способами. Одним из них является метод __prepare__, который позволяет вам предоставлять собственный настраиваемый словарь-объект, который будет использоваться вместо этого - это означает, что вы сможете отслеживать порядок, в котором определяются элементы, и располагать их соответственно окно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...