wxPython: Widgets dynamisch erstellen und löschen

Code-Stücke können hier veröffentlicht werden.
Antworten
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

wxPython: Widgets dynamisch erstellen und löschen

Bild

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

import wx

wx.SetDefaultPyEncoding("iso-8859-15")


class MyFrame(wx.Frame):
   
    def __init__(
        self, parent = None, id = -1, title = "Example"
    ):
        # Frame initialisieren
        wx.Frame.__init__(self, parent, id, title)
       
        # In dieser Variable werden die Ordnerfelder als Tuppel gespeichert
        # [(statictext, textctrl), ...]
        self.dirfields = []
       
        # Wenn sich das Fenster wie ein übliches Fenster verhalten soll, dann
        # muss als erstes Widget ein Panel in's Frame. Alle weiteren Widgets
        # werden auf dem Panel und nicht direkt auf dem Frame platziert.
        panel = wx.Panel(self)
        self.panel = panel
       
        # Äußerster Box-Sizer, der nur dazu dient, einen kleinen Abstand zwischen
        # dem Fensterrand zu lassen.
        vbox_main = wx.BoxSizer(wx.VERTICAL)
        panel.SetSizer(vbox_main)
       
        # Innerer Box-Sizer, der sich jetzt wirklich um die Anordnung der
        # Widgets kümmert. Den Namen halte ich kurz, da Sizer öfter
        # gebraucht werden.
        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox_main.Add(vbox, 1, wx.ALL | wx.EXPAND, 5)
       
        # FlexGridSizer. Den Namen halte ich kurz, da Sizer öfter
        # gebraucht werden.
        fg = wx.FlexGridSizer(cols = 2)
        vbox.Add(fg, 1, wx.EXPAND)
        fg.AddGrowableCol(1, 1)
        self.fg = fg
       
        # Startpfad
        lab_startpfad = wx.StaticText(panel, label = u"Startpfad:")
        fg.Add(lab_startpfad, 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
       
        txt_startpfad = wx.TextCtrl(panel, size = (300, -1))
        fg.Add(txt_startpfad, 0, wx.EXPAND | wx.ALL, 5)
        self.txt_startpfad = txt_startpfad
       
        # Auswahl der Möglichkeitenanzahl
        lab_quantity = wx.StaticText(panel, label = u"Ordneranzahl:")
        fg.Add(lab_quantity, 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
       
        choices = [ str(item) for item in range(1, 11) + [12, 15, 20] ]
        ch_quantity = wx.Choice(panel, choices = choices)
        fg.Add(ch_quantity, 0, wx.ALL, 5)
        ch_quantity.SetSelection(4)
       
        # Felder für die Ordner erstellen
        self.create_fields(5, True)
       
        # Linie (durchgezogen --> also in den obersten Sizer "vbox_main")
        vbox_main.Add(wx.StaticLine(panel), 0, wx.EXPAND)
       
        # HSizer für die Buttons
        hbox_buttons = wx.BoxSizer(wx.HORIZONTAL)
        vbox_main.Add(hbox_buttons, 0, wx.EXPAND | wx.ALL, 5)
       
        # Abbrechen-Button
        btn_cancel = wx.Button(panel, wx.ID_CANCEL, "Abbrechen")
        hbox_buttons.Add(btn_cancel, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        btn_cancel.Bind(wx.EVT_BUTTON, self.close_frame)
       
        # Spreizer, damit die Buttons links und rechts außen angeordnet werden
        hbox_buttons.Add((0, 0), 1)
       
        # Erzeugen-Button
        btn_show = wx.Button(panel, label = u"An&zeigen")
        hbox_buttons.Add(btn_show, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        btn_show.Bind(wx.EVT_BUTTON, self.show_data)
       
        # Layout
        panel.Fit()
        self.Fit()
        self.SetSizeHintsSz(self.GetSize())
       
        # Choice an Event binden
        ch_quantity.Bind(wx.EVT_CHOICE, self.refresh_field_quantity)
   
   
    def create_fields(self, count, no_layoutrefresh = False):
        """
        Erstellt die Felder für die Ordner. Alte Felder werden nur gelöscht,
        wenn count kleiner als vorher ist.
        """
       
        fields = self.dirfields
        fg = self.fg
        panel = self.panel
       
        # Fensteraufbau sperren
        self.Freeze()
       
        if count < len(fields):
            # Überzählige Felder löschen
            for i in range(len(fields) - count):
                old_label, old_textctrl = fields.pop()
                fg.Detach(old_textctrl)
                old_textctrl.Destroy()
                fg.Detach(old_label)
                old_label.Destroy()
       
        if count > len(fields):
            # Zusätzliche Felder hinzufügen
            basecount = len(fields) + 1
            for i in range(count - len(fields)):
                # Label
                label = "Ordner %i:" % (i + basecount)
                new_label = wx.StaticText(panel, label = label)
                fg.Add(new_label, 0, wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
                # Textctrl
                new_textctrl = wx.TextCtrl(panel)
                fg.Add(new_textctrl, 0, wx.EXPAND | wx.ALL, 5)
                # Zur Feldliste
                fields.append((new_label, new_textctrl))
       
        # Fensteraufbau entsperren
        self.Thaw()
       
        # Layout
        if not no_layoutrefresh:
            self.SetSizeHints(-1, -1)
            panel.Fit()
            self.Fit()
            self.SetSizeHintsSz(self.GetSize())
   
   
    def refresh_field_quantity(self, event):
        """
        Findet raus, wie viele Felder angezeigt werden sollen und gibt die
        Anzahl weiter.
        """
       
        choice = event.GetEventObject()
        count = int(choice.GetStringSelection())
        self.create_fields(count)
   
   
    def close_frame(self, event = None):
        """
        Schließt das Fenster
        """
       
        self.Close()
   
   
    def show_data(self, event = None):
        """
        Zeigt die eingegebenen Daten in einer Messagebox an
        """
       
        # Werte eruieren
        values = []
        for index, (label, textctrl) in enumerate(self.dirfields):
            values.append("Ordner %i: %s" % ((index + 1), textctrl.GetValue()))
       
        startpfad = self.txt_startpfad.GetValue()
       
        # Anzeigen
        message = (
            u"Werte der Felder:\n\n" +
            u"Startpfad: %s\n\n" +
            "\n".join(values)
        ) % startpfad
        wx.MessageBox(message)


class MyApp(wx.PySimpleApp):
   
    def OnInit(self):
        """
        Primäre Applikationslogik hier hinein.
        """
       
        self.show_my_frame()
        return True
   
   
    def show_my_frame(self):
        """
        Zeigt das Frame "MyFrame" an und bindet es an die Applikationsvariable
        self.my_frame.
        """
       
        self.my_frame = MyFrame()
        self.my_frame.Center()
        self.my_frame.Show()


def main():
    app = MyApp()
    app.MainLoop()


if __name__ == "__main__":
    main()
Edit: Link geändert
Zuletzt geändert von gerold am Montag 22. Oktober 2007, 11:18, insgesamt 1-mal geändert.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
alan
User
Beiträge: 81
Registriert: Dienstag 10. April 2007, 11:30

Mir ist aufgefallen, dass du öfter "abkürzt", um nicht ständig self mit rumzuschleppen (z.B. Z. 25).

Ich mach mit Python gerade meine ersten Erfahrungen mit objektorientier Programmierung und bin mir deswegen bei manchen Sachen unsicher. Ist das Konvention und sollte ich das auch so machen?
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

alan hat geschrieben:Mir ist aufgefallen, dass du öfter "abkürzt", um nicht ständig self mit rumzuschleppen (z.B. Z. 25).

Ich mach mit Python gerade meine ersten Erfahrungen mit objektorientier Programmierung und bin mir deswegen bei manchen Sachen unsicher. Ist das Konvention und sollte ich das auch so machen?
Ist nein, das dient nur dazu kurze Namen zu haben. Und ich kenne keinen Standard welcher dir das vorschreibt ;) Am ehesten noch die Beschränkung auf 80 Zeichen/Zeile.

Gruss,
Jonas
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

alan hat geschrieben:Mir ist aufgefallen, dass du öfter "abkürzt", um nicht ständig self mit rumzuschleppen (z.B. Z. 25).
Hallo alan!

Ja, ich schreibe ziemlich oft ``panel`` und nicht ``self.panel``. Ich erkläre dir mal meine Gedanken, die in mir vorgehen wenn ich so etwas mache.

Ich erstelle die Widgets in der __init__-Methode. Das ist auch die Methode, die ich vor allen anderen schreibe. Zuerst erstelle ich ein ``wx.Panel``, welches ich an den Namen ``panel`` binde. Dann lege ich die anderen Widgets auf dieses Panel. Bis jetzt gibt es keine Notwendigkeit für mich, das ``panel`` auch an die Instanz (self) zu binden. Warum auch? Wenn ich pauschal alle Widgets an self binden würde, dann wäre der zusätzliche Schreibaufwand (nur durch die zusätzlichen "self.") bei normal großen Programmen *enorm*.

Irgendwann kommt der Zeitpunkt, in dem ich z.B. in einer anderen Methode (z.b. in einem Eventhandler), also nicht in der __init__, auf das Panel zugreifen muss. Um z.B. die Hintergrundfarbe zu verändern. Jetzt weiß ich also, dass ich nicht nur in der __init__ Zugriff auf das Panel brauche. Also springe ich im Programmcode zur __init__ zurück. Genau zu dem Absatz, in dem ich das Panel erstelle und vielleicht auch noch ein paar Standardeigenschaften erstelle. In diesem Absatz binde ich jetzt das Panel *zusätzlich* an die Instanz (self). Ich binde das Panel also erst dann an den Namen ``self.panel``, wenn ich weiß, dass ich es auch außerhalb von __init__ brauche.

Und so verhält es sich mit allen Widgets. In den Beispielen, hier im Forum, sind es meist nur ein paar Widgets. Aber ein ausgewachsenes Programm kann aus hunderten Widgets bestehen... Nur aus "Prinzip" alles an self zu binden wäre dabei eine nette Arbeitsbeschaffung, aber mehr nicht.

Zusätzlich zur besseren Übersicht über den Widgetaufbau, den man hat wenn man die Widgets, schön gruppiert und kommentiert, in der __init__ erstellt, ist das auch ein Grund dafür, warum ich das Erstellen der Widgets **nicht** auf mehrere Methoden aufteile. Natürlich gibt es Ausnahmen, aber die sind selten.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
alan
User
Beiträge: 81
Registriert: Dienstag 10. April 2007, 11:30

gerold hat geschrieben: Irgendwann kommt der Zeitpunkt, in dem ich z.B. in einer anderen Methode (z.b. in einem Eventhandler), also nicht in der __init__, auf das Panel zugreifen muss.
Ja, das hatte ich jetzt auch schon ein paar mal. Ich bin dann zurückgegangen und alles mit Search&Replace entsprechend angepasst.

Ich glaub ich werde es jetzt auch so wie du machen..;-)
Antworten