Related
I want to remove the following:
I just want to show 'only' the file names.
This is my QTreeView Code:
self.model = QFileSystemModel()
self.model.setRootPath(QDir.rootPath())
self.model.setFilter(QDir.NoDotAndDotDot | QDir.AllEntries | QDir.Dirs | QDir.Files)
self.proxy_model = QSortFilterProxyModel(recursiveFilteringEnabled = True, filterRole = QFileSystemModel.FileNameRole)
self.proxy_model.setSourceModel(self.model)
self.model.setReadOnly(False)
self.model.setNameFilterDisables(False)
self.indexRoot = self.model.index(self.model.rootPath())
self.treeView = QTreeView(self)
self.treeView.setModel(self.proxy_model)
self.adjust_root_index()
self.treeView.setRootIndex(self.indexRoot)
self.treeView.clicked.connect(self.on_treeView_clicked)
self.treeView.doubleClicked.connect(self.treeMedia_doubleClicked)
self.treeView.setDragDropMode(QAbstractItemView.InternalMove)
self.treeView.setAnimated(True)
self.treeView.setIndentation(20)
self.treeView.setSortingEnabled(True)
self.treeView.setDragEnabled(True)
self.treeView.setAcceptDrops(True)
self.treeView.setDropIndicatorShown(True)
self.treeView.setEditTriggers(QTreeView.NoEditTriggers)
self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
self.treeView.customContextMenuRequested.connect(self.showContextMenu)
I've tried some options from the PyQt5 API, like setHeader() But I couldn't figure out how it quite works. I'm not sure if setHeader() is even what I'm looking for.
You can hide the columns using the hideSection() method of the QHeaderView:
for i in range(1, self.treeView.model().columnCount()):
self.treeView.header().hideSection(i)
I am currently trying to build my first GUI application through python's standard Tkinter. I soon came to grips with the computers coordinate system, and indeed I found I could pan things out as I so wish, but I came to the realisation that a drag and drop feature would be far superior then specifying coordinates explicitly. I am close, but I have one major problem; Whilst I can keep the value of the coords of a single widget in relation to where I dragged it last, I cannot do this for multiple widgets.
This is the code I have created so far:
from tkinter import *
root = Tk()
class Move_Shape:
data = {'x': 0, 'y': 0}
canvas = Canvas(width = root.winfo_screenwidth(), height = root.winfo_screenheight())
shape_coords = open('Shape_coords.py', 'r')
def __init__(self, shape, fill = 'White', *coords):
new_coords = self.shape_coords.readline().split(',')
if coords == (): coords = new_coords
if shape == 'line':
tag = 'line'
self.id = self.canvas.create_line(coords, tags = tag, fill = fill)
elif shape == 'rectangle':
tag = 'rect'
self.id = self.canvas.create_rectangle(coords, tags = tag, fill = fill)
... More code
self.canvas.tag_bind(tag, '<Button-1>', self.click)
self.canvas.tag_bind(tag, '<Button1-Motion>', self.track)
self.canvas.tag_bind(tag, '<ButtonRelease-1>', self.release)
self.canvas.grid()
def click(self, event):
self.data.update({'x': event.x, 'y': event.y})
self.item = self.canvas.find_closest(self.data['x'], self.data['y'])
def track(self, event):
x, y = event.x - self.data['x'], event.y - self.data['y']
self.canvas.move(self.item, x, y)
self.data.update({'x': event.x, 'y': event.y})
def release(self, event):
self.data.update({'x': event.x, 'y': event.y})
coords = str(self.canvas.coords(self.item))
coords = coords[1:-1]
shape_coords = open ('Shape_coords.py', 'a')
shape_coords.write(coords)
shape_coords.write('\n')
shape_coords.close()
Move_Shape('rectangle', 'blue', 50, 50, 100, 100)
Move_Shape( 'oval', 'green', 50, 50, 100, 100)
Move_Shape( 'arc', 'red', 50, 50, 100, 100)
mainloop()
If I was to start with an initial pair of coords, I would very much like to be able to delete the coords and pick up where I left of, or rather, where the shape left of. Appending the coordinates to a file does not work, the main reason being that I cannot return the final value of an updated dictionary, after exiting the mainloop.
I did some research before hand, and looked into data persistence. So I came across the Module, pickle, for the first time. Through others examples online, I managed to 'dump' the values into another file, however, if some variable, call it a, changes multiple times, those values are all appended within the file (which leads us back to square one). I would like to know if there is a way to make it so that only the last value assigned to a object through a variable, is stored.
I would go through the pickle module myself, but it's terminology overwhelms me, and I do not know what to look up in specific when it comes to data persistence.
I am the OP(internet slang for original poster), and I believe I have found an answer to my question. This just serves for those whom may run into a similar situation, not to mention that others can provide better solutions, for am sure they exist, but this is one none the less.
So, my initial solution was to store the values in a dictionary, and then append that to a file to read back, however there was no way of obtaining the final value, that is, I had to update the dictionary and append it to the file, however I could not obtain the final value.
After that, I looked into data persistence, where you assign a value to a variable, which refers to an object, however, if I assigned a value to variable 'foobar', and 'pickle' it, (erm... writing it to a file, but in bytes), and then assign another value to 'foobar', you end up storing both of those values, rather than storing one constant value for the same object.
What I did then, was to combine these two approaches, of updating a dictionary, and pickling objects. I pickled a dictionary to which I could update, and since it was referring to the same object, then only one value was binded to the dictionary. I don't know why this does not work for mutable sequences, but I suppose that whilst the variable stays the same, it points to multiple objects, though I may be wrong, so if someone could add some clarification that would be much appreciated.
So now everything works, and I can move items on the canvas/widgets to my hearts desire.
Remember, if you need to write to a file where you only want to store a single value for a given object, whose value in dependent on time, use and read up an pickle, here is the link:
https://docs.python.org/3.4/library/pickle.html
Here are exapmles of pickle in action:
This one in particular describes to how save a dict using pickle:
How can I use pickle to save a dict?
Good wiki reference:
https://wiki.python.org/moin/UsingPickle
Here is the latest source code, you may adapt it as you see fit, please note, the indentation may be wrong, because pasting this into here did some weird things with the indentation levels, but am sure you can handle that:
from tkinter import *
import pickle
import os
import __main__
class Drag_Shape:
filename = os.path.basename(__main__.__file__)
filename = filename[:filename.index('.')]+'_coords.py'
print(__main__.__file__)
if os.path.isfile(filename) == False:
foorbar = open(filename, 'w')
foorbar.close()
if os.path.getsize(filename) == 0: coords_dict = {}
else:
with open(filename, 'rb') as shape_cords:
coords_dict = pickle.load(shape_cords)
data = {'x': 0, 'y': 0}
def __init__(self, canvas, *coords, shape = 'rect', fill = 'white', outline = 'black', width = 1, activefill = '',
activeoutline = 'black', activewidth = 1, disabledfill = '', disabledoutline = 'black', disabledwidth = 1,
state = ''):
self.canvas = canvas
self.shape = shape.lower()
print(shape)
print(coords)
for i in self.coords_dict.keys():
if shape.lower() in i: shape = i.lower()
if coords != (): self.coords_dict.update({shape:coords})
else: coords = self.coords_dict[shape]
if shape in 'line':
tag = 'line'
ID = canvas.create_line(coords, tags = tag, fill = fill, width = width,
activefill = activefill, activewidth = activewidth, disabledfill = disabledfill,
disabledwidth = disabledwidth, state = '')
elif shape in 'rectangle':
tag = 'rect'
ID = canvas.create_rectangle(coords, tags = tag, fill = fill, outline = outline, width = width,
activefill = activefill, activeoutline = activeoutline, activewidth = activewidth, disabledfill = disabledfill,
disabledoutline = disabledoutline, disabledwidth = disabledwidth, state = '')
elif shape in 'oval':
tag = 'oval'
ID = canvas.create_oval(coords, tags = tag, fill = fill, outline = outline, width = width,
activefill = activefill, activeoutline = activeoutline, activewidth = activewidth, disabledfill = disabledfill,
disabledoutline = disabledoutline, disabledwidth = disabledwidth, state = '')
elif shape in 'arc':
tag = 'arc'
ID = canvas.create_arc(coords, tags = tag, fill = fill, outline = outline, width = width,
activefill = activefill, activeoutline = activeoutline, activewidth = activewidth, disabledfill = disabledfill,
disabledoutline = disabledoutline, disabledwidth = disabledwidth, state = '')
elif shape in 'polygon':
tag = 'poly'
ID = canvas.create_polygon(coords, tags = tag, fill = fill, outline = outline, width = width,
activefill = activefill, activeoutline = activeoutline, activewidth = activewidth, disabledfill = disabledfill,
disabledoutline = disabledoutline, disabledwidth = disabledwidth, state = '')
elif shape in 'window':
tag = 'win'
ID = canvas.create_window(coords, tags = tag, fill = fill)
elif shape in 'text':
tag = 'text'
ID = canvas.create_text(coords, tags = tag, fill = fill)
elif shape in 'image':
tag = 'img'
ID = canvas.create_image(coords, tags = tag, fill = fill)
elif shape in 'bitmap':
tag = 'bitmap'
ID = canvas.create_bitmap(coords, tags = tag, fill = fill)
self.ID = ID
self.tag = tag
with open(self.filename, 'wb') as shape_coords:
pickle.dump(self.coords_dict, shape_coords)
canvas.tag_bind(tag, '<Button-1>', self.click)
canvas.tag_bind(tag, '<Button1-Motion>', self.track)
canvas.tag_bind(tag, '<ButtonRelease-1>', self.release)
def click(self, event):
self.data.update({'x': event.x, 'y': event.y})
self.item = self.canvas.find_closest(self.data['x'], self.data['y'])
return self.item
def track(self, event):
x, y = event.x - self.data['x'], event.y - self.data['y']
self.canvas.move(self.item, x, y)
self.data.update({'x': event.x, 'y': event.y})
def release(self, event):
self.data.update({'x': event.x, 'y': event.y})
coords = list(self.canvas.coords(self.item))
self.coords_dict.update({self.shape : coords})
with open(self.filename, 'wb') as shape_coords:
pickle.dump(self.coords_dict, shape_coords)
return self.ID
Three important things:
You must specify the coords with a list, or possibly a tuple or other containers (though I have only tested on lists)
If you want to actually save the location of the shape on the canvas, delete the original coords that was used to create the shape, otherwise it will just reset back to it's original position, and so it should.
A file is automagically created when you use the class outside of the file that it belong too. If the file is called 'foorbar.py', than a file called 'foorbar.coords.py', is created in the same folder. If you touch this in anyway, well just don't, it will mess things up.
I know I said three, but am going to push this 'community wiki' sign and see what it does, sorry if this has an undesired effect.
need to dynamically instantiate an object of type gtk.TreeViewColumn through a loop for, as in this example: http://dpaste.com/hold/789277/
the output of the code above is:
tvc_0 = gtk.TreeViewColumn('id', gtk.CellRendererText(), text = 0)
tvc_1 = gtk.TreeViewColumn('Aspecto', gtk.CellRendererText(), text = 1)
tvc_2 = gtk.TreeViewColumn('Impactos', gtk.CellRendererText(), text = 2)
I need to transform the output of the above strings in source code
already tried using getattr, i could not results. could someone help me?
Hugs!
Juliano
On possible solution without using evil exec/eval:
tuple_val = (
('id', gtk.CellRendererText(), 0),
('Aspectos', gtk.CellRendererText(), 1),
('Impactos', gtk.CellRendererText(), 2),
)
def tree_view_factory(id, renderer, text):
return gtk.TreeViewColumn(id, renderer, text=text)
tvc_0, tvc_1, tvc_2 = map(tree_view_factory, tuple_val)
You can use the exec statement to dynamically execute source code from a string. For example:
exec """
tvc_0 = gtk.TreeViewColumn('id', gtk.CellRendererText(), text = 0)
tvc_1 = gtk.TreeViewColumn('Aspecto', gtk.CellRendererText(), text = 1)
tvc_2 = gtk.TreeViewColumn('Impactos', gtk.CellRendererText(), text = 2)
"""
Or in the script you linked:
dic = {'0':'id', '1':'Aspecto', '2':'Impacto'}
for coluna in range(0, len(dic.keys())):
exec "tvc_"+str(coluna)+"=gtk.TreeViewColumn('"+dic[str(coluna)]+"', gtk.CellRendererText(), text="+str(coluna)+")"
But be careful, the execution of arbitrary code strings could lead to serious security problems.
Here is a snippet
self.list_ctrl = wx.ListCtrl(self, size=(-1,100),
style=wx.LC_ICON|wx.LC_ALIGN_LEFT
)
il = wx.ImageList(16,16,True)
png = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN,wx.ART_OTHER, (16,16))
il.Add(png)
self.list_ctrl.AssignImageList(il,wx.IMAGE_LIST_NORMAL)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
self.SetSizer(sizer)
self.list_ctrl.InsertImageStringItem(0,"1",0)
self.list_ctrl.InsertImageStringItem(1,"2",0)
My problem is that the icons appear to the top of the text which should not happen because I put wx.LC_ALIGN_LEFT in the style. I would like the icons to appear left of the text.
Another problem is that, I want one element per row. In my code it is almost like one element per column.
Can anyone help me with any of these problems?
Thanks.
Looking at the wxPython demo for the ListCtrl, it looks like they use SetImageList() instead of AssignImageList(). Not sure what the difference is though. I don't see where you're inserting any text though. You'd need to use SetStringItem to put text in the other columns from what I can see.
EDIT: Code from wxPython Demo package, ListCtrl demo:
self.il = wx.ImageList(16, 16)
self.idx1 = self.il.Add(images.Smiles.GetBitmap())
self.sm_up = self.il.Add(images.SmallUpArrow.GetBitmap())
self.sm_dn = self.il.Add(images.SmallDnArrow.GetBitmap())
And then we add data / images to the widget
def PopulateList(self):
if 0:
# for normal, simple columns, you can add them like this:
self.list.InsertColumn(0, "Artist")
self.list.InsertColumn(1, "Title", wx.LIST_FORMAT_RIGHT)
self.list.InsertColumn(2, "Genre")
else:
# but since we want images on the column header we have to do it the hard way:
info = wx.ListItem()
info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
info.m_image = -1
info.m_format = 0
info.m_text = "Artist"
self.list.InsertColumnInfo(0, info)
info.m_format = wx.LIST_FORMAT_RIGHT
info.m_text = "Title"
self.list.InsertColumnInfo(1, info)
info.m_format = 0
info.m_text = "Genre"
self.list.InsertColumnInfo(2, info)
items = musicdata.items()
for key, data in items:
index = self.list.InsertImageStringItem(sys.maxint, data[0], self.idx1)
self.list.SetStringItem(index, 1, data[1])
self.list.SetStringItem(index, 2, data[2])
self.list.SetItemData(index, key)
I'd actually like to start by admitting that I'm terrified to ask this question. That said, I have the following combination of classes:
A Dialog Class:
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()
A Form Class:
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()
The Form is meant to be used by subclassing like so:
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)
These might be used like so:
formDialog(parent, panel = createQueue, title = 'Create a Queue', sizes = (200, -1))
formDialog(parent, panel = generalSettings, title = "General Settings")
Whew, that's a ton, and thanks to anyone who makes it down this far. The idea is that I want something that takes care of the monotonous parts of layout in wxPython. I am designing a user interface that will need to create 100's of different Dialogs and Forms. I wanted something that would allow me to generate the forms dynamically from a structured object.
I'd like to hear other developers' thoughts on this kind of approach. The closest I've seen to something similar is Drupal's Form API. I feel like it is viable for these reasons:
Easily rearrange fields.
No need to create / manage Sizers manually.
Compound / complex forms can be created easily.
Display helper elements (StaticBoxSizers, Static Lines) are easily added.
I am concerned that it is an undesirable approach for these reasons:
Long _build() function body in the Form Class.
May not be clear to other developers at first glance.
Uses Structured Strings to define fields.
There may be a better way.
Any thoughts, constructive, destructive, or otherwise will all be appreciated. Thanks!
You should also try wxFormDesigner or XRCed.
Since you're using wx, you should study wxglade. It's a graphical GUI builder which you use to build your GUI and it generates a .wxg file with the layout, and you can load that into your script.
The file is actually just xml, so you can programatically generate it and dynamically load different GUIs from it. Maybe that helps.