tkinter avoid GUI from freezing - python-2.7

I developed a simple Python application doing some stuff, then I decided to add a simple GUI using Tkinter.
The problem is that, while the I call a function called startprocess and begin doing stuff which is processor heavy and the window freezes.
I know it's a common problem and I've already read that I should use multithreads (very complicated, because the function updates the GUI too) or divide my code in different function, each one working for a little time. anyways is there any modification needed in below code to avoid GUI freezing?
import threading
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
import os, datetime, sys, subprocess
import parselog_v1
# diplay messagebox window
def MessageBox(windowLable,msg):
messagebox.showinfo(windowLable, msg)
# check if Dir empty
def checkDirEmpty(work_path):
if os.path.isdir(work_path):
if not os.listdir(work_path):
print ("No Files found in directory")
MessageBox('Log Parser', 'No Files found in directory.')
else:
return True
# launch app in center of screen
def center_window(width=300, height=200):
# get screen width and height
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# calculate position x and y coordinates
x = (screen_width/2) - (width/2)
y = (screen_height/2) - (height/2)
root.geometry('%dx%d+%d+%d' % (width, height, x, y))
# application frame
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse for logs"
self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.startProcess
self.Run_Main.pack(side='left',padx=0)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["fg"] = "red"
self.QUIT["command"] = self.quit
self.QUIT.pack(side='right',padx=5)
def startProcess(self):
global Src_foldername
Src_foldername = filedialog.askdirectory()
Src_foldername = Src_foldername.replace("/", "\\")
print("Source folder: " + Src_foldername)
if checkDirEmpty(Src_foldername):
# process logs
# multithread
print("Processing...")
self.refresh()
threading.Thread(target=parselog_v1.main(Src_foldername))
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
class IORedirector(object):
'''A general class for redirecting I/O to this Text widget.'''
def __init__(self, text_area):
self.text_area = text_area
class StdoutRedirector(IORedirector):
'''A class for redirecting stdout to this Text widget.'''
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=21, width=68)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
self.text.insert("end", "Begin by selecting log folder..." + "\n")
self.text.configure(state='disabled') # disable text editing
sys.stdout = (self) # to begin logging stdio to GUI
return
def write(self, txt):
self.text.configure(state='normal')
self.text.insert('end', txt)
self.text.configure(state='disabled')
root = tk.Tk()
root.resizable(width=False, height=False)
center_window(500, 300) # launch in center of screen
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
root.destroy()

You use thread in wrong way.
First: target= needs function name without () and arguments.
You can assign arguments to args= (it have to be tuple even if you have only one argument)
threading.Thread(target=parselog_v1.main, args=(Src_foldername,) )
Now your code runs parselog_v1.main as normal function, waits for result and it will assign this result as function name to taget= - so you have something like this:
result = parselog_v1.main(Src_foldername)
threading.Thread(target=result)
It stops mainloop so it can't get mouse/keyboard events, refresh window, etc. so it looks like window freeze.
Second: after you create thread correctly you have to start it
my_thread = threading.Thread(target=parselog_v1.main, args=(Src_foldername,) )
my_thread.start()

Related

Non-blocking print redirection to Tkinter text widget

I've been able to get print statements redirected to a Tkinter text widget based on the answer to this question. It describes a class to redirect stdout:
class TextRedirector(object):
def __init__(self, widget, tag="stdout"):
self.widget = widget
self.tag = tag
def write(self, str):
self.widget.configure(state="normal")
self.widget.insert("end", str, (self.tag,))
self.widget.configure(state="disabled")
Utilizing the TextRedirector class, I have the following code
import sys
import time
from tkinter import *
root = Tk()
t = Text(root, width = 20, height = 5)
t.grid()
old_stdout = sys.stdout
sys.stdout = TextRedirector(t)
for i in range(5):
print i + 1
time.sleep(1)
sys.stdout = old_stdout
However, this solution seems to be blocking (the text shows up all at once once the for-loop ends).
I have successfully put together a non-blocking solution using Popen and threading, but this isn't an ideal solution due to some unrelated constraints.
Is there a way to have the print statements show up in the text box in real-time?

PyQt4 label crashes when a function is called instead of using raw_input?

I boiled the original code down to a small section that still reproduces the issue. The below code works fine with action = raw_input('next action? ') instead of action = self.fake(). WHY??!! Specifically, the 'label' window will hang and crash using the class function, but will display the two overlaid images no problem using the user input. I cannot fathom how the two are impacting PyQt, especially since the changes are being made AFTER the image update.
import time
import sys
from PyQt4 import QtGui
class Basement(object):
def __init__(self):
self.app = QtGui.QApplication(sys.argv)
self.label = QtGui.QLabel()
def update_image(self):
self.im = QtGui.QImage('n-wall.png')
painter = QtGui.QPainter()
c_image = QtGui.QImage('bed.png')
painter.begin(self.im)
painter.drawImage(10, 10, c_image)
painter.end()
self.label.setPixmap(QtGui.QPixmap.fromImage(self.im))
self.label.show()
def fake(self):
return 'left'
def play_game(self):
### Update graphics / text
self.update_image()
### Decide action
action = self.fake()
#action = raw_input('next action? ')
time.sleep(5)
B = Basement()
B.play_game()

