The following contains my little app that detects changes to the clipboard and displays them in the GUI. I did my best using my limited knowledge of Python, but I have a feeling that I can definitely improve the program. It works, but Python's CPU usage shoots up to 20% whenever I run the program - which is due to my use of multiple threads and infinite loops I'm sure.
#! python3
#GUI
import tkinter
#needed for the clipboard event detection
import time
import threading
#listener class that inherits from Thread
class ClipListener(threading.Thread):
#overriding Thread constructor
def __init__(self, pause = .5):
#from documentation: If the subclass overrides the constructor, it must make sure to invoke the base class constructor (Thread.__init__()) before doing anything else to the thread.
super().__init__() #calls Thread class constructor first
#initialize parameters
self.pause = pause
self.stopping = False
#initialize event to communicate with main thread
self.copyevent = threading.Event()
#override run method
def run(self):
last_value = tkinter.Tk().clipboard_get() #initialize last_value as
#continue until self.stopping = true
while not self.stopping:
#grab clip_board value
temp_value = tkinter.Tk().clipboard_get()
#if last value is not equal to the temp_value, then (obviously) a change has occurred
if temp_value != last_value:
#set last value equal to current (temp) value and print
last_value = temp_value
print("set")
#set the event if clipboard has changed
self.copyevent.set()
time.sleep(self.pause) #sleep for indicated amount of time (.5 by default)
#override stop method to work with our paramter 'stopping'
def stop(self):
self.stopping = True
#GUI extends Frame, serving as main container for a root window
class GUI(tkinter.Frame):
#constructor for GUI - intializes with a default height and width if none are given
def __init__(self, master, ht=600, wt=800):
#uses the parent class' constructor
super().__init__(master, height=ht, width=wt)
self.var = tkinter.StringVar()
self.var.set("No copied text")
self.pack_propagate(False) #window will use it's own width and height as parameters instead of child's dimensions
self.pack()
self.label = tkinter.Label(self, textvariable=self.var)
self.label.pack()
#method to update the label
def update_label(self, newText):
self.var.set(newText)
self.label.pack()
def main():
#good practice to have a variable to stop the loop
running = True
#GUI initialized
root = tkinter.Tk()
gui = GUI(root)
#start thread containing Clipboard Listener
listener = ClipListener(.100)
listener.start()
#loop to keep updating the program without blocking the clipboard checks (since mainloop() is blocking)
while running:
#update the gui
root.update();
#wait .1 seconds for event to be set to true
event_set = listener.copyevent.wait(.100)
#if true then update the label and reset event
if event_set:
gui.update_label(root.clipboard_get())
listener.copyevent.clear()
#only run this program if it is being used as the main program file
if __name__ == "__main__":
main()
1 Answer 1
Don't use threads
You don't need the complexities of threading for this problem. You can use the Tkinter method after
to run a function periodically. Threading is necessary if the function you are running takes a long time, but that is not the case here.
You can use a class, but to keep this answer simple I'll only show the function.
Also, note that I use an existing window rather than tkinter.Tk()
on each call. There's no need to create a new root window every time you do the check.
def check_clipboard(window):
temp_value = window.clipboard_get()
...
Next, create a function that calls this function every half second:
def run_listener(window, interval):
check_clipboard(window)
root.after(interval, run_listener, window, interval)
To start running, simply call run_listener
once, and it will continue to run every 500 milliseconds:
run_listener(root, 500)
The logic to stop and pause is roughly the same as what you have now. Create a flag, and then check that flag inside of run_check_periodically
.
With this, you can remove the loop from main
since tkinter already has an efficient loop:
def main():
root = tkinter.Tk()
gui = GUI(root)
# start the listener
listen_to_clipboard(root)
# start the GUI loop
root.mainloop()
-
\$\begingroup\$ Thank you so much - I finally got around to implementing these concepts and it reduced my CPU usage by over 65% haha \$\endgroup\$avghdev– avghdev2018年01月09日 19:12:52 +00:00Commented Jan 9, 2018 at 19:12
Explore related questions
See similar questions with these tags.