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

StackOverflow https://stackoverflow.com/questions/58711

Вопрос

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

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 и т. д. и т. д.)

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

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

Это было полезно?

Решение

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

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

Другие советы

## 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.Который у меня тоже есть.Появляется идеи?

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

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

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

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

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()

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

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

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

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

По сути, вы создаете макет графического интерфейса в Glade.Вы также указываете обратные вызовы событий в 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 под названием кнопка1 и это я указал button_click_event как щелкнул перезвонить.Обертка Glade требует больших усилий при отображении событий.

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

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

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

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

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

Присоединиться к проекту может любой желающий.

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

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

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

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

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

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

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

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

Декларативный не обязательно более (или менее) питонический, чем функциональный, ИМХО.Я думаю, что многоуровневый подход был бы лучшим (снизу вверх):

  1. Собственный уровень, который принимает и возвращает типы данных Python.
  2. Функциональный динамический слой.
  3. Один или несколько декларативных/объектно-ориентированных слоев.

Похожий на Эликсир + SQLАлхимия.

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

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

Это во многом вдохновлено 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'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)

Стоит отметить одну интересную вещь: вы можете ссылаться на входные данные 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'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)

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top