Python & wxPython - Критика кода - Сокращение / Удобство / Повторение - PullRequest
2 голосов
/ 09 ноября 2010

Я действительно хотел бы начать с признания, что мне страшно задавать этот вопрос. Тем не менее, у меня есть следующая комбинация классов:

Класс диалога:

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

Класс формы:

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

Форма предназначена для использования подклассами следующим образом:

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)

Они могут быть использованы так:

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

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

Вот так, это тонна, и спасибо всем, кто сделал это так далеко. Идея в том, что я хочу что-то, что позаботится о монотонных частях макета в wxPython. Я проектирую пользовательский интерфейс, который должен будет создавать сотни различных диалогов и форм. Я хотел что-то, что позволило бы мне динамически генерировать формы из структурированного объекта.

Мне бы хотелось услышать мнение других разработчиков относительно такого подхода. Наиболее близким, что я видел к чему-то похожему, является Drupal Form API. Я чувствую, что это жизнеспособно по следующим причинам:

  • Легко переставляет поля.
  • Нет необходимости создавать / управлять Sizer вручную.
  • Сложные / сложные формы могут быть легко созданы.
  • Вспомогательные элементы отображения (StaticBoxSizer, Static Lines) легко добавляются.

Я обеспокоен тем, что это нежелательный подход по следующим причинам:

  • Длинное _build() тело функции в классе формы.
  • Может быть непонятным для других разработчиков на первый взгляд.
  • Использует структурированные строки для определения полей.
  • Возможно, есть лучший способ.

Будут оценены любые мысли, конструктивные, разрушительные или иные. Спасибо!

Ответы [ 2 ]

2 голосов
/ 09 ноября 2010

Вам также следует попробовать wxFormDesigner или XRCed.

0 голосов
/ 09 ноября 2010

Поскольку вы используете wx, вам следует изучить wxglade.Это графический конструктор графического интерфейса, который вы используете для создания вашего графического интерфейса, и он генерирует файл .wxg с макетом, и вы можете загрузить его в свой скрипт.

Файл на самом деле является просто XML, так что вы можете программногенерировать его и динамически загружать различные графические интерфейсы из него.Может быть, это поможет.

...