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,"))
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.
I am working on a project where I need to display information when a button is clicked, and hide information when that button is clicked again. The information can be several rows and extend beyond the window, so I am trying to add a scroll bar to get around this issue. The problem is that when the information is displayed, the scroll bar does not show. Scrolling is still possible but the actual bar is not there. Resizing the window fixes this issue for some reason. Also when the button is clicked again to hide the information, the size of the canvas and the scrollbar remain the same, so you can scroll far beyond the button into empty space. My theory is that the scroll region is not updating when the widgets in the canvas change size - but I'm not sure how to go about fixing that.
I realize this explanation may be a bit confusing, so I have provided a simplified example below. This example has a single button that when clicked reveals several lines of "other info". The scrollbar and/or canvas does not resize properly upon interacting with this button.
My strategy right now was to have a method called add_scrollbar that removes the current scrollbar and creates a new one every time the widgets change in hopes that the new one would be the right size; however this still is not working.
from tkinter import *
from tkinter import ttk
def add_scrollbar(outer_frame, canvas):
if len(outer_frame.winfo_children()) == 2:
# canvas is at index 0 of the outer frame, if a scrollbar has been added it will be at index 1
outer_frame.winfo_children()[1].destroy()
# my strategy here was to destroy the existing scroll bar
# and create a new one each time the widget changes size
scrollbar = ttk.Scrollbar(outer_frame, orient=VERTICAL, command=canvas.yview)
scrollbar.pack(side=RIGHT, fill=Y)
canvas.configure(yscrollcommand=scrollbar.set)
canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
return
def create_example():
root = Tk()
root.geometry("1200x1200")
my_outer_frame = Frame(root)
my_outer_frame.pack(fill=BOTH, expand=1)
my_canvas = Canvas(my_outer_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
inner_frame = Frame(my_canvas)
my_canvas.create_window((0, 0), window=inner_frame)
# ^^^ Sets up the ability to have a scroll_bar
changing_frame = Frame(inner_frame, borderwidth=4) # this is the frame that will be changing its contents
changing_frame.pack(side=LEFT, anchor="n")
display_frame(changing_frame, my_outer_frame, my_canvas)
# this method re-displays the changing frame depending on the specified size ('big' or 'small'
root.mainloop()
return
def display_frame(frame, outer_frame, canvas, size='small'):
for widget in frame.winfo_children():
widget.destroy()
if size == 'small':
Button(frame, height=5, width=5, text="Show",
command=lambda this_frame=frame: display_frame(this_frame, outer_frame, canvas, size='big')).grid(row=0,
column=0)
elif size == 'big':
Button(frame, height=5, width=5, text="Hide",
command=lambda this_frame=frame: display_frame(this_frame, outer_frame, canvas, size='small')).grid(
row=0, column=0)
for n in range(1, 100):
Label(frame, text="Other Stuff!").grid(row=n, column=0)
frame.pack(side=LEFT)
add_scrollbar(outer_frame, canvas) # this method is supposed to destroy the existing scrollbar and make a new one
return
if __name__ == '__main__':
create_example()
I want to put a scrollbar in a canvas inside a Frame with tkinter. I can see my scrollbar but she don't work. I don't anderstant why, here is my code.
labelBlesser = tk.Label(self.fen, text='Rajouter', font=fontLabel)
labelBlesser.configure(background='white')
labelBlesser.place(relx=0.7, rely=0.40, anchor='center')
frameBlesser = tk.Frame(self.fen)
vbar = tk.Scrollbar(frameBlesser, orient='vertical')
vbar.pack(side='right', fill='y')
canvasBlesser = tk.Canvas(frameBlesser)
vbar.config(command=canvasBlesser.yview)
frameBlesser.place(relx=0.7, rely=0.65, anchor='center')
for _ in range(3):
self.listeBlesser.append(tk.Label(canvasBlesser, text='Blesse {}:'.format(self.nombreBlesserAjouter + 1)))
self.listeBlesser.append(tk.Entry(canvasBlesser, font=fontEntry))
self.refreshListeBlesser()
canvasBlesser.pack()
def refreshListeBlesser(self):
for i, lab in enumerate(self.listeBlesser):
lab.place(x=10, y=i*70+13)
thank you for your help :)
You need to bind the canvas and the scrollbar together. Also, you need to create a frame inside of the canvas using canvasBlesser.create_window so that you can place stuff inside the frame as usual.
frameBlesser = tk.Frame(self.fen)
vbar = tk.Scrollbar(frameBlesser, orient='vertical')
vbar.pack(side='right', fill='y')
canvasBlesser = tk.Canvas(frameBlesser)
# adding the side and fill for packing canvas in the frame
canvasBlesser.pack(side='left', fill='both')
# connecting the canvas and scrollbar together
vbar.config(command=canvasBlesser.yview)
canvasBlesser.configure(yscrollcommand=vbar.set)
# placing a frame inside the canvas as a parent to other widgets in it
inner_frame = tk.Frame(canvasBlesser)
canvasBlesser.create_window((0,0), window=inner_frame, anchor="nw")
Use this inner_frame as a parent to place the widgets just as usual to be seen in the canvas.
The line canvasBlesser.configure(yscrollcommand=vbar.set) makes it so that whenever the view of the canvas is changed, it updates the scrollbar's value.
I have a Python tkinter program simplified to
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, height=200, width=200, bg="salmon")
canvas.pack(fill=tk.BOTH, expand=tk.YES)
def click(event):
print(event.x)
print(event.y)
def release(event):
print(event.x)
print(event.y)
canvas.bind("<Button-1>", click)
canvas.bind("<ButtonRelease-1>", release)
root.mainloop()
with a Canvas as the main element. Canvas has click/release events bound to it (e.g. returning event.x and event.y). I want to add a background image to the canvas in this manner:
canvas = tk.Canvas(root, bg='/path/to/image.png')
I have managed to set a background image by creating an image on the canvas using canvas.create_image method, as explained in Adding a background image in python. However, this broke my program as event.x and event.y return position of the background image.
I am looking for a solution that would force me to change the least of the existing code.
The only way to create a background image on a canvas is to create an image object on the canvas. Doing so will not affect the coordinates returned by the bound functions in your example.
We need to use PhotoImage to load the image for use then we use create_image to set that image to the canvas.
Give this a shot:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, height=200, width=200, bg="salmon")
canvas.pack(fill=tk.BOTH, expand=tk.YES)
def click(event):
print(event.x)
print(event.y)
def release(event):
print(event.x)
print(event.y)
canvas.bind("<Button-1>", click)
canvas.bind("<ButtonRelease-1>", release)
my_image = tk.PhotoImage(file='/path/to/image.png')
canvas.create_image(10, 10, image=my_image, anchor='nw')
root.mainloop()
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.