Я действительно хотел бы начать с признания, что мне страшно задавать этот вопрос. Тем не менее, у меня есть следующая комбинация классов:
Класс диалога:
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()
тело функции в классе формы.
- Может быть непонятным для других разработчиков на первый взгляд.
- Использует структурированные строки для определения полей.
- Возможно, есть лучший способ.
Будут оценены любые мысли, конструктивные, разрушительные или иные. Спасибо!