Tkinter Side Bar - python

I want to create a side bar like the one ABOVE that is collapsible.
In the collapsed form, the side bar will only have icons for the option and when you hover your mouse over the bar then it will expand showing you the description/name of the icon.
If you click on the icon then it will take you to a function designated to that icon such as settings page.
On the right I will have the main home page with a lot of buttons.
I have no clue how you would do this so please help me start this off and I should manage with the rest.
Is it also possible to do this with only the basic Tkinter or do I need to import more modules via pip (not preferable)
So far I have this:
def loginnow(name):
global login
login.destroy()
login= Tk()
screen_width = login.winfo_screenwidth()
screen_height = login.winfo_screenheight()
screen_height=str(screen_height)
screen_width=str(screen_width)
screen = screen_width+"x"+screen_height
login.geometry(screen)
login.title("Logged in as %s"%name)
The side bar will include
Settings = https://www.iconpacks.net/icons/2/free-settings-icon-3110-thumb.png
I will manage with the other icons, once I get a general idea.
I want the menu to be visible without having to click on something for it to appear.

Explanation:
This can actually be achieved by playing around with bindings. Take a look at this rough example below:
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
root.geometry('600x600')
min_w = 50 # Minimum width of the frame
max_w = 200 # Maximum width of the frame
cur_width = min_w # Increasing width of the frame
expanded = False # Check if it is completely exanded
def expand():
global cur_width, expanded
cur_width += 10 # Increase the width by 10
rep = root.after(5,expand) # Repeat this func every 5 ms
frame.config(width=cur_width) # Change the width to new increase width
if cur_width >= max_w: # If width is greater than maximum width
expanded = True # Frame is expended
root.after_cancel(rep) # Stop repeating the func
fill()
def contract():
global cur_width, expanded
cur_width -= 10 # Reduce the width by 10
rep = root.after(5,contract) # Call this func every 5 ms
frame.config(width=cur_width) # Change the width to new reduced width
if cur_width <= min_w: # If it is back to normal width
expanded = False # Frame is not expanded
root.after_cancel(rep) # Stop repeating the func
fill()
def fill():
if expanded: # If the frame is exanded
# Show a text, and remove the image
home_b.config(text='Home',image='',font=(0,21))
set_b.config(text='Settings',image='',font=(0,21))
ring_b.config(text='Bell Icon',image='',font=(0,21))
else:
# Bring the image back
home_b.config(image=home,font=(0,21))
set_b.config(image=settings,font=(0,21))
ring_b.config(image=ring,font=(0,21))
# Define the icons to be shown and resize it
home = ImageTk.PhotoImage(Image.open('home.png').resize((40,40),Image.ANTIALIAS))
settings = ImageTk.PhotoImage(Image.open('settings.png').resize((40,40),Image.ANTIALIAS))
ring = ImageTk.PhotoImage(Image.open('ring.png').resize((40,40),Image.ANTIALIAS))
root.update() # For the width to get updated
frame = Frame(root,bg='orange',width=50,height=root.winfo_height())
frame.grid(row=0,column=0)
# Make the buttons with the icons to be shown
home_b = Button(frame,image=home,bg='orange',relief='flat')
set_b = Button(frame,image=settings,bg='orange',relief='flat')
ring_b = Button(frame,image=ring,bg='orange',relief='flat')
# Put them on the frame
home_b.grid(row=0,column=0,pady=10)
set_b.grid(row=1,column=0,pady=50)
ring_b.grid(row=2,column=0)
# Bind to the frame, if entered or left
frame.bind('<Enter>',lambda e: expand())
frame.bind('<Leave>',lambda e: contract())
# So that it does not depend on the widgets inside the frame
frame.grid_propagate(False)
root.mainloop()
I have explained the code using comments to understand on-the-go. The icons where taken from Flat Icons. As can be seen, it has its own disadvantage, but you can quite mimic something closer to what you have shown, with this. You can improve this more by creating custom widgets to hold both icon and text when expanded and so on.
Output:
Updated image code(without PIL):
home = PhotoImage(file='home.png') # Make sure the image size is comparable to the minimum frame width
settings = PhotoImage(file='settings.png')
ring = PhotoImage(file='ring.png')

Although tkinter doesn't have anything like this built-in, you have all of the tools to implement it. You start by creating a frame to hold the sidebar, and then bind to the <Enter> and <Leave> events on the frame to show and hide it.
There are at least three ways to show and hide it. For example, if each item was a button with an image and text and added to the frame with pack you could simply add or remove the text portion of the button to cause it to shrink or example.
Or, if using grid and creating the icon and text as separate widgets, you could use grid_remove to remove everything in the second column, causing the frame to shrink.
Or, you could use place to add the sidebar to the root window, and use place to change the width of the frame when you show or hide it.

