Scrollable Frame Python Tkinter - python

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.

Related

create tkinter button with OOP on a same window: issue

I am a beginner in Python. I created a GUI with nice buttons. To do this I did a change of images: when the mouse hovers the button and when the mouse leaves the button. I did this with this pretty ugly code, but it works:
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
root.title("My first Python GUI")
root.geometry("1130x800")
canvas = Canvas(root, bg="#a9dfbf")
canvas.pack(fill=BOTH, expand=True)
button_1_onHover = Image.open("Buttons/button1_hover.png")
button_1_onLeave = Image.open("Buttons/button1_leave.png")
button_2_onHover = Image.open("Buttons/button2_hover.png")
button_2_onLeave = Image.open("Buttons/button2_leave.png")
root.button_1_onLeave = ImageTk.PhotoImage(button_1_onLeave)
root.button_1_onHover = ImageTk.PhotoImage(button_1_onHover)
root.button_2_onLeave = ImageTk.PhotoImage(button_2_onLeave)
root.button_2_onHover = ImageTk.PhotoImage(button_2_onHover)
def on_enter(event):
button1.config(image=root.button_1_onHover)
def on_leave(leave):
button1.config(image=root.button_1_onLeave)
def on_enter2(event):
button2.config(image=root.button_2_onHover)
def on_leave2(leave):
button2.config(image=root.button_2_onLeave)
button1 = Button(root, image=root.button_1_onLeave, bg="#a9dfbf", width=400, height=150, bd=0, relief="sunken", activebackground="#a9dfbf")
button2 = Button(root, image=root.button_2_onLeave, bg="#a9dfbf", width=400, height=150, bd=0, relief="sunken", activebackground="#a9dfbf")
canvas.create_window(300, 150, window=button1)
canvas.create_window(300, 350, window=button2)
button1.bind("<Enter>", on_enter)
button1.bind("<Leave>", on_leave)
button2.bind("<Enter>", on_enter2)
button2.bind("<Leave>", on_leave2)
root.mainloop()
This is the visual result:
visual result of the ugly code (it work)
BUT...
The problem is that to make a single button, it takes 15 lines of code.
If I want to create 10 buttons, it becomes incredibly repetitive and unpleasant.
Being a beginner, I heard about object-oriented programming, and so I turned my code into a class that I called NewButton:
from tkinter import *
from PIL import Image, ImageTk
class NewButton:
def __init__(self, imageHover, imageLeave, width, height, hposition, vposition):
self.root = Tk()
self.root.title("My first Python GUI")
self.root.geometry("1130x800")
canvas = Canvas(self.root, bg="#a9dfbf")
canvas.pack(fill=BOTH, expand=True)
self.width = width
self.height = height
self.hposition = hposition
self.vposition = vposition
self.imageHover = Image.open(f"Buttons/{imageHover}.png")
self.imageLeave = Image.open(f"Buttons/{imageLeave}.png")
self.root.imageLeave = ImageTk.PhotoImage(self.imageLeave)
self.root.imageHover = ImageTk.PhotoImage(self.imageHover)
self.button = Button(self.root, image=self.root.imageLeave, bg="#a9dfbf", width=self.width, height=self.height, bd=0, relief="sunken", activebackground="#a9dfbf")
canvas.create_window(self.hposition, self.vposition, window=self.button)
def on_enter(event):
self.button.config(image=self.root.imageHover)
def on_leave(leave):
self.button.config(image=self.root.imageLeave)
self.button.bind("<Enter>", on_enter)
self.button.bind("<Leave>", on_leave)
self.root.mainloop()
NewButton("button1_hover","button1_leave",400,150,300,150)
NewButton("button2_hover","button2_leave",400,150,300,350)
In the constructor of my class, I define the image used on hover, the image used on leave, the width of the button, its height, as well as the position of this button (horizontal position and vertical position).
Still in the constructor, I placed my 2 functions which change the image according to the enter/leave state.
Then I create my buttons as NewButton objects and give the characteristics of the button.
When I run my code, python creates the buttons for me, but in a different window.
This is the visual result:
result with POO code (not working)
What I want is to put all the buttons that I create on the same window, and that's not what I get with my code.
Can you tell me what's wrong?
Thank you (and sorry for my frenglish!)
SOLUTION by acw1668 that i try (it doesn't work):
His suggestion: "You need to create root and canvas outside the class and pass canvas to the class instance instead."
what I have done:
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
root.title("My first Python GUI")
root.geometry("1130x800")
canvas = Canvas(root, bg="#a9dfbf")
canvas.pack(fill=BOTH, expand=True)
class NewButton:
def __init__(self, canvas, imageHover, imageLeave, width, height, hposition, vposition):
self.canvas = canvas
self.width = width
self.height = height
self.hposition = hposition
self.vposition = vposition
self.imageHover = Image.open(f"Buttons/{imageHover}.png")
self.imageLeave = Image.open(f"Buttons/{imageLeave}.png")
self.canvas.imageLeave = ImageTk.PhotoImage(self.imageLeave)
self.canvas.imageHover = ImageTk.PhotoImage(self.imageHover)
self.button = Button(self.canvas, image=self.canvas.imageLeave, bg="#a9dfbf", width=self.width, height=self.height, bd=0, relief="sunken", activebackground="#a9dfbf")
canvas.create_window(self.hposition, self.vposition, window=self.button)
def on_enter(event):
self.button.config(image=self.canvas.imageHover)
def on_leave(leave):
self.button.config(image=self.canvas.imageLeave)
self.button.bind("<Enter>", on_enter)
self.button.bind("<Leave>", on_leave)
self.canvas.mainloop()
NewButton(canvas,"button1_hover","button1_leave",400,150,300,150)
NewButton(canvas,"button2_hover","button2_leave",400,150,300,350)
You need to create root and canvas outside the class and then pass canvas to the class:
from tkinter import *
from PIL import Image, ImageTk
class NewButton:
def __init__(self, canvas, imageHover, imageLeave, width, height, hposition, vposition):
self.canvas = canvas
self.width = width
self.height = height
self.hposition = hposition
self.vposition = vposition
imageHover = Image.open(f"Buttons/{imageHover}.png")
imageLeave = Image.open(f"Buttons/{imageLeave}.png")
self.imageLeave = ImageTk.PhotoImage(imageLeave)
self.imageHover = ImageTk.PhotoImage(imageHover)
self.button = Button(canvas, image=self.imageLeave, bg="#a9dfbf", width=self.width, height=self.height, bd=0, relief="sunken", activebackground="#a9dfbf")
self.button.bind("<Enter>", self.on_enter)
self.button.bind("<Leave>", self.on_leave)
self.item_id = canvas.create_window(self.hposition, self.vposition, window=self.button)
def on_enter(self, event):
self.button.config(image=self.imageHover)
def on_leave(self, event):
self.button.config(image=self.imageLeave)
root = Tk()
root.title("My first Python GUI")
root.geometry("1130x800")
canvas = Canvas(root, bg="#a9dfbf")
canvas.pack(fill=BOTH, expand=True)
NewButton(canvas,"button1_hover","button1_leave",400,150,300,150)
NewButton(canvas,"button2_hover","button2_leave",400,150,300,350)
root.mainloop()

Python Tkinter Scrollbar just resizes the frame it is in instead of making the canvas scrollable

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

Scrollbar disappears from Tkinter canvas, and refuses to adhere to side-packing

I am trying to attach a scrollbar to a Tkinter canvas.
To test the scrollbar, I dynamically generated 100 "Hello World" labels, and packed these inside a content-holding frame.
My code fails. Issues:
The scrollbar does not even appear.
The content frame instead expands to the full height of the 100-packed labels.
Interestingly, the scrollbar magically reappears if I shift self.frame_for_content.pack() under self.scrollbar.pack(). (Why this is so is beyond me, but the scrollbar still does not work or adhere to the side packing behaviour either.)
I have attempted to incorporate some of Brian Oakley's suggestions on scroll region and bbox, to no avail.
I have reduced the code to the bare minimum, but am unable to push through. Would appreciate the help.
import tkinter as tk
import tkinter.ttk as ttk
class TestGUI(tk.Tk):
def __init__(self):
super().__init__()
self.canvas = tk.Canvas(self)
self.frame_for_content = tk.Frame(self.canvas)
self.canvas_frame = self.canvas.create_window((0,0), window=self.frame_for_content, anchor=tk.NW)
self.scrollbar = tk.Scrollbar(self.canvas, orient=tk.VERTICAL, command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.frame_for_content.pack()
self.canvas.pack(side=tk.LEFT)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
for i in range(100):
tk.Label(self.frame_for_content, text="Hello World - " + str(i)).pack()
self.update()
self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL))
TestGUI().mainloop()
You don't want self.frame_for_content.pack() because you're using the canvas like a geometry manager for that widget, so self.canvas.create_window takes the place of .pack or .grid.
To get the sizes right, you can get the width & height from the canvas bounding box.
I think this does what you want:
import tkinter as tk
class TestGUI(tk.Tk):
def __init__(self):
super().__init__()
self.canvas = tk.Canvas(self)
self.frame_for_content = tk.Frame(self.canvas)
self.canvas_frame = self.canvas.create_window((0,0), window=self.frame_for_content, anchor=tk.NW)
self.scrollbar = tk.Scrollbar(self, orient=tk.VERTICAL, command=self.canvas.yview)
self.canvas.pack(side=tk.LEFT)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
for i in range(100):
tk.Label(self.frame_for_content, text="Hello World - " + str(i)).pack()
self.update()
bbox = self.canvas.bbox(tk.ALL)
self.canvas.config(yscrollcommand=self.scrollbar.set,
width=bbox[2], height=bbox[3], scrollregion=bbox)
TestGUI().mainloop()

