How to add tkinter Scrollbar with Grid? - python

I am new to Python programming. I started learning the language a few weeks ago. I am creating a "Temperature Converter" application using "tkinter". I am trying to add a scroll bar to the application but unable to do so. Please forgive me if I have not written something correctly. Please do let me know my mistake and I will definitely rectify it. I won't repeat the mistake in the future.
Thank you very much for your time. Following is my code:
import tkinter as tk
from tkinter.messagebox import showerror
from tkinter import *
window = tk.Tk()
window.title("Temperature Converter")
window.geometry('420x400')
window.option_add('*Font', '25')
window.option_add('*Font', 'Tahoma')
window.configure(bg='#f8f8f8')
window.resizable(0, 0)
def fahrenheit_to_celsius():
fahrenheit = fahrenheit_to_celsius_entry_temperature.get()
if fahrenheit == '':
showerror(title='Empty Field Error', message="Field is empty. Please input number.")
else:
try:
celsius = (5 / 9) * (float(fahrenheit) - 32)
fahrenheit_to_celsius_label_result["text"] = f"{round(celsius, 2)} \N{DEGREE CELSIUS}"
except:
showerror(title='Input Error', message="Invalid input. Please input only number.")
fahrenheit_to_celsius_label_heading = tk.Label(text="Fahrenheit to Celsius:", bg='#f8f8f8', fg='Black')
# Create the Fahrenheit entry frame with an Entry widget and label in it
fahrenheit_to_celsius_frame_entry = tk.Frame(master=window)
fahrenheit_to_celsius_entry_temperature = tk.Entry(master=fahrenheit_to_celsius_frame_entry, width=10)
fahrenheit_to_celsius_label_temperature = tk.Label(master=fahrenheit_to_celsius_frame_entry, text="\N{DEGREE FAHRENHEIT}")
def fahrenheit_to_celsius_clear():
fahrenheit_to_celsius_entry_temperature.delete(0, tk.END)
fahrenheit_to_celsius_label_result.config(text="\N{DEGREE CELSIUS}")
fahrenheit_to_celsius_button_clear = tk.Button(window, text="Clear", command=fahrenheit_to_celsius_clear, bg='#c82333', fg='White')
fahrenheit_to_celsius_label_heading.grid(row=0, column=0, columnspan=3, sticky="w")
fahrenheit_to_celsius_entry_temperature.grid(row=1, column=0, sticky="ew")
fahrenheit_to_celsius_label_temperature.grid(row=1, column=1, sticky="ew")
fahrenheit_to_celsius_button_clear.grid(row=1, column=2, sticky="w")
fahrenheit_to_celsius_button_convert = tk.Button(master=window, text="Convert", command=fahrenheit_to_celsius, bg='#218838', fg='White')
fahrenheit_to_celsius_label_result = tk.Label(master=window, text="\N{DEGREE CELSIUS}")
fahrenheit_to_celsius_label_heading.grid(row=0, padx=10, pady=10)
fahrenheit_to_celsius_frame_entry.grid(row=1, column=0, padx=12, sticky="ew")
fahrenheit_to_celsius_button_convert.grid(row=1, column=1, padx=0, sticky="ew")
fahrenheit_to_celsius_button_clear.grid(row=1, column=2, padx=(5,0), sticky="ew")
fahrenheit_to_celsius_label_result.grid(row=1, column=3, padx=0, sticky="w")

Adding a scrollbar
Scrollbars can't be added for a tkinter frame, but you can add them for a canvas.
procedure
Put all your widgets in a tkinter canvas
Configure canvas scrollregion
Add a scrollbar for the canvas
Example
base = tk.Canvas(window)
base.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
# change scrollregion as changes are made to the canvas
base.bind('<Configure>', lambda e: base.configure(scrollregion=base.bbox(tk.ALL)))
# add scrollbar for the canvas
scroll = tk.Scrollbar(window, command=base.yview)
scroll.pack(side=tk.LEFT, fill=tk.Y)
base.config(yscrollcommand=scroll.set)
# sample widgets
for i in range(2, 20):
btn = tk.Button(base, text=f"Button {i}")
base.create_window(200, i*30, window=btn)
For your code, you need to :
Create a scrollable canvas following above instructions
Instead of directly using grid, use canvas.create_window(x, y, window) for each widget

Related

How to make text show up in a specific location on a Tkinter window

