tkinter wiget.bind('<Activate>', callback) not working - python

I've been trying to create a message box when a button turns from disabled to active but when the button actually turns from disabled to active for some reason my callback is not even being called I've tried to get it working so for quite a bit of time now and I'm stuck.
Here is an example of the problem:
from tkinter import *
from tkinter import Tk
def disable_and_activate():
b.config(state = DISABLED)
b.config(state = ACTIVE)
def is_working(event):
print('working')
root = Tk()
b = Button (root, text = 'click me', command = disable_and_activate)
b.pack()
b.bind('<Activate>', is_working)
root.mainloop()
Console:
the button is clicked but there's nothing printed on the console

The <Activate> event is not triggered when you set the state of the button to "active". The event is triggered when the window becomes the active window.
For example, when I run your code on my OSX machine, if I click on some other application to give it focus and then I click back to the tkinter window, the event will fire when the tkinter window becomes the active window.
This is explained in the canonical tcl/tk documentation which says this:
Activate, Deactivate
These two events are sent to every sub-window of a toplevel when they change state. In addition to the focus Window, the Macintosh platform and Windows platforms have a notion of an active window (which often has but is not required to have the focus). On the Macintosh, widgets in the active window have a different appearance than widgets in deactive windows. The Activate event is sent to all the sub-windows in a toplevel when it changes from being deactive to active. Likewise, the Deactive event is sent when the window's state changes from active to deactive. There are no useful percent substitutions you would make when binding to these events.

Here the problem was just the code inside the function , It seems like you needed to call EventGenerate('<<Activate>>') I also recommend adding 2 << and 2 >>
So I rewrote the code and its now working perfectly fine:
from tkinter import *
from tkinter import Tk
import tkinter
def disable_and_activate():
b.configure(state=tkinter.DISABLED)
b.configure(state=tkinter.ACTIVE)
b.event_generate("<<Activate>>")
def is_working(event):
print('working')
root = Tk()
b = Button (root, text = 'click me', command = disable_and_activate)
b.pack()
b.bind('<<Activate>>', is_working)
root.mainloop()

Related

Is there a way to make a custom askopenfilenames dialog window or at least edit its title bar, close button, etc in tkinter?

I'm making an app in tkinter which uses the ttk.Scale widget to show the process of an mp3 song.
I have a function that I want to add buttons with the names of which (the buttons) should be relied on filenames. Therefore I've made this example:
from tkinter import Tk, Button
from tkinter.filedialog import askopenfilenames
from tkinter.ttk import Scale
from threading import Timer
root = Tk()
slider = Scale(root, from_=0, to=100, orient='horizontal')
slider.pack()
# slider is continuously set to a bigger number so that it keeps going
def update_slider(num):
slider.set(num)
num += 1
root.after(50, update_slider, num)
update_slider(num=0)
# this function creates buttons based on the files opened
def add_buttons():
# the 'X' button of this particular window slows down execution of update_slider function
files = askopenfilenames(title='Add Buttons')
for i in list(files):
Button(root, text=i).pack()
button = Button(root, text='Browse', command=lambda: Timer(0.1, add_buttons).start())
button.pack()
root.mainloop()
The problem I'm facing is that when I open the askopenfilenames dialog box or when I press its 'X' button, my slider which is running continuously in the background gets stuck, and as a result doesn't show the process correctly.
Here is a picture where I hold down the 'X' button and the ttk.Scale stops moving:
I've tried using threading to run the add_buttons function but the behavior of the program remains the same.
Can I edit the askopenfilenames dialog box with something similar like overrideredirect(True) so that I can make my own title bar and 'X' button and the events generated not to slow down my Scale?
Replying to:
I cannot reproduce the issue in Linux, the scale keeps moving no matter what I do with the filedialog window. So this may be an OS specific issue.
I'm aware that this problem doesn't appear on Linux. I faced the same problem with the root's close button and other Toplevels' close button, but I fixed it by replacing the title bar using overrideredirect(True).
Is there anything similar I can do with this askopenfilenames window?

How to put a toplevel window in front of the main window in tkinter?

Is there any way to put a toplevel window in front of the main window?
Here's the code:
from tkinter import *
root = Tk()
root.geometry('1280x720')
def create_new_window():
root2 = Toplevel()
root2.geometry('500x500')
create_new_window()
mainloop()
Here, I want the root2 window to always stay in front of the root window.
I tried using root2.attributes('-topmost' , 1), but the problem is that this line puts the window on top of all the other programs as well.
What I want is that the toplevel window should only be in front of the main window, and it should never go back when I click on the main window.
Is there any way to achieve this in tkinter?
It would be great if anyone could help me out.
What you want, i think, is a transient window, you nedd to do:
root2.wm_transient(root)
From the manual:
wm transient window ?master?
If master is specified, then the window manager is informed that window is a transient window (e.g. pull-down menu) working on behalf of master (where master is the path name for a top-level window). If master is specified as an empty string then window is marked as not being a transient window any more. Otherwise the command returns the path name of window's current master, or an empty string if window isn't currently a transient window. A transient window will mirror state changes in the master and inherit the state of the master when initially mapped. It is an error to attempt to make a window a transient of itself.
So you could do something like this, but it seems buggy for me.
What I have done is to bind the FocusOut event to the toplevel that was created, so every time it looses the focus it triggers the event stackingorder to put the windos in the right order. You may need to expire this code for several events of your choice, but to get you the idea..
Here is the code:
import tkinter as tk
def add_toplevel(idx, toplevel):
if idx == 'end':
idx = len(toplevels)
toplevels.insert(idx,toplevel)
def create_new_window():
root2 = tk.Toplevel()
root2.geometry('500x500')
add_toplevel('end',root2)
root2.bind('<FocusOut>', stackingorder)
def stackingorder(event):
for toplevel in toplevels:
toplevel.lift()
toplevel.update_idletasks()
toplevels = [] #stacking order by index
root = tk.Tk()
create_new_window()
root.mainloop()
You are maybe also intrested in this:
https://stackoverflow.com/a/10391659/13629335

