Tkinter, show Calendar in Toplevel window on button click - python

I deleted my previous question about this in order to simplify my question and communicate the question clearer. I've got a project with multiple classes inside of it and I'd like to get a Calendar to display in a new window once I click a button. I am currently using this Calendar script with a minor change inside of my overall script. I changed Frame to Toplevel in the first part of the Calendar script like this:
class Calendar(tk.Toplevel):
def __init__(self, parent, **kw):
Toplevel.__init__(self, parent, **kw)
Now this does create the Calendar in a Toplevel window along with the rest of my script but it does it as soon as the program is started. I want to get it to show when it is called later on by the user.
example:
class Application(tk.Tk): # tk.Tk creates main window
def __init__(self):
tk.Tk.__init__(self)
self.title("T")
self.geometry('550x320')#x,y
self.create_options()
self.calendar = Calendar(self)
def create_options(self):
self.widgets = tk.Frame(self)
tk.Button(self,
text = "...", command=self.show_Calendar
).place(x=525, y=130)
which would call this:
def show_Calendar(self):
'''shows calendar'''
toplevel = Toplevel()
toplevel.Calendar.place(x=0, y=0)
The button does create a window but there is nothing in it. What would be the best way to get this Calendar to only show in the window that appears when the button is clicked?

self.calendar = Calendar(self)
Putting this line within your application init will create it at the same time that the application is created. You would want to move this into your show_Calendar method.
def show_Calendar(self):
'''shows calendar'''
toplevel = Toplevel()
toplevel.Calendar.place(x=0, y=0)
toplevel = Toplevel() does not make any sense here. You are creating a blank Toplevel and making it a local variable. This Toplevel is not related to your Calendar in any way.
Within the Calendar script, you made sure that the Calendar class inherits from Toplevel, so any time you create a Calendar, it will be attached to its own Toplevel.
def show_Calendar(self):
'''shows calendar'''
self.calendar = Calendar(self)
I was looking at your previous question before you deleted it, and if you would also like to remove the calendar when the user changes focus, you should look into Events and Bindings here, specifically <FocusOut>.

Related

How to sound a bell when the user clicks outside a modal window?

