Python: Tkinter open custom widget in new window - python

My aim is to generate a window with a button "NewWindow" on it. If I press the button, the program should open a new window. This window I stored in a class "NewWindow" to quickly reproduce it.
In this "NewWindow" I have another button. If I press it the label of basic window should be updated and the window "NewWindow" should be closed automatically.
Here is my code:
from tkinter import *
class NewWindow(Toplevel):
def __init__(self, master = None):
super().__init__(master = master)
self.title('NewWindow')
self.lb = Label(self, text='Hello')
self.lb.grid(column=0, row=0, columnspan=1)
self.bt1 = Button(self, text="apply Hello", command= self.bt_press)
self.bt1.grid(column=0, row=1)
def bt_press(self):
window.basic_lb.text = "Hello"
window = Tk()
def new_Editor():
a = NewWindow(window)
window.title("BasicWindow")
window.basic_lb = Label(window, text='None')
window.basic_lb.grid(column=0, row=0, columnspan=1)
window.basic_bt = Button(window, text="NewWindow", command=new_Editor)
window.basic_bt.grid(column=0, row=1)
window.mainloop()
Problems:
At start both windows NewWindow and BasicWindow are displayd. I only want to open BasicWindow and NewWindow should be opened after button basic_bt is clicked. How can I solve it? (already solved by commed below)
Why the label text in basic_lb did not get some update after pressing self.bt1?
How is it possible to close NewWindow with use of bt_press method?

You have a few typos/errors in your code that are casuing some of your problems. As #Tim said, when you pass a function to a command like command=function(), it will be called on runtime, not when the button is pressed. You need to pass the function handle to the command, command=function. You got around this by using a lambda function in your button command, but it is easier to just have command=self.bt_press
Answering your second question, window.basic_lb.text = "Hello" is not how you change the text in a tkinter Label, use <Label>.config(text="Hello"). You also should use self.master and define self.master = master in __init__ instead of just using window, because while you can access window due to it not being defined in local scope, it's better to explicitly define it.
You can close a window using window.destroy().
Your working code is now:
from tkinter import *
class NewWindow(Toplevel):
def __init__(self, master = None):
super().__init__(master = master)
self.title('NewWindow')
self.master = master
self.lb = Label(self, text='Hello')
self.lb.grid(column=0, row=0, columnspan=1)
self.bt1 = Button(self, text="apply Hello", command=self.bt_press)
self.bt1.grid(column=0, row=1)
def bt_press(self):
self.master.basic_lb.config(text="Hello")
self.destroy()
window = Tk()
def new_Editor():
a = NewWindow(window)
window.title("BasicWindow")
window.basic_lb = Label(window, text='None')
window.basic_lb.grid(column=0, row=0, columnspan=1)
window.basic_bt = Button(window, text="NewWindow", command=new_Editor)
window.basic_bt.grid(column=0, row=1)
window.mainloop()

Related

How do I prevent prevent multiple instances of the same window opening?

