Search code examples
pythonpython-2.7treeviewgtkscrolledwindow

Gtk treeview refreshes cause the scrollbar to go back to the top of the page


I'm currently writing a program like a task manager (similar to what windows have) and I'm using tree view in gtk+ 3 for the GUI. The problem is, by making the treeview update 'live', the scrollbar stops to function correctly. Everytime the treeview is refreshed, the scrollbar goes to the top of the page. This is the line used to update the treeview:

        GObject.timeout_add_seconds(1, self.update_treeview)

And this is the function update_treeview:

def update_treeview(self):
    self.process_list_store.clear()
    p_list = create_list(self.properties, create_list_of_processes())
    for p in p_list:
        self.process_list_store.append(list(p))
    return True

This is how I created the treeview if it matters:

def create_treeview(process_list_store):
    treeview = gtk.TreeView(process_list_store)
    for i, col_title in enumerate(['Name', 'pid', 'Username', 'Memory usage', 'Cpu usage']):
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn(col_title, renderer, text=i)
        column.set_sort_column_id(i)
        column.set_min_width(110)
        column.set_resizable(i)
        treeview.append_column(column)
    return treeview

The treeview is inside a scrolled window object:

    self.scrolled_window = gtk.ScrolledWindow(hexpand=True, vexpand=True)
    self.scrolled_window.set_policy(gtk.PolicyType.NEVER, gtk.PolicyType.AUTOMATIC)

I looked for perhaps other ways to update the treeview, but I wasn't able to find a solution. I'll appreciate any help to deal with this problem, thanks!

EDIT: here is the complete code:

import psutil
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk as gtk
from gi.repository import GObject


class MainWindow(gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self, title="Task Manager")
        self.set_default_size(1000, 500)
        #basic objects in the program
        self.set_border_width(10)
        self.notebook = gtk.Notebook()
        self.grid1 = gtk.Grid(row_spacing=20)
        self.notebook.set_scrollable(True)
        self.add(self.notebook)

        #Processes Tab
        self.tab1 = gtk.Box()
        self.set_border_width(10)
        self.notebook.append_page(self.tab1, gtk.Label("Processes"))

        self.properties = ['name', 'pid', 'username', 'memory_percent', 'cpu_percent']
        p_list = create_list(self.properties, create_list_of_processes())
        print p_list

        #create a list store object for the tree view
        self.process_list_store = gtk.ListStore(str, int, str, str, str)
        for p in p_list:
            self.process_list_store.append(list(p))

        #create a window that can be scrolled
        self.scrolled_window = gtk.ScrolledWindow(hexpand=True, vexpand=True)
        self.scrolled_window.set_policy(gtk.PolicyType.NEVER, gtk.PolicyType.AUTOMATIC)
        self.grid1.attach(self.scrolled_window, 0, 0, 8, 10)

        #create a tree view and add it to the window
        self.tree_view = create_treeview(self.process_list_store)
        self.scrolled_window.add(self.tree_view)
        self.tab1.add(self.grid1)

        GObject.timeout_add_seconds(1, self.update_treeview)

    def update_treeview(self):
        self.process_list_store.clear()
        p_list = create_list(self.properties, create_list_of_processes())
        for p in p_list:
            self.process_list_store.append(list(p))
        return True


#create a list of dictionaries of processes and info about them
def create_list_of_processes():
    list_of_processes = []
    for proc in psutil.process_iter():
        try:
            pinfo = proc.as_dict(attrs=['pid', 'name', 'username', 'memory_percent', 'cpu_percent'])
        except psutil.NoSuchProcess:
            pass
        else:
            if not pinfo['username']:
                pinfo['username'] = 'SYSTEM'
            pinfo['cpu_percent'] = str(pinfo['cpu_percent'] / psutil.cpu_count()) + '%'
            pinfo['memory_percent'] = str(pinfo['memory_percent'])[0:6]
            pinfo['memory_percent'] = str(pinfo['memory_percent']) + '%'
            list_of_processes.append(pinfo)
    return list_of_processes


#create a tree view object
def create_treeview(process_list_store):
    treeview = gtk.TreeView(process_list_store)
    for i, col_title in enumerate(['Name', 'pid', 'Username', 'Memory usage', 'Cpu usage']):
        renderer = gtk.CellRendererText()
        column = gtk.TreeViewColumn(col_title, renderer, text=i)
        column.set_sort_column_id(i)
        column.set_min_width(110)
        column.set_resizable(i)
        treeview.append_column(column)
    return treeview


#create a list based on the desired properties
def create_list(properties, p_dict):
    p_list = []
    for dict in p_dict:
        small_p_list = []
        for prop in properties:
            small_p_list.append(dict[prop])
        p_list.append(small_p_list)
    return p_list


def main():
    window = MainWindow()
    window.connect("delete-event", gtk.main_quit)
    window.show_all()
    gtk.main()

if __name__ == '__main__':
    main()

Solution

  • This update works for me:

    def update_treeview(self):
        adjustment = self.scrolled_window.get_vadjustment()
        value = adjustment.get_value()
        self.process_list_store.clear()
        p_list = create_list(self.properties, create_list_of_processes())
        for p in p_list:
            self.process_list_store.append(list(p))
        GObject.idle_add(adjustment.set_value, value)
        return True
    

    And here are the docs.

    Unfortunately, this causes the window to flicker. The ultimate way is to update the treeview, line by line. But this is pretty hard code to write.