在TreeViewColumn头部使用PyGTK Entry控件

8 投票
3 回答
3000 浏览
提问于 2025-04-16 13:11

我想知道怎么才能让一个 gtk.Entry 组件在 gtk.TreeViewColumn 的标题里可以被聚焦或者编辑。我试过这样做:

# Create tree-view.
treeview = gtk.TreeView()

#...

# Create column.
renderer = gtk.CellRendererText()
column = gtk.TreeViewColumn(None, renderer, text=0)

# Set column header.
header = gtk.VBox()

title = gtk.Label("Column")
header.pack_start(title)

filter = gtk.Entry()
#...
header.pack_start(filter)

header.show_all()
column.set_widget(header)

# Add column
treeview.append_column(column)

但是在列标题里的 Entry 组件无法编辑,也无法聚焦。我尝试把 'clickable' 设置为 TrueFalse,但都没用。我现在用的是 pygtk 2.21.0-0ubuntu1 和 libgtk 2.22.0-0ubuntu1,系统是 Ubuntu 10.04。如果有人能帮忙,我会非常感激。

补充说明:

问题出在 GtkTreeViewColumn 的标题是怎么显示的。标题组件放在一个 GtkAlignment 里,而这个 GtkAlignment 的父组件是一个 GtkHBox,再往上是一个 GtkButton,最后是 GtkTreeView。这个 GtkButton 拦截了我的 GtkEntry,导致它无法聚焦和接收鼠标输入。

3 个回答

0

如果这不是你想要的答案,我建议你使用一个标准且常用的方法:使用 treeview.set_search_column(COLUMN_INDEX)。这样默认情况下不会显示任何输入框(这让界面看起来更简洁),但当用户开始输入(并且树形视图有焦点时),就会弹出一个输入框,GTK会自动进行搜索和过滤。

如果你坚持要让搜索框一直可见,可以通过 treeview.set_headers_visible(False) 来隐藏树形视图的标题,然后在树形视图上方添加一个自定义的 HBox(里面放一个标签和输入框)。

4

这个API自从提问以来已经有了很大的变化,所以我想分享一个更新的答案。(我在处理类似问题时偶然发现了这个,虽然我当时是想在列标题中放两个按钮,而不是一个输入框。)

首先,给大家一些背景信息。正如问题编辑中提到的,问题的根源在于TreeViewColumn的结构。列的标题是一个按钮,当你使用set_widget时,这个小部件就成了按钮的子元素。(这点很容易被忽视,因为除非你把列设置为可点击,否则标题不会像按钮那样响应。此外,文档也没有帮助,因为它似乎假设每个人都已经知道这一点。)另一个导致问题的原因是按钮处理事件的方式。与大多数响应事件的小部件不同,按钮在Gdk.Window层级中没有自己的位置。相反,它在被创建时会生成一个特殊的事件窗口。访问这个窗口的方法是按钮特有的:get_event_window(与更通用的get_windowget_parent_window不同)。这个事件窗口在按钮上方隐形存在,收集事件,然后再传递给按钮的任何子元素。因此,你放在列标题中的小部件无法接收到进行交互所需的事件。

被接受的解决方案是绕过这个障碍的一种方法,当时这是个不错的答案。然而,现在有了更简单的方法。(我应该提到这是一个GTK+的问题,与使用的语言绑定无关。就我个人而言,我使用的是C++绑定。我还查看了GTK+的源文件——用C写的——以确认这是核心GTK+的行为,而不是某种绑定的副作用。)

1) 找到标题按钮。

如果column是我们讨论的TreeViewColumn,那么获取按钮的API现在变得非常简单:

header_button = column.get_button()

在3.0版本中添加了get_button方法,这个版本大约是在提问六个月后发布的。真是太接近了。

2) 将事件从按钮传递到输入框。

又过了四年(3.18版本),这一步才变得简单。关键的进展是set_pass_through,这个方法可以告诉事件窗口让事件通过。正如文档所说:“在网页术语中,这可以称为‘pointer-events: none’。”

def pass_through_event_window(button, event):
    if not isinstance(button, gtk.Button):
        raise TypeError("%r is not a gtk.Button" % button)
    event_window = button.get_event_window()
    event_window.set_pass_through(True)

