2008-09-12 5 views
11

He estado jugando con los "zapatos" de la biblioteca Ruby. Básicamente se puede escribir una aplicación de interfaz gráfica de usuario de la siguiente manera:¿Cómo diseñarías un marco de interfaz de usuario muy "pitónico"?

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 

Esto me hizo pensar - ¿Cómo voy a diseñar un marco de interfaz gráfica de usuario de manera similar agradable de usar en Python? Uno que no tiene los tics habituales de ser básicamente envoltorios de una biblioteca C * (en el caso de GTK, Tk, wx, QT, etc.)

Zapatos toma cosas del web devlopment (como #f0c2f0 notación de color de estilo, técnicas de diseño CSS, como :margin => 10), y de rubí (ampliamente utilizando bloques de manera sensible)

falta de "bloques rubyish" de Python hace un (metafóricamente) puerto -direct imposible:

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) 

no está cerca tan limpio, y no sería casi tan flexible, y estoy Ni siquiera estoy seguro de si sería implementable.

El uso de decoradores parece una forma interesante para asignar bloques de código a una acción específica:

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 

Básicamente, la función decoradora almacena la función, entonces cuando se produjo la acción (por ejemplo, un clic) la función apropiada sería ejecutado.

El alcance de varias cosas (por ejemplo, la etiqueta la en el ejemplo anterior) podría ser bastante complicado, pero parece factible de una manera bastante ordenada ..

+1

Tanto Ruby como Python son adecuados para DSL. La diferencia está en 'L'; es sinónimo de 'idiomas' para Ruby y 'bibliotecas' para Python. Podrías forzar algo de magia de sintaxis de Python, por ejemplo, los modelos de Django, pero ¿deberías? – jfs

Respuesta

7

En realidad podría sacar esto adelante, pero sería requieren el uso de metaclases, que son magia profunda (hay dragones). Si quieres una introducción a las metaclases, hay una serie de articles from IBM que logran introducir las ideas sin derretir tu cerebro.

El código fuente de un ORM como SQLObject también podría ser útil, ya que utiliza este mismo tipo de sintaxis declarativa.

+1

_but_ requeriría metaclases? Las metaclases en realidad no son tan difíciles como parecen al principio. Ver este artículo: http://www.voidspace.org.uk/python/articles/five-minutes.shtml – Dan

2

Tal vez no tan elegante como la versión de Ruby, pero ¿qué tal algo como esto:

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, para poner en práctica esta que tendría que utilizar una metaclase personalizada en clase App, y un montón de propiedades en Para y Button. Esto realmente no sería tan difícil.

El problema con el que se encuentra a continuación es: ¿cómo hace un seguimiento del pedido que las cosas aparecen en la definición de clase? En Python 2.x, no hay manera de saber si t debe estar por encima de b o al revés, ya que recibe el contenido de la definición de clase como python dict.

Sin embargo, en Python 3.0 metaclasses are being changed en un par de formas (menores). Uno de ellos es el método __prepare__, que le permite suministrar su propio objeto personalizado tipo diccionario que se utilizará en su lugar; esto significa que podrá rastrear el orden en que se definen los elementos y colocarlos en consecuencia en la ventana .

+0

