Я играл с «ботинками» библиотеки 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 и т. д.)
Shoes берет вещи из веб-разработки (например, цветовую нотацию в стиле #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, тоже может помочь, поскольку он использует такой же декларативный синтаксис.
Может быть, не так гладко, как версия 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__, который позволяет вам предоставить свой собственный объект, подобный словарю, который будет использоваться вместо него - это означает, что вы сможете отслеживать порядок, в котором определены элементы, и соответствующим образом размещать их в окне. .
На самом деле есть уловка, позволяющая отслеживать порядок атрибутов. Посмотрите на поля ORM Django (code.djangoproject.com/browser/django/trunk/django/db/model s /…).
Не знаю - кроме привязки там волшебной кнопки, он очень похож на Tkinter. Я бы просто дождался, пока PyTTK будет интегрирован в Python 2.7 / 3.1 (bugs.python.org/issue2983), чтобы вы получили естественный внешний вид с помощью встроенного набора инструментов.
Это могло быть чрезмерным упрощением, я не думаю, что было бы хорошей идеей попытаться создать таким образом универсальную библиотеку пользовательского интерфейса. С другой стороны, вы можете использовать этот подход (метаклассы и друзья), чтобы упростить определение определенных классов пользовательских интерфейсов для существующей библиотеки пользовательского интерфейса и в зависимости от приложения, что может фактически сэкономить вам значительное количество времени и строк кода.
«Чрезмерное упрощение обычно приводит к обратным результатам»: тавтология. Обувь определенно является «упрощением», а если она является «чрезмерным упрощением», зависит от ваших потребностей. Я бы сказал, что создание простого пользовательского интерфейса может быть очень полезным.
С некоторой магией Metaclass, чтобы сохранить порядок, у меня получилось следующее. Я не уверен, насколько это питонично, но это хорошее развлечение для создания простых вещей.
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()
У меня такая же проблема. Я хочу создать оболочку вокруг любого набора инструментов GUI для Python, который прост в использовании и вдохновлен Shoes, но должен быть подходом ООП (против рубиновых блоков).
Дополнительная информация: http://wiki.alcidesfonseca.com/blog/python-universal-gui-revisited
Приглашаем всех желающих присоединиться к проекту.
Единственная известная мне попытка сделать это - Воск Ганса Новака (который, к сожалению, мертв).
Наиболее близким к рубиновым блокам является оператор with из pep343:
Я никогда не был удовлетворен статьями Дэвида Мертца в IBM о метактивах, поэтому недавно я написал свой собственный статья метакласса. Наслаждаться.
Если вы действительно хотите кодировать пользовательский интерфейс, вы можете попробовать получить что-то похожее на ORM django; что-то вроде этого, чтобы получить простую справку в браузере:
class MyWindow(Window):
class VBox:
entry = Entry()
bigtext = TextView()
def on_entry_accepted(text):
bigtext.value = eval(text).__doc__
Идея состоит в том, чтобы интерпретировать некоторые контейнеры (например, окна) как простые классы, некоторые контейнеры (например, таблицы, v / hboxes), распознаваемые по именам объектов, и простые виджеты как объекты.
Я не думаю, что нужно было бы называть все контейнеры внутри окна, поэтому было бы желательно использовать некоторые ярлыки (например, классы в старом стиле, распознаваемые как виджеты по именам).
О порядке элементов: в MyWindow выше вам не нужно это отслеживать (окно концептуально представляет собой контейнер с одним слотом). В других контейнерах вы можете попытаться отслеживать порядок, предполагая, что каждый конструктор виджетов имеет доступ к некоторому глобальному списку виджетов. Вот как это делается в django (AFAIK).
Несколько хаков здесь, несколько настроек ... Есть еще несколько вещей, о которых стоит подумать, но я считаю, что это возможно ... и полезно, если вы не создаете сложные пользовательские интерфейсы.
Однако я очень доволен PyGTK + Glade. Пользовательский интерфейс для меня - это просто данные, и их следует рассматривать как данные. Слишком много параметров, которые нужно настраивать (например, интервал в разных местах), и лучше управлять этим с помощью инструмента с графическим интерфейсом. Поэтому я создаю свой пользовательский интерфейс на поляне, сохраняю как xml и анализирую с помощью gtk.glade.XML ().
Декларативный не обязательно более (или менее) питонический, чем функциональный ИМХО. Я думаю, что многоуровневый подход будет лучшим (снизу вверх):
Подобно Эликсир + SQLAlchemy.
Я думаю, что в некоторых случаях декларативность определенно более интуитивна, и я бы сказал, что это более питонический код, поскольку он делает код (ИМХО) более чистым, легким для чтения и более легким для изменения - свойства, которые обычно имеют более традиционный питонический код. Это имеет смысл для Elixir, Django Models и, возможно, макетов GUI.
И я бы сказал, что код Python traditional даже может быть декларативным. Декларативное неизбежно связано с магией, и это не всегда хорошо сочетается с выражением «явное лучше, чем неявное».
Но я согласен с вами по последней части. Декларативное программирование прекрасно справляется с задачами моделирования данных любого типа. Модели ORM, макеты графического интерфейса, схемы URL ...
Лично я бы попытался реализовать 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'
Это чрезвычайно надумано и совсем не питоническое, но вот моя попытка полу-буквального перевода с использованием нового оператора «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
Вот подход, который касается определений графического интерфейса немного по-другому, используя метапрограммирование на основе классов, а не наследование.
Это большой 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)
Я еще не совсем уверен, что это супер-отличный подход, но я определенно думаю, что он на правильном пути. У меня нет времени более подробно исследовать эту идею, но если бы кто-то воспринял это как проект, мне бы они понравились.
## 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. Который у меня тоже есть. Есть идеи?
Если вы используете PyGTK с поляна и эта обертка поляны, тогда 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 с именем button1 и что я указал button_click_event как обратный вызов щелкнул. Обертка glade требует много усилий от отображения событий.
Если бы мне пришлось разработать библиотеку Pythonic GUI, я бы поддержал нечто подобное, чтобы ускорить разработку. Единственная разница в том, что я хотел бы, чтобы у виджетов тоже был более питонический интерфейс. Текущие классы PyGTK кажутся мне очень C, за исключением того, что я использую foo.bar (...) вместо bar (foo, ...), хотя я не совсем уверен, что бы я сделал по-другому. Вероятно, разрешить декларативные средства в стиле моделей Django для указания виджетов и событий в коде и предоставления вам доступа к данным через итераторы (где это имеет смысл, например, возможно, списки виджетов), хотя я действительно не думал об этом.
И Ruby, и Python подходят для DSL. Разница в «L»; это означает «языки» для Ruby и «библиотеки» для Python. Вы могли бы вызвать синтаксическую магию из Python, например, модели Django, но должны ли вы?