TKInter checkbox variable is always 0 - python

I'm using Python's TkInter module for a GUI. Below is a simple checkbox code.
def getCheckVal():
print cbVar.get()
windowTime=Tk.Tk()
cbVar = Tk.IntVar()
btnC = Tk.Checkbutton(windowTime, text="Save", variable = cbVar, command=getCheckVal)
btnC.grid()
windowTime.mainloop()
This code works fine. Each time I tick the checkbox, I get 1, else 0.
However, when I run the same code in a function that is called from another TkInter command (when a button is pressed), it stops working. I always get 0 as the value.
class GUIMainClass:
def __init__(self):
'''Create the main window'''
self.window = Tk.Tk()
def askUser(self):
def getCheckVal():
print cbVar.get()
windowTime=Tk.Tk()
cbVar = Tk.IntVar()
btnC = Tk.Checkbutton(windowTime, text="Save", variable = cbVar,
command=getCheckVal)
btnC.grid()
windowTime.mainloop()
def cmdWindow(self):
frameShow=Tk.Frame(self.window)
frameShow.grid()
btnSwitch = Tk.Button(frameShow, text='Show Plots', command=self.askUser)
btnSwitch.grid()
self.window.mainloop()
GUIObj=GUIMainClass()
GUIObj.cmdWindow()
This is very unusual. What could be going wrong?
EDIT: I've used 2 mainloops because I want a separate window (windowTime) to open up when I click "Show Plots" button. This new window should have the checkbox in it.

Your windowTime, cbVar, etc. variables are defined in the function's local scope. When askUser() completes execution, those values are thrown away. Prepend self. to them to save them as instance variables.
There should only be one mainloop() in your program, to run the main Tkinter root object. Try putting it as the very last line in the program. I recommend doing some reading on Effbot for how to set up a Tkinter application.

I'm not sure what all you're trying to do, but one problem is that the TK.IntVar called cbVar that you create in your askUser() method will be deleted when the function returns, so you need to attach it to something that will still exist after that happens. While you could make it a global variable, a better choice would be to make it an attribute of something more persistent and has a longer "lifespan".
Another likely issue is that generally there should only be one call to mainloop() in a single Tkinter application. It appears what you want to do is display what is commonly known as a Dialog Window, which Tkinter also supports. There's some standard ones built-in, plus some more generic classes to simplify creating custom ones. Here's some documentation I found which describes them in some detail. You may also find it helpful to look at their source code.
In Python 2 it's in the /Lib/lib-tk/tkSimpleDialog.py file and
in Python 3 the code's in a file named /Lib/tkinter/simpledialog.py.
Below is code that takes the latter approach and derives a custom dialog class named GUIButtonDialog from the generic one included the Tkinter library which is simply named Dialog.
try:
import Tkinter as Tk # Python 2
from tkSimpleDialog import Dialog
except ModuleNotFoundError:
import tkinter as Tk # Python 3
from tkinter.simpledialog import Dialog
class GUIButtonDialog(Dialog):
"""Custom one Button dialog box."""
def __init__(self, btnText, parent=None, title=None):
self.btnText = btnText
Dialog.__init__(self, parent, title)
def getCheckVal(self):
print(self.cbVar.get())
def body(self, master):
"""Create dialog body."""
self.cbVar = Tk.IntVar()
self.btnC = Tk.Checkbutton(master, text=self.btnText, variable=self.cbVar,
command=self.getCheckVal)
self.btnC.grid()
return self.btnC # Return the widget to get inital focus.
def buttonbox(self):
# Overridden to suppress default "OK" and "Cancel" buttons.
pass
class GUIMainClass:
def __init__(self):
"""Create the main window."""
self.window = Tk.Tk()
def askUser(self):
"""Display custom dialog window (until user closes it)."""
GUIButtonDialog("Save", parent=self.window)
def cmdWindow(self):
frameShow = Tk.Frame(self.window)
frameShow.grid()
btnSwitch = Tk.Button(frameShow, text='Show Plots', command=self.askUser)
btnSwitch.grid()
self.window.mainloop()
GUIObj = GUIMainClass()
GUIObj.cmdWindow()

Related

Python/Tkinter: need code to define / import a widget as a class outside root window

