Unresponsive tkinter SimpleDialog box - python

Below is an outline of a tkinter GUI in which I want the same dialog box to be opened in various ways. The response selected by the user from choices in the dialog then needs to be returned to the mainloop.
The SimpleDialog class looks to be ideal for this and here I have just used the example provided in the dialog code. It is accessed by both the button and popup menu in the View class, along with their bindings in the Controller class.
It works just fine when called from the button, but when called from the popup menu (from a right click) the dialog appears and then freezes the entire app.
from tkinter import simpledialog as s
import tkinter as tk
class View(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self)
self.grid(row=0, column=0, sticky='nsew')
self.configure(bg = 'blue')
self.popup = tk.Menu(self, tearoff=0)
self.bind("<Button-2>", self.make_popup) #right-click to show popup
self.button = tk.Button(self, text='Test')
self.button.grid()
def make_popup(self, event):
try:
self.popup.tk_popup(event.x_root + 15, event.y_root, 0)
finally:
self.popup.grab_release()
class Controller():
def __init__(self, view):
view.popup.add_command(label ='do test', command = lambda : self.do_test(None, view))
view.popup.add_command(label ='dummy test', command = print('This one works OK'))
view.button.bind("<Button-1>", lambda e, : self.do_test(e, view))
def do_test(self, event, view):
d = s.SimpleDialog(view,
text="This is a test dialog. "
"Would this have been an actual dialog, "
"the buttons below would have been glowing "
"in soft pink light.\n"
"Do you believe this?",
buttons=["Yes", "No", "Cancel"],
default=0,
cancel=2,
title="Test Dialog")
print(d.go())
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('200x100')
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
view = View(self)
controller = Controller(view)
if __name__ == "__main__":
app = App()
app.mainloop()
It seems to me that the dialog should either work or not work, and not care how it is called. So I would be very grateful for an explanation as to why it responds in one case but not the other, and of course equally grateful for a fix.

The problem appears to lie in the print statement in the do_test callback, as splitting this into two lines fixes it
#print(d.go())
answer = d.go()
print(answer)
As reported in a comment this may be only an issue for MacOS (I am using MacOS 11.1 and Python 3.10.8 ).

Related

How do you focus a window after launching another application in tkinter?

In my app, I have a callback that simply opens a file. The problem is that once python opens that file, my application loses focus. This behavior is going to really slow down my process. Here is an example of my issue:
import os
from tkinter import *
class App(Frame):
def __init__(self, *args, **kwargs):
Frame.__init__(self, *args, **kwargs)
Button(self, text='Open File', bg=self['bg'],
command=lambda: self.open_file('path_to_movie_or_PDF_or_anything_that_launches_application'),
relief=GROOVE).pack(padx=10, pady=10)
self.entry_field = Entry(self, bg=self['bg'], width=20)
self.entry_field.pack(padx=10, pady=10)
self.entry_field.focus()
def open_file(self, some_path):
os.startfile(some_path)
self.handle_focus()
def handle_focus(self):
# ATTEMPT 1
# self.master.after(1, lambda: self.master.focus_force())
# self.entry_field.focus()
# ATTEMPT 2
# self.master.attributes("-topmost", True)
# self.master.lift()
# self.entry_field.focus()
# ATTEMPT 3
# self.master.focus_set()
# self.entry_field.focus_set()
# ATTEMPT 4
# self.master.focus_force()
# self.master.lift()
# self.master.update()
# self.entry_field.focus()
pass
if __name__ == '__main__':
root = Tk()
root.config(bg='white')
App(root, bg='white').pack()
root.mainloop()
I am trying to open the file with the push button, then immediately turn the focus to the entry bar. How can this be achieved?
I have already looked to Tkinter main window focus for the answer, but focus_force() is not fixing the issue although it's function is exactly what I need. The documentation for focus_force() states that "Direct input focus to this widget even if the application does not have the focus. Use with caution!" For some reason, this does not work.
As you stated you wanted to set focus on the input field
inputfield.focus_set()
thats all

I get the error _tkinter.TclError: bad window path name ".!button" when I destroy the button