The situation is simple. I have a main window with a Help - About menu.
When this menu item is clicked, a modal window is opened (let's say it's an About-window).
With self.grab_set() I disabled the main-window (although the modal window does flicker when you click the main title bar).
So far, so good.
Here is the question: I really like to sound a bell when the user clicks outside the modal window on the main window.
This is what I could find about grab_set(), really not that much:
[effbot] ...a method called grab_set, which makes sure that no mouse or keyboard
events are sent to the wrong window.
[effbot] Routes all events for this application to this widget.
[kite.com] A grab directs all events to this and descendant widgets in the application.
[google books] grab_set() ensures that all of the application's events are sent to w until a corresponding call is made to grab_release ([Me:] or till the window is destroyed?)
I'm not quite sure how to understand this: does it mean you can handle an event on the main window within the modal window (like sounding my bell)?
So I tried things like:
self.bind('<Button-1>', self.bell) Exception in Tkinter callback: _tkinter.TclError: bad window path name
parent.bind('<Button-1>', self.bell) Nothing happens
So, how to sound a bell like when clicked outside the modal window on the main window, like in so many other applications?
Derived questions:
Is it still possible to cature events from the main window after using
grab_set for the modal window?
Is there a way to prevent the flickering?
I really like to understand this mysterious grab_set() method.
Stripped code:
import tkinter as tk
class About(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.geometry('200x150')
#--- OK button
btn_ok = tk.Button(self, text='OK', command=self.destroy) # destroy with OK
btn_ok.pack(side=tk.TOP)
btn_ok.focus() # destroy with spacebar
#--- Make window modal
self.grab_set()
# self.wait_window() # is this necessary in this case?
# self.bind('<Button-1>', self.bell) ??? The question
class MenuBar(tk.Menu):
def __init__(self, parent):
tk.Menu.__init__(self)
helpmenu = tk.Menu(self, tearoff=0)
helpmenu.add_command(label='About', command=lambda: About(parent))
self.add_cascade(label='Help', menu=helpmenu)
class MainApp():
def __init__(self, parent):
parent.configure(background='#000000')
parent.geometry('800x600')
menubar = MenuBar(parent)
parent.configure(menu=menubar)
if __name__ == '__main__':
root = tk.Tk()
MainApp(root)
root.mainloop()
When you set a grab, all button clicks will go to the window with the grab. You capture them the way you capture any other event. In the case of a button click you do that by binding a function to <1>.
It's important to know that a binding on a root window or a Toplevel window will apply to all widgets in that window. For example, binding to self in your code will fire even when you click on the "Ok" button. Therefore, the callback should probably only do work when the widget associated with the event is the same as the toplevel.
Example:
class About(tk.Toplevel):
def __init__(self, parent):
...
self.bind("<1>", self.capture_click)
...
def capture_click(self, event):
if event.widget == self:
<your logic here>
In the case of wanting to know if the user clicked outside the window, you can use the coordinates of the event object to compare against the window to see if the click is inside or outside.
def on_click(self, event):
if event.widget == self:
if (event.x < 0 or event.x > self.winfo_width() or
event.y < 0 or event.y > self.winfo_height()):
self.bell()
I found a second solution. Though my question was explicitly about using grab_set(), this method does the same for me: making the window as modal as possible and sound a bell.
Instead of using self.grab(), you can also disable the parent window:
parent.attributes('-disabled', True)
Of course it needs to be enabled again when the OK button is clicked (and when the windows is closed with the [X] close control. However, my original About-window has no window decoration). The command for the OK-button becomes:
btn_ok = tk.Button(self, text='OK', command=lambda: self.closeme(parent))
...which calls the closeme function:
def closeme(self, parent):
parent.attributes('-disabled', False)
self.destroy()
The bell sounds automatically when clicking a disabled window.
Method 1: Keeps you in full control of the main window but does not 'freeze' the main window: you can still move it around.
Method 2: Completely freezes the main window, but if it happens to be (partially) covered by another window (not of this application), then you can only bring back to top using Alt+Tab (windows).
I'm sure I will use both techniques in the future depending on my needs.

Python Tkinter how to hide a widget without removing it

I know similar things have been asked a lot, but I've tried to figure this out for two hours now and I'm not getting anywhere. I want to have a button in a Tkinter window that is only visible on mouseover. So far I failed at making the button invisible in the first place (I'm familiar with events and stuff, that's not what this question is about) pack_forget() won't work, because I want the widget to stay in place. I'd like some way to do it like I indicated in the code below:
import tkinter as tki
class MyApp(object):
def __init__(self, root_win):
self.root_win = root_win
self.create_widgets()
def create_widgets(self):
self.frame1 = tki.Frame(self.root_win)
self.frame1.pack()
self.btn1 = tki.Button(self.frame1, text='I\'m a button')
self.btn1.pack()
self.btn1.visible=False #This doesnt't work
def main():
root_win = tki.Tk()
my_app = MyApp(root_win)
root_win.mainloop()
if __name__ == '__main__':
main()
Is there any way to set the visibility of widgets directly? If not, what other options are there?
Use grid as geometry manager and use:
self.btn1.grid_remove()
which will remember its place.
You can try using event to call function.
If "Enter" occurs for button then call a function that calls pack()
and if "Leave" occurs for button then call a function that calls pack_forget().
Check this link for event description:List of All Tkinter Events
If you wish your button to stay at a defined place then you can use place(x,y) instead of pack()

tkinter optionmenu close event

I am trying to display a tooltip for optionmenu items in tkinter. I have figured out how to do that. The problem I am left with is closing the tooltip (toplevel) window. I want to do so when the user selects an option or clicks outside the optionmenu (which closes the menu of the option menu). The enter and leave events don't seem to be doing the trick. Any help would be appreciated!
Edit: I have added some code on what I have tried. The "test" print occurs whenever I hover over menu item 1 (which is what I want) but I want to fire enter and leave events whenever the user opens or closes the menu as a whole. I tried binding them to the optionmenu but it is unreliable and sometimes triggers leave events when it shouldn't.
from tkinter import Tk, Frame, BOTH, Menu, Label, SUNKEN, X, BOTTOM
import tkinter as tk
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent, background = "white")
self.parent = parent
vals = ["1","2","3"]
var = tk.StringVar()
var.set("1")
option = tk.OptionMenu(root,var,*vals)
option.pack()
t = option.children["menu"]
tk.Menu
#Do I need to unbind
#t.bind("<<MenuSelect>>", self.test_func)
t.bind("<<MenuSelect>>", self.test_func)
t.bind("<Enter>",self.enter_func)
t.bind("<Leave>",self.leave_func)
def test_func(self,event = None):
if self.parent.call(event.widget,"index","active") == 0:
print("test")
def enter_func(self,event=None):
print("entered")
def leave_func(self,event=None):
print("left")
root = tk.Tk()
Application(root)
root.mainloop()
One way I have found to close the tooltip (not shown in example) is to trace the variable associated with the optionmenu and when it changes kill the topwindow. However, if the user opens the menu, hovers over options, then clicks outside the menu items, the topwindow tooltip stays because the menu item never changed.
The question is very similar to this one