please see the code below. Working on a 100 days of code project. How to I make text appear in a specific position (x=400, y=150) for example on a window.
Please see my code below.
from tkinter import *
from tkinter import messagebox
BACKGROUND_COLOR = "#B1DDC6"
window = Tk()
window.title('Flashy')
window.config(padx=50, pady=50, bg=BACKGROUND_COLOR)
# Todo. Center the front of the card.
canvas = Canvas(width=800, height=526, bg=BACKGROUND_COLOR, highlightthickness=0)
card_front = PhotoImage(file='images/card_front.png')
canvas.create_image(400, 263, image=card_front)
canvas.grid(row=0, column=1, columnspan=2)
# Placing text on the card.
text_1 = Label(text="French", bg='white', font=("Ariel", 40, "italic"), fg='black')
text_1.goto(x=400, y=150)
text_1.grid(row=0, column=0, columnspan=2)
# Buttons
check_mark = PhotoImage(file='images/right.png')
check_mark_button = Button(image=check_mark, highlightthickness=0)
check_mark_button.grid(row=1, column=2)
wrong_mark = PhotoImage(file='images/wrong.png')
wrong_mark_button = Button(image=wrong_mark, highlightthickness=0)
wrong_mark_button.grid(row=1, column=1)
window.mainloop()
I tried using .config to specify the location for the text but that doesn't work.
Use the place() geometry manager to place widgets at specific coordinates.
Instead of:
text_1.goto(x=400, y=150)
Try...
text_1.place(x=400, y=150)
And remove:
text_1.grid(row=0, column=0, columnspan=2)
since you should only use one geometry manager method (pack, grid, or place) on a given widget.

Tkinter LabelFrame not expanding in both directions when using grid()