from tkinter import *
master=Tk()
class check:
def __init__(self,root):
self.root=root
self.b1=Button(root,text="Click me",command=self.undo)
self.b2=Button(root,text="Again",command=self.click)
def click(self):
self.b1.place(relx=0.5,rely=0.5)
def undo(self):
self.b1.destroy()
self.b2.place(relx=0.2,rely=0.2)
c=check(master)
c.click()
master.mainloop()
This is my code. I get _tkinter.TclError: bad window path name ".!button" error only when I use destroy method. But I want to delete previous button when another button appears.What should I do?
What are you doing? When you click the "Click me" button (and call the self.undo method, where the self.b1 button is destroyed) and then click the "Again" button (and call the self.click method, which tries to place already destroyed self.b1 button), you get the error, that the button does not exist. Of course, it doesn't because you have destroyed it.
It looks like you meant to hide the button. If you intended to do this, then you could just use .place_forget() method (there are also .pack_forget() and .grid_forget() methods for pack and grid window managers, respectively), that hides the widget, but not destroys it, and hence you would be able to restore it again when you need.
Here is your fixed code:
from tkinter import *
master = Tk()
class check:
def __init__(self, root):
self.root = root
self.b1 = Button(root, text="Click me", command=self.undo)
self.b2 = Button(root, text="Again", command=self.click)
def click(self):
self.b2.place_forget()
self.b1.place(relx=0.5, rely=0.5)
def undo(self):
self.b1.place_forget()
self.b2.place(relx=0.2, rely=0.2)
c = check(master)
c.click()
master.mainloop()
I can also give you a piece of advice about the implementation:
1) You should write the code according to the PEP8 style; classes should be named in the CamelCase.
2) You should inherit your Tkinter app class(es) either from Tk (usage is shown below) Toplevel(the same as Tk, but use ONLY for child windows), Frame class (almost the same as for Tk, but you need to pack/grid/place that Frame in a window).
3) It's better to create the widgets in a separate function (it helps while developing complex and big apps).
4) It's recommended to write if __name__ == "__main__": condition before creating the window (if you do like this, you will be able to import this code from other modules, and the window won't open in that case).
Here is an example:
from tkinter import *
class Check(Tk):
def __init__(self):
super().__init__()
self.create_widgets()
self.click()
def create_widgets(self):
self.b1 = Button(self, text="Click me", command=self.undo)
self.b2 = Button(self, text="Again", command=self.click)
def click(self):
self.b2.place_forget()
self.b1.place(relx=0.5, rely=0.5)
def undo(self):
self.b1.place_forget()
self.b2.place(relx=0.2, rely=0.2)
if __name__ == "__main__":
Check().mainloop()
After you destroyed button b1 in the undo(self) function tkinter cannot access it anymore and will be confused when you try to place is somewhere in the click(self) function.
To make button b1 only disappear visually you could place it outside of the window instead of destroying it. To do so replace
self.b1.destroy()
with
self.b1.place(relx=-5, rely=0)
This will move the button b1 far to the left, where it cannot be seen.
When calling the click(self) function, the button will reappear, because it will be moved inside the window again.

Tkinter newly created button does not execute command

A script should open an application with two buttons visible. When Hello button is pressed a new button is gridded into the row number 1 and Hello button to be deactivated. When this new button is pressed it should delete itself off the grid and reactivate hello button but it does not do it.
Please check the video to see it in action.
Code edited to comment suggestion
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
self.master = master
self.master.geometry('300x100+10+10')
Frame.__init__(self, master)
self.pack()
self.createWidgets()
def new_button(self):
print("enable_b")
self.hi_there.config(state=ACTIVE)
self.new_button.grid_remove()
def say_hi(self):
print("hi there, everyone!")
self.new_button = Button(self)
self.new_button.config(text = "New BTN", command=self.new_button)
self.new_button.grid(row=1,column=0)
self.hi_there.config(state=DISABLED)
def createWidgets(self):
self.QUIT = Button(self)
self.QUIT.config(text="QUIT",fg="red",command=self.quit)
self.QUIT.grid(row=0,column=1)
self.hi_there = Button(self)
self.hi_there["text"] = "Hello",
self.hi_there["command"] = self.say_hi
self.hi_there.grid(row=0,column=0)
def quit(self):
self.master.destroy()
def testit():
root = Tk()
app = Application(master=root)
app.mainloop()
if __name__ == '__main__':
testit()
Initially, self.new_button refers to a method. Then, you do this:
self.new_button = Button(self)
That effecting removes the method and replaces it with the button widget itself.
Also, you never assign a command to the new button, so clicking it doesn't cause anything to be called.
Where your program will technically work just fine with the 2 correction mentioned in Bryan's answer I am not sure why you are taking all the extra effort configuring the widgets for each individual field. All your configs can be done when you create the widget.
That said you can also change a few things for a cleaner code and 1 change I think that really needs to be made is how you are removing the new_button from the grid. When you do grid_remove() this only takes the widget off the screen but does not get rid of the widget. Then next time you press the say_hi button you will end up creating a new button and the old button will still exist. Instead I think I would use destroy() on the button and then let say_hi recreate it.
See this revised version of your code. You will see what I mean about configuring everything when creating the widget and also you do not need to write your own function for quit you can simply do self.master.destroy in the quit button command.
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.master = master
self.master.geometry('300x100+10+10')
self.create_widgets()
def new_btn(self):
print("enable_b")
self.hi_there.config(state="active")
self.new_button.destroy()
def say_hi(self):
print("hi there, everyone!")
self.new_button = tk.Button(self, text="New BTN", command=self.new_btn)
self.new_button.grid(row=1, column=0)
self.hi_there.config(state="disabled")
def create_widgets(self):
tk.Button(self, text="QUIT", fg="red", command=self.master.destroy).grid(row=0,column=1)
self.hi_there = tk.Button(self, text="Hello", command=self.say_hi)
self.hi_there.grid(row=0, column=0)
if __name__ == '__main__':
root = tk.Tk()
app = Application(master=root).pack()
root.mainloop()

