Python Tkinter xscrollbar not working - python

I'm almost at the point of tearing my hair out over this one:
I'm trying to get an xscrollbar and yscrollbar working so that I can move around a large canvas in Tkinter. The reason I'm so frustrated is because the yscrollbar seems to be working WITH THE EXACT SAME CODE (replacing x with y everywhere). Here's what I have:
master = Tk()
scrolly = Scrollbar(master,orient = VERTICAL)
scrollx = Scrollbar(master,orient = HORIZONTAL)
scrollx.pack(side = TOP,fill = X)
scrolly.pack(side = RIGHT,fill = Y)
w = Canvas(master, width=1000,height=1000,yscrollcommand = scrolly.set,xscrollcommand = scrollx.set,scrollregion=(0,0,1000,1000))
s = Scale(master,from_= 0, to=len(worldlist)-1,orient = HORIZONTAL,length = 595)
s.pack(side = BOTTOM)
w.pack()
setSys(worldlist[0],master,w)
def show(self):
w.delete(ALL)
setSys(worldlist[s.get()],master,w)
s.config(command = show)
scrolly.config(command = w.yview)
scrollx.config(command = w.xview)
mainloop()
I want the canvas and a scale widget sitting at the bottom. And at the right and top, a ybar and an xbar, respectively. Can anyone see what I'm doing wrong? I'm quite desperate for some help!
Thanks!
Gabe

The reason the xbar isn't working is because it's got nothing to scroll to. After you configure the canvas to a size bigger than the screen, it starts working. Take a look at the following code to see where I've added the scrollregion config. http://www.java2s.com/Code/Python/GUI-Tk/ScrolledCanvas.htm
from Tkinter import *
def show(self):
w.delete(ALL)
setSys(worldlist[s.get()],master,w)
master = Tk()
scrolly = Scrollbar(master,orient = VERTICAL)
scrollx = Scrollbar(master,orient = HORIZONTAL)
scrollx.pack(side = TOP,fill = X)
scrolly.pack(side = RIGHT,fill = Y)
w = Canvas(master, width=500, height=500, yscrollcommand = scrolly.set,
xscrollcommand = scrollx.set, scrollregion=(0,0,1000,1000))
w.config(scrollregion=(0, 0, 500, 5000))
w.pack()
s = Scale(master,from_= 0, to=100-1,orient = HORIZONTAL,length = 595)
s.pack(side = BOTTOM)
s.config(command = show)
scrolly.config(command = w.yview)
scrollx.config(command = w.xview)
mainloop()

Related

tkinter strange phenomenon with buttons and images

