TreeViewColumn 标题中的 PyGTK Entry 小部件

发布于 2024-10-20 20:36:21 字数 947 浏览 2 评论 0原文

如何使 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。我在 Ubuntu 10.04 上使用 pygtk 2.21.0-0ubuntu1 和 libgtk 2.22.0-0ubuntu1。任何帮助将不胜感激。

编辑:

问题源于 GtkTreeViewColumn 标题的显示方式。标题小部件放置在 GtkAlignment 内,其父级是 GtkHBox,其父级是 GtkButton,其父级最终是 GtkTreeView代码>. GtkButton 正在拦截并阻止我的 GtkEntry 获得焦点并接收鼠标输入。

How can I make a gtk.Entry widget focusable or editable within a gtk.TreeViewColumn header/title? I've tried this:

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

But the Entry widget in the column header is not editable and will not focus. I've tried setting 'clickable' to both True and False. I'm using pygtk 2.21.0-0ubuntu1 and libgtk 2.22.0-0ubuntu1 on Ubuntu 10.04. Any help would be greatly appreciated.

EDIT:

The issue stems from how a GtkTreeViewColumn header is displayed. The header widget is placed inside a GtkAlignment whose parent is a GtkHBox whose parent is a GtkButton whose parent is finally the GtkTreeView. The GtkButton is intercepting and preventing my GtkEntry from being focused and receiving mouse input.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

谈场末日恋爱 2024-10-27 20:36:21

为了使 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) 将 button-press-event 信号从标头 GtkButton 传播到 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)

In order to make a GtkEntry focusable within a GtkTreeView header I had to:

1) Find the header 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) Propagate the button-press-event signal from the header GtkButton to the 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) Propagate the focus (i.e., focus-in-event signal) from the header GtkButton to the 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

Example:

# 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)
川水往事 2024-10-27 20:36:21

自从提出这个问题以来,API 已经不断发展,所以我想我应该发布更新的答案。 (我在处理类似问题时偶然发现了这一点,尽管在我的例子中,我试图在列标题中放置两个按钮,而不是条目。)

首先,一些背景知识。正如问题编辑中提到的,问题源于 TreeViewColumn 的结构方式。列的标题是一个 Button,当您 set_widget 时,该小部件将成为 Button 的后代。 (这很容易被忽视,因为除非您将列设置为可单击,否则标题不会像按钮一样响应。此外,文档也没有帮助,因为它似乎假设每个人都已经知道这一点。)问题的另一个原因是按钮收集事件的方式。与大多数响应事件的小部件不同,按钮在 Gdk.Window 层次结构中没有自己的位置。相反,它在实现时会创建一个特殊的事件窗口。访问此窗口的方法是特定于按钮的: get_event_window(与更通用的 get_windowget_parent_window 不同)。该事件窗口不可见地位于 Button 上方,在事件向下传递到 Button 的任何后代之前收集事件。因此,您放置在列标题中的小部件不会接收交互所需的事件。

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

1)找到标题按钮。

如果column是有问题的TreeViewColumn,那么用于获取按钮的API现在很简单:

header_button = column.get_button()

get_button 方法是在 3.0 版本中添加的,在提出这个问题大约六个月后被标记。这么近。

2) 将事件从按钮传播到条目。

又花了四年时间(版本 3.18)才简化了这一步。关键的开发是 set_pass_through,它可以告诉事件窗口让事件通过。正如文档所述:“在网络术语中,这将被称为‘指针事件:无’。”

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)

剩下的技巧就是时机。在实现 Button 之前不会创建事件窗口,因此按顺序连接到 Button 的 realize 信号。

header_button.connect('realize', pass_through_event_window)

就是这样(没有第 3 步)。事件现在将传播到条目或您放入列标题中的任何小部件。

如果我弄乱了语法,我深表歉意;我正在从 C++ 绑定进行翻译。如果有错误,我会请求Python大师来纠正它们。

The API has evolved since this question was asked, so I thought I would post an updated answer. (I had stumbled across this while dealing with a similar issue, although in my case I was trying to put two Buttons in the column header, not an Entry.)

First, some background. As mentioned in the question's edit, the issue stems from the way a TreeViewColumn is structured. The header of the column is a Button, and when you set_widget, that widget becomes a descendant of the Button. (This can be easily overlooked since the header does not respond like a button unless you set the column to be clickable. Also, the documentation does not help, as it seems to assume everyone already knows this.) A further cause of the issue is the way Buttons collect events. Unlike most widgets that respond to events, a Button does not have its own spot in the Gdk.Window hierarchy. Instead, it creates a special event window when it is realized. The method for accessing this window is Button-specific: get_event_window (distinct from the more generic get_window and get_parent_window). This event window sits invisibly above the Button, collecting events before they trickle down to any descendants of the Button. Hence, the widget you place in the column header does not receive the events required for interactivity.

The accepted solution is one way around this obstacle, and it was a worthy answer at the time. However, there is now an easier way. (I should mention that this is a GTK+ issue, independent of the language binding being used. Personally, I was using the C++ binding. I also peeked at the GTK+ source files – in C – to confirm that this is core GTK+ behavior and not some artifact of the binding.)

1) Find the header Button.

If column is the TreeViewColumn in question, the API for getting the button is now simply:

header_button = column.get_button()

The get_button method was added in version 3.0, which was tagged about six months after this question was asked. So close.

2) Propagate events from the Button to the Entry.

It took another four years (version 3.18) for this step to simplify. The key development was set_pass_through, which can tell the event window to let events pass through. As the documentation states: "In the terminology of the web this would be called '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)

The remaining trick is one of timing. The event window is not created until the Button is realized, so connecting to the Button's realize signal is in order.

header_button.connect('realize', pass_through_event_window)

And that's it (there is no step 3). Events will now propagate to the Entry or whatever widget you put in the column header.

My apologies if I messed up the syntax; I am translating from the C++ binding. If there are errors, I would request a kindly Python guru to correct them.

烟燃烟灭 2024-10-27 20:36:21

除非这不是你的答案,但我建议你使用标准和通用的方法:使用treeview.set_search_column(COLUMN_INDEX),默认情况下不会显示任何条目(并使 UI 更干净),但是当用户开始输入(将焦点放在树视图上),将显示一个弹出输入条目,搜索和过滤将由 GTK 本身自动完成。

如果您坚持认为搜索条目应始终可见,请使用 treeview.set_headers_visible(False) 删除树视图标头,并在树视图上方添加自定义 HBox(包含标签和条目)。

Unless this is not your answer, but i suggest you to use the standard and common method: using treeview.set_search_column(COLUMN_INDEX), no entry will be displayed by default (and makes UI cleaner) but when the user starts typing (with focus on that treeview) a popup input entry will be displayed, and searching and filtering will be done by GTK itself automatically.

If you persist that the search entry should be always visible, remove treeview header using treeview.set_headers_visible(False), and add a custom HBox (containing label and entry) above the treeview.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文