I'm working on a new program in Python 3 with tkinter. I'm trying to add a scrollbar to the whole window, but it isn't working. Right now I'm just trying to make the window 1000 pixels tall (will be set later in the program) and use the a vertical scrollbar to access the parts not seen on screen. I've read multiple other threads trying to figure it out and have attempted. Could someone tell how to get it to work and what I did wrong. No error is displayed, but also no scrollbar is displayed. Here's the code:
from tkinter import *
class MusicPlayer:
def __init__(self):
self.tk = Tk()
self.tk.title("Bass Blaster")
self.screen_width, self.screen_height = self.tk.winfo_screenwidth(), self.tk.winfo_screenheight()
self.frame = Frame(self.tk, width=self.screen_width, height=self.screen_height)
self.frame.grid(row=0, column=0)
self.canvas = Canvas(self.frame, bg="#585858", width=self.screen_width, height=self.screen_height, scrollregion=(0, 0, self.screen_width, 1000))
vbar = Scrollbar(self.frame, orient=VERTICAL)
vbar.pack(side=RIGHT, fill=Y)
vbar.config(command=self.canvas.yview)
self.canvas.config(width=self.screen_width, height=self.screen_height)
self.canvas.config(yscrollcommand=vbar.set)
self.canvas.pack(side=LEFT, expand=True, fill=BOTH)
self.tk.update()
bass_blaster = MusicPlayer()
bass_blaster.tk.mainloop()
The scroll bar was there the whole time, the problem was that I made the canvas width the screen's size and the scroll bar was adding onto the width, therefore going off the screen. I just had to make the width a little less.
Related
Python beginner. I placed a scrollbar widget in window and that works, but no matter what I do I can't get the scrollbox widget to change size. Could go with a larger scrollbox or for it to resize when the window resizes, but can't figure out how to force either to happen. Tried lots of different solutions, but feels like the grid and canvas are defaulting to a size and can't figure out how to change that. Help would be appreciated. Code is below:
import tkinter as tk
from tkinter import ttk
import os
import subprocess
class Scrollable(tk.Frame):
"""
Make a frame scrollable with scrollbar on the right.
After adding or removing widgets to the scrollable frame,
call the update() method to refresh the scrollable area.
"""
def __init__(self, frame, width=16):
scrollbar = tk.Scrollbar(frame, width=width)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=True)
self.canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=self.canvas.yview)
self.canvas.bind('<Configure>', self.__fill_canvas)
# base class initialization
tk.Frame.__init__(self, frame)
# assign this obj (the inner frame) to the windows item of the canvas
self.windows_item = self.canvas.create_window(0,0, window=self, anchor=tk.NW)
def __fill_canvas(self, event):
"Enlarge the windows item to the canvas width"
canvas_width = event.width
self.canvas.itemconfig(self.windows_item, width = canvas_width)
def update(self):
"Update the canvas and the scrollregion"
self.update_idletasks()
self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item))
root = tk.Tk()
root.title("application")
root.geometry('750x800')
dbEnvs = ['a','b']
x = 1
header = ttk.Frame(root)
body = ttk.Frame(root)
footer = ttk.Frame(root)
header.pack(side = "top")
body.pack()
footer.pack(side = "top")
#setup Environment selection
envLabel = tk.Label(header, text="Environment:")
envLabel.grid(row=0,column=0,sticky='nw')
dbselection = tk.StringVar()
scrollable_body = Scrollable(body, width=20)
x = 1
for row in range(50):
checkboxVar = tk.IntVar()
checkbox = ttk.Checkbutton(scrollable_body, text=row, variable=checkboxVar)
checkbox.var = checkboxVar # SAVE VARIABLE
checkbox.grid(row=x, column=1, sticky='w')
x += 1
scrollable_body.update()
#setup buttons on the bottom
pullBtn = tk.Button(footer, text='Pull')
pullBtn.grid(row=x, column=2, sticky='ew')
buildBtn = tk.Button(footer, text='Build')
buildBtn.grid(row=x, column=3, sticky='ew')
compBtn = tk.Button(footer, text='Compare')
compBtn.grid(row=x, column=4, sticky='ew')
root.mainloop()
have tried anchoring, changing the window base size and multiple other things (8 or 19 different items, plus reading lots of posts), but they normally involve packing and since I used grids that normally and ends with more frustration and nothing changed.
If you want the whole scrollbox to expand to fill the body frame, you must instruct pack to do that using the expand and fill options:
body.pack(side="top", fill="both", expand=True)
Another problem is that you're setting expand to True for the scrollbar. That's probably not something you want to do since it means the skinny scrollbar will be allocated more space than is needed. So, remove that attribute or set it to False.
scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=False)
tip: when debugging layout problems, the problems are easier to visualize when you temporarily give each widget a unique color. For example, set the canvas to one color, body to another, the instance of Scrollable to another, etc. This will let you see which parts are visible, which are growing or shrinking, which are inside the borders of others, etc.
How are you supposed to go around sizing all the buttons to be the same size regardless of the text you insert inside, they should all size according to the biggest one.
There is an answer to a similar question like mine already, but it is done using grid and I am using a canvas to place a background image in the window and to place the buttons.
Is it even worth the hassle to get your buttons to the same size according to text automatically, since my text will always be around the same...
I tried getting the size of the buttons using cget() but that returns 0. Where does it store its width then since it has to size itself somehow even if it does it according to text? Can access that in any way? I was thinking of using that value to adjust the value of other buttons somehow, but it turned out as a fail.
If you are wondering why am I making it into a class, idk either, wanted to try it.
I had it working by putting all the buttons in a frame and telling them to fill=x but using a frame destroys the point of using a canvas since the background can't be seen because the frame covers it. Is there a way to make the frame transparent in the canvas, that could also potentially solve my problem.
from tkinter import *
class ThreeButtonMenu():
def __init__(self, button1_text, button2_text, button3_text, image_height = 600, image_width = 500, bg_input = 'space_background.png'):
self.root = Tk()
HxW = str(image_height)+'x'+str(image_width)
self.root.geometry(HxW)
self.root.maxsize(image_height,image_width)
self.root.minsize(image_height,image_width)
self.root.title('Guess')
bg = PhotoImage(file=bg_input)
background_canvas = Canvas(self.root, width = 600, height=500)
background_canvas.pack(fill="both", expand=True)
background_canvas.create_image(0,0, image=bg, anchor='nw')
button1 = Button(self.root, text=button1_text, font = ('Lato',28))
button2 = Button(self.root, text=button2_text, font = ('Lato',28))
button3 = Button(self.root, text=button3_text, font = ('Lato',28), command = self.root.destroy)
button1_window = background_canvas.create_window(300,45, anchor=N, window=button1)
button2_window = background_canvas.create_window(300,160, anchor=N, window=button2)
button3_window = background_canvas.create_window(300,275, anchor=N, window=button3)
print(button1.cget('width'))
print(button2.cget('width'))
print(button3.cget('width'))
self.root.mainloop()
start_menu = ThreeButtonMenu('Start Game', 'Leaderboard', 'Quit')
Thank you for your answers.
You would typically do this when you use a geometry manager (pack, place, or grid).
For example, you need to call pack on each of the buttons. See example of pack below.
import tkinter as tk
root = tk.Tk()
for text in (
"Hello", "short", "All the buttons are not the same size",
"Options", "Test2", "ABC", "This button is so much larger"):
button = tk.Button(root, text=text)
button.pack(side="top", fill="x")
root.mainloop()
I'm working on a GUI with tkinter and i have a problem.
When i add a scrollbar to my app, the frame on my canvas overlaps the outlines (see image)
Here is the code:
from tkinter import *
window = Tk()
window.geometry("400x225")
scrollbar1 = Scrollbar(window, orient=VERTICAL)
canvas1 = Canvas(window, bg="#003333", yscrollcommand=scrollbar1.set)
frame1 = Frame(canvas1, bg="#003333")
scrollbar1.config(command=canvas1.yview)
scrollbar1.pack(side=RIGHT, fill=Y)
canvas1.pack(fill=BOTH, expand=True)
canvas1.create_window((0, 0), window=frame1, anchor="nw")
for x in range(20):
string = "line " + str(x)
label1 = Label(frame1, fg="white", bg="#003333", text=string, font=("Calibri Bold", 14))
label1.pack(pady=5)
window.update()
canvas1.config(scrollregion=canvas1.bbox("all"))
window.mainloop()
I don't know if it's possible but i want the frame to fit within the canvas and keeping the outlines as well.
I hope you get my problem and can probably help me out! Thanks in advance.
The highlightthickness
Specifies a non-negative value indicating the width of the highlight rectangle to draw around the outside of the widget when it has the input focus.
So, this is not really the "border" that you want. It is a part of the drawing space within the canvas, when you use window_create to draw a window, the parent of that window is the canvas, which begins before the highlight and so the window slides over it.
A solution, as also suggested by #martineau would be to make this 0 by specifying highlightthickness=0 and as you suggested that you need the "border" around the whole thing, you can either create a container frame and specify the bd parameter, or just set the bd of the window window.config(bd=2).
I'm making a card game (called monster master) to develop my python skills, specifically OOP.
I have a GUI that has a few static objects: Player 1's side of the table, Player 2's side, a wee line in the middle and I'm now trying to implement an 'end turn' button.
I have tried a lot of different things to try to get this button to display, but I just can't get it to appear even if there are no errors. Just saying that there are a few commented out lines that I've temporarily taken away for the sake of trying to understand the problem with this button.
Here's the code that I'm currently using to try:
def RunGame():
class App():
"""docstring for App"""
def draw():
# Setting up canvas dimensions
canvas_width = 640
canvas_height = 480
master = Toplevel()
master.title("Monster Master by Charles Cameron - Game")
master.resizable(width=False, height=False)
master.geometry("640x480")
w = Canvas(master,
width=canvas_width,
height=canvas_height)
w.pack()
# Drawing static objects
CentrePoints = [(0, canvas_height/2), (canvas_width/2, canvas_height/2),
(canvas_width, canvas_height/2)]
#Left, centre and right centres (save me from retyping them)
Player1Area = w.create_rectangle(CentrePoints[0], canvas_width,
canvas_height, fill="#303AFE") #Player1 Area
Player2Area = w.create_rectangle(0, 0, CentrePoints[2],
fill="#C31B1B") #Player2 Area
Barrier = w.create_line(CentrePoints[0], CentrePoints[2],
fill="#0090E3", width=20) # Centre barrier
# class GameBtn():
# class EndTurnBtn():
# def __init__(self, BtnName, master):
BtnName = Button(master, bg="white", command=lambda:print("Clicked!"))
image = ImageTk.PhotoImage(file="imgs\EndTurn.png")
BtnName.config(image=image, width="70", height="70")
BtnName.pack(side=RIGHT)
# ChangeTurn = GameBtn.EndTurnBtn('ChangeTurn', master)
master.mainloop()
Window = App()
App.draw()
The button code for the actual button worked fine when I tried it in its own script but stopped working when I put it inside this program.
Hope it's not too dumb a question to ask, quite an amateur at python still and honestly can't find the answer anywhere online.
Many thanks
Your button exists, but it's past the edge of the window. This is because you made your window 640x480, and then completely filled it with a 640x480 canvas. Remove the master.geometry("640x480") line and the window will stretch to contain both your canvas and your button.
You might be thinking "but I don't want the button to appear to the side of the canvas. I want the button to be on the canvas. The canvas only really exists because I wanted to color sections of the background". Embedding widgets on a canvas is possible using create_window (see How to place a widget in a Canvas widget in Tkinter?), but it may be more practical to create colored backgrounds by stacking Frame objects together and giving them individual colors. Example:
from tkinter import Tk, Frame, Button
root = Tk()
root.geometry("640x480")
top_player_frame = Frame(root, height=230, bg="red")
barrier = Frame(root, height=20, bg="green")
bottom_player_frame = Frame(root, height = 230, bg="blue")
#configure column 0 so frames can stretch to fit width of window
root.columnconfigure(0, weight=1)
top_player_frame.grid(row=0, sticky="we")
barrier.grid(row=1, sticky="we")
bottom_player_frame.grid(row=2, sticky="we")
bottom_player_end_turn_button = Button(bottom_player_frame, text="End Turn")
#use `place` here because `pack` or `grid` will collapse the frame to be only as tall as all the widgets it contains, i.e. just this button
bottom_player_end_turn_button.place(x=10,y=10)
root.mainloop()
Result:
I am using ttk.Scrollbar to scroll elements inside a Tkinter.Canvas. Unfortunately a background image of this canvas will also scroll. How can I prevent this?
Here is an example of what I use:
import Tkinter as tk
from ttk import *
# first canvas with scrollbar
root = tk.Tk()
canv1 = tk.Canvas(root, width=200, height=200, bg='green')
sbar = Scrollbar(root, orient="vertical")
sbar.config(command=canv1.yview)
sbar.pack(side="right", fill="y")
canv1.config(yscrollcommand=sbar.set)
canv1.pack(side="right", fill="y")
# background of dirst canvas
photo_content2 = tk.PhotoImage(file = './sample_image.gif')
canv1.create_image(115,300, image=photo_content2)
# scrollable second canvas insode first
canv2 = tk.Canvas(canv1, width=50, height=30, bg='red')
canv2.pack()
canv1.create_window(20,20, window=canv2)
canv1.config(scrollregion=(0,0,300,1000))
root.mainloop()
Scrolling affects everything drawn on the canvas. You can't add a background image that doesn't scroll along with everything else.
That being said, you can create a function that moves the background image after the scrolling happens, and you get the same effect as if the image didn't scroll.
You do this by connecting the scrollbar to a custom function rather than directly to the widgets yview and/or xview commands. In this function you can call the yview and xview commands as normal, and then re-position the background image.
For example, this is how you would handle vertical scrolling; horizontal scrolling would work in a similar way:
def custom_yview(*args, **kwargs):
canvas.yview(*args, **kwargs)
x = canvas.canvasx(0)
y = canvas.canvasy(0)
canvas.coords("background", x,y)
canvas = tk.Canvas(...)
vsb = tk.Scrollbar(orient="vertical", command=custom_yview)
canvas.create_image(..., tags=("background,"))