5
\$\begingroup\$

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()
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jan 8, 2018 at 10:59
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

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()
answered Jan 8, 2018 at 13:29
\$\endgroup\$
1
  • \$\begingroup\$ Thank you so much - I finally got around to implementing these concepts and it reduced my CPU usage by over 65% haha \$\endgroup\$ Commented Jan 9, 2018 at 19:12

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.