En realidad, hay un truco para hacer un seguimiento del orden de los atributos. Mire los campos ORM de Django (http://code.djangoproject.com/browser/django/trunk/django/db/models/fields/__init__.py#L59). –

+0

No lo sé, excepto por el enlace del botón mágico, se parece mucho a Tkinter. Esperaría a que PyTTK se integre en Python 2.7/3.1 (http://bugs.python.org/issue2983) para que tenga una apariencia nativa con el kit de herramientas integrado. – Brandon

2

Esto podría ser una simplificación excesiva, no creo que sería una buena idea intentar hacer una biblioteca de ui de propósito general de esta manera. Por otro lado, podría utilizar este enfoque (metaclases y amigos) para simplificar la definición de ciertas clases de interfaces de usuario para una biblioteca ui existente y dependiendo de la aplicación que realmente podría ahorrarle una cantidad significativa de tiempo y líneas de código.

+0

"La simplificación excesiva suele ser contraproducente": tautología. Shoes es definitivamente una "simplificación": el clima es una "simplificación excesiva" que depende de sus necesidades. Yo diría que la creación de UI simple puede ser muy útil. – Neall

3

Con algo de magia de Metaclass para mantener el orden tengo el siguiente funcionamiento. No estoy seguro de cuán pitónico es, pero es divertido crear cosas simples.

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

Tengo el mismo problema. Quiero crear un envoltorio alrededor de cualquier kit de herramientas GUI para Python que sea fácil de usar e inspirado por Shoes, pero que debe ser un enfoque OOP (contra bloques de ruby).

Más información en: bienvenidos http://wiki.alcidesfonseca.com/blog/python-universal-gui-revisited

de cualquier persona para unirse al proyecto.

4

nunca estaba satisfecho con los artículos de David Mertz en IBM en metaclsses así que recientemente escribí mi propia metaclass article. Disfrutar.

1

Si realmente quieres codificar la IU, puedes intentar obtener algo similar al ORM de django; algo como esto para conseguir un simple navegador de ayuda:

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

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

La idea sería interpretar algunos contenedores (como Windows) como clases simples, algunos contenedores (como mesas, v/hboxes) reconocidos por los nombres de objetos y sencilla widgets como objetos.

No creo que haya que nombrar todos los contenedores dentro de una ventana, por lo que algunos accesos directos (como las clases antiguas que se reconocen como widgets por nombres) serían deseables.

Acerca del orden de los elementos: en MyWindow anterior no tiene que seguir esto (la ventana es conceptualmente un contenedor de una ranura). En otros contenedores puede intentar hacer un seguimiento del orden suponiendo que cada constructor de widgets tenga acceso a alguna lista de widgets global. Así es como se hace en django (AFAIK).

Pocos hackeos aquí, pocos retoques ... Todavía hay algunas cosas en las que pensar, pero creo que es posible ... y utilizable, siempre y cuando no construyas interfaces de usuario complicadas.

Sin embargo, estoy muy contento con PyGTK + Glade. La IU es solo un tipo de datos para mí y debe tratarse como datos. Hay demasiados parámetros para ajustar (como el espaciado en diferentes lugares) y es mejor administrarlo usando una herramienta GUI. Por lo tanto, construyo mi UI en glade, lo guardo como xml y lo analizo usando gtk.glade.XML().

1

declarativo no es necesariamente más (o menos) pythonic que funcional en mi humilde opinión. Creo que un enfoque en capas sería el mejor (desde arriba hacia arriba):

  1. Capa nativa que acepta y devuelve tipos de datos de python.
  2. Una capa dinámica funcional.
  3. Una o más capas declarativas/orientadas a objetos.

Similar a Elixir + SQLAlchemy.

+0

Creo que en algunos casos declarativo es definitivamente más intuitivo y yo diría más pitónico ya que hace que el código (en mi humilde opinión) sea más limpio, fácil de leer y más fácil de modificar, propiedades que el código pitónico más tradicional generalmente tiene. Tiene mucho sentido para Elixir, Django Models y quizás diseños de GUI. – Dan

+0

Y yo diría que el código python 'tradicional' incluso tiene la posibilidad de ser declarativo. Declarativo implica inevitablemente magia y eso no siempre va bien junto con "lo explícito es mejor que lo implícito". – muhuk

+0

Pero estoy de acuerdo contigo en la última parte. La programación declarativa brilla con cualquier tipo de tarea de modelado de datos. Modelos ORM, diseños de GUI, esquemas de URL ... – muhuk

1

Personalmente, intentaría implementar JQuery como API en un marco de GUI.

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' 
4

Esto es extremadamente artificial y no Pythonic en absoluto, pero aquí es mi intento de una semi-traducción literal usando el nuevo "con" comunicado.

with Shoes(): 
    t = Para("Not clicked!") 
    with Button("The Label"): 
    Alert("You clicked the button!") 
    t.replace("Clicked!") 

La parte más difícil es tratar con el hecho de que el pitón no nos dará funciones anónimas con más de una sentencia en ellos. Para conseguir alrededor de eso, se podría crear una lista de comandos y ejecutar a través de los ...

De todos modos, aquí está el código de fondo me encontré con esto con:

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 
1

Aquí es un enfoque que va sobre GUI Definiciones a un poco diferente usando la meta-programación basada en clases en lugar de la herencia.

Esto es largley Django/SQLAlchemy inspirado en que se basa en gran medida en meta-programación y separa el código de su GUI de su "código de código". También creo que debería hacer un uso intensivo de los administradores de diseño, como lo hace Java, porque cuando se elimina el código, nadie quiere ajustar constantemente la alineación de píxeles. También creo que sería genial si pudiéramos tener propiedades tipo CSS.

Aquí hay un ejemplo de tormenta de ideas que mostrará una columna con una etiqueta en la parte superior, luego un cuadro de texto, luego un botón para hacer clic en la parte inferior que muestra un mensaje.

 
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) 

Una cosa fresca a notar es la forma en que puede hacer referencia a la entrada de my_edit diciendo MAIN_WINDOW.my_layout.my_edit.text. En la declaración de la ventana, creo que es importante poder nombrar arbitrariamente los controles en la función kwargs.

aquí es la misma aplicación sólo utilizando posicionamiento absoluto (los controles aparecerá en diferentes lugares porque no estamos utilizando un controlador de distribución de fantasía):

 
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) 

No estoy totalmente seguro todavía si se trata de un gran enfoque, pero definitivamente creo que está en el camino correcto. No tengo tiempo para explorar más esta idea, pero si alguien tomara esto como un proyecto, me encantaría.

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

Sería relativamente fácil de hacer en python con un poco de esa metaclass python magic know how. Que tengo Y un conocimiento de PyGTK. Lo cual también tengo. ¿Tiene ideas?

3

Si usa PyGTK con glade y this glade wrapper, entonces PyGTK realmente se vuelve algo pitónico. Un poco al menos.

Básicamente, crea el diseño de GUI en Glade. También especifica devoluciones de eventos en glade.A continuación, se escribe una clase de su ventana como esta:

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

Aquí, estoy suponiendo que tengo un botón de llamada en algún lugar GTK botón1 y que he especificado button_click_event como el hace clic de devolución de llamada. La capa de glade hace un gran esfuerzo al mapeo de eventos.

Si tuviera que diseñar una biblioteca de GUI Pythonic, apoyaría algo similar, para ayudar al desarrollo rápido. La única diferencia es que me aseguraré de que los widgets tengan una interfaz más pitónica también. Las clases actuales de PyGTK me parecen muy C, excepto que utilizo foo.bar (...) en lugar de bar (foo, ...) aunque no estoy seguro de qué es exactamente lo que haría. Probablemente permita un medio declarativo del estilo de los modelos Django para especificar widgets y eventos en el código y permitiéndole acceder a datos a través de iteradores (donde tiene sentido, por ejemplo, listas de widgets), aunque realmente no lo he pensado.

Cuestiones relacionadas