2010-11-09 5 views
2

Je voudrais commencer par admettre que je suis terrifié à poser cette question. Cela dit, j'ai la combinaison suivante des classes:Python & wxPython - Code Critique - Courte main/Commodité/Répétition

Classe de dialogue:

class formDialog(wx.Dialog): 
    def __init__(self, parent, id = -1, panel = None, title = _("Unnamed Dialog"), 
       modal = False, sizes = (400, -1)): 
    wx.Dialog.__init__(self, parent, id, _(title), 
         style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) 

    if panel is not None: 
     self._panel = panel(self) 

     self._panel.SetSizeHints(*sizes) 

     ds = wx.GridBagSizer(self._panel._gap, self._panel._gap) 

     ds.Add(self._panel, (0, 0), (1, 1), wx.EXPAND | wx.ALL, self._panel._gap) 

     ds.Add(wx.StaticLine(self), (1, 0), (1, 1), wx.EXPAND | wx.RIGHT | wx.LEFT, self._panel._gap) 

     self.bs = self.CreateButtonSizer(self._panel._form['Buttons']) 

     ds.Add(self.bs, (2, 0), (1, 1), wx.ALIGN_RIGHT | wx.ALL, self._panel._gap) 

     ds.AddGrowableCol(0) 
     ds.AddGrowableRow(0) 

     self.SetSizerAndFit(ds) 

     self.Center() 

     self.Bind(wx.EVT_BUTTON, self._panel.onOk, id = wx.ID_OK) 
     self.Bind(wx.EVT_BUTTON, self._panel.onClose, id = wx.ID_CANCEL) 
     self.Bind(wx.EVT_CLOSE, self._panel.onClose) 

     if modal: 
     self.ShowModal() 
     else: 
     self.Show() 

Classe Forme:

class Form(wx.Panel): 
    reqFields = [ 
    ('Defaults', {}), 
    ('Disabled', []) 
    ] 

    def __init__(self, parent = None, id = -1, gap = 2, sizes = (-1, -1)): 
    wx.Panel.__init__(self, parent, id) 

    self.SetSizeHints(*sizes) 

    self._gap = gap 

    self.itemMap = {} 

    if hasattr(self, '_form'): 
     # There are a number of fields which need to exist in the form 
     # dictionary. Set them to defaults if they don't exist already. 
     for k, d in self.reqFields: 
     if not self._form.has_key(k): 
      self._form[k] = d 

     self._build() 

    def _build(self): 
    """ 
    The Build Method automates sizer creation and element placement by parsing 
    a properly constructed object. 
    """ 

    # The Main Sizer for the Panel. 
    panelSizer = wx.GridBagSizer(self._gap, self._gap) 

    # Parts is an Ordered Dictionary of regions for the form. 
    for group, (key, data) in enumerate(self._form['Parts'].iteritems()): 
     flags, sep, display = key.rpartition('-') #@UnusedVariable 

     # HR signifies a Horizontal Rule for spacing/layout. No Data Field. 
     if display == 'HR': 
     element = wx.StaticLine(self) 

     style = wx.EXPAND 

     # Any other value contains elements that need to be placed. 
     else: 
     element = wx.Panel(self, -1) 

     # The Row Sizer 
     rowSizer = wx.GridBagSizer(self._gap, self._gap) 

     for row, field in enumerate(data): 
      for col, item in enumerate(field): 
      style = wx.EXPAND | wx.ALL 

      pieces = item.split('-') 

      # b for Buttons 
      if pieces[0] == 'b': 
       control = wx._controls.Button(element, -1, pieces[1]) 

      # custom items - Retrieve from the _form object 
      if pieces[0] == 'custom': 
       control = self._form[pieces[1]](element) 

       # The row in the Grid needs to resize for Lists. 
       panelSizer.AddGrowableRow(group) 

       # Now the Row has to grow with the List as well. 
       rowSizer.AddGrowableRow(row) 

      # custom2 - Same as custom, but does not expand 
      if pieces[0] == 'custom2': 
       control = self._form[pieces[1]](element) 

       style = wx.ALL 

      # c for CheckBox 
      if pieces[0] == 'c': 
       control = wx.CheckBox(element, label = _(pieces[2]), name = pieces[1]) 

       control.SetValue(int(self._form['Defaults'].get(pieces[1], 0))) 

      # d for Directory Picker 
      if pieces[0] == 'd': 
       control = wx.DirPickerCtrl(element, name = pieces[1]) 

       control.GetTextCtrl().SetEditable(False) 

       control.GetTextCtrl().SetName(pieces[1]) 

       control.GetTextCtrl().SetValue(self._form['Defaults'].get(pieces[1], '')) 

      # f for File Browser 
      if pieces[0] == 'f': 
       control = wx.FilePickerCtrl(element, name = pieces[1], wildcard = pieces[2]) 

       control.GetTextCtrl().SetEditable(False) 

       control.GetTextCtrl().SetValue(self._form['Defaults'].get(pieces[1], '')) 

      # f2 for Save File 
      if pieces[0] == 'f2': 
       control = wx.FilePickerCtrl(element, name = pieces[1], 
       style = wx.FLP_SAVE | wx.FLP_OVERWRITE_PROMPT | wx.FLP_USE_TEXTCTRL, 
       wildcard = pieces[2]) 

       control.GetTextCtrl().SetEditable(False) 

      # h for Horizontal Rule - layout helper. 
      if pieces[0] == 'h': 
       control = wx.StaticLine(element) 
       style = wx.EXPAND 

      # l for Label (StaticText) 
      if pieces[0] == 'l': 
       control = wx.StaticText(element, label = _(pieces[1])) 

       # Labels do not expand - override default style. 
       style = wx.ALL | wx.ALIGN_CENTER_VERTICAL 

      # p for Password (TextCtrl with Style) 
      if pieces[0] == 'p': 
       control = wx.TextCtrl(element, name = pieces[1], style = wx.TE_PASSWORD) 

       control.SetValue(self._form['Defaults'].get(pieces[1], '')) 

      # s for ComboBox (Select) 
      if pieces[0] == 's': 
       control = wx.ComboBox(element, name = pieces[1], 
       choices = self._form['Options'].get(pieces[1], []), 
       style = wx.CB_READONLY) 

       control.SetValue(self._form['Defaults'].get(pieces[1], '')) 

      # s2 for Spin Control 
      if pieces[0] == 's2': 
       control = wx.SpinCtrl(element, name = pieces[1], size = (55, -1), 
       min = int(pieces[2]), max = int(pieces[3])) 

       control.SetValue(int(self._form['Defaults'].get(pieces[1], 1))) 

       # Spin Ctrl's do not expand. 
       style = wx.ALL 

      # t for TextCtrl 
      if pieces[0] == 't': 
       control = wx.TextCtrl(element, name = pieces[1]) 

       try: 
       control.SetValidator(self._form['Validators'][pieces[1]]) 
       except KeyError: pass # No Validator Specified. 

       control.SetValue(self._form['Defaults'].get(pieces[1], '')) 

      # tr for Readonly TextCtrl 
      if pieces[0] == 'tr': 
       control = wx.TextCtrl(element, name = pieces[1], style = wx.TE_READONLY) 

       control.SetValue(self._form['Defaults'].get(pieces[1], '')) 

      # Check for elements disabled by default. Store reference to 
      # Element in itemMap for reference by other objects later. 
      if len(pieces) > 1: 
       if pieces[1] in self._form['Disabled']: 
       control.Enable(False) 

       self.itemMap[pieces[1]] = control 

      # Place the control in the row. 
      rowSizer.Add(control, (row, col), (1, 1), style, self._gap) 

      if style == wx.EXPAND | wx.ALL: 
       rowSizer.AddGrowableCol(col) 

     if 'NC' not in flags: 
      sb = wx.StaticBox(element, -1, _(display)) 
      sz = wx.StaticBoxSizer(sb, wx.VERTICAL) 

      sz.Add(rowSizer, 1, flag = wx.EXPAND) 

      element.SetSizerAndFit(sz) 
     else: 
      element.SetSizerAndFit(rowSizer) 

     panelSizer.Add(element, (group, 0), (1, 1), wx.EXPAND | wx.ALL, self._gap) 

    panelSizer.AddGrowableCol(0) 

    self.SetSizerAndFit(panelSizer) 

    def getDescendants(self, elem, list): 
    children = elem.GetChildren() 

    list.extend(children) 

    for child in children: 
     self.getDescendants(child, list) 

    def getFields(self): 
    fields = [] 

    self.getDescendants(self, fields) 

    # This removes children we can't retrieve values from. This should result 
    # in a list that only contains form fields, removing all container elements. 
    fields = filter(lambda x: hasattr(x, 'GetValue'), fields) 

    return fields 

    def onOk(self, evt): 
    self.onClose(evt) 

    def onClose(self, evt): 
    self.GetParent().Destroy() 