PyQt 4: Get Position of Toolbar

Hy guys,
in my executable program there is a toolbar. Well, the user decides to move the toolbar. Now the toolbar is floating. I know I have to conntect the floating-signals that is emittted when the toolbar ist arranged by the user. How can I save the new position of the toolbar? I know the method of adding the toolbar to the main window with a position:self.addToolBar( Qt.LeftToolBarArea , toolbar_name). In the handle_floating()-method you see what I want: There I want to get the position currently, but how? You also see I have just added one member variable, named self.toolbar_pos, to hold the position of the toolbar. My idea is, when application is terminated I want to serialize this value to a file, and later, when application is ran again its will read that file and set the toolbar accordingly. But this is no problem. Currently I don't have no idea to get the position of the toolbar.
I need your help :)
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.toolbar_pos = None
self.initUI()
def initUI(self):
exitAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(QtGui.qApp.quit)
self.toolbar = QtGui.QToolBar(self)
self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.addToolBar(self.toolbar )
self.toolbar.addAction(exitAction)
self.toolbar.setAllowedAreas(QtCore.Qt.TopToolBarArea
| QtCore.Qt.BottomToolBarArea
| QtCore.Qt.LeftToolBarArea
| QtCore.Qt.RightToolBarArea)
self.addToolBar( QtCore.Qt.LeftToolBarArea , self.toolbar )
self.toolbar.topLevelChanged.connect(self.handle_floating)
def handle_floating(self, event):
# The topLevel parameter is true
# if the toolbar is now floating
if not event:
# If the toolbar no longer floats,
# then calculate the position where the
# toolbar is located currently.
self.toolbar_pos = None
print "Get position: ?"
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.setGeometry(300, 300, 300, 200)
ex.setWindowTitle('Toolbar example')
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The QMainWindow class already has APIs for this: i.e. saveState and restoreState. These can be used to save and restore the state of all the toolbars and dock-widgets in your application.
To use them, you first need to make sure that all your toolbars and dock-widgets are given a unique object-name when they are created:
class Example(QtGui.QMainWindow):
...
def initUI(self):
...
self.toolbar = QtGui.QToolBar(self)
self.toolbar.setObjectName('foobar')
Then you can override closeEvent to save the state:
class Example(QtGui.QMainWindow):
...
def closeEvent(self, event):
with open('/tmp/test.conf', 'wb') as stream:
stream.write(self.saveState().data())
(NB: I've just used a temporary file here for testing, but it would obviously be much better to use something like QSettings in your real application).
Finally, you can restore the state that was saved previously:
class Example(QtGui.QMainWindow):
def __init__(self, parent=None):
...
self.initUI()
try:
with open('/tmp/test.conf', 'rb') as stream:
self.restoreState(QtCore.QByteArray(stream.read()))
except IOError:
pass

How to create activity indicator using pyQt4 designer for python

I'm learning about GUI python using pyQt4. I have function A in another file python. and I want to run in GUI file python that I extracted from file .ui (output of designer pyQt4). How to create activity indicator which is active when the function A is running? can I use progress bar (in pyQt4 designer) without know how many time for my function A running?
Thank you.
this is the function to call A in GUI .py:
def RunFunction():
import Kdtree
_dir = kdTreeOk.getNeighbor(float(radius)) #function 'A'
file = file_open('Summary.txt',_dir) # ignore, just file to save result of `A`
with file:
textOutput=file.read()
ui.result.setPlainText(textOutput)
#### button to run RunFunction in file GUI .py
ui._run.clicked.connect(RunFunction)
QProgressDialog is made for this purpose and generally called via QThread. Here's a (messy) basic example to show how this can work (without any threading). If you are calling this dialog from another window, just set parent as the calling window and you can read attributes in this dialog by calling self.parent.some_variable.
EDITED to work properly ;).
from PyQt4 import QtCore, QtGui
from time import sleep
import sys
class ProgressBarWidget(QtGui.QProgressDialog):
def __init__(self, parent=None, app=None):
super(ProgressBarWidget, self).__init__(parent)
self.app=app
self._allow_close = True
layout = QtGui.QVBoxLayout(self)
# Create a progress bar and a button and add them to the main layout
self.progressBar = QtGui.QProgressBar(self)
self.progressBar.setRange(0,100)
layout.addWidget(self.progressBar)
self.button = QtGui.QPushButton("Start", self)
layout.addWidget(self.button)
self.button.clicked.connect(self.onStart)
self.upload_count = 10
def onStart(self):
self.progressBar.setValue(0)
self.button.setText("Uploading...")
self.run()
def makeProgress(self, current_num, total_num, message = ''):
if total_num == current_num:
self.onFinished()
elif current_num == 0:
self.progressBar.setValue(0)
else:
multiplier = int(float(float(100) / float(total_num)))
c_times_m = current_num * multiplier
for i in xrange(c_times_m - int(self.progressBar.value())):
new_val = int(self.progressBar.value()) + 1
self.progressBar.setValue(new_val)
sleep(.01)
def onFinished(self):
# progress complete
self.progressBar.setRange(0,100)
for i in xrange(int(self.progressBar.value()),101):
self.progressBar.setValue(i)
self.button.setEnabled(True)
self.button.setText('Exit')
self.button.clicked.disconnect(self.onStart)
self.button.clicked.connect(self.close)
def run(self):
self._allow_close = False
self.button.setDisabled(True)
total = self.upload_count * 2
progress_meter = 0
downloaded = []
tests_to_upload = 10
for each in xrange(tests_to_upload):
sleep(0.15)
progress_meter += 1
self.makeProgress(progress_meter,total)
sleep(0.2)
progress_meter += 1
self.makeProgress(progress_meter, total)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = ProgressBarWidget(app=app)
window.resize(640, 480)
window.show()
sys.exit(app.exec_())

