Reputation: 125
UPDATE 2: Potential fix in last update did not fix the problem. I have done more research on "how to do it in pure tk" as suggested in tkinter docs and I have a new theory about what is causing the issue, and how to fix it. guidance on implementing the fix and/or suggestions about if I am thinking about it the correct way are what I am looking for as a solution to this thread.
New theory about cause of problem:
Statement 1: The method called to get the clipboard contents in tkinter appears to return a function call as opposed to returning a value which contains the clipboard's contents.
Statement 2: In tk, every command is invoked and executed in it's own instance of a tk interpreter.
Statement 3: because of Statement 1 & 2, the calling the method to get the clipboard contents in tkinter generates a stack of tk interpreters something like this:
python step 0. assign value of a global variable to the output for tkinter method Tk.clipboard_get. This creates entrance to tk script
python step 1 print contents of global variable created in python step 0.
Statement 4: the problem, demonstrated by the example code from initial post, is that python step 1 starts to execute before all of the steps in the tk stack have completed.
My hypothesis: the problem is that the method in tkinter for clipboard_get is returning a call to a tk script rather than returning the output to the tk script.
My current direction towards finding a solution:
Create a separate python script that runs in text mode, to get clipboard contents and return them output to the program. Then, in place of calling clipboardText = self.clipboard_get()
call the script using subprocess module's method, popen, with the option built into popen to wait for the script executed by popen to be completed before moving forward in the python interpreter.
Alternate methods that could potentially solve problem:
Alt method #1: Use a trace on a tk StringVar to prevent further execution in python interpreter.
Alt method #2: modify source code tkinter._init_ so it returns a value rather than a call to a tk method by changing from this:
# Clipboard handling:
def clipboard_get(self, **kw):
"""Retrieve data from the clipboard on window's display.
The window keyword defaults to the root window of the Tkinter
application.
The type keyword specifies the form in which the data is
to be returned and should be an atom name such as STRING
or FILE_NAME. Type defaults to STRING, except on X11, where the default
is to try UTF8_STRING and fall back to STRING.
This command is equivalent to:
selection_get(CLIPBOARD)
"""
if 'type' not in kw and self._windowingsystem == 'x11':
try:
kw['type'] = 'UTF8_STRING'
return self.tk.call(('clipboard', 'get') + self._options(kw))
except TclError:
del kw['type']
return self.tk.call(('clipboard', 'get') + self._options(kw))
to this:
# Clipboard handling:
def clipboard_get(self, **kw):
"""Retrieve data from the clipboard on window's display.
The window keyword defaults to the root window of the Tkinter
application.
The type keyword specifies the form in which the data is
to be returned and should be an atom name such as STRING
or FILE_NAME. Type defaults to STRING, except on X11, where the default
is to try UTF8_STRING and fall back to STRING.
This command is equivalent to:
selection_get(CLIPBOARD)
"""
if 'type' not in kw and self._windowingsystem == 'x11':
try:
kw['type'] = 'UTF8_STRING'
val = self.tk.call(('clipboard', 'get') + self._options(kw))
return val
except TclError:
del kw['type']
val = self.tk.call(('clipboard', 'get') + self._options(kw))
return val
I will play around with these potential solutions, check back here for feedback, and update if I find a solution.
UPDATE 1: after some more troubleshooting, I think the problem is that I am calling tk's get clipboard method from within the mainloop method of the Tk app. I will experiment with using the tk.Tk.after method to avert this problem. I will add lines in my code flagged with a comment to reflect this change. I will report back here if this solves the problem.
General description:
Using pyautogui to select, then copy text, if I try to access the clipboard contents with tk.clipboard_get() within the function (after copying to clipboard) I will sometimes get the exception shown below. However afterwards, I check the clipboard again, either by again calling app.clipboard_get() in the terminal, or by 'ctrl' + 'v', and I can access the clipboard content without raising the exception, and it is not empty or missing.
Here is the exception
line 26, in highlight_then_copy_then_print
clipboardText = self.clipboard_get()
File "C:\Users\XisUnknown\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 801, in clipboard_get
return self.tk.call(('clipboard', 'get') + self._options(kw))
_tkinter.TclError: CLIPBOARD selection doesn't exist or form "STRING" not defined
Here is the sample code...
import tkinter as tk # tk.TkVersion == tk.TclVersion == 8.6
import pyautogui as pg # pg.__version__ == '0.9.36'
#running with Python 3.6.1 in Windows 10
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('200x200+300+400')
actionButton = tk.Button(self,
text='highlight, then copy, then print',
## command=self.highlight_then_copy_then_print())#OLD COMMAND
command=self.call_function_outside_main_loop())#NEW COMMAND for Update
actionButton.pack()
###New Code Section For Update###
def call_function_outside_main_loop(self):
self.after(5, self.highlight_then_copy_then_print())
###End Code Section For Update###
def highlight_then_copy_then_print(self):
#I have also tried adding
#self.clipboard_clear()
#then,
#self.clipboard_append('')
#to the beginning of the function...
pg.moveTo(100,150)
pg.dragRel(200,40,
duration=1.5,
pause=.2)
pg.hotkey('ctrl', 'c',pause=.2)
#I have also tried
## pg.rightClick(238,160, pause=.15)#right click on selected text
## pg.typewrite('c',pause=.15)#press C (as shortcut to copy option)
clipboardText = self.clipboard_get()
print(clipboardText)
return None
if __name__ == '__main__':
app = App()
As a workarounds I have the following, with various degrees of success:
First, instantiating an variable, then using a while loop to periodically test the clipboard_get function via a Try/Except statementList item. e.g.
s = None
while s == None:
try:
s = self.clipboard_get()
except:
#I have tried the below commented out lines in various combinations
tk.time.sleep(.2)
## self.update_idletasks()
## self.update()
## self.clipboard_clear()
## self.clipboard_append('')
## tk.time.sleep(.2)
## self.separateFunction# a separate function that just highlights and then does ctrl+c without the use of tkinter...
## tk.time.sleep(.2)
Additional notes:
Upvotes: 0
Views: 745
Reputation: 661
You can try this, it is modified code of your program:
import tkinter as tk
import pyautogui as pg
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('200x200+300+400')
actionButton = tk.Button(self,
text='highlight, then copy, then print',
## command=self.highlight_then_copy_then_print())#OLD COMMAND
command=self.call_function_outside_main_loop) # NEW COMMAND for Update
actionButton.pack()
def call_function_outside_main_loop(self):
self.after(5, self.highlight_then_copy_then_print())
def highlight_then_copy_then_print(self):
# pg.moveTo(100, 150) #I've commented this out for concentration on the clipboard.
# pg.dragRel(200, 40,
# duration=1.5, # this line was giving an error - No attribute duration, so I also commented this out.
# pause=.2)
pg.hotkey('ctrl', 'c', pause=.2)
clipboardText = self.selection_get(selection="CLIPBOARD")
print(clipboardText)
return None
app = App()
app.mainloop()
I think this answer might help you.
Upvotes: -1
Reputation: 661
Use this code for copying text from clipboard:
import time
from tkinter import Tk
while True:
r = Tk()
try:
result = r.selection_get(selection="CLIPBOARD")
print(result)
time.sleep(1)
except:
selection = None
Upvotes: 1