Pregunta

He estado jugando con los "zapatos" de la biblioteca Ruby.Básicamente puedes escribir una aplicación GUI de la siguiente manera:

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 diseñaría un marco GUI igualmente agradable de usar en Python?Uno que no tiene los vínculos habituales de ser básicamente contenedores de una biblioteca C* (en el caso de GTK, Tk, wx, QT, etc., etc.)

Shoes toma cosas del desarrollo web (como #f0c2f0 notación de color de estilo, técnicas de diseño CSS, como :margin => 10), y de Ruby (uso extensivo de bloques de manera sensata)

La falta de "bloques rubí" de Python hace imposible un port (metafóricamente) directo:

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á ni cerca de estar tan limpio, y no lo estaría. cerca de es flexible y ni siquiera estoy seguro de si sería implementable.

Usar decoradores parece una forma interesante de 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, luego, cuando ocurre la acción (por ejemplo, un clic), se ejecuta la función apropiada.

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

¿Fue útil?

Solución

De hecho, podrías lograr esto, pero requeriría el uso de metaclases, que son profundo magia (habrá dragones).Si desea una introducción a las metaclases, hay una serie de artículos de IBM que logran introducir las ideas sin derretir tu cerebro.

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

Otros consejos

## 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 hacerlo en Python con un poco de ese conocimiento mágico de Python de metaclase.Que tengo.Y un conocimiento de PyGTK.Que también tengo.¿Tiene ideas?

Nunca estuve satisfecho con los artículos de David Mertz en IBM sobre metaclases, así que recientemente escribí el mío propio. artículo de metaclase.Disfrutar.

Esto es extremadamente artificial y no es pitónico en absoluto, pero aquí está mi intento de realizar una traducción semiliteral utilizando la nueva declaración "con".

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 lidiar con el hecho de que Python no nos dará funciones anónimas con más de una declaración.Para evitar eso, podríamos crear una lista de comandos y ejecutarlos...

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

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

Con algo de magia de Metaclass para mantener el orden, tengo lo siguiente funcionando.No estoy seguro de qué tan pitónico es, pero es muy 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()

El único intento de hacer esto que yo sepa es Cera de Hans Nowak (que lamentablemente está muerto).

Lo más cercano que puede llegar a los bloques rubíes es la declaración with de pep343:

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

Si utiliza PyGTK con claro y este envoltorio claro, entonces PyGTK en realidad se vuelve algo pitónico.Un poco al menos.

Básicamente, creas el diseño de la GUI en Glade.También especificas devoluciones de llamadas de eventos en glade.Luego escribes una clase para tu 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í, supongo que tengo un botón GTK en algún lugar llamado botón 1 y eso lo especifiqué evento_clic_boton como el hizo clic llamar de vuelta.El contenedor glade requiere mucho esfuerzo en el mapeo de eventos.

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

Quizás no sea tan ingenioso como la versión 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!'

como dijo justin, para implementar esto necesitarías usar una metaclase personalizada en la clase App, y un montón de propiedades en Para y Button.En realidad, esto no sería demasiado difícil.

El problema con el que te encuentras a continuación es:¿Cómo haces un seguimiento de las orden que las cosas aparecen en la definición de clase?En Python 2.x, no hay forma de saber si t debería estar arriba b o al revés, ya que recibes el contenido de la definición de clase como Python dict.

Sin embargo, en Python 3.0 Las metaclases están siendo cambiadas. en un par de formas (menores).Uno de ellos es el __prepare__ método, que le permite proporcionar su propio objeto personalizado similar a un diccionario para utilizarlo en su lugar; esto significa que podrá realizar un seguimiento del orden en el que se definen los elementos y colocarlos en consecuencia en la ventana.

Esto podría ser una simplificación excesiva, no creo que sea una buena idea intentar crear una biblioteca de interfaz de usuario 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 de interfaz de usuario existente y, dependiendo de la aplicación, eso podría ahorrarle una cantidad significativa de tiempo y líneas de código.

Yo tengo este mismo problema.Quiero crear un contenedor para cualquier conjunto de herramientas GUI para Python que sea fácil de usar e inspirado en Shoes, pero que deba ser un enfoque OOP (contra bloques Ruby).

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

Cualquiera es bienvenido a unirse al proyecto.

Si realmente desea codificar la interfaz de usuario, puede intentar obtener algo similar al ORM de Django;algo como esto para obtener un navegador de ayuda simple:

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 ventanas) como clases simples, algunos contenedores (como tablas, v/hboxes) reconocidos por nombres de objetos y widgets simples como objetos.

No creo que sea necesario nombrar todos los contenedores dentro de una ventana, por lo que serían deseables algunos atajos (como que las clases de estilo antiguo se reconozcan como widgets por sus nombres).

Sobre el orden de los elementos:en MyWindow arriba no es necesario realizar un seguimiento de esto (la ventana es conceptualmente un contenedor de una sola ranura).En otros contenedores, puede intentar realizar un seguimiento del orden, suponiendo que cada constructor de widgets tenga acceso a alguna lista global de widgets.Así se hace en Django (AFAIK).

Algunos trucos aquí, algunos ajustes allá...Aún quedan pocas cosas en las que pensar, pero creo que es posible...y utilizable, siempre y cuando no crees interfaces de usuario complicadas.

Sin embargo, estoy bastante contento con PyGTK+Glade.La interfaz de usuario es solo una especie de datos para mí y debe tratarse como datos.Simplemente hay demasiados parámetros para modificar (como el espaciado en diferentes lugares) y es mejor administrarlos usando una herramienta GUI.Por lo tanto, construyo mi interfaz de usuario en glade, la guardo como xml y la analizo usando gtk.glade.XML().

En mi humilde opinión, lo declarativo no es necesariamente más (o menos) pitónico que funcional.Creo que un enfoque en capas sería lo mejor (de abajo hacia arriba):

  1. Una 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 + SQLAlquimia.

Personalmente, intentaría implementar JQuery como API en un marco 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'

Aquí hay un enfoque que aborda las definiciones de GUI de manera un poco diferente utilizando metaprogramación basada en clases en lugar de herencia.

Esto está inspirado en gran medida en Django/SQLAlchemy porque se basa en gran medida en la metaprogramación y separa su código 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 sueltas código, nadie quiere modificar constantemente la alineación de los píxeles.También creo que sería genial si pudiéramos tener propiedades similares a CSS.

Aquí hay un ejemplo aproximado de una lluvia de ideas que mostrará una columna con una etiqueta en la parte superior, luego un cuadro de texto y 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 interesante a tener en cuenta es la forma en que puedes 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 controles arbitrariamente en la función kwargs.

Aquí está la misma aplicación solo que usa posicionamiento absoluto (los controles aparecerán en diferentes lugares porque no estamos usando un administrador de diseño sofisticado):

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)

Todavía no estoy del todo seguro de si este es un enfoque excelente, pero definitivamente creo que va por el camino correcto.No tengo tiempo para explorar más esta idea, pero si alguien tomara esto como un proyecto, me encantaría.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top