剩下的技巧就是时机。事件窗口在按钮被创建之前是不会生成的,所以需要连接到按钮的realize信号。

header_button.connect('realize', pass_through_event_window)

就这样了(没有第三步)。现在事件会传递到输入框或你放在列标题中的任何小部件。

如果我搞错了语法,我深表歉意;我是在从C++绑定中进行转换。如果有错误,我希望有经验的Python高手能帮我纠正。

13

为了让一个 GtkEntryGtkTreeView 的头部可以被聚焦,我需要做以下几步:

1) 找到头部的 GtkButton

def find_closest_ancestor(widget, ancestor_class):
    if not isinstance(widget, gtk.Widget):
        raise TypeError("%r is not a gtk.Widget" % widget)
    ancestor = widget.get_parent()
    while ancestor is not None:
        if isinstance(ancestor, ancestor_class):
            break;
        ancestor = ancestor.get_parent() if hasattr(ancestor, 'get_parent') and callable(ancestor.get_parent) else None
    return ancestor

2) 把头部 GtkButton 的点击事件(也就是 button-press-event 信号)传递给 GtkEntry

def propagate_button_press_event(parent, event, *data):
    parent_alloc = parent.get_allocation()
    x = parent_alloc.x + int(event.x)
    y = parent_alloc.y + int(event.y)
    children = parent.get_children()
    print "Propagating event:%r" % event
    print "- from parent:%r" % parent
    while children:
        for child in children:
            child_alloc = child.get_allocation()
            if child_alloc.x <= x <= child_alloc.x + child_alloc.width and child_alloc.y <= y <= child_alloc.y + child_alloc.height:
                print "- to child:%r" % child
                if child.get_property('can-focus'):
                    event.send_event = True
                    child.grab_focus()
                    child.emit('button-press-event', event, *data)
                    return True
                else:
                    children = child.get_children() if hasattr(child, 'get_children') and callable(child.get_children) else None
                    break;
        else:
            children = None
    return False

3) 把焦点事件(也就是 focus-in-event 信号)从头部的 GtkButton 传递给 GtkEntry

def propagate_focus_in_event(parent, event, *data):
    print 'focus-in', parent, event
    child = parent.get_child()
    if child.get_property('can-focus'):
        child.grab_focus()
    else:
        if not child.child_focus(gtk.DIR_TAB_FORWARD):
            parent.get_toplevel().child_focus(gtk.DIR_TAB_FORWARD)
    return True

示例:

# Fix style glitches
_gtk_styles = """
    # Use the default GtkEntry style for GtkEntry widgets in treeview headers.
    widget "*.treeview-header-entry" style "entry" 
"""
gtk.rc_parse_string(_gtk_styles)

# Columns
_columns = [
    (0, "Title"),
    (1, "Description")
    # etc.
]

# Create tree-view.
items_view = gtk.TreeView(self.items_store)
items_view.show()

# Setup treeview columns.
renderer = gtk.CellRendererText()
for column in _columns:
    column_index, column_title, column_filter = column
    column_view = gtk.TreeViewColumn(None, renderer, text=column_index)
    column_view.set_clickable(True)

    column_widget = gtk.VBox()
    column_widget.show()

    column_align = gtk.Alignment(0, 0, 0, 0)
    column_align.show()
    column_widget.pack_start(column_align)
    column_label = gtk.Label(column_title)
    column_label.show()
    column_align.add(column_label)

    column_entry = gtk.Entry()
    column_entry.set_name('treeview-header-entry')
    column_entry.show()
    column_widget.pack_start(column_entry)

    column_view.set_widget(column_widget)
    items_view.append_column(column_view)

# Setup column headers.
columns = items_view.get_columns()
for column in columns:
    column_widget = column.get_widget()
    column_header = find_closest_ancestor(column_widget, gtk.Button)
    if column_header:
        column_header.connect('focus-in-event', propagate_focus_in_event)
        column_header.connect('button-press-event', propagate_button_press_event)
        column_header.set_focus_on_click(False)

撰写回答