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!
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. –