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.
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:
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()
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
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.
Related
I have a script which has two tkinter.Tk() objects, two windows. One is hidden from the start (using .withdraw()), and each has a button which hides itself and shows the other (using .deiconify()). I use .mainloop() on the one shown in the beginning. Everything works, but when I close either window, the code after the mainloop() doesn't run, and the script doesn't end.
I suppose this is because one window is still open. If that is the case, how do I close it? Is it possible to have a check somewhere which closes a window if the other is closed?
If not, how do I fix this?
The essence of my code:
from tkinter import *
window1 = Tk()
window2 = Tk()
window2.withdraw()
def function1():
window1.withdraw()
window2.deiconify()
def function2():
window2.withdraw()
window1.deiconify()
button1 = Button(master=window1, text='1', command=function1)
button2 = Button(master=window2, text='2', command=function2)
button1.pack()
button2.pack()
window1.mainloop()
Compiling answers from comments:
Use Toplevel instead of multiple Tk()s. That's the recommended practice, because it decreases such problems and is a much better choice in a lot of situations.
Using a protocol handler, associate the closing of one window with the closing of both. One way to do this is the following code:
from _tkinter import TclError
def close_both():
for x in (window1,window2):
try:
x.destroy()
except TclError:
pass
for x in (window1,window2):
x.protocol("WM_DELETE_WINDOW", close_both)
Dear fellow programmers,
I use Python 2.7 on windows 10 64 bits.
I have an issue with a Tkinter window. In a parent program, I want to save a file and I ask the name of the file in a Tkinter window. My problem is that I don't succeed to get this name outside of the Tkinter window. Here is the Python code:
from Tkinter import *
globalFilename = ""
class Master:
def __init__(self, top):
self.filename = ""
frame_e = Frame(top)
frame_e.pack()
self.t_filename = StringVar()
entry = Entry(frame_e, textvariable=self.t_filename, bg="white")
entry.pack()
entry.focus_force()
saveButton = Button(frame_e, text="Save", command=self.on_button)
saveButton.pack(side=BOTTOM, anchor=S)
def on_button(self):
self.filename = self.t_filename.get()
print self.filename
root.quit()
root.destroy()
root = Tk()
root.geometry("100x100+100+50")
M = Master(root)
print M.filename
root.mainloop( )
print M.filename
globalFilename = M.filename
print globalFilename
All print statements in this code give nothing when I enter any text into the Entry textbox. This is not what I expect. If I enter "test" I expect "test" to appear for each print statement (i. e. four times here). I tried to go everywhere on the Internet, I tried various tutorials, I tried to copy various examples, to follow various videos, I just don't succeed in fixing this issue.
Note that this piece of code is embedded into a function called saveGame, which is used in a pygame loop.
Thanks in advance! All the best!
Your code works.
The window is not drawn on the screen until you call mainloop(), so printing M.filename before that point prints an empty string (the initialization value). The mainloop() blocks until the window closes, after which 3 print statements successfully print the value that the user entered into the box.
You may be interested in the easygui module, which does exactly what your program does except you don't have to make it yourself.
Ok, as other posters told, the above code works in a vacuum. It did not work as embedded in my program because I initialized a duplicate tk() before calling my function and initialize it again. I removed this duplicate and it worked.
I'm working with Tkinter in Python 2.7 on Windows 7, and found the need to create a popup box with a tree-style list of checkboxes. I could not find this in Tkinter, or ttk. I did, however, find it in Tix in the CheckList widget. I got a working standalone example using Tix, but I cannot figure out how to add my Tix.CheckList to my ttk.Frame that controls my main program.
Surely I am not forced to use Tix framework from the ground up?
import Tix
import pandas as pd
import Tkinter as tk
class TreeCheckList(object):
def __init__(self, root):
self.root = root
self.cl = Tix.CheckList(self.root)
self.cl.pack(fill=Tix.BOTH, expand=Tix.YES)
self.cl.hlist.config(bg='white', bd=0, selectmode='none', selectbackground='white', selectforeground='black', drawbranch=True, pady=5)
self.cl.hlist.add('ALL', text='All Messages')
self.cl.hlist.add('ALL.First', text='First')
self.cl.setstatus('ALL.First', "off")
self.cl.hlist.add('ALL.Second', text='Second')
self.cl.setstatus('ALL.Second', "off")
self.cl.autosetmode()
def main():
root = Tix.Tk()
top = Tix.Toplevel(root)
checklist = TreeCheckList(top)
root.update()
top.tkraise()
root.mainloop()
if __name__ == '__main__':
main()
The above code works in a standalone program using all Tix widgets. However, when I try to implement this into my larger program, I receive a TclError: invalid command name "tixCheckList"
To simulate this in the standalone, I changed the lines:
root = Tix.Tk()
top = Tix.Toplevel(root)
to
root = tk.Tk()
top = tk.Toplevel(root)
I was hoping I could just implement a Tix.Toplevel, placing it on a tk.Tk() root, but same issue.
Am I only allowed to use Tix frames when using a Tix widget, or am I misunderstanding something? If anyone has good Tix documentation, I would LOVE whatever I can get. It seems good docs on it are few and far between. Or is this same functionality included in ttk and I've just overlooked it? It seems to be one of the only things left out.
I have just learned that apparently only root needs to be a Tix class. Since Tk, and therefore ttk, classes appear to be added to the Tix root just fine (since most of them extend the Tkinter classes anyway), this appears to be a "fix". So my problem may have been solved by changing just
root = tk.Tk()
to
root = Tix.Tk()
This did require that I pull Tix into a part of my program I wasn't wanting for encapsulation purposes, but I guess there's no other way.
I am creating a little time management tool, using Tkinter, so I can keep on task at work. I am having trouble with one aspect that I cannot seem to get working. I'm using the error box so that it is displayed in front of all other windows.
As it is now, the program starts a new thread on a function that keeps track of time, and compares it to the time the user entered for their task. Once real time > the time entered by the user, it starts another thread to spawn the tkMessageBox. I have tried this without starting a new thread to spawn the tkMessageBox, and the problem is the same. If the user enters the same time for 2 separate tasks, the error pop up freezes. I'm having trouble finding information on this topic specifically... The behaviour is odd because if I have 2 alerts, lets say 1 at 0600 and one at 0601, but I do not close the first error box that pops up and let it stay up until the second alert triggers, the second alert will just replace the first one(I would like multiple error boxes to pop up if possible). It's only the alerts that have the same trigger time that cause the pop up to freeze though.
This is my first GUI program and only started learning the concept of threading, and GUIs in the past 24 hours, so I'm not sure if this is a problem with threading or the tkMessageBox. Because of the behaviour of the error box, I’m thinking it is the thread module combined with the tkMessageBox module. The command I'm using is:
tkMessageBox.showerror('TIMER ALERT!!!', comp_msg)
Here is the source I put comments in there to help. The tkMessageBox I’m talking about is line 56.
I guess I'm not sure if I can even do what I am trying to do with the pop-up box, if I can, I'm not sure how. If I can't, is there a alternative way to spawn multiple error type pop-up boxes with Tkinter? I just want multiple boxes to be able to appear at any given time.
Thanks in advance, and I really appreciate any help at all.
EDIT:
import thread
from Tkinter import *
#Spawns Error Box. Runs in it's own thread.
def message_box(comp_msg,q): # q is an empty string because of thread module.
print "Spawning Error Box..."
eb =Toplevel()
eb.config(master=None,bg="red")
pop_l = Label(eb,text="ALERT!!!")
pop_l2=Label(eb,text=comp_msg)
pop_l.pack(pady=10,padx=10)
pop_l2.pack(pady=15,padx=10)
return eb
thread.start_new_thread(message_box,(comp_msg,""))
tkmessageBox default dialog boxes are modal. You could implement a simple none modal dialog box for this application. Here is a good document about creating custom dialog boxes.
This way you can create as many new custom dialog boxes as your app requires, since each one is just a new Toplevel.
Here is a simple Tkinter app that shows the clock on the main window. When you click on the button it starts new tkMessageBox dialog boxes in new threads. (If you run it) You could see that the main thread that runs the TK event loop is working (since the time is getting updated), but the error boxes are not showing up as expected.
#!/usr/bin/env python
import datetime
import threading
from Tkinter import *
import tkMessageBox
class MyApp(Frame):
def __init__(self, root=None):
if not root:
root = Tk()
self.time_var = StringVar()
self.time_var.set('starting timer ...')
self.root = root
Frame.__init__(self, root)
self.init_widgets()
self.update_time()
def init_widgets(self):
self.label = Label(self.root, textvariable=self.time_var)
self.label.pack()
self.btn = Button(self.root, text='show error', command=self.spawn_errors)
self.btn.pack()
def update_time(self):
self.time_var.set( str(datetime.datetime.now()) )
self.root.after(1000, self.update_time)
def spawn_errors(self):
for i in range(3):
t = threading.Thread(target=self.show_error)
t.start()
def show_error(self):
now = datetime.datetime.now()
tkMessageBox.showerror('Error: %s' % (str(now)), now)
if __name__ == '__main__':
app = MyApp()
app.mainloop()
I am building an Interface with TKinter and I have the problem, that whenever create a new window the standardized tkinter window of 200x200 pixel with nothing in it flashes up for a fraction of a second and AFTER that all my modifications (widgets ect.) are made. This happens before AND after calling the mainloop.
The Main-Interface is created.
Mainloop stats
Flashing window
Main-Interface appears
Also after Mainloop was called this happens with newly created windows.
Main-Interface appears--> Push a button, that creates a new window
Flashing window
new window appears
Sadly I cannot give you a sample code... If I try to do a minimal example, this doesn't happen. Maybe the standard window is created, but it is changed so fast, that it doesn't appear on screen. I don't even know what to look up in this case... searching "tkinter flashing window" yields nothing.
EDIT: I found the source of the problem. It seems to be caused by wm_iconbitmap, FigureCanvasTkAgg and tkinter.Toplevel. If you remove the the icon out of the code, it works fine, no flashing. But if I use it together with one of the other, the window flashes when created. Try it out with the code below. You have to put the icon in the working directory of course.
Here is a code sample and the link to the icon I am using, but I suppose any icon will do.
# coding=utf-8
import numpy as np
import matplotlib as mpl
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
import os
class INTERFACE(object):
def __init__(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.EXIT)
self.root.wm_iconbitmap( os.path.abspath("icon.ico")) #<---- !!!!!!
self.root.geometry("1024x768")
canvas = FigureCanvasTkAgg(self.testfigure(), master=self.root) #<---- !!!!!!
canvas.get_tk_widget().grid(sticky=tk.N+tk.W+tk.E+tk.S)
self.root.rowconfigure(0, weight=1)
self.root.columnconfigure(0, weight=1)
def testfigure(self):
x=np.linspace(0, 2*np.pi,100)
y=np.sin(x)
fig = mpl.figure.Figure()
sub = fig.add_subplot(111)
sub.plot(x,y)
return fig
def EXIT(self):
Top = tk.Toplevel(master=self.root)
Top.wm_iconbitmap( os.path.abspath("icon.ico")) #<---- !!!!!!
Top.transient(self.root)
Top.resizable(width=False, height=False)
Top.title("Exit")
tk.Message(Top,text="Do you really want to quit?", justify=tk.CENTER, width=300).grid(row=0,columnspan=3)
tk.Button(Top,text="YES",command=self.root.destroy).grid(row=1,column=0)
tk.Button(Top,text="No",command=self.root.destroy).grid(row=1,column=1)
tk.Button(Top,text="Maybe",command=self.root.destroy).grid(row=1,column=2)
def start(self):
self.root.mainloop()
if __name__ == '__main__':
INTERFACE().start()
I know this is an old question, but I've experienced a similar situation and have found a solution.
In my case, I've isolated the issue to the use of iconbitmap. I've managed to solve it by calling iconbitmap with the after method just before calling root.mainloop().
Example:
from tkinter import *
root = Tk()
w = Label(root, text="Hello, world!")
w.pack()
root.geometry('300x300+500+500')
root.after(50, root.iconbitmap('icon.ico'))
root.mainloop()
This method has worked on Toplevel() windows with icons as well.
Tested on Win 8.1 with Python 3.5.0.
Edit: Upon further inspection I've noticed that the behavior changes relative to root.geometry's presence as well. My initial example didn't have it and I only noticed after a few tries that it still had the same issue. The time delay in the after method doesn't seem to change anything.
Moving root.geometry below the after method yields the same issue for some reason.
Most likely, somewhere in your initialization code you're calling update or update_idletasks, which causes the current state of the GUI to be drawn on the screen.
Another possible source of the problem is if you're creating multiple instances of Tk rather than Toplevel.
Without seeing your code, though, all we can do is guess.
Your best route to solving this problem is to create a small example that has the same behavior. Not because we need it to help you, but because the effort you put into recreating the bug will likely teach you what is causing the bug.
This should work but it requires win32gui
import win32gui
def FlashMyWindow(title):
ID = win32gui.FindWindow(None, title)
win32gui.FlashWindow(ID,True)