How do I remove an idlelib "Hovertip" once it's been created? - python

I have a dialog window with some entries on it, and as part of validating those entries I will occasionally disable my 'Okay' button and place an idlelib Hovertip on it letting the user know that there was an issue.
The trouble is that once I re-enable the 'Okay' button, I can't figure out how to remove the Hovertip...so I just set the hover_delay to an arbitrarily long time and hope the user clicks 'Okay' before the tip shows up.
Is there a way to remove the Hovertip once it's been attached to my button, and can this be done in such a way that the Hovertip can be added again when needed?
Setting the message to an empty string just results in an empty Hovertip, and deleting the Hovertip makes it go away forever (even when I want it back).
import tkinter as tk
from tkinter import ttk
from idlelib.tooltip import Hovertip
class App(tk.Tk):
"""Example App for Helpful Folks"""
def __init__(self):
super().__init__()
self.toggle_state = False
self.toggle_btn = ttk.Button(
self,
text='Toggle',
command=self.toggle
)
self.toggle_btn.pack()
self.okay_btn = ttk.Button(
self,
text='Okay',
command=self.bell, # ding
)
self.okay_btn.pack()
def toggle(self):
if self.toggle_state:
self.okay_btn.configure(state=tk.NORMAL)
self.tooltip.__del__() # gone forever, never to return
else:
self.okay_btn.configure(state=tk.DISABLED)
self.tooltip = Hovertip(
self.okay_btn,
'No dice!',
hover_delay=300
)
self.toggle_state = not self.toggle_state
if __name__ == '__main__':
app = App()
app.mainloop()

Related

How to prevent Tkinter multithread from running the function twice?

I made a Tkinter tool which may help me keep copying the text and search it on google without pasting.
In the beginning, when I pressed "Enable Copy", the Tkinter freezed.
So, I used threading module to separate the copy function. Otherwise, I cannot press "Stop" while the copy function is working.
However, now when I pressed "Enable Copy", I can only search the text from my clipboard for the first time. After that, I copied the text once but searched it twice no matter what.
I guess the reason might be the "threading" module, but the Tkinter would freeze without it...
That's where I got stucked.
If anyone has better idea, please kindly advise.
My Code:
import tkinter as tk
import keyboard
import webbrowser
import threading
class myGUI:
def __init__(self):
self.thread_running = bool
self.win = tk.Tk()
self.win.title("Copy Tool")
self.win.geometry('300x50')
tk.Button(self.win, text="Enable Copy", command=self.start_thread).pack()
tk.Button(self.win, text="Stop", command=self.close_thread).pack()
def copy_(self):
while self.thread_running:
if keyboard.read_key()== "ctrl" and keyboard.read_key()== "c":
text_fromcopy=self.win.clipboard_get()
print(text_fromcopy)
if "&" in text_fromcopy:
text_fromcopy = text_fromcopy.replace("&", "%26")
url = "https://www.google.com/search?q={}".format(text_fromcopy)
webbrowser.open(url,new=0)
def close_thread(self):
self.thread_running = False
def start_thread(self):
self.thread_running = True
threading.Thread(target=self.copy_).start()
if __name__ == '__main__':
app = myGUI()
app.win.mainloop()

How to pause window to turn on another window?

I use tkinter and CTK:
I have created a page for login and I want to stop or use this page when the user is logged in, I want to show a new window and I want to resume the window when I want? How can I do that, I didn't know how to make it
I'll bite. Here's an example application that opens a second window when the user clicks a button on the main window, and disables interaction with the main window until the second window is closed.
Some configuration has been omitted for brevity, and I'm not using CTk here because I don't know how you've implemented it in your specific application - however, it should be easy enough to modify this example to work with CTk.
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.open_button = ttk.Button(
self,
text='Open 2nd Window',
command=self.modal
)
self.open_button.pack()
def modal(self):
self.window = tk.Toplevel(self) # create new window
# bind a handler for when the user closes this window
self.window.protocol('WM_DELETE_WINDOW', self.on_close)
# disable interaction with the main (root) window
self.attributes('-disabled', True)
self.close_button = ttk.Button(
self.window,
text='Close Modal',
command=self.on_close
)
self.close_button.pack()
def on_close(self):
# re-enable interaction the root window
self.attributes('-disabled', False)
# close the modal window
self.window.destroy()
if __name__ == '__main__':
app = App()
app.mailoop() # run the app
In the future:
Provide code that shows you made a good-faith effort to solve the problem on your own
Don't post the same question multiple times within hours - if you need to make changes, edit the original question
If you mean you want to open up another window to do something before going back to the original window, you should consider using message box. Here is a link that goes over the types of messageboxes: https://docs.python.org/3/library/tkinter.messagebox.html.