Tkinter set geometry of Toplevel before displaying

I'm trying to create a Toplevel window that appears beside one of my other windows, but i'm having trouble figuring out how to set the location of the window before it is displayed. It's a simple couple lines:
histogram_window = Toplevel(self)
histogram_window.geometry('+%d+%d' % (self.__root.winfo_rootx() + self.winfo_x()*2,
self.__root.winfo_rooty()))
histogram_window.transient(self.__root)
But the window will first appear, then quickly move to the location I specified. How can I simply make it appear in the location I specified?
I've found the issue thanks to a reminder from Bryan Oakley, the problem was that upon the creation of the class I bound a matplotlib canvas to the screen and packed the widget, then tried to create a Toplevel window at the same time. I fixed this by calling the update() function for my window
Class some(Toplevel):
def __init__(self, root):
Toplevel(self, root)
# created widget
# pack widget
self.read()
def read(self):
hist = Toplevel(self)
hist.geometry(...)
hist.update() #fixed the issue

Multiple tkinter windows look different when closing and reopening window

I got the following code from a tutorial. I then modified main() so that two windows are created as seperate threads. When I run it, only one window is created. Then when I press the Quit button in that window, a second window appears. In this new window the button has a different look than the first one (a look which I like better) and then if I press either of the two Quit buttons, both windows close and the program exits.
Why does the second window not appear until the first Quit button is pressed, and why does it look different when it does appear?
EDIT: This happens when no threads are used as well, where only one window is created at a time.
EDIT: This is a screenshot of the two windows that are created. The one on the left is created with the program is run, the one on the right is created after clicking the "Quit" button on the first.
from Tkinter import Tk, BOTH
from ttk import Frame, Button, Style
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Quit button")
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
quitButton = Button(self, text="Quit",
command=self.quit)
quitButton.place(x=50, y=50)
from threading import Thread
def main():
for i in range(2):
root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
Thread(target=root.mainloop()).start()
if __name__ == '__main__':
main()
You cannot use tkinter this way. Tkinter isn't thread safe, you can only ever access tk widgets and commands except from the thread that created the root window.
As for one window only showing after the other is destroyed even without threading, it's hard to say since you don't show the code. If you're creating more than one instance of Tk, and calling mainloop more than once, that's the problem. Tkinter is designed to work when you create precisely one instance of Tk, and call mainloop precisely once.
If you want more than one window, create a single instance of Tk for the first window, and instances of Toplevel for additional windows.

Categories