4

The code below creates a column of three labels. I would like to take the middle label, and replace it with another widget using the text from the label after the initial creation of the UI.

My actual use case is to take a GTKBuilder populated UI, and replace any particular named label with a dynamically wrapped label at run time. (I used a button here because it's simple but distinct.) Then I can still use Glade to set up the UI, including the labels, and not pepper my Python code with errant labels and strings if I later want to make a label wrap.

The code as it stands does not work - the new button gets added to the end of the column, and I want it to remain in the middle, where label2 was to begin with. What can I do, preferably in wrap_in_button, to make sure it ends up in the correct place? I'd rather keep it general, since the parent may be a Box or a Table or any general Container.

import pygtk
import gtk

def destroy(widget, data=None):
    gtk.main_quit()

def wrap_in_button(label):
    text = label.get_text()
    button = gtk.Button(text)

    parent = label.get_parent()

    if parent:
        parent.remove(label)
        parent.add(button)

def Main():
    # Pretend that this chunk is actually replaced by GTKBuilder work
    # From here...
    window = gtk.Window()
    window.connect('destroy', destroy)

    box = gtk.VBox()
    window.add(box)

    label1 = gtk.Label("Label 1")
    label2 = gtk.Label("Label 2")
    label3 = gtk.Label("Label 3")

    box.pack_start(label1)
    box.pack_start(label2)
    box.pack_start(label3)

    # ...up to here

    # Comment this to see the original layout
    wrap_in_button(label2)

    window.show_all()

    gtk.main()

if __name__ == "__main__":
    Main()
Community
  • 1
  • 1
detly
  • 29,332
  • 18
  • 93
  • 152
  • If what you need is only to replace the TEXT in a label (instead of replacing the label itself) you could use the `label.set_text()` method of your label instance. – heltonbiker May 13 '11 at 14:10
  • @heltonbiker - No, I'd be replacing it with the dynamically wrapping label widget (see link). – detly May 13 '11 at 14:37

4 Answers4

8

I found the solution in the code of the gazpacho interface designer.

You can use this function:

def replace_widget(current, new):
    """
    Replace one widget with another.
    'current' has to be inside a container (e.g. gtk.VBox).
    """
    container = current.parent
    assert container # is "current" inside a container widget?

    # stolen from gazpacho code (widgets/base/base.py):
    props = {}
    for pspec in gtk.container_class_list_child_properties(container):
        props[pspec.name] = container.child_get_property(current, pspec.name)

    gtk.Container.remove(container, current)
    container.add(new)

    for name, value in props.items():
        container.child_set_property(new, name, value)

The key seems to be to transfer the child properties from the old widget to the new one after running gtk.Container.add().

Applied to your example, this would be:

import pygtk
import gtk

def replace_widget(current, new):
    """
    Replace one widget with another.
    'current' has to be inside a container (e.g. gtk.VBox).
    """
    container = current.parent
    assert container # is "current" inside a container widget?

    # stolen from gazpacho code (widgets/base/base.py):
    props = {}
    for pspec in gtk.container_class_list_child_properties(container):
        props[pspec.name] = container.child_get_property(current, pspec.name)

    gtk.Container.remove(container, current)
    container.add(new)

    for name, value in props.items():
        container.child_set_property(new, name, value)

def destroy(widget, data=None):
    gtk.main_quit()

def wrap_in_button(label):
    text = label.get_text()
    button = gtk.Button(text)

    replace_widget(label, button)

def Main():
    # Pretend that this chunk is actually replaced by GTKBuilder work
    # From here...
    window = gtk.Window()
    window.connect('destroy', destroy)

    box = gtk.VBox()
    window.add(box)

    label1 = gtk.Label("Label 1")
    label2 = gtk.Label("Label 2")
    label3 = gtk.Label("Label 3")

    box.pack_start(label1)
    box.pack_start(label2)
    box.pack_start(label3)

    # ...up to here

    wrap_in_button(label2)

    window.show_all()
    gtk.main()

if __name__ == "__main__":
    Main()

This works for me using Python 2.6.6 and PyGTK 2.17.

As a solution to your original problem, I used label_set_autowrap() from here and it worked most of the time. However, it's not a perfect solution as I wasn't able to correctly right-align auto-wrapped text.

Community
  • 1
  • 1
Matthias
  • 4,524
  • 2
  • 31
  • 50
3

Instead of putting the labels directly into the main container you can put each one into it's own box.

Change this:

label1 = gtk.Label("Label 1")
label2 = gtk.Label("Label 2")
label3 = gtk.Label("Label 3")

box.pack_start(label1)
box.pack_start(label2)
box.pack_start(label3)

To this:

box1 = gtk.HBox()
label1 = gtk.Label("Label 1")
box1.pack_start(label1)

box2 = gtk.HBox()
label2 = gtk.Label("Label 2")
box2.pack_start(label2)

box3 = gtk.HBox()
label3 = gtk.Label("Label 3")
box3.pack_start(label3)

box.pack_start(box1)
box.pack_start(box2)
box.pack_start(box3)

The rest of the code can stay the same. You just have to make sure that you only have 1 child widget in those boxes at a time.

John
  • 544
  • 4
  • 4
0

Similar to Matthias'es answer, I wrote the code below once:

it doesn't keep all the properties though, thus it only works with some containers, those that I cared to test at the time.

Made for python 2.4.x and pygtk of same age, might still work for you.

def replace_widget(cur, replace):
  """replace cur widget with another in a container keeping child properties"""
  con = cur.get_parent()
  pos = con.child_get_property(cur, "position")
  pak = con.query_child_packing(cur)
  con.remove(cur)
  if replace.get_parent(): replace.get_parent().remove(replace)
  con.add_with_properties(replace, "position", pos)
  con.set_child_packing(replace, *pak)
Dima Tisnek
  • 11,241
  • 4
  • 68
  • 120
0

The pygtk docs has some useful information on the add function:

This method is typically used for simple containers such as gtk.Window, gtk.Frame, or gtk.Button that hold a single child widget. For layout containers that handle multiple children such as gtk.Box or gtk.Table, this function will pick default packing parameters that may not be correct.

For containers with more than one child item, it goes on to say that they have specialised functions for adding items. In your case, gtk.Box has three options: pack_start, pack_end, and reorder_child.

I'm not sure how it will handle using pack_start (or pack_end) after an item has been removed. Failing that, reorder_child sounds like it might help.

If none of these work, you could always remove and re-add all widgets in the correct order using pack_start.

Jon
  • 9,815
  • 9
  • 46
  • 67
  • I can't use `pack_start` and friends on a `Table` though. And removing and re-adding all widgets presumes that there weren't special settings to do with packing, padding etc. tweaked in Glade. – detly Jun 24 '10 at 06:38
  • Table has an `attach` function. Will that help? Under what circumstances do you want to dynamically change the control, anyway? won't the user be confused by that? – Jon Jun 24 '10 at 06:48
  • The label might be situated inside **any** `gtk.Container`. And it's not changing dynamically, it's changing when the interface is first constructed. I just want to keep my labels and their text in the gtkbuilder files, and the code in the Python files. – detly Jun 24 '10 at 07:14
  • Does that mean you're using the labels in glade just as placeholders? If you don't have a real requirement to swap widgets at run-time, I wouldn't do it. Just take the labels out of glade and add the correct widgets to the correct container. – Jon Jun 24 '10 at 07:36
  • That's what I was hoping to avoid. The labels are there in Glade so I can see roughly what is where. It also means I don't have to screw around with translation stuff in Python. Otherwise I have a UI that looks like Swiss cheese while I'm trying to lay it out, and a lot of code duplication. The middle ground for this is to wrap any label in a named `gtk.Alignment`, and then do the swap on the child of that. – detly Jun 24 '10 at 08:06
  • The way I see it, you're writing more complicated code to work around a problem that doesn't affect users. In the app I'm developing, I have a treeview where the model and columns are created in code because of a problem with glade. I think you'll have less problems just building the gui by hand for these labels than trying to automate it just for the sake of seeing it in the designer. Anyway, is it really that much trouble to write? – Jon Jun 24 '10 at 08:29
  • 1
    No, but what kind of programmer would I be if I didn't spend twice as long as it would have taken looking for an "easier" way? – detly Jun 24 '10 at 08:44