I don't know what to say because I am clueless on why this is not working.
The first button appears but the image does not the second and third button does not appear.
from tkinter import *
Master = Tk()
Master.geometry("1408x768")
Master.configure(background = "#000000")
# Top
Top = Frame(Master)
Top.configure(background = "#1C1C1C", width = 1024.0, height = 384.0)
Top.place(x = 0.0, y = -5.684341886080802e-14)
Nextimg = PhotoImage(file = "Next.png")
Next = Button(master = Top, background = "#0084FF", image = Nextimg)
Next.place(x = 624.0, y = 551.0, width = 100, height = 50)
# Bottom
Bottom = Frame(Master)
Bottom.configure(background = "#8C8C8C", width = 1024.0, height = 384.0)
Bottom.place(x = 0.0, y = 384.0)
Nextimg = PhotoImage(file = "Next.png")
Next = Button(master = Bottom, background = "#0084FF", image = Nextimg)
Next.place(x = 624.0, y = 551.0, width = 100, height = 50)
# Dashboard
Dashboard = Frame(Master)
Dashboard.configure(background = "#252525", width = 384.0, height = 768.0)
Dashboard.place(x = 1024.0, y = 0.0)
Continueimg = PhotoImage(file = "Continue.png")
Continue = Button(master = Dashboard, background = "#FF8900", image = Continueimg)
Continue.place(x = 1091.0, y = 359.0, width = 250, height = 50)
Your primary issue here appears that you are expecting your widget placement to be relative to the root window but in fact they are relative to the frames they are placed in.
Try this. Change all your buttons X/Y to 20 and see what I mean.
You will see all the buttons show up in the top left corner of each frame they are assigned to.
One issue is that you are using place to manage widgets and this is very bad for code maintenance. It is much better to use grid() and/or pack() to build your windows for several reasons.
Automatic weights to adjust placement based on window size and easy of maintainability as your code grows.
The issue you will see with your 1st and 2nd buttons is the 1st button does not show the image due to the reference being reassigned.
If you want to use the same image for both just save a reference once.
In this case you can remove the 2nd Nextimg = from your code.
See what happens when we change the placement to 20's
from tkinter import *
Master = Tk()
Master.geometry("1408x768")
Master.configure(background = "#000000")
Top = Frame(Master)
Top.configure(background = "#1C1C1C", width = 1024.0, height = 384.0)
Top.place(x = 0.0, y = -5.684341886080802e-14)
Nextimg = PhotoImage(file = "Next.png")
Next = Button(master = Top, background = "#0084FF", image = Nextimg)
Next.place(x = 20, y = 20, width = 100, height = 50)
Bottom = Frame(Master)
Bottom.configure(background = "#8C8C8C", width = 1024.0, height = 384.0)
Bottom.place(x = 0.0, y = 384.0)
# Nextimg = PhotoImage(file = "Next.png") Removed this as it is why your image does not show up on the first button.
Next = Button(master = Bottom, background = "#0084FF", image = Nextimg)
Next.place(x = 20, y = 20, width = 100, height = 50)
Dashboard = Frame(Master)
Dashboard.configure(background = "#252525", width = 384.0, height = 768.0)
Dashboard.place(x = 1024.0, y = 0.0)
Continueimg = PhotoImage(file = "Continue.png")
Continue = Button(master = Dashboard, background = "#FF8900", image = Continueimg)
Continue.place(x = 20, y = 20, width = 250, height = 50)
Master.mainloop()
Note that I replaced your images with grey squares since I did not have your image to use.
Results:
A more conventional way to write this would be to use grid() or pack().
Also I have rewritten the code to more closely fit PEP* standards.
See below example:
import tkinter as tk
master = tk.Tk()
master.geometry("1408x768")
master.configure(background="#000000")
top = tk.Frame(master)
top.grid_propagate(False)
top.configure(background="#1C1C1C", width=1024.0, height=384.0)
top.grid(row=0, column=0)
nextimg = tk.PhotoImage(file="gray.png")
tk.Button(master=top, background="#0084FF", image=nextimg).grid(row=0, column=0)
bottom = tk.Frame(master)
bottom.grid_propagate(False)
bottom.configure(background="#8C8C8C", width=1024.0, height=384.0)
bottom.grid(row=1, column=0)
tk.Button(master=bottom, background="#0084FF", image=nextimg).grid(row=0, column=0)
dashboard = tk.Frame(master)
dashboard.grid_propagate(False)
dashboard.configure(background = "#252525", width = 384.0, height = 768.0)
dashboard.grid(row=0, column=1, rowspan=2)
continueimg = tk.PhotoImage(file="gray.png")
tk.Button(master=dashboard, background="#FF8900", image=continueimg).grid(row=0, column=0)
master.mainloop()
Results:
You can also manage spacing of the widgets from the edges if you like. That can be done by providing a padx/pady parameter to the grid().

How to implement a scrollbar to grid in tkinter

