4

Environment

  • Python 3.6.2
  • Windows 10

The Problem

I use the tk method clipboard_append() to copy a string to the clipboard.

When my program is run from the Python interpreter data is copied to the clipboard correctly.

When run using "C:\Python36.exe myprogram.py" however, I get some weird behavior.

  1. If I paste the data while the program is still running, it works as expected.
  2. If I paste the data while the program is running, then close the program, I can continue to paste the data.
  3. If I close the program after copying but before pasting, the clipboard is empty.

Question

How do I make tk copy to the clipboard regardless of what happens to the containing window?

My Code

from tkinter import *
from tkinter import messagebox

url = 'http://testServer/feature/'

def copyToClipboard():
    top.clipboard_clear()
    top.clipboard_append(fullURL.get())
    top.update()
    top.destroy()

def updateURL(event):
    fullURL.set(url + featureNumber.get())

def submit(event):
    copyToClipboard()

top = Tk()
top.geometry("400x75")
top.title = "Get Test URL"

topRow = Frame(top)
topRow.pack(side = TOP)

bottomRow = Frame(top)
bottomRow.pack(side = BOTTOM)

featureLabel = Label(topRow, text="Feature Number")
featureLabel.pack(side = LEFT)

featureNumber =  Entry(topRow)
featureNumber.pack(side = RIGHT)

fullURL = StringVar()
fullURL.set(url)

fullLine = Label(bottomRow, textvariable=fullURL)
fullLine.pack(side = TOP)

copyButton = Button(bottomRow, text = "Copy", command = copyToClipboard)
copyButton.pack(side = TOP)

featureNumber.focus_set()
featureNumber.bind("<KeyRelease>", updateURL)
featureNumber.bind("<Return>", submit)

top.mainloop()

Purpose of the Program

My company has a test server we use for new features. Every time we create a new feature we need to post a url to it on the test server. The urls are identical save for the feature number, so I created this python program to generate the url for me and copy it to the clipboard.

I can get this working if I comment out "top.destroy" and paste the url before manually closing the window, but I would really like to avoid this. In a perfect world I would press a shortcut, have the window pop up, enter my feature number, then just press enter to close the window and paste the new url, all without taking my hands off the keyboard.

TripleD
  • 199
  • 1
  • 15
  • `ipdateLink` in `featureNumber.bind("", updateLink)` is not shown in your code causing your code to be not testable. Please provide a testable version of your code. This Post should help: [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) – Mike - SMT Sep 12 '17 at 15:00
  • This appears to be a duplicate of https://stackoverflow.com/q/20866593/7432 – Bryan Oakley Sep 12 '17 at 15:23
  • I suggest trying the suggestion here to see if it solves your problem: https://bugs.python.org/msg254055 – Bryan Oakley Sep 12 '17 at 15:26
  • @SierraMountainTech Apologies, that was an error in copying. I have corrected it to "updateURL". – TripleD Sep 12 '17 at 15:31
  • @Bryan Oakly. That sounds exactly like the problem I am facing except in Ubuntu. From the info below it sounds like this is a problem in tk itself. The solution in the link does provide a valid workaround though. Thanks. – TripleD Sep 12 '17 at 15:48
  • This question has alternate answers for writing to the clipboard, bypassing tk: https://stackoverflow.com/questions/579687/how-do-i-copy-a-string-to-the-clipboard-on-windows-using-python – Mark Ransom Sep 12 '17 at 15:52

1 Answers1

3

Your issue about the clipboard being empty if you close the tk app before pasting the clipboard is due to an issue in tkinter itself. This has been reported a few times and it has to due with the lazy way tkinter handles the clipboard.

If something is set to the tkinter clipboard but is not pasted then tkinter will not append the windows clipboard before the app is closed. So one way around that is to tell tkinter to append to the windows clipboard.

I have been testing a method to do that however it is causing some delay in the applications process so its probably not the best solution but is a start. Take a look at this modified version of your code using the import os with the system method.

