I have a lot of Labels in a frame in TKinter. I would like a scrollbar to see all the labels that appear out of the screen. What I have currently tried does not work. Here is a MWE:
from tkinter import *
from tkinter import ttk
import tkinter.font as font
from tkinter import Tk
from PIL import Image, ImageTk
fontcolor = '#3a346f'
class SecurityProperties(Frame):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
# Set up scroll bar
self.main_frame = Frame(self)
self.main_frame.pack(fill=BOTH, expand=1)
self.my_canvas = Canvas(self.main_frame)
self.my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
self.my_scrollbar=ttk.Scrollbar(self.main_frame, orient=VERTICAL, command=self.my_canvas.yview)
self.my_scrollbar.pack(side=RIGHT, fill=Y)
self.my_canvas.configure(yscrollcommand=self.my_scrollbar.set)
self.my_canvas.bind('<Configure>', lambda e: self.my_canvas.configure(scrollregion = self.my_canvas.bbox("all")))
self.second_frame = Frame(self.my_canvas)
self.my_canvas.create_window((0,0), window=self.second_frame, anchor= "nw")
helv15 = font.Font(family="Helvetica",size=15,weight="bold")
self.UpperTextBox = Label(self.second_frame, bg = "white", relief = GROOVE, text = "Information", font = helv15, fg = fontcolor)
self.UpperTextBox.place(relheight = 0.15, relwidth = 0.8, relx = 0.1, rely = 0.1)
A scrollbar appears but is not interactive. Note the MWE has only one button, but the scrollbar does not work even when there are more labels that disappear off-screen. How can I make the scrollbar work?
You need to make sure that you update the scrollregion whenever you add or remove data to the canvas, or when the data on the canvas changes size. Your code only updates it when the canvas itself is resized.
Related
I want to put a background image in a Frame, this is the code that I'm trying to run without success.
import tkinter as tk
from tkinter import *
root = tk.Tk()
F1 = Frame(root)
F1.grid(row=0)
photo = PhotoImage(file="sfondo.png")
label = Label(F1, image=photo)
label.image = photo
label.place(x=0, y=0)
b = tk.Button(label, text="Start")
b.grid(row=8, column=8)
root.mainloop()
If I run the code as this, only a little point in the top left corner is displayed (the frame without nothing in, even if I placed the label inside of it). If I replace the label parent with root, it displays the button with a little part of the image as backgound (only the perimeter of the button is colored for a few pixels). However what I want is a full displayed background image in the frame where I can put the widgets that I want.
I tried to with the place method as this and PIL module
import tkinter as tk
from tkinter import *
from PIL import Image, ImageTk
root = tk.Tk()
F1 = Frame(root)
F1.grid(row=0)
image = Image.open("sfondo.png")
render = ImageTk.PhotoImage(image)
img = tk.Label(F1, image=render)
img.image = render
img.place(x=0, y=40)
b = tk.Button(img, text="Start")
b.grid(row=8, column=8)
root.mainloop()
Here more or less I'm having the same problems, if I set the parent of the label to root, the button is displayed with the perimeter coloured.
If I set the parent to F1 nothing happens and in both cases if I set the parent as root and remove the button, the image is fully displayed.
But what I want is that the image is fully displayed in the frame and after diplay on the background image the widgets.
You could put an image on a Canvas, and then place a Button on that by putting it inside a Canvas window object which can hold any Tkinter widget.
Additional widgets can be added in a similar fashion, each inside its own Canvas window object (since they can hold only a single widget each). You can workaround that limitation by placing a Frame widget in the Canvas window, and then putting other widgets inside it.
Here's an example showing how to display a single Button:
from PIL import Image, ImageTk
import tkinter as tk
IMAGE_PATH = 'sfondo.png'
WIDTH, HEIGTH = 200, 200
root = tk.Tk()
root.geometry('{}x{}'.format(WIDTH, HEIGHT))
canvas = tk.Canvas(root, width=WIDTH, height=HEIGTH)
canvas.pack()
img = ImageTk.PhotoImage(Image.open(IMAGE_PATH).resize((WIDTH, HEIGTH), Image.ANTIALIAS))
canvas.background = img # Keep a reference in case this code is put in a function.
bg = canvas.create_image(0, 0, anchor=tk.NW, image=img)
# Put a tkinter widget on the canvas.
button = tk.Button(root, text="Start")
button_window = canvas.create_window(10, 10, anchor=tk.NW, window=button)
root.mainloop()
Screenshot:
Edit
While I don't know of a way to do it in Frame instead of a Canvas, you could derive your own Frame subclass to make adding multiple widgets easier. Here's what I mean:
from PIL import Image, ImageTk
import tkinter as tk
class BkgrFrame(tk.Frame):
def __init__(self, parent, file_path, width, height):
super(BkgrFrame, self).__init__(parent, borderwidth=0, highlightthickness=0)
self.canvas = tk.Canvas(self, width=width, height=height)
self.canvas.pack()
pil_img = Image.open(file_path)
self.img = ImageTk.PhotoImage(pil_img.resize((width, height), Image.ANTIALIAS))
self.bg = self.canvas.create_image(0, 0, anchor=tk.NW, image=self.img)
def add(self, widget, x, y):
canvas_window = self.canvas.create_window(x, y, anchor=tk.NW, window=widget)
return widget
if __name__ == '__main__':
IMAGE_PATH = 'sfondo.png'
WIDTH, HEIGTH = 350, 200
root = tk.Tk()
root.geometry('{}x{}'.format(WIDTH, HEIGTH))
bkrgframe = BkgrFrame(root, IMAGE_PATH, WIDTH, HEIGTH)
bkrgframe.pack()
# Put some tkinter widgets in the BkgrFrame.
button1 = bkrgframe.add(tk.Button(root, text="Start"), 10, 10)
button2 = bkrgframe.add(tk.Button(root, text="Continue"), 50, 10)
root.mainloop()
Result:
Update
It recently dawned on me that there actually is a simpler way to do this than creating a custom Frame subclass as shown in my previous edit above. The trick is to place() a Label with image on it in the center of the parent Frame — you are then free to use other geometry managers like pack() and grid() as you normally would — as illustrated in the sample code below. Not only is it less complicated, it's also a more "natural" way of adding widgets than needing to call a non-standard method such as add().
from PIL import Image, ImageTk
import tkinter as tk
IMAGE_PATH = 'sfondo.png'
WIDTH, HEIGHT = 250, 150
root = tk.Tk()
root.geometry('{}x{}'.format(WIDTH, HEIGHT))
# Display image on a Label widget.
img = ImageTk.PhotoImage(Image.open(IMAGE_PATH).resize((WIDTH, HEIGHT), Image.ANTIALIAS))
lbl = tk.Label(root, image=img)
lbl.img = img # Keep a reference in case this code put is in a function.
lbl.place(relx=0.5, rely=0.5, anchor='center') # Place label in center of parent.
# Add other tkinter widgets.
button = tk.Button(root, text="Start")
button.grid(row=0, column=0)
button = tk.Button(root, text="Continue")
button.grid(row=0, column=1, padx=10)
root.mainloop()
Result#2
P.S. You can download a copy of the sfondo.png background image from here.
I´d like to make a scrollbar to scroll over a canvas filled with pics. The Problem i´m facing is, that instead of using the scrollbar the frame just gets bigger. No matter what I do the Frame always resizes with the the canvas filled with pictures. If I force the the maxsize to any value the Frame won't get bigger, but the Scrollbar then isn't scrollable either.
Here is the Code i wrote:
from Tkinter import *
from tkFileDialog import *
from PIL import Image, ImageTk
class Gui(Frame):
def __init__(self, master=None):
Frame.__init__(self,master)
self.pack(expand=False, fill=BOTH)
self.master.title("Picscroll")
self.master.minsize(400,400)
scroll = Picscroll(self)
scroll.pack(fill=Y, side=LEFT)
class Picscroll(Frame):
def __init__(self, master=None):
Frame.__init__(self,master)
canvas = Canvas(self)
canvas.config(scrollregion=canvas.bbox("all"))
yscrollbar = Scrollbar(self, width=16, orient=VERTICAL)
yscrollbar.pack(side=RIGHT, fill=Y)
yscrollbar.config(command=canvas.yview)
canvas.config(yscrollcommand=yscrollbar.set)
canvas.pack(side=LEFT, expand=True, fill = BOTH)
size = 64, 64 #rendering TestPic
File = r"Download.png"
img = Image.open(File)
img.thumbnail(size)
render = ImageTk.PhotoImage(img)
for i in range(20): #filling canvas with testpics
for j in range(2):
label1 = Label(canvas, image=render)
label1.image = render
label1.grid(column=j, row=i)
a = Gui()
a.mainloop()
Please excuse my English skills
I am trying to create an interface with tkinter. It should have a control panel on the left, and a set of 3 tabs on the right.
The tabs will show images, which may be too large for the screen, however the exact size will only be known after they have been dynamically created by the program. So I want to have a Notebook that expands to fill the top window, and if the image is too large for the notebook frame, scrollbars should appear to allow for all the image to be seen. I can't find a way to attach scrollbars to the notebook, so I have attached them to the canvases inside the notebook.
My code below expands the canvas to fit the image, but the scrollbar is useless as it stretches to fit the canvas, which is larger than the frame, and so can end up outside the window.
I think I want to set the scrollregion to the size of the containing frame. Dynamically changing scrollregion of a canvas in Tkinter seems to be related, but I can't work out how to apply that to my code:
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
class App(tk.Frame):
def __init__(self, parent, *args, **kwargs):
self.parent = parent
super().__init__(parent, *args, **kwargs)
self.pack(expand=True, fill=tk.BOTH)
self.inputframe = ttk.Frame(self)
self.inputframe.pack(side=tk.LEFT, expand=False, fill=tk.Y)
self.outputnotebook = ttk.Notebook(self)
self.outputnotebook.pack(side=tk.RIGHT, expand=True, fill=tk.BOTH)
self.build_inputframe()
self.build_outputnotebook()
def build_inputframe(self):
run_button = ttk.Button(self.inputframe, text="Run", command=self.draw)
run_button.grid(column=2, row=0, sticky=(tk.W, tk.E))
# rest of app...
def build_outputnotebook(self):
actnet_frame = ttk.Frame(self.outputnotebook)
critnet_frame = ttk.Frame(self.outputnotebook)
xscrollbar = ttk.Scrollbar(actnet_frame, orient=tk.HORIZONTAL)
xscrollbar.grid(row=1, column=0, sticky=tk.E + tk.W)
self.outputnotebook.add(
actnet_frame,
text="Tab 1",
sticky=tk.N + tk.S + tk.E + tk.W)
self.outputnotebook.add(critnet_frame, text="Tab 2")
self.actnet_canvas = tk.Canvas(actnet_frame, width=400, height=400,
xscrollcommand=xscrollbar.set)
self.actnet_canvas.grid(row=0, sticky=tk.N + tk.S + tk.E + tk.W)
xscrollbar.config(command=self.actnet_canvas.xview)
def draw(self):
act_image = Image.open('critnet.png') # image is 875 x 175
width, height = act_image.size
actphoto = ImageTk.PhotoImage(act_image)
self.actnet_canvas.delete("all")
self.actnet_canvas.create_image(0, 0, anchor=tk.NW, image=actphoto)
self.actnet_canvas.image = actphoto
self.actnet_canvas['scrollregion'] = (0, 0, width, height)
self.actnet_canvas['width'] = width
self.actnet_canvas['height'] = height
#do similar for other tabs.
root = tk.Tk()
root.geometry("800x600")
app = App(root)
app.mainloop()
The problem in your code is that you resize the canvas at the size of the picture, so the size of the canvas and of the scrollregion are the same. And it makes your canvas to big for your tab and your scrollbar useless.
I suppressed the lines
self.actnet_canvas['width'] = width
self.actnet_canvas['height'] = height
from the draw function and the scrollbar behaved as expected.
Edit: I had missed the other part of the question. To resize the canvas and scrollbar with the window, I added the following lines in build_outputnotebook:
actnet_frame.rowconfigure(0,weight=1)
actnet_frame.columnconfigure(0,weight=1)
I am working n a project that has a scroll able frame. It lets me add widgets to the frame but I can not get the frame to scroll and show the rest of the widgets. I have compared my code to other scroll able frames online and I could not notice the difference. Any one see the solution.
Code:
from Tkinter import *
import ttk
import os
class GUI(Frame):
def __init__(self, parent):
Frame.__init__(self,parent)
self.pack(fill=BOTH, expand=YES)
def gameView(self):
self.mainFrame = Frame(self)
self.mainFrame.pack(side=TOP)
self.scroller = ttk.Scrollbar(self.mainFrame, orient=VERTICAL)
self.scroller.pack(side=RIGHT, fill=Y)
self.canvas = Canvas(self.mainFrame, bd=0)
self.canvas.pack(fill=BOTH, side=LEFT)
self.viewArea = Frame(self.canvas, bg="Pink")
self.viewArea.pack(side=TOP, fill=BOTH)
self.canvas.config(yscrollcommand=self.scroller.set)
self.scroller.config(command=self.canvas.yview)
self.canvas.create_window((0,0), window=self.viewArea, anchor=NW, width=783, height=650)
self.viewArea.bind("<Configure>", self.scrollCom)
self.itemHolder = Frame(self.viewArea, bg="Pink")
self.itemHolder.pack(side=TOP)
self.gameGather()
def scrollCom(self, event):
self.canvas.config(scrollregion=self.canvas.bbox("all"), width=783, height=650)
def gameGather(self):
for i in range(0, 50):
label = Label(self.viewArea, text="Pie")
label.pack(side=TOP)
root = Tk()
root.title("School Vortex 2.0")
root.geometry("800x650")
root.resizable(0,0)
gui = GUI(root)
gui.gameView()
root.mainloop()
When you put the window on the canvas you are explicitly giving it a height and a width. Because of that, the actual width and height of the frame is completely ignored. Because the frame is almost exactly the height of the canvas, there's nothing to scroll.
If you remove the width and height options from the call to create_window your frame will be scrollable.
Creating a program that will place an image onto a canvas. I want to add an X-axis, and Y-axis scrollbar onto the canvas, but I am not sure how to.
I would normally use a Frame, but the program I'm working on is using tkinter.Toplevel instead of using a Frame.
#----- Imports ---------------------
import os
import os.path
import sys
import tkinter
tk = tkinter
from tkinter import font
#----- Set flag for JPEG support ---
noJPEG = False
try:
from PIL import Image
Pimg = Image
from PIL import ImageDraw
Pdraw = ImageDraw.Draw
from PIL import ImageTk
Pimgtk = ImageTk
except ImportError:
noJPEG = True
#
#------------------------------------
# Create an invisible global parent window to hold all children.
# Facilitates easy closing of all windows by a mouse click.
_root = tk.Tk()
_root.withdraw()
#
#------------------------------------
# A writeable window for holding an image.
#
class ImageView(tk.Canvas):
def __init__(self, image, title=''):
master = tk.Toplevel(_root)
master.protocol("WM_DELETE_WINDOW", self.close)
tk.Canvas.__init__(self, master,
width = 600, height = 500,
scrollregion=(0,0, image.getWidth(),
image.getHeight()))
# Define class fields
## Image properties
self.master.title(title)
self.pack()
master.resizable(0,0)
self.foreground = "black"
self.image = image
self.height = image.getHeight()
self.width = image.getWidth()
# for later
#self.customFont = font.Font(family="Helvetica", size=12)
## Actionable items
self.mouseX = None
self.mouseY = None
self.mouseFxn = None
self.bind("<Button-1>", self.onClick) #bind action to button1 click
self.tags = None
_root.update() #redraw global window
There's nothing special about using a Toplevel rather than a Frame. You attach a scrollbar the way you do it for any scrollable widget in any container.
I've never seen a widget create it's own parent. That's quite unusual, though it really doesn't change anything. Just add the scrollbars inside the toplevel.
You might want to switch to grid instead of pack since that makes it a bit easier to get the scrollbars to line up properly. You just need to remove the call to self.pack(), and add something like this to your code:
vsb = tk.Scrollbar(master, orient="vertical", command=self.yview)
hsb = tk.Scrollbar(master, orient="horizontal", command=self.xview)
self.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
self.grid(row=0, column=0, sticky="nsew")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
master.grid_rowconfigure(0, weight=1)
master.grid_columnconfigure(0, weight=1)