So I'm trying to expand my LabelFrame named "Admin Frame" in the X and Y directions somewhat like the pack() system's fill=BOTH argument but it doesn't seem to be working. But I want to be able to do this with the grid()system because I have a complex interface.
This is my code for the LabelFrame:
from tkinter import *
from tkinter import ttk
root = Tk()
root.title("Election App Mockup")
root.geometry("800x600")
root.resizable(0,0)
frameStyle = ttk.Style().configure("my.TLabelframe.Label")
adminFrame = ttk.LabelFrame(root, text="Admin Panel", style="my.TLabelframe")
adminWinLabel = ttk.Label(adminFrame, text="Welcome Admin")
voterOpBtn = ttk.Button(adminFrame, text="Configure Voters' List", style="my.TButton")
candidateOpBtn = ttk.Button(adminFrame, text="Configure Candidates' List", style="my.TButton")
setVoteSessionBtn = ttk.Button(adminFrame, text="Setup Voting Session", style="my.TButton")
startVoterSessionBtn = ttk.Button(adminFrame, text="Start a Voting Session", style="my.TButton", padding=(25,3,25,3))
adminSettingBtn = ttk.Button(adminFrame, text="Admin Settings", style="my.TButton", )
adminLogoutBtn = ttk.Button(adminFrame, text="Logout", style="my.TButton", padding=(20,3,20,3))
adminWinLabel.grid(row=0, column=0, columnspan=2, pady=(170,5), padx=(150,0))
voterOpBtn.grid(row=1, column=0, padx=(150,5), pady=(10,10), ipadx=15)
candidateOpBtn.grid(row=1, column=1, padx=(10,5), pady=(10,10))
setVoteSessionBtn.grid(row=2, column=0, padx=(150,5), pady=(10,10), ipadx=15)
startVoterSessionBtn.grid(row=2, column=1, padx=(10,5), pady=(10,10))
adminSettingBtn.grid(row=3, column=0, padx=(130,0), pady=(10,10), columnspan=2)
adminLogoutBtn.grid(row=4, column=0, padx=(130,0), pady=(10,10), columnspan=2)
adminFrame.grid(row=0, column=0, sticky=NSEW)
adminFrame.grid_rowconfigure(0, weight=1)
adminFrame.grid_columnconfigure(0, weight=1)
root.mainloop()
I've tried adding extra arguments like ipadx and ipady in but it doesn't work:
adminFrame.grid(row=0, column=0, sticky=NSEW, ipadx=200, ipady=20)
Adding a padding argument in the adminFrame does work but it is very tricky to work with just to expand the frame to the window's full length and breadth.
Thanks in advance! Oh and do note that I did not learn object oriented programming in python, so I won't understand any answers using the class system.
So I'm trying to expand my LabelFrame named "Admin Frame" in the X and Y directions somewhat like the pack() system's fill=BOTH argument but it doesn't seem to be working.
As a rule of thumb, any time you use grid to manage widgets, you should give at least one row and one column a weight greater than zero. You are putting adminFrame in the root window with grid, but you haven't given any rows or columns in the root window a weight.
So, if you want adminFrame to expand to fill the window, you need to give a weight of 1 to row 0 and column 0. The other thing that needs to be done -- that you are already doing -- is to set the sticky attribute so that the frame "sticks" to all four sides of the space allocated to it.
adminFrame.grid(row=0, column=0, sticky=NSEW)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
Look at this:
from tkinter import *
from tkinter import ttk
root = Tk()
root.title("Election App Mockup")
root.geometry("800x600")
# root.resizable(False, False)
frameStyle = ttk.Style().configure("my.TLabelframe.Label")
adminFrame = ttk.LabelFrame(root, text="Admin Panel", style="my.TLabelframe")
adminWinLabel = ttk.Label(adminFrame, text="Welcome Admin")
# A frame for the centre 4 buttons
centre_buttons_frame = Frame(adminFrame)
voterOpBtn = ttk.Button(centre_buttons_frame, text="Configure Voters' List", style="my.TButton")
candidateOpBtn = ttk.Button(centre_buttons_frame, text="Configure Candidates' List", style="my.TButton")
setVoteSessionBtn = ttk.Button(centre_buttons_frame, text="Setup Voting Session", style="my.TButton")
startVoterSessionBtn = ttk.Button(centre_buttons_frame, text="Start a Voting Session", style="my.TButton", padding=(25,3,25,3))
adminSettingBtn = ttk.Button(adminFrame, text="Admin Settings", style="my.TButton", )
adminLogoutBtn = ttk.Button(adminFrame, text="Logout", style="my.TButton", padding=(20,3,20,3))
# I think the buttons looks better when they have an equal size so I added
# sticky="ew" to expand them in the horizontal direction.
voterOpBtn.grid(row=0, column=0, sticky="ew", padx=(0,15), pady=10, ipadx=15)
candidateOpBtn.grid(row=0, column=1, sticky="ew", padx=(15,0), pady=10)
setVoteSessionBtn.grid(row=1, column=0, sticky="ew", padx=(0,15), pady=10, ipadx=15)
startVoterSessionBtn.grid(row=1, column=1, sticky="ew", padx=(15,0), pady=10)
adminWinLabel.grid(row=1, column=1, pady=(0,5))
centre_buttons_frame.grid(row=2, column=1)
adminSettingBtn.grid(row=3, column=1, pady=10)
adminLogoutBtn.grid(row=4, column=1, pady=(10,0))
# `adminFrame` is packed in the root so it can expand. There should be a way
# to make it work with .grid, but .pack is easier in this case
adminFrame.pack(fill="both", expand=True)
# Expand the 0th and 6th row - there are no widgets
adminFrame.grid_rowconfigure((0, 6), weight=1)
adminFrame.grid_columnconfigure((0, 3), weight=1)
root.mainloop()
It looks like this on my computer:
I added a frame for the centre 4 buttons so that I can avoid columnspan. Also I used adminFrame.pack(fill="both", expand=True) because it is easier than using .grid_propagate(False) and manually setting the width and height of the frame.
Right now, the GUI should look good with any window size.

How to set a specified positions for RadioBox and Slider steps with tkinter

