I am writing a single thread program with a GUI, which executes a series of tasks. During these tasks the GUI is refreshed regularly to check for a few available inputs (e.g. abort). To avoid halting the task with unwanted inputs all unnecessary GUI elements are disabled by .config(state='disabled') during execution.
This however does not seem work for scrollbars which for some reason are unique and don't have a "-state" option.
Another possible approach to get the scroll bar to display with a disabled look (instead of being hidden) is to:
Remove all entries in the list (the scroll bar will become disabled)
Detach the scroll bar from the list
Re-insert the removed entries with the scroll bar detached (the scroll bar will remain disabled)
In the code below, 'listbox' is my tk.Listbox object,'scrollbar' is the associated 'tk.Scrollbar' object, and self is a reference to the parent object. Code tested on Window with Python 3.8.4rc1.
Disable code:
entries = listbox.get(0, "end")
listbox.delete(0, "end")
self.update()
listbox.config(yscrollcommand="")
scrollbar.config(command="")
listbox.insert("end", *entries)
listbox['state'] = "disabled"
Enable code:
listbox['state'] = "normal"
listbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=listbox.yview)
I think you can't disable the scrollbar widget using the -state option, I've searched in the Tcl/Tk website and I didn't find anything about it.
But I am sure you can use the pack_forget() method to delete it from the window if you are using the pack geometry or the grid_forget() if you are using the grid geometry, so you can later show the scrollbar widget again calling pack/grid again.
Here's a example:
from tkinter import *
master = Tk()
scrollbar = Scrollbar(master)
scrollbar.pack(side=RIGHT, fill=Y)
def disable_scroll():
scrollbar.pack_forget()
def active_scroll():
scrollbar.pack(side=LEFT, fill=BOTH)
scrollbar.config(command=None)
btn1 = Button(master, text="OK 1", command=disable_scroll)
btn1.pack()
btn2 = Button(master, text="OK 2", command=active_scroll)
btn2.pack()
listbox = Listbox(master, yscrollcommand=scrollbar.set)
for i in range(1000):
listbox.insert(END, str(i))
listbox.pack(side=LEFT, fill=BOTH)
scrollbar.config(command=listbox.yview)
mainloop()
The Scrollbar changes according the Listbox's values, so as I said here's the code:
from tkinter import *
import random
master = Tk()
scrollbar = Scrollbar(master)
scrollbar.pack(side=RIGHT, fill=Y)
def disable_scroll():
scrollbar.pack_forget()
def active_scroll():
scrollbar.pack(side=LEFT, fill=BOTH)
scrollbar.config(command=None)
btn1 = Button(master, text="disable_scroll", command=disable_scroll)
btn1.pack()
btn2 = Button(master, text="active_scroll", command=active_scroll)
btn2.pack()
listbox = Listbox(master, yscrollcommand=scrollbar.set)
#NEW FUNCTIONS
def count():
listbox.delete(0, END)
lista = [5, 8, 500, 1000]
for i in range(random.choice(lista)):
listbox.insert(END, str(i))
def delete():
listbox.delete(0, END)
#NEW BUTTONS
btn3 = Button(master, text="start", command=count)
btn3.pack()
btn4 = Button(master, text="delete", command=delete)
btn4.pack()
listbox.pack(side=LEFT, fill=BOTH)
scrollbar.config(command=listbox.yview)
mainloop()
Related
I'm using .place() in my tkinter program, so I wanted to remove references to grid(). So far my program works but for some reason there's a single .grid() line that makes my whole program turn blank if it's removed. This shouldn't happen, since I'm entirely using .place(). Here is that line:
AllFrames.grid(row=0, column=0, sticky='nsew')
And here is my full code:
from tkinter import *
root = Tk()
root.title("Account Signup")
DarkBlue = "#2460A7"
LightBlue = "#B3C7D6"
root.geometry('350x230')
Menu = Frame()
loginPage = Frame()
registerPage = Frame()
for AllFrames in (Menu, loginPage, registerPage):
AllFrames.grid(row=0, column=0, sticky='nsew')
AllFrames.configure(bg=LightBlue)
def show_frame(frame):
frame.tkraise()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
show_frame(Menu)
# ============= Menu Page =========
menuTitle = Label(Menu, text="Menu", font=("Arial", 25), bg=LightBlue)
menuTitle.place(x=130, y=25)
loginButton1 = Button(Menu, width=25, text="Login", command=lambda: show_frame(loginPage))
loginButton1.place(x=85, y=85)
registerButton1 = Button(Menu, width=25, text="Register", command=lambda: show_frame(registerPage))
registerButton1.place(x=85, y=115)
# ======== Login Page ===========
loginUsernameL = Label(loginPage, text='Username').place(x=30, y=60)
loginUsernameE = Entry(loginPage).place(x=120, y=60)
loginPasswordL = Label(loginPage, text='Password').place(x=30, y=90)
loginPasswordE = Entry(loginPage).place(x=120, y=90)
backButton = Button(loginPage, text='Back', command=lambda: show_frame(Menu)).place(x=0, y=0)
loginButton = Button(loginPage, text='Login', width=20).place(x=100, y=150)
# ======== Register Page ===========
root.mainloop()
I've also noticed that changing anything in the parentheses causes the same result. For example, if I change sticky='nsew' to sticky='n' or row=0 to row=1 it will show a blank page.
How do I remove .grid() from my program without it turning blank?
The place() manager does not reserve any space, unless you tell it directly.
The grid(sticky='nsew') makes the widget expand to fill the entire available space, in this case the containing widget. The widgets inside all use place() which will not take any space. When you change to grid(sticky='n') you place the zero size widget at the top of the containing widget.
But, for your current problem you can assign a size to the widgets:
AllFrames.place(relwidth=1, relheight=1) # w/h relative to size of master
I would recommend using the grid() geometry manager if you are going to make any more complicated layouts.
For more info have a look at effbot on archive.org
I have a GUI using Tkinter, it has a main screen and then when you press a button a popup window appears, where you select a checkbutton and then a email will get sent to you.
Not matter what I do, I cannot read the value of the checkbutton as 1 or True it always = 0 or False.
This is my code:
import tkinter as tk
from tkinter import *
import time
root = tk.Tk()
root.title('Status')
CheckVar1 = IntVar()
def email():
class PopUp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
popup = tk.Toplevel(self, background='gray20')
popup.wm_title("EMAIL")
self.withdraw()
popup.tkraise(self)
topframe = Frame(popup, background='gray20')
topframe.grid(column=0, row=0)
bottomframe = Frame(popup, background='gray20')
bottomframe.grid(column=0, row=1)
self.c1 = tk.Checkbutton(topframe, text="Current", variable=CheckVar1, onvalue=1, offvalue=0, height=2, width=15, background='gray20', foreground='snow', selectcolor='gray35', activebackground='gray23', activeforeground='snow')
self.c1.pack(side="left", fill="x", anchor=NW)
label = tk.Label(bottomframe, text="Please Enter Email Address", background='gray20', foreground='snow')
label.pack(side="left", anchor=SW, fill="x", pady=10, padx=10)
self.entry = tk.Entry(bottomframe, bd=5, width=35, background='gray35', foreground='snow')
self.entry.pack(side="left", anchor=S, fill="x", pady=10, padx=10)
self.button = tk.Button(bottomframe, text="OK", command=self.on_button, background='gray20', foreground='snow')
self.button.pack(side="left", anchor=SE, padx=10, pady=10, fill="x")
def on_button(self):
address = self.entry.get()
print(address)
state = CheckVar1.get()
print (state)
time.sleep(2)
self.destroy()
app = PopUp()
app.update()
tk.Button(root,
text="EMAIL",
command=email,
background='gray15',
foreground='snow').pack(side=tk.BOTTOM, fill="both", anchor=N)
screen = tk.Canvas(root, width=400, height=475, background='gray15')
screen.pack(side = tk.BOTTOM, fill="both", expand=True)
def latest():
#Other code
root.after(300000, latest)
root.mainloop()
The popup works perfectly, and the email will print when entered but the value of checkbox is always 0.
I have tried:
CheckVar1 = tk.IntVar() - No success
self.CheckVar1 & self.CheckVar1.get() - No success
Removing self.withdraw() - No success
I only have one root.mainloop() in the script, I am using app.update() for the popup window because without this it will not open.
I have checked these existing questions for solution and none have helped:
Self.withdraw - Can't make tkinter checkbutton work normally when running as script
Self.CheckVar1 - TKInter checkbox variable is always 0
Only one instance of mainloop() - Python tkinter checkbutton value always equal to 0
I have also checked very similar questions but I wasn't going to post them all.
Any help is appreciated.
The problem is that you have two root windows. Each root window gets its own internal tcl interpreter, and the widgets and tkinter variables in one are completely invisible to the other. You're creating the IntVar in the first root window, and then trying to associate it with a checkbutton in a second root window. This cannot work. You should always only have a single instance of Tk in a tkinter program.
because of variable scope
try to put CheckVar1 = IntVar() inside the class
use it with self like this
self.CheckVar1 = tk.IntVar() # object of int
self.CheckVar1.set(1) # set value
variable=self.CheckVar1 # passing to the checkbutton as parameter
state = self.CheckVar1.get() # getting value
from tkinter import *
import tkinter as tk
class dashboard(Frame):
def __init__(self, master):
super(dashboard, self).__init__(master)
self.grid()
self.buttons()
def buttons(self):
#student dashboard button
bttn1 = Button(self, text = "Student",
command=self.student, height = 2, width= 15)
bttn1.grid()
#teacher dashboard button
bttn2 = Button(self, text = "Teacher", height = 2, width= 15)
bttn2.grid()
#exit button
bttn3 = Button(self, text = "Exit",
command=root.destroy, height = 2, width= 15)
bttn3.grid()
def student(self):
#view highscores button
bttn1 = Button(self, text = "Highscores", height = 2, width= 15)
bttn1.grid()
#print score button
bttn2 = Button(self, text = "Print Score", height = 2, width= 15)
bttn2.grid()
#exit button
bttn3 = Button(self, text = "Main Menu",
command=root.destroy, height = 2, width= 15)
bttn3.grid()
#main
root = Tk()
root.title("Dashboard")
root.geometry("300x170")
app = dashboard(root)
root.mainloop()
Wondered if someone could help me basically, with this GUI I am creating I want to be able to access a new page on the same frame but the buttons from the main menu stay once I go to another page, does anyone know how I can hide/forget the buttons and go back to them at a later stage? Thanks.
Updated to use sub-Frames
You could do it using the universal grid_remove() method (here's some documentation). One way to use it would be to keep references to each of the Button widgets created so you can call this method on them as needed.
However that can be simplified slightly—even though it takes about the same amount of code—by putting all the Buttonss for each page into a separate sub-Frame and just showing or hiding it which will automatically propagate do to all the widgets it contains. This approach also provides a better foundation for the rest of your program.
I've implemented this by adding a main_button_frame attribute to your class, as well as one named student_button_frame to hold those you have on the student page (since you'll probably need it to hide them too).
One nice thing about grid_remove() is that if you later call grid() on the same widget, it will remember all the settings it (and its sub-widgets) had before it was removed, so you don't need to create and maintain a list of every single one of them yourself.
Also note I also made some general modifications to your code so it conforms to the PEP 8 - Style Guide for Python Code recommendations better. I highly recommend you read and start following it.
from tkinter import *
import tkinter as tk
class Dashboard(Frame):
def __init__(self, master):
super().__init__(master)
self.grid()
self.main_button_frame = None
self.student_button_frame = None
self.create_main_buttons()
def create_main_buttons(self):
if self.student_button_frame: # Exists?
self.student_button_frame.grid_remove() # Make it invisible.
if self.main_button_frame: # Exists?
self.main_button_frame.grid() # Just make it visible.
else: # Otherwise create it.
button_frame = self.main_button_frame = Frame(self)
button_frame.grid()
# Student Dashboard button
bttn1 = Button(button_frame, text="Student",
command=self.create_student_buttons, height=2,
width=15)
bttn1.grid()
# Teacher Dashboard button
bttn2 = Button(button_frame, text="Teacher", height=2, width=15)
bttn2.grid()
# Dashboard Exit button
bttn3 = Button(button_frame, text="Exit", command=root.destroy,
height=2, width=15)
bttn3.grid()
def create_student_buttons(self):
if self.main_button_frame: # Exists?
self.main_button_frame.grid_remove() # Make it invisible.
if self.student_button_frame: # Exists?
student_button_frame.grid() # Just make it visible.
else: # Otherwise create it.
button_frame = self.student_button_frame = Frame(self)
button_frame.grid()
# Highscores button
bttn1 = Button(button_frame, text="Highscores", height=2, width=15)
bttn1.grid()
# Print Score button
bttn2 = Button(button_frame, text="Print Score", height=2, width=15)
bttn2.grid()
# Student Exit button
bttn3 = Button(button_frame, text="Exit", command=root.destroy,
height=2, width=15)
bttn3.grid()
# main
root = Tk()
root.title("Dashboard")
root.geometry("300x170")
app = Dashboard(root)
root.mainloop()
I'm running Python 2.7.9 on a Mac. I've been unable to figure out why it is when I run my programs that only the Entry Widgets highlight each time I hit the Tab key to move to the next widget. Following is some test code. When I run the script and hit the Tab key, the first entry field is highlighted. The next time I hit the Tab key, the second entry field is highlighted. However, when I hit the tab key to move to the Button Widget, the Button is receiving the focus but there is not highlight to visually indicate to the user the focus.
The OptionMenu widget is skipped altogether, which is also a mystery. Both the radiobutton and the checkbox receives focus, just like the button widget, and again no highlight is present.
I've tried a variety of .config() arrangements to no avail. What am I missing?
from tkinter import *
class App:
def __init__(self, master):
frame = Frame(master)
frame.grid()
#Tests to make sure that Button receives focus.
def yup(self):
print "yup"
entry1 = Entry(frame)
entry1.pack()
entry2 = Entry(frame)
entry2.pack()
button1 = Button(frame, text="Test")
button1.pack()
button1.bind('<Return>', yup)
var1 = IntVar()
c = Checkbutton(frame, text="Expand", variable=var1)
c.pack()
var2 = StringVar()
radio = Radiobutton(frame, text="Test", variable=var2, value=1)
radio.pack()
var3 = StringVar()
optionmenu1 = OptionMenu(frame, var3, "one", "two", "three")
optionmenu1.pack()
root = Tk()
root.geometry('400x400+0+0')
app = App(root)
root.mainloop()
It sounds like you need to configure OS X for "Full Keyboard Access" to allow Tab to focus on all UI controls (versus just text boxes and lists).
In Yosemite (10.10), this setting can be found under System Preferences > Keyboard > Shortcuts, and can be toggled with Control+F7. Note that has nothing to do with Python, and will occur system-wide.
EDIT
So after doing some more testing, there appears to be some issues with the actual highlighting of certain widgets using tk on a Mac. Below is a condensed version of your original sample with some minor modifications for simplicity.
import Tkinter as tk
import ttk # more on this in a minute
class App(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
entry1 = tk.Entry(self)
entry1.pack()
entry2 = tk.Entry(self)
entry2.pack()
button1 = tk.Button(self, text="Test", command=self.yup)
button1.pack()
def yup(self):
print("yup")
# ...
root = tk.Tk()
app = App(root).pack()
root.mainloop()
With full keyboard access enabled as previously mentioned, I can verify that the button does indeed receive focus: on the third tab, after the first two entry widgets, hitting <space> "clicks" the button and prints to stdout. However there is no visual indication that the button is selected.
Changing the button from a tk.Button to a ttk.Button "fixes" this, and does indeed show the normal "selection" frame around the button when tabbing through the UI.
button1 = tk.Button(self, text="Test", command=self.yup)
# change this to:
button1 = ttk.Button(self, text="Test", command=self.yup)
I have no idea why this is, and I don't know what the consensus about tkinter.ttk is, but I prefer ttk to "plain" tk as it seems to produce widgets which appear more "native" to OS X in my experience. Note I also removed the bind statement, and am reporting my result using the OS X default of space to activate UI elements with full keyboard access enabled.
More on ttk here. Note also that not all tk widgets have a ttk implementation, and that there are also some ttk widgets which do not exist in tk.
Lastly below find the "ttk" version of the original snippet.
import Tkinter as tk
import ttk
class App(ttk.Frame):
def __init__(self, master):
ttk.Frame.__init__(self, master)
self.master = master
entry1 = ttk.Entry(self)
entry1.pack()
entry2 = ttk.Entry(self)
entry2.pack()
button1 = ttk.Button(self, text="Test", command=self.yup)
button1.pack()
def yup(self):
print("yup")
# ...
root = tk.Tk()
app = App(root).pack()
root.mainloop()
Hope this helps.
Try changing the background and highlightbackground colors as below but the problem is possibly because of the way the program is indented --> run the second code block.
top=Tk()
## active background
Button(top, text="Quit", bg="lightblue", activebackground="orange",
command=top.quit).grid(row=1)
top.mainloop()
##--------------- note the 3 lines that have been changed ---------
class App:
## function was not indented
def __init__(self, master):
frame = Frame(master)
frame.grid()
entry1 = Entry(frame)
entry1.pack()
entry1.focus_set()
entry2 = Entry(frame)
entry2.pack()
button1 = Button(frame, text="Test")
button1.pack()
## function called incorrectly
button1.bind('<Return>', self.yup)
var1 = IntVar()
c = Checkbutton(frame, text="Expand", variable=var1)
c.pack()
var2 = StringVar()
radio = Radiobutton(frame, text="Test", variable=var2, value=1)
radio.pack()
var3 = StringVar()
optionmenu1 = OptionMenu(frame, var3, "one", "two", "three")
optionmenu1.pack()
Button(frame, text="Quit", bg="orange", command=master.quit).pack()
## function indented too far
#Tests to make sure that Button receives focus.
def yup(self, args):
print "yup"
root = Tk()
root.geometry('400x400+0+0')
app = App(root)
root.mainloop()
I want to create a GUI in tkinter with two Frames, and have the bottom Frame grayed out until some event happens.
Below is some example code:
from tkinter import *
from tkinter import ttk
def enable():
frame2.state(statespec='enabled') #Causes error
root = Tk()
#Creates top frame
frame1 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame1.grid(column=0, row=0, padx=10, pady=10)
button2 = ttk.Button(frame1, text="This enables bottom frame", command=enable)
button2.pack()
#Creates bottom frame
frame2 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame2.grid(column=0, row=1, padx=10, pady=10)
frame2.state(statespec='disabled') #Causes error
entry = ttk.Entry(frame2)
entry.pack()
button2 = ttk.Button(frame2, text="button")
button2.pack()
root.mainloop()
Is this possible without having to individually gray out all of the frame2's widgets?
I'm using Tkinter 8.5 and Python 3.3.
Not sure how elegant it is, but I found a solution by adding
for child in frame2.winfo_children():
child.configure(state='disable')
which loops through and disables each of frame2's children, and by changing enable() to essentially reverse this with
def enable(childList):
for child in childList:
child.configure(state='enable')
Furthermore, I removed frame2.state(statespec='disabled') as this doesn't do what I need and throws an error besides.
Here's the complete code:
from tkinter import *
from tkinter import ttk
def enable(childList):
for child in childList:
child.configure(state='enable')
root = Tk()
#Creates top frame
frame1 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame1.grid(column=0, row=0, padx=10, pady=10)
button2 = ttk.Button(frame1, text="This enables bottom frame",
command=lambda: enable(frame2.winfo_children()))
button2.pack()
#Creates bottom frame
frame2 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame2.grid(column=0, row=1, padx=10, pady=10)
entry = ttk.Entry(frame2)
entry.pack()
button2 = ttk.Button(frame2, text="button")
button2.pack()
for child in frame2.winfo_children():
child.configure(state='disable')
root.mainloop()
Based on #big Sharpie solution here are 2 generic functions that can disable and enable back a hierarchy of widget (frames "included"). Frame do not support the state setter.
def disableChildren(parent):
for child in parent.winfo_children():
wtype = child.winfo_class()
if wtype not in ('Frame','Labelframe','TFrame','TLabelframe'):
child.configure(state='disable')
else:
disableChildren(child)
def enableChildren(parent):
for child in parent.winfo_children():
wtype = child.winfo_class()
print (wtype)
if wtype not in ('Frame','Labelframe','TFrame','TLabelframe'):
child.configure(state='normal')
else:
enableChildren(child)
I think you can simply hide the whole frame at once.
If used grid
frame2.grid_forget()
If used pack
frame2.pack_forget()
In your case the function would be
def disable():
frame2.pack_forget()
To enable again
def enable():
frame2.pack()
grid_forget() or pack_forget() can be used for almost all tkinter widgets
this is a simple way and reduces the length of your code, I'm sure it works