how do i put dynamically added entry fields in a set frame or canvas that does not resize

i have dynamically addable and delete able entry fields that i want to set inside a frame or canvas inside of a main frame but when i try the frame dissappears or dynamically grows with the entry fields. i want the canvas to use the scrollbar if entry fields exceed the window size.
from tkinter import *
import tkinter as tk
class Demo2:
def __init__(self, master):
global rows
self.master = master
self.frame = tk.Frame(self.master)
master.title("test")
self.frame.pack()
addboxButton = Button(self.frame, text='<Add Time Input>', fg="Red", command=self.addBox)
addboxButton.pack()
this is where my buttons are added and deleted.
def addBox(self):
def delete():
delboxButton.grid_remove()
ent1.delete(0,END)
ent2.delete(0,END)
ent1.grid_remove()
ent2.grid_remove()
root = self.frame
frame=Frame(root,width=900,height=900)
frame.pack()
canvas=Canvas(frame,bg='#FFFFFF',width=700,height=300,scrollregion=(0,0,700,300))
vbar=Scrollbar(frame,orient=VERTICAL)
vbar.pack(side=RIGHT,fill=Y)
vbar.config(command=canvas.yview)
canvas.config(width=700,height=300)
canvas.config(yscrollcommand=vbar.set)
canvas.pack(side=LEFT,expand=TRUE,fill=BOTH)
I am trying to figure out now how to make the first set of entry start out on the screen when its opened. and bind the add call to an action.
i = 0
ent1 = Entry(canvas)
ent1.grid(row=i, column=0,sticky="nsew")
i += 1
i = 0
ent2 = Entry(canvas)
ent2.grid(row=i, column=1,sticky="nsew")
i += 1
delboxButton = Button(canvas, text='delete', fg="Red", command=delete)
delboxButton.grid(row=0 ,column=2)
root = tk.Tk()
root.title("test Complete")
root.geometry("500x500")
app = Demo2(root)
root.mainloop()
The normal way this is tackled is to create a single frame and add it to the canvas with the canvas create_window method. Then, you can put whatever you want in the frame using pack, place or grid.
For a description of the technique see Adding a scrollbar to a group of widgets in Tkinter
Here's an example illustrating how the technique works for widgets created by a button. I didn't include the delete functionality or the ability for everything to resize properly to keep the example short, but you seem to have a pretty good idea of how to make the delete function work and I don't know exactly what sort of resize behavior you want.
import tkinter as tk
class Demo2:
def __init__(self, master):
self.master = master
self.entries = []
self.canvas = tk.Canvas(master, width=400, height=200)
self.vsb = tk.Scrollbar(master, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.vsb.set)
self.add_button = tk.Button(master, text="Add", command=self.add)
self.container = tk.Frame()
self.canvas.create_window(0, 0, anchor="nw", window=self.container)
self.add_button.pack(side="top")
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
# start with 3 entry widgets
self.add()
self.add()
self.add()
def add(self):
entry = tk.Entry(self.container)
entry.pack(side="top", fill="x")
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
self.entries.append(entry)
root = tk.Tk()
demo = Demo2(root)
root.mainloop()

A tkinter notebook with scrollbars if the contents are too large

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)

Categories

Resources