Tkinter: Keep from moving focus to window

I am trying to make an on screen keyboard. It works for widgets in the window but when I press a button it moves focus from the window I am trying to type in to the window that has the buttons. How do I prevent python from moving?
from tkinter import *
from pynput.keyboard import Key, Controller
keyboard = Controller()
class App:
def __init__(self, master):
self.entry = Entry()
self.buttonOne = Button(text='1')
self.buttonTwo = Button(text='2')
self.buttonThree = Button(text='3')
self.buttonOne.bind("<Button-1>", lambda event, keyPressed='1': self.pressed(event, keyPressed))
self.buttonTwo.bind("<Button-1>", lambda event, keyPressed='2': self.pressed(event, keyPressed))
self.buttonThree.bind("<Button-1>", lambda event, keyPressed='3': self.pressed(event, keyPressed))
self.entry.grid(row=0, column=0, columnspan=3)
self.buttonOne.grid(row=1, column=0)
self.buttonTwo.grid(row=1, column=1)
self.buttonThree.grid(row=1, column=2)
def pressed(self, event, keyPressed):
keyboard.press(keyPressed)
keyboard.release(keyPressed)
root = Tk()
app = App(root)
root.mainloop()
I would suggest using withdraw() and deiconify(). This will make it so the window with the button is invisible once you call it on that window. Once you use deiconify() it reverses this and makes it visible again.
More information can be found here.
Question: Keep from moving focus to window
On X11, you can set the -type attribute:
self.wm_attributes("-type", 'dock')
'dock' will working for me not to grab the focus, but are not supported by all window managers.
Reference:
wm attributes
Communicate with window manager
A list of types
-type
Requests that the window should be interpreted by the window manager as being of the specified type(s). This may cause the window to be decorated in a different way or otherwise managed differently, though exactly what happens is entirely up to the window manager.
'dock'
indicates a dock/panel feature,
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.wm_attributes("-type", 'dock')
for n in range(64, 68):
btn = tk.Button(self, text=chr(n), takefocus=0)
btn.bind("<Button-1>", self.on_click)
btn.pack()
def on_click(self, event):
w = event.widget
print(w['text'])
if __name__ == '__main__':
App().mainloop()

How do you validate a window in Tkinter?

def createWidgets(self):
self.INSTRUCTIONS = Button(self) #creating button linked to instructions_window
self.INSTRUCTIONS["text"] = "Instructions"
self.INSTRUCTIONS["fg"] = "green"
self.INSTRUCTIONS["command"] = self.instruction_window #command which opens instructions_window
self.INSTRUCTIONS.pack({"side": "left"})
Currently, if I press the button multiple times then the instructions window will open multiple times. How do I ensure that when the button is pressed, if the window is already open then it will flash to show that the same window can't be opened. Is there a command? Or do I need to use a validation of some sort?
Here's a great article dealing with more complicated examples of dialog boxes.
Essentially what you are looking for is almost like a modal dialog window except it seems with the additional ability to still interact with the parent window to a degree. At this point it may be worth considering making it totally modal, but I do not know your use case. If not, you can definitely adapt the scripts given on the tutorial website to fit your needs.
The way I do this is to create a function that will create the window if it doesn't exist, and then display the window. Personally I don't think there's a need to flash the window, but you could do that if you want. Tkinter doesn't know how to flash a window, but you can do something simple like changing the colors briefly.
Here's an example:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.instruction_window = None
self.instructions = tk.Button(self, text="Instructions", foreground="green",
command=self.show_instructions)
self.instructions.pack(side="left")
def show_instructions(self):
'''show the instruction window; create it if it doesn't exist'''
if self.instruction_window is None or not self.instruction_window.winfo_exists():
self.instruction_window = InstructionWindow(self)
else:
self.instruction_window.flash()
class InstructionWindow(tk.Toplevel):
'''A simple instruction window'''
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.text = tk.Text(self, width=40, height=8)
self.text.pack(side="top", fill="both", expand=True)
self.text.insert("end", "these are the instructions")
def flash(self):
'''make the window visible, and make it flash temporarily'''
# make sure the window is visible, in case it got hidden
self.lift()
self.deiconify()
# blink the colors
self.after(100, lambda: self.text.configure(bg="black", fg="white"))
self.after(500, lambda: self.text.configure(bg="white", fg="black"))
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()

Categories

Resources