Related

How to change tkinter geometry values in regard to (for example) a scrolled text height?

I was wondering how do the geometry() function values in tkinter come to play with the height value of for example a scrolled text? How can I convert the units to work with each other?
For example: If I want to open a scrolled text box underneath my tkinter window with a click of a button, I know I need then to change my window's geometry() (height) value, but I don't know by how much exactly.
(In the following example I randomly added 100 to the geometry value in the function open. But I want a more specific value that translates to the 7 of the height value of the scrolled text)
from tkinter import *
from tkinter import scrolledtext
def open():
root.geometry(f'{frame_width}x{frame_height+100}')
st.pack(side="bottom")
frame_width = 900
frame_height = 500
root = Tk()
root.geometry(f'{frame_width}x{frame_height}')
root.configure(bg='white')
root.resizable(False, False)
open_st = Button(root, text="OPEN SCROLLED TEXT", command= open)
open_st.pack()
st = scrolledtext.ScrolledText(root, width=frame_width, height=7)
root.mainloop()
Widgets that contains text usually provide their width and height in character space. This means tkinter takes the average width and height of your font and multiplies it with the given number in width and height.
There are mainly two ways to deal with that, either tk.Font.measure or metrics, if you want to convert characters to pixel or the much more comfortable way by just asking the widget for it's size via winfo. Happily your case fits for the latter.
The alternate code would looks like this:
def open_text():
st.pack(side="bottom")
width, window_height = root.winfo_width(), root.winfo_height()
requested_text_height = st.winfo_reqheight()
new_height = window_height + requested_text_height
root.geometry(f'{width}x{new_height}')
Please note that I have named your function differently. Cause you redefined open in the global namespace of your module and this could lead to unintended behavior. In addition I wonder why you want to do it like this and not just let the geometry managers do their job?

Tkinter scrollbar isn't reacting to the size of my canvas

I'm trying to implement a scrollbar that will scroll through a frame in a canvas window. The canvas, frame, and frame elements are all functioning well as far as I can see. I have no issues with adding or removing elements from the frame, and they show up as I want them to. The only issue is with the scrollbar.
The scrollbar doesn't work at all. It shows up exactly where it should, but it doesn't react at all to the canvas or frame elements. If I resize the window and squish down the canvas, the scrollbar does nothing — it just stays empty as though there's nothing for it to scroll through.
I recognize there are a million other questions about scrollbars in canvases, but I've carefully read through as many of them as I could and didn't find any sort of solution to my problem. So with that, I ask: any idea where I'm going wrong?
Any feedback or suggestions for possible fixes would be greatly appreciated.
lbl_frame = Canvas(menu, bg=root_bg, width=(menu.winfo_width() - 35), height=canvas_size, highlightthickness=0)
f = Frame(lbl_frame, bg=root_bg) # Frame that goes in the canvas
scroll = Scrollbar(menu, bg=root_bg, orient='vertical', width=17) # Scrollbar
scroll.grid(row=3, column=2, sticky='ns')
scroll.config(command=lbl_frame.yview) # Scrollbar's command reads information about the height of the canvas
lbl_frame.create_window(0, 0, window=f, anchor="nw") # Creating a canvas window for the frame
lbl_frame.config(yscrollcommand=scroll.set)
def canvas_resize(event):
global min_size # Currently set to 95
canvas_size = menu.winfo_height() - 170 # Sum of all other elements
if canvas_size < min_size:
canvas_size = min_size # Canvas size must be >= 100
lbl_frame.configure(height=canvas_size) # Scale canvas size with window
lbl_frame.configure(scrollregion=lbl_frame.bbox("all"))
# Set scrollregion to the total region of all elements in the frame
menu.bind("<Configure>", lambda e: canvas_resize(e))
Note that elements are added to the frame shortly after this. Because the process I add them by is very long and contains a lot of stuff that isn't relevant to my problem, plus I don't have any issues with adding these elements, I've decided not to include that part.
I figured it out. It was with how I was adding the elements. I added them to the canvas, not the frame. So the frame was left empty.

Button grid shrinking vertically when adding a certain amount of images

