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.
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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With