Domanda

Ho giocato con le "scarpe" della libreria Ruby.Fondamentalmente puoi scrivere un'applicazione GUI nel modo seguente:

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

Questo mi ha fatto pensare: come progetterei un framework GUI altrettanto piacevole da usare in Python?Uno che non ha i soliti legami di essere fondamentalmente wrapper per una libreria C* (nel caso di GTK, Tk, wx, QT ecc. Ecc.)

Shoes prende cose dallo sviluppo web (come #f0c2f0 notazione dei colori di stile, tecniche di layout CSS, come :margin => 10), e dal rubino (utilizzando ampiamente i blocchi in modi sensati)

La mancanza di "blocchi rubino" in Python rende impossibile un port (metaforicamente) diretto:

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)

Non è neanche lontanamente pulito e non lo sarebbe quasi altrettanto flessibile e non sono nemmeno sicuro che sarebbe implementabile.

L'uso dei decoratori sembra un modo interessante per mappare blocchi di codice su un'azione specifica:

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

Fondamentalmente la funzione di decorazione memorizza la funzione, quindi quando si verifica l'azione (ad esempio, un clic) verrà eseguita la funzione appropriata.

La portata di varie cose (ad esempio, il la label nell'esempio sopra) potrebbe essere piuttosto complicato, ma sembra fattibile in modo abbastanza accurato.

È stato utile?

Soluzione

Potresti effettivamente farcela, ma richiederebbe l'uso di metaclassi, che sono profondo magia (ci sono draghi).Se vuoi un'introduzione alle metaclassi, c'è una serie di articoli dell'IBM che riescono a introdurre le idee senza scioglierti il ​​cervello.

Anche il codice sorgente di un ORM come SQLObject potrebbe essere d'aiuto, poiché utilizza lo stesso tipo di sintassi dichiarativa.

Altri suggerimenti

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

Sarebbe relativamente facile da fare in Python con un po' di quel know-how magico della metaclasse Python.Che ho.E una conoscenza di PyGTK.Che ho anche io.Hai idee?

Non sono mai stato soddisfatto degli articoli di David Mertz all'IBM sui metacls, quindi di recente ne ho scritto uno mio articolo di metaclasse.Godere.

Questo è estremamente artificioso e non è affatto pitonico, ma ecco il mio tentativo di traduzione semi-letterale utilizzando la nuova istruzione "con".

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

La parte più difficile è affrontare il fatto che Python non ci fornirà funzioni anonime con più di una istruzione al loro interno.Per aggirare questo problema, potremmo creare un elenco di comandi ed eseguirli...

Ad ogni modo, ecco il codice backend con cui l'ho eseguito:

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 un po' di magia di Metaclass per mantenere l'ordine, faccio funzionare quanto segue.Non sono sicuro di quanto sia Pythonico, ma è molto divertente creare cose semplici.

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

L'unico tentativo di farlo che io sappia è La cera di Hans Nowak (che purtroppo è morto).

La cosa più vicina ai blocchi color rubino è l'istruzione with di pep343:

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

Se usi PyGTK con radura E questo involucro di radura, allora PyGTK diventa effettivamente in qualche modo pitonico.Un po' almeno.

Fondamentalmente, crei il layout della GUI in Glade.Puoi anche specificare i callback degli eventi in glade.Quindi scrivi una classe per la tua finestra in questo modo:

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

Qui presumo di avere un pulsante GTK da qualche parte chiamato pulsante1 e questo l'ho specificato button_click_evento come il cliccato richiamare.Il wrapper della radura richiede molto impegno nella mappatura degli eventi.

Se dovessi progettare una libreria GUI Python, supporterei qualcosa di simile, per favorire un rapido sviluppo.L'unica differenza è che mi assicurerei che anche i widget abbiano un'interfaccia più Pythonic.Le attuali classi PyGTK mi sembrano molto C, tranne per il fatto che utilizzo foo.bar(...) invece di bar(foo, ...) anche se non sono sicuro esattamente cosa farei diversamente.Probabilmente consentire un mezzo dichiarativo in stile modello Django per specificare widget ed eventi nel codice e consentire l'accesso ai dati tramite iteratori (dove ha senso, ad esempio elenchi di widget forse), anche se non ci ho davvero pensato.

Forse non così efficace come la versione Ruby, ma che ne dici di qualcosa del genere:

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

Come ha detto Justin, per implementarlo dovresti utilizzare una metaclasse personalizzata su class App, e un sacco di proprietà su Para E Button.In realtà questo non sarebbe troppo difficile.

Il problema che incontri dopo è:come fai a tenere traccia di ordine che le cose appaiono nella definizione della classe?In Python 2.x, non c'è modo di sapere se t dovrebbe essere sopra b o viceversa, poiché ricevi il contenuto della definizione della classe come python dict.

Tuttavia, in Python 3.0 le metaclassi vengono modificate in un paio di modi (minori).Uno di questi è il __prepare__ metodo, che ti consente di fornire invece il tuo oggetto personalizzato simile a un dizionario da utilizzare: ciò significa che sarai in grado di tenere traccia dell'ordine in cui gli elementi sono definiti e posizionarli di conseguenza nella finestra.

Questa potrebbe essere una semplificazione eccessiva, non penso che sarebbe una buona idea provare a creare una libreria dell'interfaccia utente per scopi generali in questo modo.D'altra parte potresti utilizzare questo approccio (metaclassi e amici) per semplificare la definizione di determinate classi di interfacce utente per una libreria dell'interfaccia utente esistente e, a seconda dell'applicazione, ciò potrebbe effettivamente farti risparmiare una notevole quantità di tempo e righe di codice.

Ho questo stesso problema.Voglio creare un wrapper attorno a qualsiasi toolkit GUI per Python che sia facile da usare e ispirato a Shoes, ma deve essere un approccio OOP (contro i blocchi Ruby).

Maggiori informazioni in: http://wiki.alcidesfonseca.com/blog/python-universal-gui-revisited

Chiunque è il benvenuto a partecipare al progetto.

Se vuoi davvero codificare l'interfaccia utente, potresti provare a ottenere qualcosa di simile all'ORM di Django;qc in questo modo per ottenere un semplice browser di aiuto:

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

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

L'idea sarebbe quella di interpretare alcuni contenitori (come finestre) come semplici classi, alcuni contenitori (come tabelle, v/hbox) riconosciuti dai nomi degli oggetti e semplici widget come oggetti.

Non penso che sarebbe necessario nominare tutti i contenitori all'interno di una finestra, quindi alcune scorciatoie (come le classi vecchio stile riconosciute come widget dai nomi) sarebbero desiderabili.

Informazioni sull'ordine degli elementi:in MyWindow sopra non è necessario tenerne traccia (window è concettualmente un contenitore a uno slot).In altri contenitori puoi provare a tenere traccia dell'ordine assumendo che ciascun costruttore di widget abbia accesso a un elenco di widget globale.Ecco come viene fatto in Django (AFAIK).

Pochi hack qui, poche modifiche là...Ci sono ancora poche cose a cui pensare, ma credo che sia possibile...e utilizzabile, purché non si creino interfacce utente complicate.

Tuttavia sono abbastanza soddisfatto di PyGTK+Glade.L'interfaccia utente è solo una specie di dati per me e dovrebbe essere trattata come dati.Ci sono semplicemente troppi parametri da modificare (come la spaziatura in luoghi diversi) ed è meglio gestirli utilizzando uno strumento GUI.Pertanto creo la mia interfaccia utente in glade, la salvo come xml e la analizzo utilizzando gtk.glade.XML().

Il dichiarativo non è necessariamente più (o meno) pitonico che funzionale IMHO.Penso che un approccio a più livelli sarebbe il migliore (dal basso verso l'alto):

  1. Un livello nativo che accetta e restituisce tipi di dati Python.
  2. Uno strato dinamico funzionale.
  3. Uno o più livelli dichiarativi/orientati agli oggetti.

Simile a Elisir + SQLAlchemy.

Personalmente, proverei ad implementare JQuery come l'API in un framework 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'

Ecco un approccio che riguarda le definizioni della GUI in modo leggermente diverso utilizzando la metaprogrammazione basata su classi anziché l'ereditarietà.

Questo è in gran parte ispirato a Django/SQLAlchemy in quanto è fortemente basato sulla meta-programmazione e separa il codice della GUI dal "codice codice".Penso anche che dovrebbe fare un uso massiccio di gestori di layout come fa Java perché quando rilasci il codice, nessuno vuole modificare costantemente l'allineamento dei pixel.Penso anche che sarebbe bello se potessimo avere proprietà simili ai CSS.

Ecco un esempio approssimativo di brainstorming che mostrerà una colonna con un'etichetta in alto, quindi una casella di testo, quindi un pulsante su cui fare clic in basso che mostra un messaggio.

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 interessante da notare è il modo in cui puoi fare riferimento all'input di my_edit dicendo MAIN_WINDOW.my_layout.my_edit.text.Nella dichiarazione della finestra, penso che sia importante poter nominare arbitrariamente i controlli nella funzione kwargs.

Ecco la stessa app che utilizza solo il posizionamento assoluto (i controlli appariranno in posti diversi perché non stiamo utilizzando un sofisticato gestore di layout):

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)

Non sono ancora del tutto sicuro che questo sia un approccio davvero eccezionale, ma penso sicuramente che sia sulla strada giusta.Non ho tempo per esplorare ulteriormente questa idea, ma se qualcuno lo prendesse come progetto, lo adorerei.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top