Tkinter: Put simpledialog.askinteger in a toplevel box - python

I have trouble using the simpledialog widget within a toplevel widget. The code extract below results in an empty pop-up window (entitled "Blocked fields"), a second pop-up window with the correct simpledialog (also working fine) and the main game window (not featured here in the code).
I want to get rid of the second obsolete window, and I reckon it must be a simple thing, but I am stuck (complete python newbie, if you can't tell already). Any hints highly appreciated!
import tkinter as tk
from tkinter import simpledialog
root = tk.Tk()
root.geometry("580x400+300+200")
root.title("Pah Tum")
#Popup window
block_request_top = tk.Toplevel()
block_request_top.title("Blocked fields")
entry_block = simpledialog.askinteger("Blocked fields", \
"Please enter a number of fields to be blocked. Choose an \
uneven number between 5,13]", parent=block_request_top, minvalue=5, \
maxvalue=13)

You shouldn't need the Toplevel() window at all. askinteger() is a dialogbox and does not require a container widget. Just skip the block_request_top window code.
import tkinter as tk
from tkinter import simpledialog
root = tk.Tk()
root.geometry("580x400+300+200")
root.title("Pah Tum")
# Popup window
#block_request_top = tk.Toplevel()
#block_request_top.title("Blocked fields")
entry_block = simpledialog.askinteger("Blocked fields",
"Please enter a number of fields to be blocked. Choose an \
uneven number between 5,13]", parent=root, minvalue=5, # parent changed...
maxvalue=13)
print('Okay, I will block %d fields.' % entry_block) # new, to see value
# set up the rest of your GUI
root.mainloop() # You need this for the GUI to remain alive.
The value of parent was updated to root, to reflect the Toplevel window going away.
You also need the root.mainloop() call at the end, to keep the GUI active and running. Once your program gets here, the Tkinter system essentially just waits for "events" to happen, like the user clicking a button or typing into a field. You still have to tie all this together with all the buttons you have to draw. There are a few people posting about this same problem.

Easiest way to achieve this for this specific problem may be making use of withdraw, iconify and deiconify methods by creating entry_block in between them as in:
...
block_request_top.withdraw()
entry_block = simpledialog.askinteger("Blocked fields", \
"Please enter a number of fields to be blocked. Choose an \
uneven number between 5,13]", parent=block_request_top, minvalue=5, \
maxvalue=13)
block_request_top.iconify()
block_request_top.deiconify()
entire code:
import tkinter as tk
from tkinter import simpledialog
root = tk.Tk()
root.geometry("580x400+300+200")
root.title("Pah Tum")
#Popup window
block_request_top = tk.Toplevel()
block_request_top.title("Blocked fields")
block_request_top.withdraw()
entry_block = simpledialog.askinteger("Blocked fields", \
"Please enter a number of fields to be blocked. Choose an \
uneven number between 5,13]", parent=block_request_top, minvalue=5, \
maxvalue=13)
block_request_top.iconify()
block_request_top.deiconify()
I highly doubt that this is what you will eventually end up using though.

Related

Is it possible to center a tkinter.simpledialog window?

I'm including a conditional password capture in a larger script. The code looks like this:
if thing: # found token, don't need password
do stuff
else: # no token, get password in popup
try:
import tkinter as tk
import tkinter.simpledialog
tk.Tk().withdraw()
passwd = tkinter.simpledialog.askstring("Password", "Enter password:", show="*")
return passwd
functionally it's fine, but most of my users are on monitors at 3840x1600 resolution, so when the dialog pops up at the top left it's easy to miss.
Is there a brief way to override the simpledialog class to tell it to appear at a certain X/Y on the monitor, or is my only option to build a full mainloop()?
so when the dialog pops up at the top left it's easy to miss.
If you specify the optional argument parent, the dialogbox will appear in the middle of your window and gets the focus internally. Make sure your window is mapped via .update_idletasks() to get corresponding coordinates. You may consider also to make your window transparent instead of withdraw.
import tkinter as tk
import tkinter.simpledialog
def ask_pw():
pw = tkinter.simpledialog.askstring("Password",
"Enter password:",
show="*",
parent=root)
root = tk.Tk()
root.geometry('250x250+500+500')
root.update_idletasks()
ask_pw()
#root.withdraw()
root.mainloop()
As the dialog is placed relative to the position of its parent, so you can center its parent, i.e. root window in your case:
import tkinter as tk
from tkinter import simpledialog
root = tk.Tk()
# center root window
root.tk.eval(f'tk::PlaceWindow {root._w} center')
root.withdraw()
# set parent=root
passwd = simpledialog.askstring('Password', 'Enter password:', show='*', parent=root)
I couldn't find a way to override any of the classes to directly modify the underlying _QueryString class (also it is private so I didn't want to use that and I probably wouldn't have found an easy way with that anyways). So I just wrote a custom askstring function. The functionality is such that you need to provide the parent and the geometry and it will first schedule an inner function (could be a proper outer one but it should be fine) to get the last widget in the widgets of that parent and after the scheduled time it should be the new dialog so it should get that. (Check in place to check if that widget is derived from dialog.Dialog which is from what the _QueryString inherits). Then just change the geometry (because up the inheritance chain there is Toplevel which obviously has a geometry method):
import tkinter as tk
import tkinter.simpledialog as dialog
def askstring(title, prompt, geometry='', **kwargs):
def change_geometry():
if not geometry:
return
widget = kwargs['parent'].winfo_children()[-1]
if isinstance(widget, dialog.Dialog):
widget.geometry(geometry)
if 'parent' in kwargs:
kwargs['parent'].after(10, change_geometry)
return dialog.askstring(title, prompt, **kwargs)
root = tk.Tk()
root.withdraw()
askstring('Title', 'Prompt', geometry='300x200+200+200', parent=root)
Useful:
source code for tkinter.simpledialog which is what I used to solve this (also from previous experience)

Need to show tkinter toplevel window then sleep; happens the other way around

I am trying to show a tkinter Toplevel window, have the script sleep for one second, then continue running. However, the sleep function always occurs before the toplevel window appears. I'm doing this because I want to show an indication to the user that a label text has been copied on click.
I am planning to have the Toplevel show, sleep for one second, then destroy it. However, as it is currently, the sleep occurs first and then the Toplevelshows and is destroyed immediately too fast for the user to perceive.
Is there a way to have these events happen in my desired order? Here is a script demonstrating what I'm dealing with:
from tkinter import *
from tkinter import ttk
import time
root = Tk() # Creating instance of Tk class
def make_new_window(event):
new_window = Toplevel()
clabel = Label(new_window, text = "Copied")
clabel.pack()
time.sleep(1)
l = Label(root, text = "Example text")
l.pack()
l.bind('<1>', make_new_window)
root.mainloop()

Tkinter Focus lost after askstring

I am currently implementing a program that uses many tkinter frames and while subframe is being opened I want the superframe to be locked for the user (otherwise things will not work out). After some research I found the grab_set and grab_release method which worked quite fine.
However once the subframe (instanciated by Toplevel) calls the askstring the grab is "losed" and the user can interact with the superlevel window again. An example would be this (very simplified code):
import tkinter as tk
import tkinter.simpledialog
root = tk.Tk()
def open_sublevel():
tl = tk.Toplevel(root)
def ask():
print(tk.simpledialog.askstring("askstring", "askstring"))
tk.Button(tl, text="ask", command=ask).pack()
tl.grab_set()
root.wait_window(tl)
tl.grab_release()
print("release")
tk.Button(root, text="asdf", command=open_sublevel).pack()
tk.mainloop()
Once the user opens the subframe by clicking "asdf" the frame containing "asdf" will be locked for the duration while the subframe is opened. However once the user selects the "ask"-Button in the subframe this "lock" somehow disappears.
According to the notes in the tkinter library:
A grab directs all events to this and descendant widgets in the application.
I am not able so far to find any documentation that would explain why the grab_set() is falling off after you finish submitting your askstring but I would imaging it is because once the widget is gone the grab_set() falls off. Just like if you were to close out the Toplevel window.
In this case tl.grab_release() does not appear to be needed as grab releases once the window closes.
From what I have tested if you reset the grab_set() after the askstring is done then it will still work properly.
You need to simply add tl.grab_set() just below print(tk.simpledialog.askstring("askstring", "askstring")).
Modified code below:
import tkinter as tk
import tkinter.simpledialog
root = tk.Tk()
def open_sublevel():
tl = tk.Toplevel(root)
tl.grab_set()
def ask():
print(tk.simpledialog.askstring("askstring", "askstring"))
tl.grab_set()
tk.Button(tl, text="ask", command=ask).pack()
print("release")
tk.Button(root, text="asdf", command=open_sublevel).pack()
tk.mainloop()
setting the parent for simpledialog will make simpledialog take focus
x = simpledialog(parent = window_x, title = z etc.)
this will make sure x takes focus and not withdraw

My python code is not working

This simple code is not working.
I mean its running without errors but
is not showing any gui window for text entry.
from Tkinter import *
from tkMessageBox import *
root=Tk()
Label(root,text="first").grid(row=0)
Label(root,text="second").grid(row=2)
e1=Entry(root)
e1.grid(row=0,column=2)
e2=Entry(root)
e2.grid(row=2,column=3)
def info():
s=showinfo(title="wish",message=e1.get()+''+"welcome to python")
Button(root,text="ok",command=info).pack()
root.mainloop()
You can't use grid and pack on two widgets owned by the same parent. This causes the geometry manager to loop forever.
Change your Button positioning to:
Button(root,text="ok",command=info).grid(row=3, column=0)
(or whatever row/column you want it to be in).
Result:

How to know whether a window with a given title is already open in Tk?

I’ve writen a little python script that just pops up a message box containing the text passed on the command line. I want to pop it up only when the window —resulting from a previous call— is not open.
from Tkinter import *
import tkMessageBox
root = Tk()
root.withdraw()
# TODO not if a window with this title exists
tkMessageBox.showinfo("Key you!", " ".join(sys.argv[1:]))
Any idea how to check that?
I believe you want:
if 'normal' != root.state():
tkMessageBox.showinfo("Key you!", " ".join(sys.argv[1:]))
The previous answer works accordingly to the code you have provided. You say it does not work because the answerer complies with "sois bête et discipliné" rule in that he did not add root.mainloop() to his code since your question does not either.
By adding the later line, for some reason caused by the event loop, you should test the exact string "withdrawn" as follows:
import tkinter as tk
from tkinter import messagebox
import sys
root = tk.Tk()
root.withdraw()
if 'withdrawn' != root.state():
messagebox.showinfo("Key you!", sys.argv[1:])
root.mainloop()
Note: do not run this code otherwise your Terminal session will hang up. To circumvent this discomfort, you will have to reset the window state using either root.state("normal") which will lead to the message box to disappear as if a click on the Ok button occurred, or root.iconify() through which you can stop the Terminal session to hang up by right clicking on the tkinter icon appearing on your OS taskbar.

Categories