I have a hard time implementing a scrollbar into my Tkinter project. I've been through numerous articles and answered questions on how to implement a scrollbar, but I'm just unable to implement a working solution after an entire day of researching this one 'simple' matter.
My current code looks like this:
import tkinter as tk
from tkinter import Button, ttk
from PIL import ImageTk, Image
from functools import partial
import queue as qu
import math
import re
import os
window = tk.Tk()
queue = qu.Queue()
#Basic values
#the window size
windowSize = "700x1000"
#picture and container size
x, y = 200, 300
#tmp
sidepanelsize = 200
window.geometry(windowSize)
#button identifier
def change(i):
print(I)
#temporary content generator
for g in range(12):
for item in os.listdir("."):
if re.search(r"\.(jpg|png)$", item):
queue.put(item)
n = queue.qsize()
#other panels that are going to be used later
frameLeft = tk.Frame(master=window, width=sidepanelsize, relief=tk.RIDGE)
frameLeft.pack(fill=tk.Y, side=tk.LEFT)
label1 = tk.Label(master=frameLeft, text="Left Panel")
label1.pack()
buttonLeft1 = tk.Button(master=frameLeft, text="Button 1", command=lambda: print("I'm a side button!"))
buttonLeft1.pack()
frameMain = tk.Frame(master=window, relief=tk.GROOVE, borderwidth=1)
frameMain.pack(side=tk.TOP, fill=tk.X, expand=1)
# SCROLLBAR IF YOU DISABLE THIS SECTION AND PUTS SOME PICTURES IN THE FOLDER WHITH THE FILE THE CODE WORKS #
myCanvas = tk.Canvas(master=frameMain)
myCanvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
myScrollbar = ttk.Scrollbar(master=frameMain, orient=tk.VERTICAL, command=myCanvas.yview)
myScrollbar.pack(side=tk.RIGHT, fill=tk.Y)
myCanvas.configure(yscrollcommand=myScrollbar.set)
myCanvas.bind('<Configure>', lambda e: myCanvas.configure(scrollregion=myCanvas.bbox("all")))
secondFrame = tk.Frame(master=myCanvas)
myCanvas.create_window((0, 0), window=secondFrame, anchor=tk.NW)
############################ END OF SCROLLBAR ############################
noOfImgPerRow = math.floor((int(windowSize.split("x")[0])-sidepanelsize+100)/x)
imgs = []
#generates the grid
for i in range(n):
o = i
i = (o % noOfImgPerRow) + 1
j = math.floor(o/noOfImgPerRow) + 1
frameMain.columnconfigure(i, weight = 1, minsize=x+15)
frameMain.rowconfigure(i, weight = 1, minsize=y+50)
frameBox = tk.Frame(
master=frameMain,
relief=tk.RAISED,
borderwidth=1,
width = x,
height = y
)
# here the error references to
frameBox.grid(row=j, column=i, padx=5, pady=5)
img = Image.open(queue.get()).convert("RGBA")
width, height = img.size
if width/x >= height/y:
left = width/2-(round((height*x)/y))/2
right = width/2+(round((height*x)/y))/2
upper = 0
lower = height
else:
left = 0
right = width
upper = height/2-(round((width*y)/x))/2
lower = height/2+(round((width*y)/x))/2
img2 = img.crop([left, upper, right, lower])
img2 = img2.resize((x, y), Image.Resampling.LANCZOS)
imgs.append(ImageTk.PhotoImage(img2))
label = tk.Label(master = frameBox, image = imgs[-1])
label.pack()
mainButton = Button(master=frameBox, text="Start", command=partial(change, o))
mainButton.pack()
window.mainloop()
I've tried to highlight the only thing of concern, that being the scrollbar, everything else is working at the moment, I just wanted to post the whole code for better understanding if it would help in any way.
My problem is whenever I implement the scrollbar, it throws back an error stating:
Traceback (most recent call last):
File "e:\Python\starter\main.py", line 85, in <module>
frameBox.grid(row=j, column=i, padx=5, pady=5)
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.1264.0_x64__qbz5n2kfra8p0\lib\tkinter\__init__.py", line 2522, in grid_configure
self.tk.call(
_tkinter.TclError: cannot use geometry manager grid inside .!frame2 which already has slaves managed by pack
This error seems pretty self-explanatory, just grid the canvas instead of packing it, but when after a lot of small tweaking and doing things a roundabouts things
My second thought was if it has a problem with the grid to wrap the gridded frame in another bigger packed frame, like so:
yetAnotherFrame = tk.Frame(frameMain)
yetAnotherFrame.pack()
noOfImgPerRow = math.floor((int(windowSize.split("x")[0])-sidepanelsize+100)/x)
imgs = []
for i in range(n):
o = i
i = (o % noOfImgPerRow) + 1
j = math.floor(o/noOfImgPerRow) + 1
yetAnotherFrame.columnconfigure(i, weight = 1, minsize=x+15)
yetAnotherFrame.rowconfigure(i, weight = 1, minsize=y+50)
frameBox = tk.Frame(
master=yetAnotherFrame,
relief=tk.RAISED,
borderwidth=1,
width = x,
height = y
)
frameBox.grid(row=j, column=i, padx=5, pady=5)
This actually runs to my surprise, but the scrollbar still isn't working and the layout is broken again.
Solution
In your code frameBox's parent is frameMain. Instead you need to have the canvas as parent or the secondFrame which have the canvas as its parent.
Example
This is basically your code with fixes, but some of the unnecessary parts are removed.
import tkinter as tk
from tkinter import ttk
window = tk.Tk()
window.geometry("400x400")
frameLeft = tk.Frame(master=window, width=400, height=400, relief=tk.RIDGE)
frameLeft.pack(fill=tk.Y, side=tk.LEFT)
myCanvas = tk.Canvas(master=frameLeft)
myCanvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
myScrollbar = ttk.Scrollbar(master=frameLeft, orient=tk.VERTICAL, command=myCanvas.yview)
myScrollbar.pack(side=tk.RIGHT, fill=tk.Y)
myCanvas.configure(yscrollcommand=myScrollbar.set)
myCanvas.bind('<Configure>', lambda e: myCanvas.configure(scrollregion=myCanvas.bbox("all")))
secondFrame = tk.Frame(master=myCanvas)
myCanvas.create_window((0, 0), window=secondFrame, anchor=tk.NW)
for i in range(100):
lbl = tk.Label(secondFrame, text=f"Label {i}")
lbl.grid(column=0, row=i, sticky=tk.W)
window.mainloop()

create a scrollbar on entry with cavnas, python tkinter

I'm trying to put a scrollbar on entry that created on canvas..
I've tried this code
from tkinter import *
root = Tk()
root.geometry('400x600')
root.resizable(0,0)
page = Canvas(root, width=400, height=600, bd=0, highlightthickness=0,scrollregion=(0,0,500,500))
MyImage1 = PhotoImage(file='Study With4.png')
CanvasImage = page.create_image(0,0,image= MyImage1, anchor='nw')
entry =Text(page,height=29,width =46,wrap=WORD,bg='#F8F8F8')
scroll = Scrollbar(entry, orient=VERTICAL)
scroll.pack(side=RIGHT, fill=Y)
scroll.config(command=page.yview)
page.config(yscrollcommand=scroll.set)
page.create_window(200,285, window=entry)
page.pack()
mainloop()
but it doesn't work and I don't know where is the problem.
I've made minimal changes to your code, the biggest is creating a Frame called "label"
to contain Text and Scrollbar then inserting that into Canvas window.
Your scroll object was not defined properly with confusion around page and entry objects.
from tkinter import *
root = Tk()
root.geometry('500x600')
root.resizable(0,0)
page = Canvas(
root, width = 500, height = 600, bd = 0,
highlightthickness = 0,scrollregion = (0,0,1000,1000))
page.pack(side = LEFT, fill = BOTH, expand = True)
MyImage1 = PhotoImage(file='Study With4.png')
CanvasImage = page.create_image(0, 0, image = MyImage1, anchor = NW)
label = Frame(root)
label.pack(fill = BOTH, expand = True)
entry = Text(label, height = 29, width = 46, wrap = NONE, bg = '#F8F8F8')
entry.pack(side = LEFT, fill = BOTH, expand = True)
scroll = Scrollbar(label, orient = VERTICAL)
scroll.pack(side=RIGHT, fill=Y)
scroll.config(command = entry.yview)
entry.config(yscrollcommand = scroll.set)
page.pack()
page.create_window(25, 25, window = label, anchor = NW)
mainloop()

A _tkinter.TclError occured while trying to make a scrollable frame in python

So I have been trying to make a scrollable frame. I've had been searching and these 3 had the most impact:
https://stackoverflow.com/questions/16188420/tkinter-scrollbar-for-frame\
https://stackoverflow.com/questions/3085696/adding-a-scrollbar-to-a-group-of-widgets-in-tkinter\
How could I get a Frame with a scrollbar in Tkinter?
And I have come up with the following code:
from tkinter import *
class Test_tk_stuff():
def __init__(self):
self.screen = Tk()
self.screen.geometry("500x500")
self.structure()
self.screen.mainloop()
def scroller(self, canvas):
canvas.configure(scrollregion = canvas.bbox("all"))
def frame_expander(self, event):
canvas_width = event.width
self.canvas.itemconfig(self.frame, width = canvas_width)
def structure(self):
parent = Frame(self.screen)
parent.pack(expand = True, fill = BOTH)
self.canvas = Canvas(parent, bg = "green", highlightthickness = 0, relief = RAISED)
self.frame = Frame(self.canvas, bg = "blue")
myscrollbar = Scrollbar(parent, orient = "vertical", command = self.canvas.yview)
self.canvas.configure(yscrollcommand = myscrollbar.set)
myscrollbar.pack(side = RIGHT, fill = Y)
self.canvas.pack(side = LEFT, fill = BOTH, expand = True)
self.canvas.create_window((0, 0), window = self.frame)
# can't do: self.frame.pack(expand = True, fill = BOTH) because it will become unscrollable
# Event Bind
self.frame.bind("<Configure>", lambda event, canvas = self.canvas: self.scroller(self.canvas))
self.canvas.bind("<Configure>", self.frame_expander)
# initialize number of minimum columns
for num_columns in range(3):
self.frame.columnconfigure(num_columns, weight = 1)
a = "Button Text!"
# fill it to - 1.) test scrollbar 2.) actually using the frame inside
for place2 in range(10):
Button(self.frame, text = a, bg = "black", fg = "white").grid(row = place2, column = 1, sticky = "NSEW", ipady = 15)
if __name__ == "__main__":
Test_tk_stuff()
But somehow when I run it, it shows a _tkinter.TclError. I tried searching what that is and how to fix it, but, as you can see, I wasn't able to fix it.
Is there something wrong with my implementation?
Thanks in advance.
canvas.itemconfig() is used on canvas item returned by canvas.create_xxxxx() functions, not on tkinter widget (self.frame).
Save the canvas item ID for the self.frame and use it in canvas.itemconfig():
def frame_expander(self, event):
canvas_width = event.width
self.canvas.itemconfig(self.frame_item, width=canvas_width)
def structure(self):
...
self.frame_item = self.canvas.create_window((0, 0), window = self.frame)
...
use self.frame.config(width=canvas_width) instead of canvas.itemconfig()

Python RawTurtle not following goto() command

I am trying to create a program where you click on a Tkinter canvas and a RawTurtle moves to the mouse, but my code is not working. The canvas has a Button-1 event binded to it to tell the program the coordinates of the mouse.
But, when you click on the canvas, instead of the turtle going to the mouse, it kind of mirrors what you would expect it to do (moves away from the mosue as if another mosue is being mirrored). Both the event and the turtle position coordinates are the same when they are printed out.
Code:
import turtle
from tkinter import *
def move(event):
global t
x = event.x
y = event.y
t.setpos(x,y)
print(t.pos())
print(event)
def penState(event):
global penDown,t
if penDown == True:
t.penup()
penDown = False
else:
t.pendown()
penDown = True
def changeWidth(w):
t.pensize(w)
def changeColour(e=None):
global colourBox
t.color(colourBox.get())
colourBox.configure(fg=colourBox.get())
def doCircle():
global checkFillIsTrue,circleSizeBox
if checkFillIsTrue.get() == 1:
begin_fill()
t.circle(int(circleSizeBox.get()))
end_fill()
else:
circle(int(circleSizeBox.get()))
window = Tk('Paint')
window.title('onionPaint')
root = Frame(window)
root.pack(side=LEFT)
cv = Canvas(window,width=500,height=500)
t = turtle.RawTurtle(cv)
t.resizemode('user')
cv.bind('<Button-1>',move)
cv.bind('<Button-2>',penState)
cv.pack(side=RIGHT)
checkFillIsTrue=IntVar()
penDown = True
#Pen width box
sizeLabel = Label(root, text="Pen Width")
sizeLabel.grid()
sizeScale = Scale( root, variable = \
'var',orient=HORIZONTAL,command=changeWidth )
sizeScale.grid()
#Colour box
colourLabel = Label(root, text="Color(HEX or name):")
colourLabel.grid()
colourFrame = Frame(root)
colourFrame.grid()
colourBox = Entry(colourFrame, bd=1)
colourBox.pack(side=LEFT)
colourSubmit = Button(colourFrame,text="OK",command=changeColour)
colourSubmit.pack(side=RIGHT)
#Fill
fillLabel = Label(root,text='Fill')
fillLabel.grid()
fillFrame = Frame(root)
fillFrame.grid()
beginFill = Button(fillFrame,text='Begin Fill',command=t.begin_fill)
endFill = Button(fillFrame,text='End Fill',command=t.end_fill)
beginFill.pack(side=LEFT)
endFill.pack(side=RIGHT)
#Mmore shapes
Label(root,text='Shapes').grid()
#Circle form
Label(root,text='Circle',font=('Heveltica',8)).grid()
circleSize = Frame(root)
circleSize.grid()
circleSizeBox = Entry(circleSize,bd=1)
circleSizeBox.insert(0,'Radius')
circleSizeBox.pack(side=LEFT)
fillCheck =
Checkbutton(circleSize,text='Fill',variable=checkFillIsTrue).pack(side=LEFT)
circleSizeSubmit =
Button(circleSize,text='Draw',command=doCircle).pack(side=RIGHT)
#Text form
Label(root,text='Text',font=('Heveltica',8)).grid()
textFrame = Frame(root)
textFrame.grid()
window.mainloop()
Any help with this problem would be greatly appreciated.
Your code is a disaster. You should specifically read up on the 'global' keyword in Python and when it must be used. And Python code style in general. I believe the key fix to your program is to introduce a TurtleScreen overlay on the canvas and then switch to turtle event handing rather than tkinter event handing.
I've reworked your code below, fixing as many problems as I could:
import turtle
from tkinter import *
FONT = ('Helvetica', 8)
def move(x, y):
terrapin.setpos(x, y)
print(terrapin.pos())
def penState(x, y):
global penDown
if penDown:
terrapin.penup()
penDown = False
else:
terrapin.pendown()
penDown = True
def changeWidth(w):
terrapin.pensize(w)
def changeColour():
color = colourBox.get()
terrapin.color(color)
colourBox.configure(fg=color)
def doCircle():
radius = int(circleSizeBox.get())
if checkFillIsTrue.get():
terrapin.begin_fill()
terrapin.circle(radius)
terrapin.end_fill()
else:
terrapin.circle(radius)
window = Tk('Paint')
window.title('onionPaint')
root = Frame(window)
root.pack(side=LEFT)
canvas = Canvas(window, width=500, height=500)
screen = turtle.TurtleScreen(canvas)
terrapin = turtle.RawTurtle(screen)
screen.onclick(move, btn=1)
screen.onclick(penState, btn=2)
canvas.pack(side=RIGHT)
checkFillIsTrue = BooleanVar()
penDown = True
# Pen width box
sizeLabel = Label(root, text="Pen Width")
sizeLabel.grid()
sizeScale = Scale(root, variable='var', orient=HORIZONTAL, command=changeWidth)
sizeScale.grid()
# Colour box
colourLabel = Label(root, text="Color(HEX or name):")
colourLabel.grid()
colourFrame = Frame(root)
colourFrame.grid()
colourBox = Entry(colourFrame, bd=1)
colourBox.pack(side=LEFT)
colourSubmit = Button(colourFrame, text="OK", command=changeColour)
colourSubmit.pack(side=RIGHT)
# Fill
fillLabel = Label(root, text='Fill')
fillLabel.grid()
fillFrame = Frame(root)
fillFrame.grid()
beginFill = Button(fillFrame, text='Begin Fill', command=terrapin.begin_fill)
endFill = Button(fillFrame, text='End Fill', command=terrapin.end_fill)
beginFill.pack(side=LEFT)
endFill.pack(side=RIGHT)
# More shapes
Label(root, text='Shapes').grid()
# Circle form
Label(root, text='Circle', font=FONT).grid()
circleSize = Frame(root)
circleSize.grid()
circleSizeBox = Entry(circleSize, bd=1)
circleSizeBox.insert(0, 'Radius')
circleSizeBox.pack(side=LEFT)
fillCheck = Checkbutton(circleSize, text='Fill', variable=checkFillIsTrue).pack(side=LEFT)
circleSizeSubmit = Button(circleSize, text='Draw', command=doCircle).pack(side=RIGHT)
# Text form
Label(root, text='Text', font=FONT).grid()
textFrame = Frame(root)
textFrame.grid()
window.mainloop()

Categories