Is there a reliable way without Voodoo, to scroll in an GtkListBox to a new inserted Entry?

Hello

I want after inserting a new Entry into a GtkListBox, that it scrolls without any further
action by the User to this new entry; no matter, which Entry was selected before and
what the ListScrollposition was.

Problem: It works “not every time”.

Here is an example:

/*

    I want after inserting a new Entry into a GtkListBox, that it scrolls without any further action by
    the User to this new entry; no matter, which Entry was selected before and what the ListScrollposition was.


    Problem: It works "not every time".

    And if you click on the "Add new Entry"-Button several times in quick succession, it fails most if the time!


    I tried "gtk_widget_show_all" for the viewport and even for the scrolledwindow as well. It doesn* solve the problem.


*/



#include <stdbool.h>
#include <gtk/gtk.h>


#pragma GCC diagnostic ignored "-Wdeprecated-declarations"


GtkWidget *gListBox;
GtkWidget *gWindow;


bool callback_buttonTest1(void);
bool callback_buttonTest2(void);
bool callback_buttonTest3(void);

GtkWidget *add_listentry(GtkListBox *ListP,char *TextP,gint idx);

bool get_line_in_visible_area(GtkListBox *lbP,gint idx);
bool get_selected_line_in_visible_area(GtkListBox *lbP);;
gint get_number_of_ListBoxEntrys(GtkListBox *ListP);

gboolean jump2Entry_idle_(gpointer data);
gboolean jump2Entry_idle__Step2(gpointer data);





// Get the selected Entry into the visible Area
bool callback_buttonTest1(void)
{

    get_selected_line_in_visible_area((GtkListBox *)gListBox);

    return true;
}



// Insert a new Entry to the ListBox
bool callback_buttonTest2(void)
{
    add_listentry((GtkListBox *)gListBox,"new entry",-1);

    return true;
}


// Get the last Entry into the visible Area
bool callback_buttonTest3(void)
{

    get_line_in_visible_area((GtkListBox *)gListBox,get_number_of_ListBoxEntrys((GtkListBox *)gListBox) - 1);

    return true;
}




int main ( void )
{
    GtkWidget *scroll_window;
    GtkWidget *grid_window;
    GtkWidget *buttonTest1,*buttonTest2,*buttonTest3;
    GtkWidget *viewport;

    int i;
    char stringTP[100];

    gtk_init ( NULL, NULL );

    gWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_default_size(GTK_WINDOW(gWindow),600,200);
    g_signal_connect(gWindow,"delete-event",gtk_main_quit,NULL);
    g_signal_connect(gWindow,"destroy",gtk_main_quit,NULL);

    grid_window = gtk_grid_new();
    gtk_container_add( GTK_CONTAINER ( gWindow ), (GtkWidget*)grid_window );


    buttonTest1 = gtk_button_new_with_label("Jump to selected line");
    gtk_grid_attach((GtkGrid*)grid_window,buttonTest1,1,1,10,10);
    g_signal_connect(buttonTest1,"clicked",(GCallback)callback_buttonTest1,NULL);

    buttonTest2 = gtk_button_new_with_label("Add new Entry");
    gtk_grid_attach((GtkGrid*)grid_window,buttonTest2,1,15,10,10);
    g_signal_connect(buttonTest2,"clicked",(GCallback)callback_buttonTest2,NULL);

    buttonTest3 = gtk_button_new_with_label("scroll to end");
    gtk_grid_attach((GtkGrid*)grid_window,buttonTest3,1,29,10,10);
    g_signal_connect(buttonTest3,"clicked",(GCallback)callback_buttonTest3,NULL);


    scroll_window = gtk_scrolled_window_new(NULL,NULL);
    gtk_widget_set_size_request(scroll_window, 500, 100);

    gtk_grid_attach((GtkGrid*)grid_window,scroll_window,11,1,150,150);


    viewport = gtk_viewport_new (NULL,NULL);
    gtk_container_add(GTK_CONTAINER(scroll_window),viewport);

    gListBox = gtk_list_box_new();

    gtk_container_add(GTK_CONTAINER(viewport),gListBox);


    for(i=0;i!=30;i++)
    {
        sprintf(stringTP,"Entry-%02d",i);
        add_listentry((GtkListBox *)gListBox,stringTP,-1);
    }

    // add_listentry : Doesn't work here;


    gtk_widget_show_all(gWindow);
    gtk_main();

}