Tkinter window hangs after restoring from system tray (using pystray)

I'm creating a Tkinter-based GUI in Python. I would like the window to hide to system tray when it is minimized (using pystray module). It hides, but it only appears on the screen and hangs, when I'm trying to restore it.
Here's what I have tried:
from tkinter import *
from PIL import Image
import pystray
def hide_to_tray(_event=None):
tray_icon = pystray.Icon("MyTrayIcon", title="My tray icon") # create the tray icon
tray_icon.icon = Image.open("app_icon.ico") # open the icon using PIL
tray_icon.menu = pystray.Menu(pystray.MenuItem("Open", lambda: tray_icon.stop(), default=True)) # create the menu
root.withdraw() # hide the window
tray_icon.run() # run the icon's main loop
# icon mainloop
root.deiconify() # when the icon mainloop had been stopped, show the window again
root.focus_force() # focus on it
root = Tk()
btn = Button(root, text="Sample button")
btn.grid()
root.bind("<Unmap>", hide_to_tray) # hide to tray on minimizing
root.mainloop()
How can I solve this problem?
1. Use infi.systray
It runs on a seperate thread and thus won't block, install it with pip:
pip install infi.systray
Don't call tkinter methods from the info.systray thread:
Since infi.systray runs on a seperate thread, you mustn't call tkinter methods directly in the callback functions that you pass to the systray icon on creation. Use a thread safe way (e.g. queue) to inform the main thread about events in the systray icon instead!
2. Don't run pystray and tkinter at the same time
You can't run them both because they block the running thread and both have to run on the mainthread.
See Osher's answer that only displays the system tray icon when the tkinter app is closed.

Closing a window in python 3.7.2 with tkinter

I've made a program in python with Tkinter that allows you to free draw and choose different colors. I decided to make a button that would close the window instead of clicking the exit button in the top right corner. My question is how do I make the window close when the button is pressed?
If you are using a main loop for your application, then you can use the .destroy() method to release all the resources associated with the window and close the application. You call this method within the command function for your button like so:
from tkinter import *
root = Tk()
frame = Frame(root)
frame.pack(side=LEFT)
button = Button(frame, text="Exit", command=exit)
button.pack()
root.mainloop()
def exit():
root.destroy()
That should close your window. Optionally, the destroy() method may also be used at the end of your main loop if the X button of your application won't close the window immediately.
See these examples for more info:
http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.destroy-method
http://effbot.org/tkinterbook/tkinter-hello-again.htm

window freezes in Python 3

I am using python3 on a mac and run scripts with the IDLE which comes automatically with the python3 installation.
I am trying to make an alert to the user and found the command
tkinter.messagebox.showinfo("title","some text")
So I i tried a minimal script to check if I can get along with that command
import tkinter
tkinter.messagebox.showinfo("test" , "blabla")
The window is displayed correctly but it doesn't respond when I click on the "OK" button.
Addtionally there is a second empty window which appears when I start the script.
What is the explanation for this or at least how can I fix that?
tkinter isn't designed to work this way. Every tkinter requires a root window. If you don't explicitly create one (and you didn't), one will be created for you. That's what the blank window is.
Also, a tkinter GUI can't function properly unless it has a running event loop. This is necessary because some functions, such as responding to buttons and redrawing the window, only happens in response to events. If the event loop isn't running, events can't be processed.
Bottom line: the dialogs aren't designed to be used outside of the context of a proper tkinter app.
Wrapper for standalone use
The following code can be used to display one of the dialogs in standalone mode. It works by creating and hiding a root window, displaying the dialog, and then destroying the root window.
import tkinter as tk
from tkinter import messagebox
def show_dialog(func, *args, **kwargs):
# create root window, then hide it
root = tk.Tk()
root.withdraw()
# create a mutable variable for storing the result
result = []
# local function to call the dialog after the
# event loop starts
def show_dialog():
# show the dialog; this will block until the
# dialog is dismissed by the user
result.append(func(*args, **kwargs))
# destroy the root window when the dialog is dismissed
# note: this will cause the event loop (mainloop) to end
root.destroy()
# run the function after the event loop is initialized
root.after_idle(show_dialog)
# start the event loop, then kill the tcl interpreter
# once the root window has been destroyed
root.mainloop()
root.quit()
# pop the result and return
return result.pop()
To use it, pass the dialog you want as the first option, followed by dialog-specific options.
For example:
result = show_dialog(messagebox.askokcancel, "title", "Are you sure?")
if result:
print("you answered OK")
else:
print("you cancelled")

Categories

Resources