I'm currently using tkinter to create a GUI for my program. If I open the golf quiz window and open the help window, then close the golf quiz window and re-open it, I am able to click the help window button and open another instance of the help button. How do I set the Help button to be disabled while the Help window is open?
from tkinter import *
from functools import partial
class Welcome_Screen:
def __init__(self, parent):
self.welcome_screen_frame = Frame(width=200, height=200, pady=10)
self.welcome_screen_frame.grid()
self.quiz_welcome_screen_label = Label(self.welcome_screen_frame, text = "quiz game", font="Arial 20 bold", padx=10)
self.quiz_welcome_screen_label.grid(row=0)
self.welcome_screen_buttons_frame = Frame(self.welcome_screen_frame)
self.welcome_screen_buttons_frame.grid(row=2)
self.golf_quiz_welcome_screen_button = Button(self.welcome_screen_buttons_frame, text="Golf Quiz", font="Arial 10 bold", command=self.golf_quiz_game, padx=10, pady=10)
self.golf_quiz_welcome_screen_button.grid(row=2, column=0, padx=5)
def golf_quiz_game(self):
get_golf_quiz_game = golf_quiz_game(self)
class golf_quiz_game:
def __init__(self, partner):
partner.golf_quiz_welcome_screen_button.config(DISABLED)
self.golf_quiz_box = Toplevel()
self.golf_quiz_box.protocol('WM_DELETE_WINDOW', partial(self.close_golf_quiz_game, partner))
self.golf_quiz_frame = Frame(self.golf_quiz_box)
self.golf_quiz_frame.grid()
self.golf_quiz_heading = Label(self.golf_quiz_frame, text="Golf Quiz game",
font="arial 18 bold", padx=10, pady=10)
self.golf_quiz_heading.grid(row=0)
self.golf_quiz_history_help_dismiss_buttons_frame = Frame(self.golf_quiz_frame)
self.golf_quiz_history_help_dismiss_buttons_frame.grid(row=6, pady=10)
self.help_button = Button(self.golf_quiz_history_help_dismiss_buttons_frame, text="Help", font="Arial 10 bold",command=self.Help, padx=10, pady=10)
self.help_button.grid(row=6, column=1, padx=5)
def close_golf_quiz_game(self, partner):
partner.golf_quiz_welcome_screen_button.config(state=NORMAL)
self.golf_quiz_box.destroy()
def Help(self):
get_help = Help(self)
class Help:
def __init__(self, partner):
partner.help_button.config(state=DISABLED)
self.help_box = Toplevel()
self.help_box.protocol('WM_DELETE_WINDOW', partial(self.close_Help, partner))
self.help_frame = Frame(self.help_box)
self.help_frame.grid()
self.help_heading = Label(self.help_frame, text="Help", font="arial 18 bold")
self.help_heading.grid(row=0)
self.help_text = Label(self.help_frame, text="Test",
width=60, wrap=400)
self.help_text.grid(row=1)
self.help_button = Button(self.help_frame, text="Dismiss", width=10, font="Arial 10 bold", command=partial(self.close_Help, partner), padx=10, pady=10)
self.help_button.grid(row=2, pady=10)
def close_Help(self, partner):
if partner.help_button.winfo_exists():
partner.help_button.config(state=NORMAL)
self.help_box.destroy()
# main routine
if __name__ == "__main__":
root = Tk()
root.title("quiz game")
something = Welcome_Screen(root)
root.mainloop()
Here is how you can do that (this is also the size (linewise (approx.)) of the minimal reproducible example which you should have provided):
from tkinter import Tk, Toplevel, Button
def close_top(top):
btn.config(state='normal')
top.destroy()
def open_help():
btn.config(state='disabled')
top = Toplevel(root)
top.protocol('WM_DELETE_WINDOW', lambda: close_top(top))
top.focus_force()
# put the rest of help stuff here
root = Tk()
btn = Button(root, text='Help', command=open_help)
btn.pack()
root.mainloop()
Class based approach:
from tkinter import Tk, Toplevel, Button
# this would be the window from where you open the help window
class MainWindow(Tk):
def __init__(self):
Tk.__init__(self)
self.btn = Button(self, text='Help',
command=lambda: self.open_help(self.btn))
self.btn.pack()
def open_help(self, btn):
HelpWindow(self, btn)
# this would be the help window
class HelpWindow(Toplevel):
def __init__(self, master, button):
Toplevel.__init__(self, master)
self.button = button
self.button.config(state='disabled')
self.focus_force()
self.protocol('WM_DELETE_WINDOW', self.close)
def close(self):
self.button.config(state='normal')
self.destroy()
MainWindow().mainloop()
Few things:
First of you can simply inherit from container and window classes that way you don't have to separately create them in the class and you can easily reference them in the class using just self
.focus_force() does what it says, it forces focus on the widget
Important (suggestions)
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Don't have space around = if it is used as a part of keyword argument (func(arg='value')) but use if it is used for assigning a value (variable = 'some value'). Have two blank lines around function and class declarations.
You need to make use of class variables inside golf_quiz_game in order to have only one instance of Help window:
class golf_quiz_game:
# class variables
_help_button = None # reference to instance "Help" button
_help_win = None # reference to instance "Help" window
def __init__(self, partner):
...
# set button state based on whether "Help" window is open or not
self.help_button.config(state="normal" if self.__class__._help_win is None else "disabled")
# update class reference of "Help" button
self.__class__._help_button = self.help_button
def close_golf_quiz_game(self, partner):
partner.golf_quiz_welcome_screen_button.config(state=NORMAL)
self.golf_quiz_box.destroy()
# update class reference of "Help" button
self.__class__._help_button = None
def Help(self):
if self.__class__._help_win is None:
# no "Help" window is open, create one
self.__class__._help_win = Help(self)
def help_closed(self):
if self.__class__._help_button:
# enable the "Help" button
self.__class__._help_button.config(state="normal")
# update "Help" window status
self.__class__._help_win = None
class Help:
...
def close_Help(self, partner):
self.help_box.destroy()
# notify partner that "Help" window is closed
partner.help_closed()

how can i use the Entry function in tkinter in an if argument?

