How to auto-activate a tkinter simpledialog pop-up

2019-09-17 17:24发布

问题:

I have this function inside one of my python scripts which throws up a Tkinter simple dialog screen to ask for some simple user-input. The function works. However, there are 2 problems with it.

  1. It opens up two windows, while all I need is one. But if I remove the master = Tk() I get the error:

AttributeError: 'NoneType' object has no attribute 'winfo_viewable'

It would be nice to at one point figure that one out, but my main problem however is the second one:

  1. Whenever the simple dialog screen turns up, I have to click it first before it gets activated, which is annoying. To fix it I tried the solutions offered here and here but they do not work. The first link didn't do anything for me at all, the second link helped me to lift the master.Tk() window to the front, but that is not what I need. I need the simple dialog window to become the topmost window and I need it to be auto-activated so that when I run my code and the screen pops-up I can automatically type in it without having to click on it first.

Any help would be greatly appreciated!

My code:

def getUser():
    master = Tk()
    newList2=str(newList).replace(", ","\n")
    for ch in ['[',']',"'"]:
        if ch in newList2:
            newList5=newList2.replace(ch,"")
    userNr=simpledialog.askinteger("Enter user number", newList2)
    chosenUsernr= userNr - 1
    global chosenUsernrdef
    chosenUsernrdef = chosenUsernr
    master.destroy()

回答1:

I don't think there is a way to lift/give focus to it but askinteger is merely a combination of couple widgets so you can easily recreate it yourself.

import tkinter as tk
from tkinter import messagebox

class CustomAskInteger(tk.Tk):
    def __init__(self,  numbers):
        tk.Tk.__init__(self)

        self.value = None
        self.label = tk.Label(self, text=", ".join(map(str, numbers))).pack(fill="both", expand=True)
        self.entry = tk.Entry(self)
        self.button = tk.Button(self, text="Ok", command=self.get_number)

        self.entry.pack()
        self.button.pack()

    def get_number(self):
        """
        You can customize these error handlings as you like to
        """
        if self.entry.get():
            try:
                int(self.entry.get())
                self.value = self.entry.get()
                self.destroy()
            except ValueError:
                messagebox.showwarning("Illegal Value", "Not an integer.\nPlease try again.")
        else:
            messagebox.showwarning("Illegal Value", "Not an integer.\nPlease try again.")

To use this in your code, you can do

def getUser():       
    newList2=str(newList).replace(", ","\n")
    askInteger = CustomAskInteger("Enter user number", newList2)
    #since it is a Tk() instance, you can do lift/focus/grab_set etc. on this
    askInteger.lift()
    askInteger.mainloop()
    userNr = askInteger.value


回答2:

First, credits to Lafexlos for showing a solution of how to apply .lift() and similar commands on a Tkinter simpledialog.askinteger() window by recreating such a window as a Tk() instance.

For those however looking how to automatically activate a Tk-window (so you do not have to click on it before being able to type in it), there appear to be multiple options.

The most common solution seems to be to use .focus() or .force_focus() as seen implemented here and here. However over here it seems those options may not work on (at least some versions of) Windows OS. This question shows a possible solution for those systems. Also, the previous solutions appear not to work on multiple versions of OS X. Based on the solution offered here, using Apple's osascript, I was able to solve my problem.

The working code eventually looks like this:

def getUser():
    master = Tk()
    newList2=str(newList).replace(", ","\n")
    for ch in ['[',']',"'"]:
        if ch in newList2:
            newList2=newList2.replace(ch,"")
    cmd = """osascript -e 'tell app "Finder" to set frontmost of process "Python" to true'"""
    def stupidtrick():
        os.system(cmd)
        master.withdraw()
        userNr=simpledialog.askinteger("Enter user number", newList2)
        global chosenUsernrdef
        chosenUsernr= userNr - 1
        chosenUsernrdef = chosenUsernr
    stupidtrick()
    master.destroy()

Simplified / general solution:

import os
from tkinter import Tk
from tkinter import simpledialog

def get_user():
    root = Tk()
    cmd = """osascript -e 'tell app "Finder" to set frontmost of process "Python" to true'"""
    def stupid_trick():
        os.system(cmd)
        root.withdraw()
        new_window=simpledialog.askinteger("Title of window", "Text to show above entry field")
    stupid_trick()
    root.destroy()

get_user()

EDIT: Now I am figuring out what to look for the solution appears to be found already by multiple posts. For those on OS X wanting to activate a specific Tkinter window when multiple instances of Tkinter and/or python are running simultaneously, you might want to look here.