Domanda

I have a form interface with a large number of horizontal drop down and text boxes that are inside a GTKscrolledWindow. The form has to be aligned horizontally because there are an unlimited number of forms in the window (think tabel of forms).

Is it (possible/how do I) auto-scroll the GTKScrolledWindow to the right when the operator tabs to the next form control which is off to the right of the screen.?

Related question would be how do I detect if the focused element (button etc) is visible in it's parent scrolledwindow.

Thanks.

È stato utile?

Soluzione

Here's what I did, based on Michy's answer.

To make a scrolled window auto-scroll, run in the constructor:

FocusScroll(scrolledwindow).

    class FocusScroll(object):
    """
    Get a gtk.ScrolledWindow which contains a gtk.Viewport.
    Attach event handlers which will scroll it to show the focused widget.
    """
    def __init__(self, scrolledwindow):
        self.scrolledwindow = scrolledwindow
        self.viewport = scrolledwindow.get_child()
        assert isinstance(self.viewport, gtk.Viewport)
        self.main_widget = self.viewport.get_child()
        self.vadj = scrolledwindow.get_vadjustment()
        self.window = self.get_window(scrolledwindow)

        self.viewport.connect('set-focus-child', self.on_viewport_set_focus_child)

    def get_window(self, widget):
        if isinstance(widget, gtk.Window):
            return widget
        else:
            return self.get_window(widget.get_parent())

    def is_child(self, widget, container):
        """
        Go recursively over all children of container, to check if widget is
        a child of it.
        """
        for child in container.get_children():
            if child is widget:
                return True
            elif isinstance(child, gtk.Container):
                if self.is_child(widget, child):
                    return True
        else:
            return False

    def on_viewport_set_focus_child(self, _viewport, _child):
        idle_add(self.scroll_slide_viewport)

    def scroll_slide_viewport(self):
        """Scroll the viewport if needed to see the current focused widget"""
        widget = self.window.get_focus()
        if not self.is_child(widget, self.main_widget):
            return

        _wleft, wtop = widget.translate_coordinates(self.main_widget, 0, 0)
        wbottom = wtop + widget.get_allocation().height

        top = self.vadj.value
        bottom = top + self.vadj.page_size

        if wtop < top:
            self.vadj.value = wtop
        elif wbottom > bottom:
            self.vadj.value = wbottom - self.vadj.page_size

Altri suggerimenti

I hope you don't mind I am using GTK+ C API in the description, the sollution could be converted to the PyGTK easily and the principles remains the same.

Starting with the second question - if you know which widget to test, you can detect its visibility by calling gtk_widget_translate_coordinates(child, parent, 0, 0, &x, &y) to get the position of the child relative to the parent. By gtk_widget_get_allocation() you get the size of parent and child and you simply test if whole child rectangle is in the scrolled window.

gboolean is_visible_in (GtkWidget *child, GtkWidget *scrolled)
{
    gint x, y;
    GtkAllocation child_alloc, scroll_alloc;
    gtk_widget_translate_coordinates (child, scrolled, 0, 0, &x, &y);
    gtk_widget_get_allocation(child, &child_alloc);
    gtk_widget_get_allocation(scrolled, &scroll_alloc);

    return (x >= 0 && y >= 0) 
        && x + child_alloc.width <= scroll_alloc.width
        && y + child_alloc.height <= scroll_alloc.height;
}

You can obtain the curently focused widget in window by gtk_window_get_focus () or you can detect it when focus is changed.

In the autoscroll problem you can handle "focus" signal connected to the widget which can be focussed or the "set-focus-child" event connected to the container containing the widgets. In the signal handler you should check, if the focused widget is visible. If not, determinate its position and scroll properly.

To do so you have to detect the position of the widget inside the whole scrolled area. If you are using some container which does not support scrolling (such GtkHBox) iside GtkScrolledWindow (adapted by viewport), you can get the coordinates of the focused widget relative to the container by gtk_widget_translate_coordinates() again - now using the container instead of scrolled window. The value of the adjustment, if using GtkViewport, the adjustment value correspond to the position in pixels in the scrolled area, so setting adjustment value to x relative coordinate will do scrolling. So the important part of the handler could be

    GtkWidget *scrolled = /* The scrolled window */
    GtkWidget *container = /* The container in the scrolled window */
    GtkWidget *focused = /* The focused widget */

    GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment( 
        GTK_SCROLLED_WINDOW(scrolled));
    gint x, y;
    gtk_widget_translate_coordinates (focused, container, 0, 0, &x, &y);
    gtk_adjustment_set_value(hadj, min(x, maximal adjustment value allowed);

The maximal adjustment value allowed is adjustment.upper - adjustment.page_size. The focused widget is passed as signal handler argument for both signals, in the case of "set-focus-child" signal you get also the container as argument.

First, the first. How do you detect the focus ? You connect to GtkWidget::focus signal. And in that callback you can scroll the window to whereever you like, how to do that, you need to get the GtkAdjustment of GtkScrolledWindow and move it accordinly to show what you want.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top