So i am making a password organisator in python, and i don't know how i can get user input from an Entry and use it in an if argument?
text1 = StringVar()
def but():
text1.get()
print(text1.get())
knapp2 = Button(root, command="but").pack()
entry1 = Entry(root, textvariable=text1).place(x=270, y=100)
You can call the .get() function on on the Entry widget too to get the text.
import tkinter
from tkinter import Tk, Button, Entry
mw = Tk()
entry = Entry(mw)
entry.pack()
def but():
text = entry.get()
print(text)
button.config(text='Button Clicked')
button = Button(mw, command=but, text='Test')
button.pack()
mw.mainloop()
This code does work but will become complicated with larger code. You will have to define the function before creating a widget that calls that function. In the above example if you created the button widget before the function you would get an exception. You could create the widget, then create the function, then change the configuration of the button to call that function when clicked but that's still pretty complicated and will be confusing in large programs.
I would recommend putting everything in a class. It makes it easy to reference widgets in functions.
import tkinter
from tkinter import Tk, Button, Entry
class Main:
def __init__(self, master):
self.master = master
self.entry = Entry(self.master)
self.entry.pack()
self.button = Button(self.master, text='Test', command=self.But)
self.button.pack()
def But(self):
print(self.entry.get())
self.button.config(text='Button Clicked.')
mw = Tk()
main = Main(mw)
mw.mainloop()

Checking if Tkinter Widget (OptionMenu) is disabled

