I've got this basic "test" application, in which I would like to display a spinner while it is doing its long launching process (functions with database requests) to let the user know that it is not bugging but launching. I've read in other posts that it is possible to do this with Gtk.events_pending()
function but I don't know how/where to use it.
I've tried many ways, but the main window always displays only when requests are done :
Here is the main .py file:
#!/usr/bin/python3
# -*- coding: Utf-8 -*-
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkPixbuf, GObject
import Mng,os.path
path = os.path.dirname(os.path.realpath(__file__))
# MAIN WINDOW ######################################################################################
class PyApp:
def __init__(self):
builder = Gtk.Builder()
builder.add_from_file(path + "/test.glade")
self.obj = builder.get_object
"""
I would like to display on main window a
spinner while doing requests. There is a
self.obj('spinner') in main window,
in glade file to do so.
"""
self.do_requests()
self.obj('main').show_all()
def do_requests(self):
mng = Mng.Grab([
[1,'getPlayers'],
[2,'getFactions'],
[3,'getBoards']
])
data = mng.grab_data()
players, nb = data[1]
factions, nb = data[2]
boards, nb = data[3]
"""
Here will be the code to display data in GUI,
like for example : self.obj('label_players').set_text(str(players))
"""
if __name__ == "__main__":
app = PyApp()
Gtk.main()
Here is the Mng.py file in which I will manage all my requests within a class (I don't know if it is well coded because I just discovered multiple threading. But it does the trick):
#!/usr/bin/python3
# -*- coding: Utf-8 -*-
import os.path, DB
import concurrent.futures
path = os.path.dirname(os.path.realpath(__file__))
class Grab:
"""
Retrieves multiple database requests datas
& returns results in a dict : {name of request: [result, lenght of result]}
"""
def __init__(self, req_list):
self.req_list = req_list
def grab_data(self):
def do_req(var, funct_name, args):
if None in args:
funct = getattr(self, str(funct_name))()
else:
#print("function",name,"*args : ", *args)
funct = getattr(self, str(funct_name))(*args)
res = [var, funct]
return res
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
res_list = {}
future_to_req = {executor.submit(do_req, req[0], req[1], req[2:]): req for req in self.req_list}
for future in concurrent.futures.as_completed(future_to_req):
req = future_to_req[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (req, exc))
else:
res_list[data[0]] = data[1]
return res_list
def getFactions(self, ext1=False):
req = DB.DB('SELECT * FROM factions')
res = req.res
nb = len(res)
return res, nb
def getBoards(self, ext1=False):
req = DB.DB('SELECT * FROM boards')
res = req.res
nb = len(res)
return res, nb
def getPlayers(self):
req = DB.DB('SELECT * FROM players')
res = req.res
nb = len(res)
return res, nb
And the DB.py file doing requests:
#!/usr/bin/python3
# -*- coding: Utf-8 -*-
import mysql.connector as sql
class DB(object):
"""DB initializes and manipulates MySQL databases."""
def __init__(self, query):
"""Initialize a new or connect to an existing database.
Accept setup statements to be executed.
"""
self.database = '******'
self.host = '**********'
self.port = '********'
self.user = '******'
self.password = '***********'
self.connect()
self.execute(query)
self.close()
def connect(self):
"""Connect to the MySQL database."""
self.connection = sql.connect(host=self.host,port=self.port,user=self.user,password=self.password, database=self.database)
self.cursor = self.connection.cursor()
self.connected = True
def close(self):
"""Close the MySQL database."""
self.connection.close()
self.connected = False
def execute(self, query):
"""Execute complete SQL statements. """
res = close = False
if not self.connected:
self.connect()
close = True
try:
self.cursor.execute(query)
if query.upper().startswith('SELECT'):
res = self.cursor.fetchall()
except sql.Error as e:
try:
print ("MySQL Error [%d]: %s" % (e.args[0], e.args[1]))
except IndexError:
print ("MySQL Error: %s" % str(e))
if close:
self.close()
self.res = res
Could you please tell me how to do this?
This may help you understand how multiprocessing is supposed to work. Sorry, but I can't give you a full demo with your code built in, but hopefully you can figure it out.
#!/usr/bin/env python3
from gi.repository import Gtk, GLib, Gdk
from multiprocessing import Queue, Process
from queue import Empty
import os, sys, time
UI_FILE = "src/pygtk_foobar.ui"
class GUI:
def __init__(self):
self.builder = Gtk.Builder()
self.builder.add_from_file(UI_FILE)
self.builder.connect_signals(self)
self.window1 = self.builder.get_object('window1')
self.window1.show_all()
self.builder.get_object('spin1').start()
self.data_queue = Queue()
thread = Process(target=self.thread_start)
thread.start()
GLib.timeout_add(100, self.get_result )
def thread_start (self):
time.sleep(5)
self.data_queue.put("done")
def get_result (self):
try:
result = self.data_queue.get_nowait()
print (result)
self.builder.get_object('spin1').stop()
except Empty:
return True
def on_window_destroy(self, window):
Gtk.main_quit()
def main():
app = GUI()
Gtk.main()
if __name__ == "__main__":
sys.exit(main())
EDIT
An explanation: GLib.timeout_add() will keep on polling as long as get result
returns True. When the timeout gets returned None or False, it will quit polling. get result
will try to get results from the data_queue, but if nothing is found, it will return True.
In your case, you would open the database requests with def thread_start
and check the queue with def get_result
until the info has been loaded. So multiprocessing will load the db info in one thread, while Gtk can do its window drawing in another thread, while periodically checking if the multiprocessing db thread is finished. When it is finished loading, cancel the timeout by not returning True, and do your thing with the db data.
I use this a lot to populate scanners, for example, while the user can operate the GUI.
Hope this helps.