I'm making a chess board in Tkinter with chess piece images to get used to the module, and have success with placing the first N-1 images in a row in an NxN grid.
However, upon coding an Nth image on any row, the row in question shrinks vertically.
I'm trying to use an OOP approach and am thinking there may be a method i'm misusing or missing entirely.
I've tried adjusting the arguments for the grid() method such as row and column, but they don't appear to affect the result. Adjusting the width and height of the buttons did not do anything significant. I used a 4x3 grid as a testing example and the images fit with the grid if I leave an empty button without an image in a row.
import tkinter as tk
class ChessBoard(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self,parent,*args,**kwargs)
self.parent = parent
self.button_identities = [[[] for x in range(3)] for y in range(3)]
self.pictures = {"Rook":tk.PhotoImage(file="rook.png")}
self.initialize_board()
def initialize_board(self):
for r in range(3):
for c in range(3):
button = tk.Button(self.parent,bg="white",\
width=10,height=5)
button.grid(row=r,column=c,sticky="sewn")
self.button_identities[r][c] = button
self.setup_starting_positions()
def setup_starting_positions(self):
# Commenting out one of the lines in this method keeps the example row intact
# In this example I'm trying to make 3 images fit in a single row without the row shrinking
self.button_identities[0][0].config(image=self.pictures["Rook"])
self.button_identities[0][1].config(image=self.pictures["Rook"])
self.button_identities[0][2].config(image=self.pictures["Rook"])
class MainApplication(tk.Frame):
# The Main Application hosts the Chessboard frame
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self,parent,*args,**kwargs)
self.parent = parent
self.parent.geometry('{}x{}'.format(1000,800))
self.chessboard = ChessBoard(self)
self.chessboard.grid_propagate(False)
self.chessboard.grid(row=1,column=1,columnspan=3)
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).grid(row=0,column=0)
root.mainloop()
I expected the buttons to show some sign of changing when I tried editing the grid() methods or width/height arguments when initializing a Button, but nothing remarkable seemed to change. I feel as though I'm not understanding how the Grid manager works in this case. Sorry for the lack of images.
In empty Button (or with text) height=5 means 5 lines of text. When you put image then it means 5 pixels.
When you have empty button in row then it has size 5 lines and it is the highest widget in row so other buttons are resized to row size. When you add last button then all buttons has height 5 pixels and there is bigger widget to resize row.
So you have to set width and height in pixels and put all images.
Or simple remove width, height from buttons and they will use width, height of image.
button = tk.Button(self.parent, bg="white")
See: Button

Is it possible to place an image in Tkinter without a Label or canvas

I am trying to place images next to each other side by side but the labels overlap and the label sticks out.
from tkinter import *
root = Tk()
root.geometry("1000x700")
root.resizable(0, 0)
##############################################
TFi = PhotoImage(file="images/Topframe.png")
TF = Label(root, image=TFi)
TF.place(x=-3, y=-3)
BFi = PhotoImage(file="images/Botframe.png")
BF = Label(root, image=BFi)
BF.place(x=-3, y=650)
LF1i = PhotoImage(file="images/LeftFrame1.png")
LF1 = Label(root, image=LF1i)
LF1.place(x=-3, y=50)
##############################################
root.mainloop()
Is it possible to place an image in Tkinter without a Label or canvas
Your most common choices for labels are a canvas or a label. You can also put images on buttons, and embed them in a text widget.
The best choice for creating labels that are next to each other are to use pack or grid. pack is good if you're making a single horizontal or vertical grouping, but grid is better if you're making both rows and columns of widgets.
You can use place, but that requires that you do all of the math to compute the location of each image, and usually results in a user interface that isn't very resilient to changes in screen resolution. It also sometimes ends up causing you to have to do changes to every widget even if you only want to tweak the layout slightly.
My guess for why they overlap is that you aren't aware that the coordinates you give place by default specify the center of the image rather than the upper-left corner. You can specify which part of the image is at the given coordinate with the anchor option.
the label sticks out.
I'm not exactly sure what you mean by that, but if you mean that it has a 3D appearance, you can control that by giving it a borderwidth of zero and/or a relief of "flat".

How to make a fixed size button with PGU

I'm using python,pygame and PGU to develop a game. I'd like to create a fixed size button, but it seems resize() doesn't have any effect. Here is a snippet of my code:
app = gui.App()
top = gui.Container()
button = gui.Button("Start")
button.resize(200, 100)
top.add(button, 0, 0)
app.init(top, screen)
The button will always appear at the minimum size required to display its text. Is there any way to enforce the button size with PGU?
If you want to create a fixed size button, you should initialize it, passing width and height.
Remember that, if the dimensions in pixel aren't "large enough", it forces the button to have the minimum size required to display its text, also considering padding (from style/theme, which you can also configure) etc.
Theese are three useful (of course alternative) examples:
button = gui.Button("Start", width = 200)
button = gui.Button("Start", height = 100)
button = gui.Button("Start", width = 200, height = 100)
Where the one you are looking for is the last.
The question is rather old, but still useful for others, I think.

Categories