I have been wrestling for a very long time with the issue of creating a Tkinter gui in modular fashion using classes. While there are many examples on this site - and believe me, I have read them all - they have all been too complex for me to understand. In particular, I could not work out how the imported modules could 'talk to' functions in the main application. I have finally had a eureka moment - I created a class in a module that defines a root window and a button. Then, I wrote a main python file that imports the root window / button module, and tests interactions between the imported module and the main app. All of those tests were successful, which is huge progress for me, as far as it goes. Here is the module code, saved as 'fmod.py':
import tkinter as tk
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
# configure the root window
self.title('Tkinter titlebar title')
self.geometry('300x50')
# create button within root window
self.button = tk.Button(self, text='Click Me')
self.button.pack()
Here is the python file written to import the module, and test all the interactions I was interested in understanding:
import tkinter as tk
# import module
import fmod
# create gui root window as an instance of imported MainWindow class
MainWin = fmod.MainWindow()
# define a local function for testing purposes
def ButtonClicked():
print("Button clicked")
# alter attributes of imported root/button from within this file
MainWin.geometry("700x400")
MainWin.config(bg = "yellow")
MainWin.button.config(bg="lightblue")
# add a new widget to root from within this file
NewLabel = tk.Label(MainWin,text="Label")
NewLabel.pack()
# connect an imported widget to the above local function
MainWin.button.config(command=ButtonClicked)
# mainloop
MainWin.mainloop()
As mentioned, all of the tests in the above code worked successfully. The question is this: I would rather have the MainWindow class define the root window and nothing else. So rather than including the button in that code, I'd like to write another entirely separate class that simply defines a button, which could be imported into my app separately. Would anyone be kind enough to help me write that code? I tried copying code from the MainWindow class, and it worked, but it opened an entirely new window (probably because of the init / super init code, which I do not fully understand, and don't really need to understand at the moment - it works, and I'm fine with that). I want code for a simple button that I could import into the main app, as a widget, and that I could place in the MainWin window inside the app.
You can define the class in a separate module and then import it, but you will still want to initiate the button inside of your main window like you are doing now. After all the button does belong on the window.
To create a button class it would be similar to how you created the MainWindow...
import tkinter as tk
class MyButton(tk.Button):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
... do something
def buttonClicked(self, *args):
print("Button Clicked")
Then you could just leave you mainwindow the way it is except switch out the class for your class. I also suggest moving a lot of your logic that is in the global scope inside of your MainWindow, and minimizing your use of the global scope as much as possible. For example:
import tkinter as tk
from mybuttonmodule import MyButton
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
# configure the root window
self.title('Tkinter titlebar title')
self.geometry('300x50')
# create button within root window
self.button = MyButton(self, text='Click Me')
self.button.pack()
self.button.config(command=self.button.buttonClicked)
self.newlabel = tk.Label(self, text="Label")
self.newlabel.pack()
self.geometry("700x400")
self.config(bg = "yellow")
self.button.config(bg="lightblue")
if __name__ == "__main__":
window = MainWindow()
window.mainloop()

Is there a way to reopen a window after closing it using destroy() in tkinter?

window6.after(1,lambda:window6.destroy())
is what I've been using to close my windows, is there any way to get them back after doing this?
basically, is there something that is the opposite of this?
ps. these are the libraries that I've imported, if it helps in any way
import tkinter as tk
from tkinter import *
import time
from tkinter import ttk
Is there a way to reopen a window after closing it using destroy() in tkinter?
The short answer is "no". Once it has been destroyed, it is impossible to get back. You should either create the window via a frame or class so that it's easy to recreate, or hide the window by calling .withdraw() rather than .destroy().
If you put the window code into a class or a function then after destroying it you can create a new instance of it by
1: creating a new instance of the class with the window code in the init function
2: call the function the has the code for the window
By doing this you are essentially creating a new instance of the program, but without initiating the script.
from tkinter impot *
from tkinter import ttk
#creating window function, not class
def main_window():
#window code here
root = Tk()
Label(root, text = "Hello World").pack()
#destroying main window
root.destroy()
root.mainloop()
main_window()
Of course, there are a few hurdles such as the window shutting down as soon as it opens, but this is to show that you can create a new instance of a window from your program.
You can wait for user input to see whether or not the window will open or close.
If you took an OOP approach, you can pass a reference to the Parent Widget as argument to the New_Window and store it in a class attribute.
You´ll have a two way reference: Parent knows child and child knows parent.
Then you can set the Parent Reference to the New_Window to None, from within the child Widget self.parent.new_window = None in a close_me() method right after you call self.destroy() on the New_Window:
1st Bonus: this code prevents the opening of more than 1 instance of a Window at a time. You won´t get more than 1 New_Window on the screen. I don´t think having two loggin windows opened or two equal options window makes sense.
2nd Bonus: It is possible to close the window from other parts of the code, as in a MVC patter, the Controller can close the window after doing some processing.
Here´s a working example:
import tkinter as tk
class Toolbar(tk.Frame):
'''Toolbar '''
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
# to store the New Window reference
self.new_window = None
self.button_new_window = tk.Button(self, text = 'New Window', command = lambda : self.get_window(self))
self.configure_grid()
def configure_grid(self):
'''Configures the Grid layout'''
self.grid(row=1, column=0, columnspan=3, sticky=(tk.N,tk.S,tk.E,tk.W))
self.button_new_window.grid(row = 2, column = 2, padx=5, pady=5)
def get_window(self, parent):
''' If window exists, return it, else, create it'''
self.new_window = self.new_window if self.new_window else Window(parent)
return self.new_window
class Window(tk.Toplevel):
'''Opens a new Window.
#param parent -- tk.Widget that opens/reference this window
'''
def __init__ (self, parent : tk.Widget):
# Stores reference to the Parent Widget, so you can set parent.new_window = None
self.parent = parent
super().__init__(master = parent.master)
self.title('New Window')
self.button_dummy = tk.Button(self, text = 'Do the thing', width = 25, command = lambda : print("Button pressed on window!"))
self.button_close = tk.Button(self, text = 'Close', width = 25, command = self.close_me)
self.configure_grid()
def configure_grid(self):
'''Grid'''
self.button_dummy.grid(row = 1, column = 0)
self.button_close.grid(row = 2, column = 0)
def close_me(self):
'''Tkinter widgets are made of two parts. 1. The python Object and 2. The GUI Widget.
The destroy() method gets rid of the widget part, but leaves the object in memory.
To also destroy the object, you need to set all of its references count to ZERO on
the Parent Widget that created the new Window, so the Garbage Collector can collect it.
'''
# Destroys the Widget
self.destroy()
# Decreasses the reference count on the Parent Widget so the Garbage Collector can destroy the python object
self.parent.new_window = None
if __name__ == '__main__':
root = tk.Tk()
toolbar = Toolbar(root)
root.mainloop()
I don´t know if this: .destroy() and re-instantiate approach is more efficient than the .withdraw() and .deiconify(). Maybe if you have a program that runs for long periods of time and opens a lot of windows it can be handy to avoid stackoverflow or heapoverflow.
It sure frees up the object reference from memory, but it has the additional cost of the re-instantiation, and that is processing time.
But as David J. Malan would say on CS50, “There´s always a tradeoff”.

