PyGTK:动态标签换行

9

在GTK中,标签不会在父级更改时动态调整大小,这是一个已知的错误/问题。这是那些非常烦人的小细节之一,如果可能的话,我想绕过它。

我遵循了16软件上的方法,但是根据免责声明,您不能将其缩小。因此,我尝试了在其中一个评论中提到的技巧(信号回调中的set_size_request调用),但这会导致某种无限循环(您可以试试)。

有没有其他想法?

(您不能仅在调用期间阻止信号,因为正如print语句所示,问题出现在函数离开后。)

以下是代码。如果您运行并尝试将窗口调整为较大或较小,则可以看到我想表达的意思。(如果要查看原始问题,请注释掉“连接到大小分配信号”后面的行,然后运行它,并将窗口调整为更大。)

Glade文件(“example.glade”):

<?xml version="1.0"?>
<glade-interface>
  <!-- interface-requires gtk+ 2.16 -->
  <!-- interface-naming-policy project-wide -->
  <widget class="GtkWindow" id="window1">
    <property name="visible">True</property>
    <signal name="destroy" handler="on_destroy"/>
    <child>
      <widget class="GtkLabel" id="label1">
        <property name="visible">True</property>
        <property name="label" translatable="yes">In publishing and graphic design, lorem ipsum[p][1][2] is the name given to commonly used placeholder text (filler text) to demonstrate the graphic elements of a document or visual presentation, such as font, typography, and layout. The lorem ipsum text, which is typically a nonsensical list of semi-Latin words, is a hacked version of a Latin text by Cicero, with words/letters omitted and others inserted, but not proper Latin[1][2] (see below: History and discovery). The closest English translation would be "pain itself" (dolorem = pain, grief, misery, suffering; ipsum = itself).</property>
        <property name="wrap">True</property>
      </widget>
    </child>
  </widget>
</glade-interface>

Python 代码:

#!/usr/bin/python

import pygtk
import gobject
import gtk.glade

def wrapped_label_hack(gtklabel, allocation):
    print "In wrapped_label_hack"
    gtklabel.set_size_request(allocation.width, -1)
    # If you uncomment this, we get INFINITE LOOPING!
    # gtklabel.set_size_request(-1, -1)
    print "Leaving wrapped_label_hack"

class ExampleGTK:

    def __init__(self, filename):
        self.tree = gtk.glade.XML(filename, "window1", "Example")
        self.id = "window1"
        self.tree.signal_autoconnect(self)

        # Connect to the size-allocate signal
        self.get_widget("label1").connect("size-allocate", wrapped_label_hack)

    def on_destroy(self, widget):
        self.close()

    def get_widget(self, id):
        return self.tree.get_widget(id)

    def close(self):
        window = self.get_widget(self.id)
        if window is not None:
            window.destroy()
        gtk.main_quit()

if __name__ == "__main__":
    window = ExampleGTK("example.glade")
    gtk.main()

可能更简单的方法:请查看我的帖子,位于最后。https://dev59.com/jkvSa4cB1Zd3GeqPcCrS - Murat G
7个回答

4
这是killown解决方案的一行式变体:

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

上述代码将确保标签占用分配给它的宽度,这样就可以正常换行了。
不确定为什么会有“-1”的宽度,但似乎没有影响!

添加第二行... label.connect_after("realize", lambda label: label.set_size_request(width=-1, height=-1)) ... 以使其在某些容器和/或滚动窗口的特定配置中在初始绘制时正确调整大小 - Dave

3
动态调整和换行标签的示例:

编辑:

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()

@killown - 运行这段代码,动态调整大小至300x300以下是不可能的,对吧?在这里肯定是这种情况。 - detly
删除 set_size_request有点 有效,但它的行为有点奇怪。我认为也许他们在我的问题中提到的问题上进行了半修复。 - detly
@detly:哦,抱歉,是的,无法调整大小低于300x300,但有一个问题,请尝试删除set_size_request,然后最大化窗口,之后,您将无法再次调整窗口宽度,因此使用set_size_request进行修复。 - killown
修复:使用set_size_request()和set_default_size()。它将以小于300x300的尺寸工作。 - killown

3

VMware的libview有一个名为WrapLabel的小部件,它应该可以满足您的需求,但它是用C++编写的。在Meld repository中提供了Python版本的翻译(从busybox.py中分离出来)。


哇,我绝对不可能找到这个。谢谢。我通过在Glade中创建一个包含HBox并添加插入代码的方式手动添加它。(如果你非常勤奋,你可以为它创建一个Glade目录文件...)。在添加标签后,我还花了大约半个小时才意识到需要在HBox上使用“show_all()”,所以我希望这个评论能够帮助其他人避免这种麻烦... - detly

2

您可以使用这个功能。不确定它最初来自哪里。创建您的标签,然后调用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)

很不错的东西,但出于某种原因,当标签足够大以便在单行上显示文本时,它仍然会换行,因此所有标签的“y对齐”值必须设置为0,否则文本将向上移动并消失在标签内部... - Giorgio Gelardi

1

我想分享一下我如何使用wraplabel.py在PyGtk和Glade-3中使Kai的解决方案生效。

我不想修改Glade目录以在Glade中获取WrapLabel,而且我也不确定这是否适用于PyGtk组件。但令人惊喜的是,只需在调用gtk.Bilder()之前将WrapLabel类放入Python环境中,它就会将该类加载为组件。

现在唯一的问题是如何将WrapLabels添加到glade文件中。首先,我将所有要包装的标签名称更改为wlabel###,其中###是某个数字。然后,我使用sed表达式替换了类名,但由于我不想向构建系统添加额外的处理,因此最终在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)

我相信有更优雅的方法来做这个替换,但这个也很好用。但是,我发现我还有一个问题。当我打开其中一个带包装标签的对话框时,有些文本是不可见的。然后当我用鼠标调整窗口大小时,即使只有一点点,所有东西都会自动排列好。不知何故,在初始化时标签的大小不正确。我用另一种方法解决了这个问题。当我打开其中一个对话框时,运行这段代码:

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)

这只是将大小设置得稍微大了一点,然后呈现窗口,然后重新设置正确的大小。这样一来,WrapLabels 就会在对话框布局完成后收到调整大小的信号。

仍然存在一个小问题。有时候打开对话框时,您可以看到文本正在快速对齐。否则,它似乎可以正常工作。

注意:1)在 size-allocate 中调用 label.set_size_request(size.width - 1, -1) 的所有变体都会导致 GUI 在我这里挂起。这可能取决于父控件。

注意:2)另一个解决方案是使用 TextView 并禁用编辑、光标和灵敏度。然而,TextView 具有不同于背景的颜色,很难在 Gtk 主题面前进行修复。这个解决方案的另一个问题是 TextView 捕获鼠标滚动事件。这使得鼠标在此类 TextView 所在的框内滚动非常不稳定。我尝试了许多方法来解决鼠标滚动问题,但始终没有找到解决办法。否则,使用 TextView 是行得通的。因此,如果您的文本标签不在滚动窗格中,并且 WrapLabel 解决方案无法为您工作,请考虑使用此解决方案。


1
在GTK 3中,使用高度为宽度和宽度为高度的大小请求自动完成此操作。

0

我修改了其他答案中的代码,以获得一个表现更好的回调函数:

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)

这些-20和+6的数字是通过试错得到的。虽然我无法找到与小部件相关的任何关系,但从小部件中获取它们会很好。这使标签在增长和缩小时都能很好地调整大小,而且不会被切断。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接