PyGTK: dynamic label wrapping

核能气质少年 提交于 2019-11-29 06:45:12

VMware's libview has a widget called WrapLabel which should do what you want, but it's in C++. A Python translation is available in the Meld repository (separated out from busybox.py).

Here's a one-line variation on killown's solution:

label.connect('size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1))

The above will make sure that the label takes on the width allocated to it, so that word-wrapping is happy.

Not sure why there's a "-1" for the width, but it seems harmless!

example for resize and wrap the label dynamically:

EDIT:

import gtk

class DynamicLabel(gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self)

        self.set_title("Dynamic Label")
        self.set_size_request(1, 1)
        self.set_default_size(300,300) 
        self.set_position(gtk.WIN_POS_CENTER)

        l = gtk.Label("Dynamic Label" * 10)
        l.set_line_wrap(True)
        l.connect("size-allocate", self.size_request)

        vbox = gtk.VBox(False, 2)
        vbox.pack_start(l, False, False, 0)

        self.add(vbox)
        self.connect("destroy", gtk.main_quit)
        self.show_all()

    def size_request(self, l, s ):
        l.set_size_request(s.width -1, -1)

DynamicLabel()
gtk.main()

You can use this. Not sure where it came from originally. Create your label and then call label_set_autowrap(label)

def label_set_autowrap(widget): 
    "Make labels automatically re-wrap if their containers are resized.  Accepts label or container widgets."
    # For this to work the label in the glade file must be set to wrap on words.
    if isinstance(widget, gtk.Container):
        children = widget.get_children()
        for i in xrange(len(children)):
            label_set_autowrap(children[i])
    elif isinstance(widget, gtk.Label) and widget.get_line_wrap():
        widget.connect_after("size-allocate", _label_size_allocate)


def _label_size_allocate(widget, allocation):
    "Callback which re-allocates the size of a label."
    layout = widget.get_layout()
    lw_old, lh_old = layout.get_size()
    # fixed width labels
    if lw_old / pango.SCALE == allocation.width:
        return
    # set wrap width to the pango.Layout of the labels
    layout.set_width(allocation.width * pango.SCALE)
    lw, lh = layout.get_size()  # lw is unused.
    if lh_old != lh:
        widget.set_size_request(-1, lh / pango.SCALE)

In GTK 3, this is done automatically using height-for-width and width-for-height size requests.

I just wanted to share how I made Kai's solution work with PyGtk and Glade-3 using wraplabel.py.

I didn't want to have to modify Glade catalogs to get WrapLabel in Glade and I'm not sure if that would work anyway with a PyGtk component. I was however pleasantly surprised to find that simply by putting the WrapLabel class in the python environment before calling into gtk.Bilder() it will load the class as a component.

So now the only problem was to get the WrapLabels into the glade file. First I changed the names of all the labels I wanted to wrap to wlabel###, where ### is some number. Then I used a sed expression to replace the classes, but since I didn't want to add extra processing to the build system I ended up adding the following in python:

import re
import gtk
from wraplabel import WrapLabel

. . .

# Filter glade
glade = open(filename, 'r').read()
glade = re.subn('class="GtkLabel" id="wlabel',
                'class="WrapLabel" id="wlabel', glade)[0]

# Build GUI
builder = gtk.Builder()
builder.add_from_string(glade)

I'm sure there are more elegant ways to do the substitution but this worked well. However, I found I had one more problem. When I opened one of the dialogs with the wrapped labels some of the text was not visible. Then when I resized the window with the mouse, even a little bit, everything would snap in to place. Some how the labels were not getting the right sizes when initialized. I fixed this with another work around. When opening one of the dialogs I run this code:

def open_dialog(self, dialog):
    # Hack to make WrapLabel work
    dims = dialog.get_size()
    dialog.resize(dims[0] + 1, dims[1] + 1)
    dialog.present()
    dialog.resize(*dims)

This just sets the size one point too big, presents the window and then resets to the correct size. This way the WrapLabels get the signal to resize after the dialog layout is complete.

There is still one small glitch. When you open the dialog sometimes you can see the text snapping in to place. Otherwise, it seems to work.

NOTE 1) All the variations of calling label.set_size_request(size.width - 1, -1) on size-allocate caused the GUI to lockup for me. Probably depends on the parent widgets.

NOTE 2) Another solution is to use TextView's and disable editing, the cursor and sensitivity. However, TextViews have a different color than the background which is difficult to fix in the face of Gtk themes. The other problem with this solution is that TextViews capture mouse scroll events. This makes mouse scrolling a box with these TextViews inside it very erratic. I tried many things to solve the mouse scroll problem but never did figure it out. Otherwise using TextViews does work. So you might consider this if your text labels are not inside a scroll pane and the WrapLabel solution doesn't work for you.

I have modified the code that was in the other answers to get a callback that behaved a little better:

def on_label_size_allocate(self, label, allocation, *args):
  """ Callback that re-allocates the size of a label to improve word wrap. """
  layout = label.get_layout()
  layout.set_width((allocation.width-20) * pango.SCALE)
  _, lh = layout.get_pixel_size()
  label.set_size_request(-1, lh+6)

The -20 and +6 numbers were obtained by trial and error. It would be nice to get them from somewhere in the widgets, but I couldn't find any relationship to the widgets. This makes the label resize fine both in growing and shrinking and lines are not cut.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!