from tkinter import *
from tkinter import messagebox
import os

top = Tk()
top.geometry("400x75")
top.title = "Get Test URL"

url = 'http://testServer/feature/'
fullURL = StringVar()
fullURL.set(url)

def copyToClipboard():
    top.clipboard_clear()
    top.clipboard_append(fullURL.get())
    os.system('echo {}| clip'.format(fullURL.get()))
    top.update()
    top.destroy()

def updateURL(event):
    fullURL.set(url + featureNumber.get())

def submit(event):
    copyToClipboard()

topRow = Frame(top)
topRow.pack(side = TOP)
bottomRow = Frame(top)
bottomRow.pack(side = BOTTOM)
featureLabel = Label(topRow, text="Feature Number")
featureLabel.pack(side = LEFT)
featureNumber =  Entry(topRow)
featureNumber.pack(side = RIGHT)
fullLine = Label(bottomRow, textvariable=fullURL)
fullLine.pack(side = TOP)
copyButton = Button(bottomRow, text = "Copy", command = copyToClipboard)
copyButton.pack(side = TOP)
featureNumber.focus_set()
featureNumber.bind("<Return>", submit)

top.mainloop()

When you run the code you will see that the code freezes the app but once its finishes processing after a few seconds it will have closed the app and you can still paste the clipboards content. this servers to demonstrate that if we can write to the windows clipboard before the tkinter app is closed it will work as intended. I will look for a better method but this should be a starting point for you.

Here is a few links of the same issue that has been reported to tkinter.

issue23760

1844034fffffffffffff

732662ffffffffffffff

822002ffffffffffffff

UPDATE:

Here is a clean solution that uses the library pyperclip

This is also cross platform :)

from tkinter import *
from tkinter import messagebox
import pyperclip

top = Tk()
top.geometry("400x75")
top.title = "Get Test URL"

url = 'http://testServer/feature/'
fullURL = StringVar()
fullURL.set(url)

def copyToClipboard():
    top.clipboard_clear()
    pyperclip.copy(fullURL.get())
    pyperclip.paste()
    top.update()
    top.destroy()

def updateURL(event):
    fullURL.set(url + featureNumber.get())

def submit(event):
    copyToClipboard()

topRow = Frame(top)
topRow.pack(side = TOP)
bottomRow = Frame(top)
bottomRow.pack(side = BOTTOM)
featureLabel = Label(topRow, text="Feature Number")
featureLabel.pack(side = LEFT)
featureNumber =  Entry(topRow)
featureNumber.pack(side = RIGHT)
fullLine = Label(bottomRow, textvariable=fullURL)
fullLine.pack(side = TOP)
copyButton = Button(bottomRow, text = "Copy", command = copyToClipboard)
copyButton.pack(side = TOP)
featureNumber.focus_set()
featureNumber.bind("<Return>", submit)

top.mainloop()
Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • Works perfectly. Like you said there's a bit of a delay, but I only need to run it once or twice a day at most, so that's not a deal breaker. Thanks! – TripleD Sep 12 '17 at 15:42
  • I am glad it works for you. I would still look into a better method as the delay should not be happening. – Mike - SMT Sep 12 '17 at 15:43
  • The link provided by Bryan Oakly also pointed to a solution using 'after' to run 'destroy' after a small delay and returning to the mainloop. That works, and has no delay, but I'm not a personal fan of 'delay and hope for the best' solutions. – TripleD Sep 12 '17 at 15:46
  • @TripleD I just added a 2nd option to my answer that uses the import `pyperclip` and it works perfectly with no delay! You will need to add pyperclip library to your python. – Mike - SMT Sep 12 '17 at 15:48
  • @TripleD I did actually try the `after()` method but that did not fix the issue for me. Even at a 5 second delay. So I came up with the `os.system` method. – Mike - SMT Sep 12 '17 at 15:50
  • Interesting. In any case thank you for the Pyperclip solution. I just tested it out and it's working exactly as I envisioned it. – TripleD Sep 12 '17 at 15:56