Как бы вы создали очень "Pythonic" интерфейс пользовательского интерфейса?

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

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 text
  end
end

Это заставило меня подумать - как я мог бы создать аналогичную красивую графическую среду GUI в Python? Тот, у которого нет обычных привязок в основном к обертке в библиотеке C * (в случае GTK, Tk, wx, QT и т.д.)

Обувь берет вещи из веб-дизайна (например, #f0c2f0 стиль цвета, методы компоновки 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 в приведенном выше примере) может быть довольно сложным, но это кажется выполнимым довольно аккуратным способом.

Ответ 1

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

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

Ответ 2

## 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. Который я также имею. Получает идеи?

Ответ 3

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

Ответ 4

Это крайне надуманное, а не питоническое вообще, но здесь моя попытка полусловного перевода с использованием нового оператора "с".

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

Ответ 5

С некоторыми метаклассами, чтобы сохранить порядок, у меня есть следующая работа. Я не уверен, как это 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()

Ответ 6

Единственная попытка сделать это, о которой я знаю, - Hans Nowak Wax (который, к сожалению, мертв).

Ответ 8

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

В принципе, вы создаете макет графического интерфейса в 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 в качестве обратного вызова с щелчком. Обертка для полянов требует много усилий из сопоставления событий.

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

Ответ 9

Может быть, не такой гладкий, как версия 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 или наоборот, поскольку вы получаете содержимое определения класса как питон dict.

Однако в Python 3.0 метаклассы меняются несколькими способами. Одним из них является метод __prepare__, который позволяет вам использовать собственный пользовательский объект, похожий на словарь, который будет использоваться вместо этого - это означает, что вы сможете отслеживать порядок, в котором определяются элементы, и соответственно размещать их в окна.

Ответ 10

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

Ответ 11

У меня такая же проблема. Я хотел создать обертку вокруг любого инструментария GUI для Python, который прост в использовании и вдохновлен Обувьми, но должен быть подход ООП (против блоков ruby).

Дополнительная информация в: http://wiki.alcidesfonseca.com/blog/python-universal-gui-revisited

Приглашаем всех присоединиться к проекту.

Ответ 12

Если вы действительно хотите кодировать интерфейс, вы можете попытаться получить что-то похожее на ORM django; sth, чтобы получить простой браузер помощи:

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

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

Идея заключалась бы в том, чтобы интерпретировать некоторые контейнеры (например, окна) как простые классы, некоторые контейнеры (например, таблицы, v/hboxes), распознаваемые именами объектов, и простые виджеты как объекты.

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

О порядке элементов: в MyWindow выше вам не нужно отслеживать это (окно представляет собой контейнер с одним слотом). В других контейнерах вы можете попытаться отслеживать заказ, предполагая, что каждый конструктор виджета имеет доступ к некоторому глобальному списку виджетов. Вот как это делается в джанго (AFAIK).

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

Однако я очень доволен PyGTK + Glade. Пользовательский интерфейс - это всего лишь данные для меня, и его следует рассматривать как данные. Там слишком много параметров для настройки (например, расстояние в разных местах), и лучше управлять этим с помощью инструмента GUI. Поэтому я создаю свой интерфейс в поляне, сохраняю как xml и анализирую с помощью gtk.glade.XML().

Ответ 13

Декларативный не обязательно больше (или меньше) pythonic, чем функциональный IMHO. Я думаю, что многоуровневый подход будет лучшим (от buttom up):

  • Нативный слой, который принимает и возвращает типы данных python.
  • Функциональный динамический слой.
  • Один или несколько декларативных/объектно-ориентированных слоев.

Подобно Elixir + SQLAlchemy.

Ответ 14

Лично я попытался бы реализовать JQuery, как 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'

Ответ 15

Здесь подход, который описывает определения графического интерфейса несколько иначе, используя мета-программирование на основе классов, а не наследование.

Это большой 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 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)

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

Я не совсем уверен, если это супер отличный подход, но я определенно думаю, что это на правильном пути. У меня нет времени, чтобы исследовать эту идею больше, но если бы кто-то воспринял это как проект, я бы их полюбил.