I am new with tkinter.
I am trying to make GUI and for the moment I made 3 RadioBoxe and a slider. and this what I got:
The problem that I want the 3 RadioBox to be in the same line and bellow them the slider. Is it possibe?
and I want the slider to go through odd numbers only (1,3,5,7,9 ...)
This is my code so far:
window = Tk()
window.title("Welcome to LikeGeeks app")
panelA = None
menu = Menu(window)
new_item = Menu(menu)
new_item.add_command(label='Open File', command=open_file)
new_item.add_separator()
new_item.add_command(label='Save File')
new_item.add_separator()
new_item.add_command(label='Exit')
menu.add_cascade(label='File', menu=new_item)
window.config(menu=menu)
label_blur = Label(window, text="Blurring")
label_blur.pack(anchor="w", padx=10, pady=10)
blur_rad1 = Radiobutton(window, text='Average', value=0)
blur_rad2 = Radiobutton(window, text='Gaussian', value=1)
blur_rad3 = Radiobutton(window, text='Median', value=2)
blur_rad1.pack(anchor="w", padx=10, pady=0)
blur_rad2.pack(anchor="w", padx=10, pady=0)
blur_rad3.pack(anchor="w", padx=10, pady=0)
blur_slide = Scale(window, from_=1, to=31, length=600,tickinterval=5, orient=HORIZONTAL)
blur_slide.pack(anchor="w", padx=10, pady=10)
window.mainloop()
There are two simple solutions: use grid so that you can specify the row and columns, or continue to use pack, but place the radiobuttons in a separate frame.
Since you're already using pack, it's easiest to use the second method.
Create a frame for the buttons, and use pack to add it to the window where you want it to appear:
button_frame = Frame(window)
button_frame.pack(side="top", fill="x")
Make the buttons children of the frame:
blur_rad1 = Radiobutton(button_frame, ...)
blur_rad2 = Radiobutton(button_frame, ...)
blur_rad3 = Radiobutton(button_frame, ...)
Use pack to lay out the buttons left-to-right:
blur_rad1.pack(side="left", expand=True)
blur_rad2.pack(side="left", expand=True)
blur_rad3.pack(side="left", expand=True)

Button on far right despite only being in 2nd (1) column

import random
from tkinter import *
root = Tk()
FirstPageFrame = Frame(root) # CREATES FIRST PAGE FRAME
FirstPageFrame.pack() # CREATES FIRST PAGE FRAME
RolltheDiceTitle = Label(FirstPageFrame, text="Roll the Dice")
RolltheDiceTitle.config(font=("Courier", 30))
RolltheDiceTitle.grid()
LoginRegisterWelcomeMessage = Label(FirstPageFrame, text="Welcome to the Roll the Dice Game.")
LoginRegisterWelcomeMessage.grid(row=1, padx=10, pady=10)
DiceImage = PhotoImage(file="dice.png")
DiceImageLabel = Label(FirstPageFrame, image=DiceImage)
DiceImageLabel.grid()
registerButton = Button(FirstPageFrame, text="Register", fg="orange") # CREATES REGISTER BUTTON
registerButton.grid(row=4, padx=10, pady=10)
loginButton = Button(FirstPageFrame, text="Login", fg="Green") # CREATES LOGIN BUTTON
loginButton.grid(row=4, column=1, padx=10, pady=10)
root.mainloop() # Continues tkinter window loop so program does not close
I'm trying to have both the register and login buttons in the middle of the window (where register button is located) but side by side, not stacked.
Apologies for any incorrect formatting with this question or any easy fixes with the code. I'm super new to Tkinter and trying to figure stuff out :)
When you put the Login button in column 1 all the other widgets are in column 0, so the Login button will be displayed on the far right.
One solution is to create a contaner frame for the buttons which can be placed in the center of column 0, and then place the buttons in culmns in that frame.
I have stated row and colun explicitly for all widgets which makes it easier to follow, and also assigned a bg color to the buttonFrame so you can see where it goes.
import random
from tkinter import *
root = Tk()
FirstPageFrame = Frame(root) # CREATES FIRST PAGE FRAME
FirstPageFrame.pack() # CREATES FIRST PAGE FRAME
RolltheDiceTitle = Label(FirstPageFrame, text="Roll the Dice")
RolltheDiceTitle.config(font=("Courier", 30))
RolltheDiceTitle.grid(row=0, column=0)
LoginRegisterWelcomeMessage = Label(FirstPageFrame, text="Welcome to the Roll the Dice Game.")
LoginRegisterWelcomeMessage.grid(row=1, column=0, padx=10, pady=10)
DiceImage = PhotoImage(file="dice.png")
DiceImageLabel = Label(FirstPageFrame, image=DiceImage)
DiceImageLabel.grid(row=2, column=0)
# Here is the container frame for the buttons.
buttonFrame = Frame(FirstPageFrame, bg='tan') # bg color to indicate
buttonFrame.grid(row=3) # position and extent of frame
registerButton = Button(buttonFrame, text="Register", fg="orange") # CREATES REGISTER BUTTON
registerButton.grid(row=0, column=0, padx=10, pady=10)
loginButton = Button(buttonFrame, text="Login", fg="Green") # CREATES LOGIN BUTTON
loginButton.grid(row=0, column=1, padx=10, pady=10)
root.mainloop() # Continues tkinter window loop so program does not close

Is there a way to gray out (disable) a tkinter Frame?

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

Categories

Resources