How to fade in/out on a Tkinter Frame

How can you fade in and/or out on a Tkinter.Frame or any other widget for that matter. All of the examples that I have seen have been for either root (Tkinter.Tk) or Toplevel setting the alpha e.g.
https://stackoverflow.com/a/22491808/1552953
Is it possible to apply this to an individual widget?
You can't do it on individual widgets. Transparency in Tkinter is only available for instances of Tk and Toplevel.
Based on Bryan's answer I came up with this solution, which he inadvertently provided most of the code for also.
One thing to note is if you move the main window the toplevel doesn't move with it...
import Tkinter
import Queue
class Flash(Tkinter.Toplevel):
def __init__(self, root, **options):
Tkinter.Toplevel.__init__(self, root, width=100, height=20, **options)
self.overrideredirect(True) # remove header from toplevel
self.root = root
self.attributes("-alpha", 0.0) # set transparency to 100%
self.queue = Queue.Queue()
self.update_me()
def write(self, message):
self.queue.put(message) # insert message into the queue
def update_me(self):
#This makes our tkinter widget threadsafe
# http://effbot.org/zone/tkinter-threads.htm
try:
while 1:
message = self.queue.get_nowait() # get message from the queue
# if a message is received code will execute from here otherwise exception
# http://stackoverflow.com/questions/11156766/placing-child-window-relative-to-parent-in-tkinter-pythons
x = root.winfo_rootx() # set x coordinate of root
y = root.winfo_rooty() # set y coordinate of root
width = root.winfo_width() # get the width of root
self.geometry("+%d+%d" % (x+width-self.winfo_width() ,y)) # place in the top right cornder of root
self.fade_in() # fade in when a message is received
label_flash = Tkinter.Label(self, text=message, bg='black', fg='white', padx=5, pady=5)
label_flash.pack(anchor='e')
self.lift(self.root)
def callback():
label_flash.after(2000, label_flash.destroy) # destroy the label after 5 seconds
self.fade_away() # fade away after 3 seconds
label_flash.after(3000, callback)
except Queue.Empty:
pass
self.after(100, self.update_me) # check queue every 100th of a second
# http://stackoverflow.com/questions/3399882/having-trouble-with-tkinter-transparency
def fade_in(self):
alpha = self.attributes("-alpha")
alpha = min(alpha + .01, 1.0)
self.attributes("-alpha", alpha)
if alpha < 1.0:
self.after(10, self.fade_in)
# http://stackoverflow.com/questions/22491488/how-to-create-a-fade-out-effect-in-tkinter-my-code-crashes
def fade_away(self):
alpha = self.attributes("-alpha")
if alpha > 0:
alpha -= .1
self.attributes("-alpha", alpha)
self.after(10, self.fade_away)
if __name__ == '__main__':
root = Tkinter.Tk()
root.minsize(700, 300)
root.geometry("700x500")
flash = Flash(root) # create toplevel instance
def callback():
# put a delay between each message so we can check the behaviour depending on the lenght of the delay between messages
import time
flash.write('Hello World')
time.sleep(1)
flash.write('Ready!')
time.sleep(2)
flash.write('Steady!')
time.sleep(4)
flash.write('Go!')
# create a thread to prevent the delays from blocking our GUI
import threading
t = threading.Thread(target=callback)
t.daemon = True
t.start()
root.mainloop()
exit()