Search code examples
pythongtkpygtk

updating the list store of gtk periodically in python


I'm new at phyton programming and developing gui interface has gtk framework and serial port. It has a treeview whose liststore model. I could the insert new rows easily. I'm using the serialport recive callback in different thread from main gui thread avoid to not miss any data. After the received a new data, it should update the treeview. However, since the serialport is in different thread, I don't know how to update the list. Please help me to do this.

the gui class:

class MainGUI():
def __init__(self):
    self.builder = Gtk.Builder()
    self.builder.add_from_file("main.glade")
    self.builder.connect_signals(MainGUI)
    self.window = self.builder.get_object("window1")
    self.mycombobox = self.builder.get_object('comboboxtext1')
    self.toggle = self.builder.get_object('togglebutton1')
    self.table = self.builder.get_object('treeview2')
    self.list = self.builder.get_object('liststore1')
    self.scroll_window = self.builder.get_object('scrolledwindow1')

def show(self):
    print("App main thread number", format(threading.get_ident()))
    self.window.show()
    Gtk.main()

@staticmethod
def connect_toggled(_self):
    if main.toggle.get_active():
        main.toggle.set_label("Disconnect")
        serial_port.connect(main.mycombobox.get_active_text())
        t3 = threading.Thread(target=serial_port.read_from_port)
        t3.start()
        serial_port.disconnect()

def row_inserted_event(self, path, iter):
    """The actual scrolling method"""
    adj = main.scroll_window.get_vadjustment()
    adj.set_value(adj.get_upper() - adj.get_page_size())

def update_table(self):
    # for i in range(256):
    #     main.list.append(['aaa', 'ddds', i])
    #     if len(main.list) > 50:
    #         main.list.remove(main.list.get_iter(0))
    main.list.append(['aaa', 'ddds', 0])
    if len(main.list) > 50:
        main.list.remove(main.list.get_iter(0))
    print(len(main.list))

if __name__ == "__main__":
    serial_port = SerialPort()
    ports = SerialPort().list_ports()
    main = MainGUI()
    for port in ports:
        main.mycombobox.append_text(port)
    main.mycombobox.set_active(0)
    main.toggle.set_label("Connect")
    main.update_table()
    main.show()

the serial port class:

class SerialPort:
    def __init__(self):
        self.ser = serial.Serial()
        self.baud_rate = 115200

    def write(self, data):
        self.ser.write(bytes(data))
        print(data)

    def connect(self, port):
        print("serial port thread number = %d" % (threading.get_ident()))
        print("connected the port =  %s" % (port))
        self.ser.port = port
        self.ser.baudrate = self.baud_rate
        self.ser.timeout = 0
        if self.ser.isOpen():
            print("already connected this port = %s" % (port))
        else:
            self.ser.open()

    def disconnect(self):
        if self.ser.isOpen():
            self.ser.close()
            print("disconnected port")

    def read_from_port(self):
        while True:
            if self.ser.isOpen():
                reading = self.ser.readline()
                if len(reading) > 0:
                    self.received_callback(reading)
            time.sleep(0.1)

    def received_callback(self, data):
        print(data)

    def list_ports(self):
        if sys.platform.startswith('win'):
            ports = ['COM%s' % (i + 1) for i in range(256)]
        elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
            # this excludes your current terminal "/dev/tty"
            # ports = glob.glob('/dev/tty[A-Za-z]*')
            ports = ['/dev/pts/%s' % (i + 1) for i in range(256)]
        elif sys.platform.startswith('darwin'):
            ports = glob.glob('/dev/tty.*')
        else:
            raise EnvironmentError('Unsupported platform')

        result = []
        for port in ports:
            try:
                s = serial.Serial(port)
                s.close()
                result.append(port)
            except (OSError, serial.SerialException):
                pass
        return result

Solution

  • I believe that your problem is more related to threading + GUI, than GTK.

    As far as I know, when you modify the liststore that is the model for the treeview, the latter should be updated instantly. So, there should be no problem there.

    A fundamental principle when working with threads and a GUI, is that you should only update the GUI from within its own thread (main loop). So what you need to do, is have your worker thread (serial port connection thread) send the update to the main GUI thread and let it update the treeview. The update can be scheduled with the GLib.idle_add function to let GTK do it when most convenient.

    Now, to communicate between threads, you could use the queue module.

    I don't quite understand your code. So I'll write a simple example (using gtk3 PyGObject, since you didn't specify).

    import threading
    import queue
    import gi
    gi.require_version('Gtk', '3.0')
    gi.require_version('GLib', '2.0')
    from gi.repository import Gtk, GLib
    
    def do_work(com_queue):
        # do some work
        com_queue.put("update for your treeview")
        # continue
    
    class MainGUI(object):
        def __init__(self):
            self.com_queue = queue.Queue()
            self.worker_thread = None
            self.liststore = None
            # more gui initialization...
    
        def launch_worker_thread(self):
            self.worker_thread = threading.Thread(target=do_work, args=(self.com_queue,))
            self.worker_thread.start()
            Glib.timeout_add(1000, self.check_queue) # run check_queue every 1 second
    
        def check_queue(self):
            if self.worker_thread.is_alive():
                try:
                    update = self.com_queue.get()
                    GLib.idle_add(self.update_treeview, (update,)) # send tuple
                except queue.Empty:
                    pass
                return True # to keep timeout running
            else:
                return False # to end timeout
    
        def update_treeview(self, update):
            self.liststore.append(update) # here update the treeview model with tuple
    
    if __name__ == "__main__":
        gui = MainGUI()
        Gtk.main()
    

    I hope this helps.