I feel like I've scoured the web for an eternity, rephrased my question a thousand times for something I feel like should be very simple.
I wonder if there is a way to check if a Tkinter Widget is active (not greyed out / disabled). I have a set of OptionMenus that start out disabled, and are configured to state=ACTIVE when they click a checkbox, so that the user can select which OptionMenus they want to use.
When I try to "submit" the fields in the OptionMenus, I only want the ones that are ACTIVE. I already tried if OptionMenu.state == ACTIVE but then I get an error that OptionMenu has no attribute state, even though I configure that earlier.
Here is a sample of my code:
from tkinter import *
class Application(Frame):
# Initializing the window and the required variables
def __init__(self, master=None):
Frame.__init__(self, master)
self.checkbox_in_use = BooleanVar(self, False)
self.checkbox = Checkbutton(self, text="check",
var=self.checkbox_in_use,
command=self.check_change
self.checkbox.grid(row=0, column=1, sticky='W')
self.menu = OptionMenu(title_setting,
"Menu",
"Menu",
["Menu1", "Menu2"])
self.menu.grid(row=1, column=1)
self.menu.config(state=DISABLED)
submit = Button(self, text="submit",
command=self.submit_function)
submit.grid(row=2, column=0)
self.master = master
self.init_window()
# Initialize the window
def init_window(self):
self.master.title("Example")
self.pack(fill=BOTH, expand=1)
def check_change(self):
if self.checkbox_in_use.get():
self.menu.config(state=ACTIVE)
else:
self.menu.config(state=DISABLED)
def submit_function(self):
# This is the part I want to do something with.
if self.menu.state == ACTIVE:
print("You are good to go! Do the stuff.")
root = Tk()
root.geometry("400x300")
app = Application(root)
root.mainloop()
Thank you for all responses.
All you need is cget() for this. self.menu.cget('state') will do the trick.
That said I want to point out some other things in your code.
You Application class already has an __init__ at the start so why use:
# Initialize the window
def init_window(self):
self.master.title("Example")
self.pack(fill=BOTH, expand=1)
You really should not pack the frame from inside the frame class but rather when calling the class. Also pack wont work here it will throw an error. Do this instead: app = Application(root).grid().
Take a look at the reformatted example below (with cget()).
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master.title("Example")
self.checkbox_in_use = BooleanVar(self, False)
self.checkbox = Checkbutton(self, text="check", var=self.checkbox_in_use, command=self.check_change)
self.checkbox.grid(row=0, column=1, sticky='W')
self.menu = OptionMenu(master,"Menu","Menu",["Menu1", "Menu2"])
self.menu.grid(row=1, column=1)
self.menu.config(state=DISABLED)
Button(self, text="submit", command=self.submit_function).grid(row=2, column=0)
def check_change(self):
if self.checkbox_in_use.get():
self.menu.config(state=ACTIVE)
else:
self.menu.config(state=DISABLED)
def submit_function(self):
print(self.menu.cget('state'))
root = Tk()
root.geometry("400x300")
app = Application(root).grid()
root.mainloop()

IntVar().trace() not working

I'm just getting started coding in Python/Tkinter for a small Pymol plugin. Here I'm trying to have a toggle button and report its status when it is clicked. The button goes up and down, but toggleAVA never gets called. Any ideas why?
from Tkinter import *
import tkMessageBox
class AVAGnome:
def __init__(self, master):
# create frames
self.F1 = Frame(rootGnome, padx=5, pady=5, bg='red')
# checkbuttons
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(self.F1, text='AVA', indicatoron=0, variable=self.AVAselected)
# start layout procedure
self.layout()
def layout(self):
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
#entry and buttons
self.AVAbutton.pack(side=LEFT)
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
def __init__(self):
open_GnomeUI()
def open_GnomeUI():
# initialize window
global rootGnome
rootGnome = Tk()
rootGnome.title('AVAGnome')
global gnomeUI
gnomeUI = AVAGnome(rootGnome)
I tested your code with Pymol.
Problem is because you use Tk() to create your window. You have to use Toplevel() and then it will work correctly with trace() or with command=.
Pymol is created with tkinter which can have only one window created with Tk() - it is main window in program. Every other window has to be created with Toplevel().
I have attached a working version of your code below. You can refer to it to learn where you went wrong. Generally, you have to mind how you structure your code if you are using a class format.This will help you visualize your code and debug better. You can read this discussion to help you.
from Tkinter import *
import tkMessageBox
class AVAGnome(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
# create frames
self.F1 = Frame(self, padx=5, pady=5, bg='red')
# checkbutton
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(
self.F1, text='AVA', indicatoron=0, width=10,
variable=self.AVAselected)
# start layout procedure
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
self.AVAbutton.pack(side=LEFT) #entry and buttons
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
if __name__ == '__main__':
rootGnome = Tk()
rootGnome.title('AVAGnome')
gnomeUI = AVAGnome(rootGnome)
gnomeUI.pack(fill="both", expand=True)
gnomeUI.mainloop()
Update: The above code structure is for standalone tkinter programme. I am attempting to convert this working code to follow Pymol plugin example. Revised code is posted below and is susceptible to further revision.
# https://pymolwiki.org/index.php/Plugins_Tutorial
# I adapted from the example in the above link and converted my previous code to
#
from Tkinter import *
import tkMessageBox
def __init__(self): # The example had a self term here.
self.open_GnomeUI()
class AVAGnome(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
# create frames
self.F1 = Frame(self, padx=5, pady=5, bg='red')
# checkbutton
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(
self.F1, text='AVA', indicatoron=0, width=10,
variable=self.AVAselected)
# start layout procedure
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
self.AVAbutton.pack(side=LEFT) #entry and buttons
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
# Note, I added a "self" term throughout function.
# Try w/ & w/o "self" to see which works.
def open_GnomeUI(self):
self.rootGnome = Tk()
self.rootGnome.title('AVAGnome')
self.gnomeUI = AVAGnome(self.rootGnome)
self.gnomeUI.pack(fill="both", expand=True)
self.gnomeUI.mainloop()

How can I create a non-unique browse button in python?

I am using; Python 3.4, Windows 8, tkinter. I am trying to create a generic browse button that will get a file name and assign it to a variable.
I have created the following code to do this.
from tkinter import *
from tkinter import filedialog
from tkinter import ttk
class Application(Frame):
# A GUI Application.
# Initialize the Frame
def __init__(self, master):
Frame.__init__(self, master)
nbook = ttk.Notebook(root)
nbook.pack(fill='both', expand='yes')
f1 = ttk.Frame(nbook)
nbook.add(f1, text='QC1')
self.qc1_tab(f1)
# create QC1 tab contents
def qc1_tab(self, tab_loc):
# Set up file name entry.
Label(tab_loc, text="Select file:").grid(pady=v_pad, row=0, column=0, sticky=W)
self.flnm = ttk.Entry(tab_loc, width=60)
self.flnm.focus_set()
self.flnm.grid(pady=v_pad, row=0, column=1, columnspan=2, sticky=W)
ttk.Button(tab_loc, text="Browse...", width=10, command=self.browse).grid(row=0, column=3)
def browse(self):
temp = filedialog.askopenfilename()
self.flnm.delete(0, END)
self.flnm.insert(0, temp)
root = Tk()
app = Application(root)
root.mainloop()
The only problem with this is that the browse button is tied to self.flnm and cannot be used for anything else. I plan to use the browse button several times to acquire the file name of several different files and would rather not have multiple browse commands.
I need to call it from a button and somehow assign it to a variable afterwards.
I was thinking of something like
ttk.Button(..., command=lambda: self.flnm = self.browse)
...
def browse(self):
filename = filedialog.askopenfilename()
return filename
but that failed terribly.
How can I make a general purpose browse button?
You can write:
def browse(self, target):
temp = filedialog.askopenfilename()
target.delete(0, END)
target.insert(0, temp)
ttk.Button(..., command=lambda: self.browse(self.flnm))

Categories