Tkinter - My custom icon "disturbs" the Top Level window when it opens

when I opened my Top Level window, I always saw an annoying and weird behaviour.. at the end I realized that it was because of my custom icon.
below an exaple code:
from tkinter import *
from tkinter import ttk
class MainWindow:
def __init__(self):
self.parent=Tk()
self.parent.geometry("494x410+370+100")
self.parent.title("My Software - WITH ICON")
self.parent.iconbitmap("icon.ico")
Button = ttk.Button(self.parent, text="open a new widnow", command=self.OpenNewWindow)
Button.place(x=16, y=16)
def OpenNewWindow(self):
self.obj = NewWindow(self)
class NewWindow:
def __init__(self, mw):
self.window, self.mw = Toplevel(mw.parent), mw
self.window.geometry("200x150+360+200")
self.window.title("New Window")
self.window.iconbitmap("icon.ico") # it creates the issue..
self.window.protocol("WM_DELETE_WINDOW", self.on_close)
self.window.focus()
self.mw.parent.attributes('-disabled', 1)
self.window.transient(mw.parent)
self.window.grab_set()
self.mw.parent.wait_window(self.window)
def on_close(self):
self.mw.parent.attributes('-disabled', 0)
self.window.destroy()
def main():
app=MainWindow()
app.parent.mainloop()
if __name__=="__main__":
main()
to make it clear where is the issue, I create a GIF:
here we have two softwares, "without icon.py" and "with icon.py". they are the same, but the first one doesn't use my custom icon for his second window.
as you can see, if I run "with icon.py" the second window will be always affected by something when I will open it, but for "without icon.py" this "something" doesn't exist.
what is the issue? the software opens his second window, focus the root one (it's the issue), and then focus the second window again. you can see it clearly from the GIF.
how can I solve the issue? and why with the default icon this weird behaviour doesn't happen?

How do I get the Tkinter event-listener to work?

I'm using Tkinter for the GUI of a little tool I wrote with Python. Basically I just want a callback-method to be executed as soon as the contents of an entry widget have changed. This can be done with Tkinter's own variable classes (StringVar, BooleanVar, etc. - see documentation for details: http://effbot.org/tkinterbook/variable.htm).
So I couldn't get the mechanism to work and I found a snippet online, where it works perfectly fine. Now I'm trying to figure out why my version does not work.
As you can see in the two code examples the only difference is, that I'm using the event-listening functionality inside a class, whereas the snippet I found online only demonstrates it in a straight top-to-bottom manner.
Here's what I've already tried:
I instantiated the Tk instance directly in the constructor of my GUI class - same behaviour.
I inherited directly from the Tk class (instead of Frame) - same behaviour.
I placed the callback outside of the class - same behaviour.
The only idea I have is that the problem might be scope related, which I tried to verify.
Working code snippet:
from tkinter import *
import tkinter as tk
def text_changed(*args):
print("Text changed.")
top = tk.Tk()
string_listener = StringVar()
string_listener.set("Init Text")
string_listener.trace("w", text_changed)
entry_widget = tk.Entry(top, textvariable = string_listener)
entry_widget.pack()
top.mainloop()
Not working code snippet
from tkinter import *
import tkinter as tk
root = tk.Tk()
class GUI(tk.Frame):
def __init__(self, master=root):
super(GUI, self).__init__(master)
string_listener = StringVar()
string_listener.set("Init Text")
string_listener.trace("w", self.text_changed_callback)
entry_widget = tk.Entry(master, textvariable=string_listener)
entry_widget.pack()
def text_changed_callback(self, *args):
print("Text changed.")
gui = GUI()
gui.mainloop()
Like in the working example, my code ought to print Text changed., everytime a character is either deleted from or appended to the string in the extry-widget.
The problem is that string_listener is a local variable, and python is destroying the variable when __init__ finishes running. This doesn't happen in your original code since the variable is created in the global scope.
A simple solution is to save a reference as an attribute of the class:
import tkinter as tk
root = tk.Tk()
class GUI(tk.Frame):
def __init__(self, master=root):
super(GUI, self).__init__(master)
self.string_listener = tk.StringVar()
self.string_listener.set("Init Text")
self.string_listener.trace("w", self.text_changed_callback)
entry_widget = tk.Entry(master, textvariable=self.string_listener)
entry_widget.pack()
def text_changed_callback(self, *args):
print("Text changed.")
gui = GUI()
gui.mainloop()
note: I also changed StringVar to tk.StringVar so that I could remove the redundant wildcard import of tkinter.

How to close Toplevel window after the function it calls completes?

Edit:
let me include my code so I can get some specific help.
import Tkinter
def goPush():
win2=Tkinter.Toplevel()
win2.geometry('400x50')
Tkinter.Label(win2,text="If you have prepared as Help describes select Go otherwise select Go Back").pack()
Tkinter.Button(win2,text="Go",command=bounceProg).pack(side=Tkinter.RIGHT,padx=5)
Tkinter.Button(win2, text="Go Back", command=win2.destroy).pack(side=Tkinter.RIGHT)
def bounceProg():
d=1
print d
root=Tkinter.Tk()
root.geometry('500x100')
Tkinter.Button(text='Go', command=goPush).pack(side=Tkinter.RIGHT,ipadx=50)
root.mainloop()
So when you run the program it opens a window that says Go. Then Go opens a window that asks if youve read the help(which I didnt include in this code sample) and offers Go Back(which goes back) and Go. When you select Go it calls a function which prints 1. After it prints 1 I want the Window to close returning to the original window containing the Go button. How do I do such a thing?
#Kosig It won't quit root. Ie. self.foo = tk.Toplevel(self) and then self.foo.destroy()
For example:
class Foo(tk.Frame):
"""Foo example"""
def __init__(self, master=None):
"""Draw Foo GUI"""
tk.Frame.__init__(self, master)
self.grid()
self.draw_window_bar()
def draw_window_bar(self):
"""Draw bar TopLevel window"""
self.window_bar = tk.Toplevel(self)
# Some uber-pythonian code here...
ask_yes_or_no = messagebox.askyesno('Brian?', 'Romani Ite Domum')
if not ask_yes_or_no:
self.window_bar.destroy()
You have one main object, which is Foo. Foo has one main window (called "frame"), which it gets from tk.Frame. Afterwards, all Toplevel windows (frames) must be created within it. So, your new window here is self.window_bar and all its "objects" are in there, including the method for destroying it (self.window_bar.destroy()). You can call self.window_bar.destroy() from any part of the code, but here it is called after the user clicks "no".
If you create a toplevel window with the Toplevel command, you destroy it with the destroy method of the window object. For example:
import Tkinter as tk
class MyToplevel(tk.Toplevel):
def __init__(self, title="hello, world", command=None):
tk.Toplevel.__init__(self)
self.wm_title(title)
button = tk.Button(self, text="OK", command=lambda toplevel=self: command(toplevel))
button.pack()
if __name__ == "__main__":
def go(top):
print "my work here is done"
top.destroy()
app = tk.Tk()
t = MyToplevel(command=go)
t.wm_deiconify()
app.mainloop()
Apparently you just call quit on the root object that's running your mainloop
edit: All Tkinter widgets have a destroy() method which destroys that widget and its children. So you should be able to call this on your Toplevel

Categories