问题
I'm trying to develop a "multitrack" GUI (similar to multitrack audio editors); however, I'd like to develop it in glade
first, and check how overflowing content (in this case, multiple "tracks") will behave with scollbars. Then, upon instantiation in Python, I'd first like to take the first of these "multiple tracks" as a "template", then delete all these multiple "tracks" - then allow the user to add new ones based on the "template" by, say, clicking an "Add" button.
From the Gtk palette, it seems to me that handlebox
is the right object to use as a base for a "track" (I'd want to draw in these tracks eventually). The only thing I managed to get through so far (given how few tutorials can be found on glade
UI usage), is to get the scrollbars to behave within the GUI - here's a screenshot of the scrolled window section only (corresponding file is below):
The right structure seems to be:
scrolled window
viewport
vbox
handlebox
drawingarea
handlebox ...
... and all I have to do is set the "Height request" of (all) handlebox
to 150px (I want a constant height, and width scaling according to window); and set its Packing/Expand to "No". Also, set the scrolledwindow
Horizontal and Vertical Scrollbar Policy to "Always" - otherwise the scrollbars are not shown (and I was otherwise wrongly trying to place an additional scrollbar to get to see it). Finally, to get the scrollbar to work, click exactly on its arrowheads - dragging the scroll bar doesn't work from within Glade (at least not on glade3 3.8.0 on Ubuntu 11.04 I use).
So far so good - at least I can see the overflowing content behave as I want in glade
, but:
- Is this the right
glade
UI structure to use? I see a Layout object, and a Frame object too - would those maybe be more appropriate here? (tried them, couldn't really figure them out) - Once the
.glade
file is read in Python, how to a proceed in "extracting" a template fromhandlebox1
, and duplicating it on demand? - Would I also have to change the partitioning of the vbox upon add/delete of a track? If so, is there a way to achieve the same layout as above for adding/deleting tracks, without using a vbox?
- Currently I'm happy with width of the tracks scaling with width of the window; but should I decide I want them fixed width bigger than width of the window, I tried setting Width Request of the handlebox to say 1000, and horizontal scrollbar seems to work properly in Glade; would Width Request be all that there is to it?
- Are special handlers needed if I want to let the user rearrange vertical track order by dragging?
And a side question - is there a way to quickly "preview" a Glade object directly from Glade (just in an "empty window"), without writing an instantiation script - maybe by using some shortcut?
Here's the code of multitrack.glade
(in GtkBuilder):
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.24"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkWindow" id="window1">
<property name="can_focus">False</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkViewport" id="viewport1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkHandleBox" id="handlebox1">
<property name="height_request">150</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkDrawingArea" id="drawingarea1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHandleBox" id="handlebox2">
<property name="height_request">150</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHandleBox" id="handlebox3">
<property name="height_request">150</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>
回答1:
Boy, this was something... well, to do a proper templating programaticaly, you'd need to recursively copy Gtk Objects, which are not susceptible to deepcopy... So I wrote one such function, deep_clone_widget
, included in the source below; for reference:
- Is there a good way to copy a Gtk widget?
- How to use the same widget twice in pygtk?
- How do I iterate through all Gtk children in PyGtk recursively?
- Editing GtkWidget attributes/properties
- How to set default style for widgets in pyGTK?
Of course, not extensively tested, but seems to work for me. Interestengly, until one does a full "deep clone" of handlebox and drawing area, the handleboxes do not stretch to fit the width of the window!
Good thing is - can just add to Vbox, no need to manage it; but it seems the dragging behavior will be a challenge... But I'd still like to know if this is the right Gtk/Glade UI hierarchy to use (and if there is a shortcut to preview from Glade)
The code below will also output the starting hierarchy:
window1 :: GtkWindow
-scrolledwindow1 :: GtkScrolledWindow
--viewport1 :: GtkViewport
---vbox1 :: GtkVBox
----handlebox1 :: GtkHandleBox
-----drawingarea1 :: GtkDrawingArea
----handlebox2 :: GtkHandleBox
----handlebox3 :: GtkHandleBox
... and the ending hierarchy:
window1 :: GtkWindow
-scrolledwindow1 :: GtkScrolledWindow
--viewport1 :: GtkViewport
---vbox1 :: GtkVBox
----hb1_template :: GtkHandleBox
-----drawingarea1 :: GtkDrawingArea
----hb1_template :: GtkHandleBox
-----drawingarea1 :: GtkDrawingArea
----hb1_template :: GtkHandleBox
-----drawingarea1 :: GtkDrawingArea
----hb1_template :: GtkHandleBox
-----drawingarea1 :: GtkDrawingArea
... hopefully confirming that the deep clone code is ok.
Here's the code multitrack.py
, that uses the multitrack.glade
above:
# needs gtk-builder (not for libglade)
import pygtk
pygtk.require("2.0")
import gtk
import copy
from pprint import pprint
import inspect
def main():
global window
gladefile = "/tmp/multitrack.glade"
wTree = gtk.Builder()
wTree.add_from_file(gladefile)
window = wTree.get_object("window1")
if not(window): return
print "E: " + str( get_descendant(window, "nofind", level=0, doPrint=True) )
doCopy()
print "E: " + str( get_descendant(window, "nofind", level=0, doPrint=True) )
window.connect("destroy", gtk.main_quit)
window.set_size_request(600, 300)
window.show_all() # must have!
gtk.main()
def doCopy():
global window
# get template object
hb1_ref = get_descendant(window, "handlebox1", level=0, doPrint=False)
#hb1_template = copy.deepcopy(hb1_ref) # GObject non-copyable
hb1_template = deep_clone_widget(hb1_ref)
gtk.Buildable.set_name(hb1_template, "hb1_template")
# get the container to be cleared
vb1 = get_descendant(window, "vbox1", level=0, doPrint=False)
# delete pre-existing in vbox (incl. hb1_ref)
for i in vb1.get_children():
vb1.remove(i)
# create new content
hb1_a = deep_clone_widget(hb1_template)
vb1.pack_start(hb1_a, expand=False, fill=True, padding=0)
hb1_b = deep_clone_widget(hb1_template)
vb1.pack_start(hb1_b, expand=False, fill=True, padding=0)
hb1_c = deep_clone_widget(hb1_template)
vb1.pack_start(hb1_c, expand=False, fill=True, padding=0)
hb1_d = deep_clone_widget(hb1_template)
vb1.pack_start(hb1_d, expand=False, fill=True, padding=0)
if 0: #small deep_clone_test
print ".....>"
vb1_ref = get_descendant(window, "vbox1", level=0, doPrint=False)
vb1_copy = deep_clone_widget(vb1_ref)
print "EEEEE "+ str( get_descendant(vb1_copy, "nofind", level=0, doPrint=True) )
print ".....<"
# https://stackoverflow.com/questions/20461464/how-do-i-iterate-through-all-gtk-children-in-pygtk-recursively
def get_descendant(widget, child_name, level, doPrint=False):
if widget is not None:
if doPrint: print("-"*level + str(gtk.Buildable.get_name(widget)) + " :: " + widget.get_name())
else:
if doPrint: print("-"*level + "None")
return None
if(gtk.Buildable.get_name(widget) == child_name):
return widget;
if (hasattr(widget, 'get_child') and callable(getattr(widget, 'get_child')) and child_name != ""):
child = widget.get_child()
if child is not None:
return get_descendant(child, child_name,level+1,doPrint)
elif (hasattr(widget, 'get_children') and callable(getattr(widget, 'get_children')) and child_name !=""):
children = widget.get_children()
found = None
for child in children:
if child is not None:
found = get_descendant(child, child_name,level+1,doPrint)
if found: return found
def deep_clone_widget(widget, inparent=None):
dbg = 0
widget2 = clone_widget(widget)
if inparent is None: inparent = widget2
if (hasattr(widget, 'get_child') and callable(getattr(widget, 'get_child'))):
child = widget.get_child()
if child is not None:
if dbg: print "A1 inp", inparent.get_name(), "w2", widget2.get_name()
childclone = deep_clone_widget(child, widget2)
if dbg: print "A2", childclone.get_name()
widget2.add( childclone )
#return inparent
elif (hasattr(widget, 'get_children') and callable(getattr(widget, 'get_children')) ):
children = widget.get_children()
for child in children:
if child is not None:
if dbg: print "B1 inp", inparent.get_name(), "w2", widget2.get_name()
childclone = deep_clone_widget(child, widget2)
if dbg: print "B2", childclone.get_name()
inparent.add( childclone )
#return childclone
return widget2
# https://stackoverflow.com/questions/1321655/how-to-use-the-same-widget-twice-in-pygtk
def clone_widget(widget):
print(" > clone_widget in: " + str(gtk.Buildable.get_name(widget)) + " :: " + widget.get_name() )
widget2=widget.__class__()
# these must go first, else they override set_name from next stage
for pspec in widget.props:
if pspec.name not in ['window', 'child', 'composite-child', 'child-detached', 'parent']:
#print(" > " + pspec.name)
try:
widget2.set_property(pspec.name, widget.get_property(pspec.name))
except Exception as e:
print e
# here set_name is obtained
for prop in dir(widget):
if prop.startswith("set_") and prop not in ["set_buffer"]:
#print(" ! " + prop + " ")
prop_value=None
try:
prop_value=getattr(widget, prop.replace("set_","get_") )()
except:
try:
prop_value=getattr(widget, prop.replace("set_","") )
except:
continue
if prop_value == None:
continue
try:
#print(" > " + prop + " " + prop_value )
if prop != "set_parent": # else pack_start complains: assertion `child->parent == NULL' failed
getattr(widget2, prop)( prop_value )
except:
pass
gtk.Buildable.set_name(widget2, gtk.Buildable.get_name(widget))
## style copy:
#for pspec in gtk.widget_class_list_style_properties(widget):
# print pspec, widget.style_get_property(pspec.name)
# #gtk.widget_class_install_style_property(widget2, pspec) #nope, for class only, not instances!
# none of these below seem to change anything - still getting a raw X11 look after them:
widget2.ensure_style()
widget2.set_style(widget.get_style().copy())
widget2.set_style(gtk.widget_get_default_style())
widget2.modify_style(widget.get_modifier_style())
#widget2.set_default_style(widget.get_default_style().copy()) # noexist; evt. deprecated? https://stackoverflow.com/questions/19740162/how-to-set-default-style-for-widgets-in-pygtk
# this is the right one, so we don't get raw X11 look:
widget2.set_style(widget.rc_get_style())
return widget2
if __name__ == "__main__":
main()
来源:https://stackoverflow.com/questions/20460848/templating-overflowing-content-with-glade-and-pygtk