GtkWidget *add_listentry(GtkListBox *ListP,char *TextP,gint idx)
{
    GtkWidget *labelTP;

    labelTP = gtk_label_new(TextP);
    gtk_label_set_xalign((GtkLabel*)labelTP,0); // Align left


    gtk_list_box_insert(ListP,labelTP,idx);


    g_idle_add_full(G_PRIORITY_LOW,jump2Entry_idle_,(GtkListBox*)ListP,NULL);


    gtk_widget_show_all(gWindow);   // It has to be here, since I did some experiments


    return labelTP;

}




bool get_selected_line_in_visible_area(GtkListBox *lbP)
{
    GtkListBoxRow *LBrow;

    LBrow = gtk_list_box_get_selected_row(lbP);

    if(LBrow != NULL)
    {
        gtk_widget_grab_focus((GtkWidget *)LBrow);
    }
    else
    {
        return false;
    }


    gtk_widget_activate((GtkWidget*)lbP);


    return true;

}




bool get_line_in_visible_area(GtkListBox *lbP,gint idx)
{
    GtkListBoxRow *LBrow;

    LBrow = gtk_list_box_get_row_at_index(lbP,idx);



    if(LBrow != NULL)
    {
        gtk_widget_grab_focus((GtkWidget *)LBrow);
    }
    else
    {
        return false;
    }

    gtk_widget_activate((GtkWidget*)lbP);

    return true;

}





gint get_number_of_ListBoxEntrys(GtkListBox *ListP)
{
    gint i;

    for(i=0; true; i++)
    {
        if(gtk_list_box_get_row_at_index (ListP,i) == 0) break;
    }

    return i;

}




gboolean jump2Entry_idle_(gpointer data)
{
    gint last_index;
    GtkListBoxRow *LBrow;

    last_index = get_number_of_ListBoxEntrys((GtkListBox *)gListBox) - 1;


    LBrow = gtk_list_box_get_row_at_index ((GtkListBox *)gListBox,last_index);
    gtk_list_box_select_row((GtkListBox *)gListBox,LBrow);

    g_idle_add_full(G_PRIORITY_LOW,jump2Entry_idle__Step2,(GtkListBox*)gListBox,NULL);


    return G_SOURCE_REMOVE;
}





gboolean jump2Entry_idle__Step2(gpointer data)
{
    get_selected_line_in_visible_area((GtkListBox *)gListBox); // geht nicht !   Ich muss es per Knopf-1 tun !

    return G_SOURCE_REMOVE;
}

If I see correctly, it’s with GTK 3.

I also found GtkListBox difficult to use, I needed to write additional utility functions for the Tepl library: see the tepl_utils_list_box_*() here. But I really don’t know if it can help for your problem, I didn’t read in details your message.

1 Like

Yes. Thank you, for the suggestion. I will look at it.

There seems to be no other solution at present, as no further responses have been received so far. I will atm not mark the Thread as solved.

It seems that you need to scroll the listbox explicitly. Something like this:

GtkWidget *add_listentry(GtkListBox *ListP, char *TextP,  gint idx)
{
    GtkWidget *labelTP;
    GtkWidget *row;    
    GtkAdjustment *ad = gtk_scrolled_window_get_vadjustment((GtkScrolledWindow *)scrolled_window);	// scrolled_window is a global var    
    int n = 0;

    labelTP = gtk_label_new(TextP);

	gtk_widget_show(labelTP);				// instead of gtk_widget_show_all(gWindow);

    gtk_list_box_insert(ListP, labelTP, idx);
    
    n = get_number_of_ListBoxEntrys(ListP);
    
    row = (GtkWidget *)gtk_list_box_get_row_at_index(ListP, n - 1);
    
    gtk_list_box_select_row(ListP, (GtkListBoxRow *)row);
    
    gtk_adjustment_set_upper(ad, n * 17);	// 17 is the height of a row
    
    gtk_adjustment_set_value(ad, (n - 1) * 17);
    
    return labelTP;		
}

If you want to scroll to any row and not only to the last one, then you set the second parameter of gtk_adjustment_set_value to the index of the row.

For my part, I would be interested to know why gtk_widget_grab_focus scrolls the ListBox to the selected row by clicking on the button “Jump to selected line” of the test program (i.e. some time after the list box was updated), but not immediately after the new row has been created and inserted into the list box. I guess that some race conditions have impact. If you put sleep(1); just after the row is inserted, the scrolling always occurs.

Thank you, for that suggestion. I will test it. Sorry for the late answer, I was a bit busy.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.