Can't get tkinter tabs to show

I decided I want to learn how to make GUIs with something more than entry fields and buttons, so I'm starting off with tabs. After a little bit of research I made myself a program, but don't know why it doesn't work.
# --IMPORTS--
from tkinter import *
import tkinter.ttk as ttk
import time
# --CLASSES--
class Gui:
def __init__(self):
self.root = Tk()
self.root.title("tab test")
def setup(self):
# tabs
tabc = ttk.Notebook(self.root)
tab1 = ttk.Frame(tabc)
tabc.add(tab1, text="test 1")
tabc.grid()
def run(self):
self.root.mainloop()
# --MAIN--
if __name__ == "__main__":
gui = Gui()
gui.run()
When I run the program I just get a blank screen (see screenshot) and there is no way to see if there is a tab, let alone which one is selected.
Like I said, I don't see the reason why it isn't working. There are no error messages to point me in the right direction and I'm not 100% sure on how tabs work to begin with, so I thought I'd ask here. I've tried changing .grid() to .pack() but I think it's more of an error on my end than a bug with tkinter. Thanks in advance!
you have to run your setup method.
# --MAIN--
if __name__ == "__main__":
gui = Gui()
gui.setup()
gui.run()

Python tk entry box not active until window deselected

I have a small Tk-based application that uses a standard layout of a window, defined in init. For one of the submenu items, I need to temporarily create a small form, which I remove after it is successfully submitted. I do this on the fly with the code in start_make_canvas in the mcve below:
import random
import tkinter
from tkinter import *
from tkinter import messagebox
from PIL import ImageTk, Image
NONE=0
TAGGING=1
MAKECANVAS=4
TAGS=["some text","some more text"]
PICS=["c:/users/rob/desktop/camera.jpg","c:/users/rob/desktop/fridge.jpg"]
class Window(Frame):
def __init__(self,master):
Frame.__init__(self, master)
self.master=master
self.mode=NONE
self.init_window()
self.start_tagging()
def start_tagging(self):
if self.photo is not None:
self.photo.destroy()
self.photo=None
messagebox.showinfo("Start tagging")
self.mode=TAGGING
self.configure_buttons()
self.show_pic()
def init_window(self):
menubar=Menu(self.master)
menu=Menu(menubar,tearoff=0)
menu.add_command(label="Start tagging",command=self.start_tagging)
menu.add_command(label="Make canvas",command=self.start_make_canvas)
menubar.add_cascade(label="Tag",menu=menu)
self.master.config(menu=menubar)
self.pack(fill=BOTH,expand=1) #take full space of root window
self.photo=None
self.tag_trk={}
row=1
for tag in TAGS:
self.tag_trk[tag]=IntVar()
Checkbutton(self,text=tag,variable=self.tag_trk[tag]).place(x=500,y=10+20*row)
row+=1
self.tag_count=StringVar()
self.button1_label=StringVar()
self.btn1=Button(self,textvariable=self.button1_label,command=self.button1_click)
self.btn1.place(x=10,y=495)
self.max_score=StringVar()
def configure_buttons(self):
if self.mode==NONE:
self.button1_label.set("Tag")
elif self.mode==TAGGING:
self.button1_label.set("Next")
elif self.mode==MAKECANVAS:
self.button1_label.set("Make")
def button1_click(self):
if self.mode==TAGGING:
self.show_pic()
elif self.mode==MAKECANVAS:
# do some things here
for e in self.form: e.destroy()
self.mode=NONE
self.configure_buttons()
elif self.mode==NONE:
self.start_tagging()
def show_pic(self):
if self.photo is not None:
self.photo.destroy()
img=ImageTk.PhotoImage(Image.open(random.choice(PICS)))
self.photo=tkinter.Label(self,image=img,borderwidth=0)
self.photo.image=img
self.photo.place(x=15,y=5)
def start_make_canvas(self):
if self.photo is not None:
self.photo.destroy()
self.photo=None
self.mode=MAKECANVAS
self.form=[]
e=Label(self,text='Max score')
e.place(x=80,y=200)
self.form.append(e)
e=Entry(self,textvariable=self.max_score,width=20)
e.place(x=180,y=200)
self.form.append(e)
self.form[1].focus_set()
self.configure_buttons()
def target_tags():
global root
root=tkinter.Tk()
root.geometry("700x570")
root.protocol("WM_DELETE_WINDOW", on_closing)
app=Window(root)
root.mainloop()
def on_closing():
global root
root.destroy()
if __name__ == "__main__":
target_tags()
The problem occurs after selecting "Make Canvas" from the menu - the form creation works just fine, except that the newly created Entry elements are not active when first created: I cannot see an insertion cursor, and typed text does not go into the entry. When I select a different window and the reselect my application window, all is fine. Is there a method I need to call after I create the form for mainloop to recognize that there are new bits to be looking after?
Note: in creating the mcve, I found that the messagebox in start_tagging was necessary to recreate the problem. Without it, everything works from the get-go. With it, the checkboxes that are created initially work fine, but the new entry box doesn't (until the window is unselected/reselected).
For some reason, the Entry control looks as if it hasn't been fully initialized yet: I cannot even click on the control and input something into it, it doesn't react. It goes to normal if I Alt-Tab out of the app and back in. Using focus_force() helps as a workaround (it's justified in this case since it's done as a response to user's affirmative action).
This could as well be a bug in Tk, or some additional step is required after .place() that place manual page "forgot" to mention. (With ttk.Entry, it's just the same, so not a case of obsolete code.) You can ask # tcl-core#lists.sourceforge.net about this.

Testing tkinter application

I wrote a small application using python 3 and tkinter. Testing every widget, even though there are not many of them feels daunting so I wanted to write a couple of automated tests to simplify the process. I read some other question that seemed relevant to this problem but none fit my needs. Right now I'm doing the testing in a very simple manner - I invoke the command for every widget and manually click through it to see if it works. It does make things a bit faster, but I constantly run into some problems - i.e. I can't automatically close popup windows (like showinfo) even with using libraries to simulate keyboard clicks (namely pynput). Is there an efficient approach for testing applications using tkinter?
Here is the code I use right now:
import tkinter as tkinter
import unittest
from mygui import MyGUI
class TKinterTestCase(unittest.TestCase):
def setUp(self):
self.root = tkinter.Tk()
def tearDown(self):
if self.root:
self.root.destroy()
def test_enter(self):
v = MyGUI(self.root)
v.info_button.invoke()
v.close_button.invoke()
v.btnOut.invoke()
if __name__ == "__main__":
unittest.main()
I don't know much about unittest but I found a workaround to close popup dialogs like showinfo during the tests. The idea is to use keyboard event to invoke the button of the dialog. But since the app is waiting for the user to close the popup dialog, we need to schedule in advance the keyboard event using after:
self.root.after(100, self.root.event_generate('<Return>'))
v.button.invoke()
Full example
import tkinter
from tkinter import messagebox
import unittest
class MyGUI(tkinter.Frame):
def __init__(self, master, **kw):
tkinter.Frame.__init__(self, master, **kw)
self.info_button = tkinter.Button(self, command=self.info_cmd, text='Info')
self.info_button.pack()
self.quit_button = tkinter.Button(self, command=self.quit_cmd, text='Quit')
self.quit_button.pack()
def info_cmd(self):
messagebox.showinfo('Info', master=self)
def quit_cmd(self):
confirm = messagebox.askokcancel('Quit?', master=self)
if confirm:
self.destroy()
class TKinterTestCase(unittest.TestCase):
def setUp(self):
self.root = tkinter.Tk()
self.root.bind('<Key>', lambda e: print(self.root, e.keysym))
def tearDown(self):
if self.root:
self.root.destroy()
def test_enter(self):
v = MyGUI(self.root)
v.pack()
self.root.update_idletasks()
# info
v.after(100, lambda: self.root.event_generate('<Return>'))
v.info_button.invoke()
# quit
def cancel():
self.root.event_generate('<Tab>')
self.root.event_generate('<Return>')
v.after(100, cancel)
v.quit_button.invoke()
self.assertTrue(v.winfo_ismapped())
v.after(100, lambda: self.root.event_generate('<Return>'))
v.quit_button.invoke()
with self.assertRaises(tkinter.TclError):
v.winfo_ismapped()
if __name__ == "__main__":
unittest.main()

Categories

Resources