Le formulaire est destiné à être utilisé par le sous-classement comme ceci:

class createQueue(Form): 
    def __init__(self, parent): 
    self._form = { 
     'Parts' : OrderedDict([ 
     ('Queue Name', [ 
      ('t-Queue Name',) 
     ]) 
     ]), 
     'Buttons' : wx.OK | wx.CANCEL 
    } 

    Form.__init__(self, parent) 

class generalSettings(Form): 
    def __init__(self, parent): 
    self._form = { 
     'Parts': OrderedDict([ 
     ('Log Settings', [ 
      ('l-Remove log messages older than: ', 's2-interval-1-10', 's-unit') 
     ]), 
     ('Folder Settings', [ 
      ('l-Spool Folder Location:', 'd-dir'), 
      ('l-Temp Folder Location:', 'd-temp') 
     ]), 
     ('Email Notifications', [ 
      ('l-Alert Email To:', 't-alert_to'), 
      ('l-Alert Email From:', 't-alert_from'), 
      ('l-Status Email From:', 't-status_from'), 
      ('l-Alert Email Server:', 't-alert_host'), 
      ('l-Login:', 't-alert_login'), 
      ('l-Password:', 'p-alert_password') 
     ]), 
     ('Admin User', [ 
      ('c-req_admin-Require Admin Rights to make changes.',) 
     ]), 
     ('Miscellaneous', [ 
      ('l-Print Worker Tasks:', 's2-printtasks-1-256', 'l-Job Drag Options:', 's-jobdrop') 
     ]) 
     ]), 
     'Options': { 
     'unit': ['Hours', 'Days', 'Months'], 
     'jobdrop': ['Move Job to Queue', 'Copy Job to Queue'] 
     }, 
     'Buttons': wx.OK | wx.CANCEL 
    } 

    Form.__init__(self, parent) 

Ceux-ci peuvent être utilisés comme ceci:

formDialog(parent, panel = createQueue, title = 'Create a Queue', sizes = (200, -1)) 

formDialog(parent, panel = generalSettings, title = "General Settings") 

Ouf, c'est une tonne, et merci à tous ceux qui le font jusque là. L'idée est que je veux quelque chose qui s'occupe des parties monotones de la mise en page dans wxPython. Je suis en train de concevoir une interface utilisateur qui devra créer des centaines de dialogues et de formulaires différents. Je voulais quelque chose qui me permettrait de générer dynamiquement les formes à partir d'un objet structuré. Je voudrais entendre les réflexions d'autres développeurs sur ce type d'approche. Le plus proche que j'ai vu à quelque chose de similaire est l'API de formulaire de Drupal. J'ai l'impression que c'est viable pour les raisons suivantes:

  • Réorganisez facilement les champs.
  • Pas besoin de créer/gérer manuellement les Sizers.
  • Les formes composées/complexes peuvent être créées facilement.
  • Les éléments auxiliaires d'affichage (StaticBoxSizers, Static Lines) sont facilement ajoutés.

Je crains que c'est une approche indésirable pour ces raisons:

  • long corps de la fonction _build() dans la classe de forme. Peut ne pas être clair pour les autres développeurs à première vue.
  • Utilise des chaînes structurées pour définir des champs.
  • Il peut y avoir un meilleur moyen. Toutes les pensées, constructives, destructives ou autres seront toutes appréciées.

Merci!

Répondre

2

Vous devriez également essayer wxFormDesigner ou XRCed.

+0

Je n'aime pas XML, mais j'ai trouvé des choses dans les projets suggérés que je peux utiliser pour nettoyer mes classes ci-dessus. Merci pour les suggestions.Je finirai probablement avec quelque chose qui ressemble à ce que j'ai ci-dessus, mais c'est nettoyé un peu. –

0

Puisque vous utilisez wx, vous devriez étudier wxglade. Il s'agit d'un générateur graphique GUI que vous utilisez pour créer votre interface graphique et il génère un fichier .wxg avec la mise en page, et vous pouvez le charger dans votre script.

Le fichier est en fait un fichier XML, vous pouvez donc le générer par programme et charger dynamiquement différentes interfaces graphiques. Peut-être que ça aide.

+0

J'ai regardé un peu wxglade, mais j'ai trouvé que le code généré était trop bavard. Je ne savais pas que le fichier .wxg était en XML, alors peut-être que je devrais le revoir. Merci! –