Search code examples
pythontkinterfocuslost-focus

How do I use Tkinter and allow my application to keep the focus?


I have a small application that the user can interact with as a command line. I want the user to be able to copy to the Windows clipboard information that the application has just displayed on-screen. Obviously, the user can do this manually, but it takes several steps: right-click on the window, select "Mark", select the rectangle of text, and press Enter to copy it. I want to allow the user to do this automatically by typing a short command like "cb" or "copy".

Per this answer, an easy way to get clipboard functionality is using the tkinter library. This does indeed work well. However, I find that when my application starts up, it loses the focus. It seems that a hidden window (opened by Tk() and then hidden by withdraw()) has it. The act of hiding the window with withdraw() did not give focus back to my application. This is inconvenient, because having opened the application, the user has to manually switch back to it rather than being able to just begin typing.

I want to create a tkinter object and either give the focus back to my application after I hide the new window, or have my application not lose focus in the first place. How can I do this?

There are various questions already relating to tkinter and focus, but they seem generally to relate to giving focus to the windows that tkinter itself opens, whereas I want to keep focus on the original window of my application, and deny it to the tkinter window.

I'm working at a Windows 8 machine.

Pastebin http://pastebin.com/6jsasiNE


Solution

  • On Windows NT, Windows Server 2003, and Windows 7+

    You don't need to use Tkinter at all to achieve your goal.

    clip.py:

    import subprocess
    
    def write_to_clipboard(string):
        p = subprocess.Popen(['clip'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        p.communicate(input=string)
    

    This code just calls the standard windows clip.exe utility, pasting whatever passed in the string variable.

    Usage:

    from clip import write_to_clipboard
    try:
        while True:
            write_to_clipboard(raw_input())        
    except KeyboardInterrupt:
        pass
    

    On Windows 95, 98, ME, and XP

    Those versions of windows don't come with clip.exe, so here's a python only version:

    clip.py:

    import subprocess
    
    def write_to_clipboard(string):
        p = subprocess.Popen(['python', __file__], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        p.communicate(input=string)
    
    if __name__ == "__main__":
        import sys
        from Tkinter import Tk
    
        r = Tk()
        r.withdraw()
        r.clipboard_clear()
        r.clipboard_append(sys.stdin.read())
        r.update()
        r.destroy()
    

    This will work on all version of windows and in face any OS supporting TK.

    Note that you must run the Tk code in a separate process like this, because even though we call r.destroy(), Tk seems to "lock" the clipboard (no other process can access the clipboard until this process has exited).

    Reading and Writing Clipboard

    If you want to be able to read from the clipboard as well as write to it, this is the solution.

    clip.py:

    import subprocess
    
    def write(string):
        p = subprocess.Popen(['python', __file__, 'write'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        p.communicate(input=string)
    
    def read():
        p = subprocess.Popen(['python', __file__, 'read'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        return p.communicate()[0]
    
    if __name__ == "__main__":
        import sys
        from Tkinter import Tk
    
        if len(sys.argv) != 2:
            sys.exit(1)
    
        r = Tk()
        try:
            r.withdraw()
            if sys.argv[1] == "write":
                r.clipboard_clear()
                r.clipboard_append(sys.stdin.read())
                r.update()
            elif sys.argv[1] == "read":
                sys.stdout.write(r.clipboard_get()),
            else:
                sys.exit(1)
        finally:
            r.destroy()
    

    Usage:

    import clip
    print "clipboard contains: %s" % clip.read()