Adjust scrollbar height in Tkinter? - python

I have created a scrollbar in Tkinter and it's working fine, but the size of the Scroll Button is not being scaled correctly (normally it's adjusted to the size of the scrollable area).
I'm placing all my widgets with a .pack(), therefore the .grid sticky configuration is something I would like to avoid.
My question is: Which part of the scrollbar configuration is responsible for scaling the height?
The code example:
master = Tk()
FrameBIG = Frame(master)
Main = Canvas(FrameBIG,height = 1200,width =1500,scrollregion=Main.bbox("all"))
scroll = Scrollbar(FrameBIG ,orient="vertical", command=Main.yview)
scroll.pack(side="right", fill="y")
Main.pack(side = BOTTOM, anchor = NW,fill="x")
FrameBIG.pack(anchor = W, fill = "x")

The code
Main = Canvas(FrameBIG,height=1200,width=1500,scrollregion=Main.bbox("all"))
is wrong because Main does not exists yet. It should be
Main = Canvas(FrameBIG,background="blue", height = 500,width =500)
Main.configure(scrollregion=Main.bbox("all"))
But it is meaningless because Main canvas was created right now and is empty (so the bbox method returns None)
When you created the scrollbar with
scroll = Scrollbar(FrameBIG ,orient="vertical", command=Main.yview)
you forgot to complete the two step contract between scroll and Main, so you have to add the line below (just after the creation of scroll)
Main.configure(yscrollcommand=scroll.set)
Now the code is like this
from tkinter import *
master = Tk()
FrameBIG = Frame(master)
Main = Canvas(FrameBIG,background="blue", height = 500,width =500)
Main.configure(scrollregion=Main.bbox("all"))
scroll = Scrollbar(FrameBIG ,orient="vertical", command=Main.yview)
Main.configure(yscrollcommand=scroll.set)
scroll.pack(side="right", fill="y")
Main.pack(side = BOTTOM, anchor = NW,fill="x")
FrameBIG.pack(anchor = W, fill = "x")
master.mainloop()
Now you can notice that the scroll bar does not have the button. Its because the Main canvas is empty. Let's add something to the Main canvas
FrameBIG.pack(anchor = W, fill = "x")
# creates a diagonal from coordinates (0,0) to (500,1000)
Main.create_line(0, 0, 500, 1000)
master.mainloop()
Now the line is there but the scroll button is not there yet, why?
Because you have to update the scrollregion of the Main canvas.
So let's do it with
FrameBIG.pack(anchor = W, fill = "x")
Main.create_line(0, 0, 500, 1000)
Main.configure(scrollregion=Main.bbox("all"))
master.mainloop()
Now it is working properly.
Here the complete code.
from tkinter import *
master = Tk()
FrameBIG = Frame(master)
Main = Canvas(FrameBIG,background="blue", height = 500,width =500)
Main.configure(scrollregion=Main.bbox("all"))
scroll = Scrollbar(FrameBIG ,orient="vertical", command=Main.yview)
Main.configure(yscrollcommand=scroll.set)
scroll.pack(side="right", fill="y")
Main.pack(side = BOTTOM, anchor = NW,fill="x")
FrameBIG.pack(anchor = W, fill = "x")
Main.create_line(0, 0, 500, 1000)
Main.configure(scrollregion=Main.bbox("all"))
master.mainloop()
In the next question, post a question with a complete working code that shows up you problem. You will get faster and better answers, ok?
Have a nice day.

Related

Frame of fixed size staying at the bottom of the window

I am using Tk GUI and I want my window to be split into 2 frames, something like that:
import tkinter as tk
root = tk.Tk()
root.geometry('1000x800')
df_frame = tk.LabelFrame(root)
df_frame.place(relwidth = 1, height = 720)
open_file_frame = tk.LabelFrame(root)
open_file_frame.place(x = 0, y = 720, relwidth = 1, height = 80)
root.mainloop()
My problem is that I don't know how to make it adaptable to the size of the window. If the user enlarges the size of the window, I want the second frame to stay at the bottom, and the first frame to enlarge accordingly.
Thanks in advance for your help.
You can combine relheight and height options to control the height of the top frame.
And combine rely and y options to put the bottom frame at the bottom of the window:
import tkinter as tk
root = tk.Tk()
root.geometry('1000x800')
df_frame = tk.LabelFrame(root)
# frame_height = window_height - 80
df_frame.place(relwidth=1, relheight=1, height=-80)
open_file_frame = tk.LabelFrame(root)
# frame y position = 80 pixel from the bottom
open_file_frame.place(rely=1, y=-80, relwidth=1, height=80)
root.mainloop()

Tkinter buttons getting hidden behind other frames

I am trying to create a battlemap for dnd (picture) with adjustable grid and movable enemy/creature tokens. The idea is to drag one of the token from the right onto the map on the left.
The window is made of 3 frames. The frame for the map, the frame for the "new map" button and slider. And then frame for the tokens, which are buttons tiled using button.grid()
I found a drag and drop system here that I'm using to drag the tokens. However, when I bring them over the map, they go behind it and you can't see them (I know they go behind because they can be partially visible between the two frames). Is there any way to bring them to the front?
import tkinter as tk
class DragManager():
def add_dragable(self, widget):
widget.bind("<ButtonPress-1>", self.on_start)
widget.bind("<B1-Motion>", self.on_drag)
widget.bind("<ButtonRelease-1>", self.on_drop)
widget.configure(cursor="hand1")
def on_start(self, event):
# you could use this method to create a floating window
# that represents what is being dragged.
pass
def on_drag(self, event):
# you could use this method to move a floating window that
# represents what you're dragging
event.widget.place(x=event.x_root + event.x, y= event.y_root + event.y)
#when button is dropped, create a new one where this one originally was
def on_drop(self, event):
# find the widget under the cursor
x,y = event.widget.winfo_pointerxy()
target = event.widget.winfo_containing(x,y)
try:
target.configure(image=event.widget.cget("image"))
except:
pass
if x > window.winfo_screenwidth() - 200:
del event.widget
return
if not event.widget.pure:
return
button = tk.Button(master=entity_select_frame, text = "dragable", borderwidth=1, compound="top")
#avoiding garbage collection
button.gridx = event.widget.gridx
button.gridy = event.widget.gridy
button.grid(row = event.widget.gridx, column = event.widget.gridy)
button.grid()
button.pure = True
dnd.add_dragable(button)
window = tk.Tk()
window.geometry("1000x800")
map_frame = tk.Frame()
controls_frame = tk.Frame(width=200, borderwidth=1, relief=tk.RAISED)
tk.Label(master=controls_frame, text="controls here").pack()
entity_select_frame = tk.Frame(width=200, relief=tk.RAISED, borderwidth=1)
dnd = DragManager()
button = tk.Button(master=entity_select_frame, text = "dragable", borderwidth=1)
button.gridx = 0
button.gridy = 0
button.grid(row = 0, column = 0)
button.pure = True
dnd.add_dragable(button)
map_frame.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
controls_frame.pack(fill=tk.BOTH)
entity_select_frame.pack(fill=tk.BOTH)
window.mainloop()
I played around a little bit and used stuff from this post. I did not structure it as a class and I used the picture frame as my root-frame and put the control-frame inside that. I'm not sure how this would be best combined with your "draw-grid", "token" functionalities etc., however I hope it helps. I did not find a way to drag widgets across frames though (tried to set a new master for the button, recreate it after dropping it etc.). Get the image used in my code from here.
from tkinter import Tk, Frame, Label, Button, Canvas, font
from tkinter import ttk
from PIL import Image, ImageTk
root = Tk()
""" ####################### Configuration parameters ###################### """
image_file_path = "Island_AngelaMaps-1024x768.jpg"
resize_img = False # set to True if you want to resize the image > window size
resize_to = (600, 600) # resolution to rescale image to
""" ####################### Drag and drop functionality ################### """
def make_draggable(widget):
widget.bind("<Button-1>", on_drag_start)
widget.bind("<B1-Motion>", on_drag_motion)
def on_drag_start(event):
widget = event.widget
widget._drag_start_x = event.x
widget._drag_start_y = event.y
def on_drag_motion(event):
widget = event.widget
x = widget.winfo_x() - widget._drag_start_x + event.x
y = widget.winfo_y() - widget._drag_start_y + event.y
widget.place(x=x, y=y)
""" ################################# Layout ############################## """
# picture frame with picture as background
picture_frame = Frame(root)
picture_frame.pack(side="left", anchor="w", fill="both", expand=True)
# load the image
if resize_img:
img = ImageTk.PhotoImage(Image.open(image_file_path).resize(resize_to, Image.ANTIALIAS))
else:
img = ImageTk.PhotoImage(Image.open(image_file_path))
# create canvas, set canvas background to the image
canvas = Canvas(picture_frame, width=img.width(), height=img.height())
canvas.pack(side="left")
canvas.background = img # Keep a reference in case this code is put in a function.
bg = canvas.create_image(0, 0, anchor="nw", image=img)
# subframe inside picture frame for controls
ctrl_subframe = Frame(picture_frame)
ctrl_subframe.pack(side="right", anchor="n")
# separator between picture and controls, inside picture frame
ttk.Separator(picture_frame, orient="vertical").pack(side="right", fill="y")
# underlined label 'Controls' in subframe
ctrl_header = Label(ctrl_subframe, text="Controls", font=("Arial", 10, "bold"))
f = font.Font(ctrl_header, ctrl_header.cget("font"))
f.configure(underline=True)
ctrl_header.configure(font=f)
ctrl_header.pack(side="top", pady=2)
# update window to get proper sizes from widgets
root.update()
# a draggable button, placed below ctrl_header
# (based on X of ctrl_subframe and height of ctrl_header, plus padding)
drag_button = Button(picture_frame, text="Drag me", bg="green", width=6)
drag_button.place(x=ctrl_subframe.winfo_x()+2, y=ctrl_header.winfo_height()+10)
make_draggable(drag_button)
""" ################################ Mainloop ############################# """
root.mainloop()

Tkinter is there a way to resize the scroll bar in a canvas window

I have made most of this window already, and would prefer to not have to restart because of a hitch with a scrollbar not resizing properly. Problem being that the scrollbars appear way too small for the listboxes and I want them to span the whole height of each box respecitvely, but as of now they can only function if you spam the arrows as the actual scrolling bit can't move for lack of space. Any help would be appreciated, stuck on this for a while now. (Using python 3.8).
import tkinter as tk
from tkinter import *
setup = tk.Tk()
setup.title("Set Up Game")
setup.geometry("450x650")
setup.resizable(width=False, height=False)
select_Box = tk.Canvas(setup, width=450, height=496, bg="#cd3636")
select_Box.pack(padx=10)
listbox1 = Listbox(setup, width=33, height=30)
listbox1_win = select_Box.create_window(110,250, window=listbox1)
listbox2 = Listbox(setup, width=33, height=30)
listbox2_win = select_Box.create_window(320,250, window=listbox2)
scroll1 = Scrollbar(setup)
scroll1_win = select_Box.create_window(200,250, window=scroll1)
scroll2 = Scrollbar(setup)
scroll2_win = select_Box.create_window(410,250, window=scroll2)
listbox1.config(yscrollcommand = scroll1.set, selectmode=SINGLE)
scroll1.config(command = listbox1.yview)
listbox2.config(yscrollcommand = scroll2.set, selectmode=SINGLE)
scroll2.config(command = listbox2.yview)
nameArray = ["Bulbasaur", "Ivysaur", "Venasaur", "Charmander", "Charmelion", "Charazard", "Squirtle", "Wartortle", "Blastoise", "Lucario", "Garchomp", "Gengar", "Snorlax", "Reuniclus", "Joel","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder","placeholder"]
for item in nameArray:
listbox1.insert(END, item)
setup.mainloop()
If you want to use Canvas.create_window to place all of your widgets, all you have to do is define the height of your scrollbar (you may need to play around with the numbers a little to get it to the right size).
So the edited snippet from your code will be:
scroll1 = Scrollbar(setup)
scroll1_win = select_Box.create_window(200,
250,
height=480, # this is all you're missing!
window=scroll1)

how to scroll a canvas with pictures in python?

I tried to scroll a column of images in a canvas with code bellow, when I run the code, the scrollbar shows up but has nothing to scroll. and images are in the main directory.
CODE(python 3):
import tkinter as tk
root = tk.Tk()
root.configure(bg='#1d1d1d')
root.geometry('100x200')
f = tk.PhotoImage(file = 'folder.png')
d = tk.PhotoImage(file = 'database.png')
can = tk.Canvas(root , bg = 'red' )
scroll = tk.Scrollbar(root , command=can.yview)
for i in range(20):
tk.Label(can , image = f , anchor = 'w').pack( side = 'top')
for i in range(10):
tk.Label(can , image = d , anchor = 'w').pack( side = 'top')
can.pack( side = 'left')
scroll.pack(side = 'right' , fill = 'y')
can.config(yscrollcommand=scroll.set)
can.configure(scrollregion=can.bbox("all"))
root.mainloop()
Does anyone know where the problem is?
The canvas cannot scroll items added with pack, place, or grid. To add something to the canvas that can be scrolled you must use one of the create_* methods (create_window, create_image, etc).

Tkinter recursive behavior with '<Configure>' callback

I try to have a tkinter.Frame that have a full screen image and some buttons underneath it
WIDTH, HEIGHT = 800, 600
root = Tk()
mainframe = Frame(root, padding="3 3 12 12")
mainframe.pack(fill=BOTH, expand=True)
infovariable = StringVar()
infovariable_label = Label(mainframe, textvariable=infovariable, anchor=S)
infovariable_label.pack(fill=X, side=TOP)
label = Label(mainframe)
label.pack(fill=BOTH, expand=True)
image_base = Image.open('hello.jpg')
# setting the photo
image = (image_base
.resize(2500, 1000)
.crop(0, 0, WIDTH,HEIGHT))
label.configure(image=photo)
When I do a window resize, I want my photo to be the same dimensions (width/height), if I do that:
def onResize(event):
global WIDTH, HEIGHT
WIDTH = event.width
HEIGHT = max(0, event.height - 50)
# setting the photo
image = (image_base
.resize(2500, 1000)
.crop(0, 0, WIDTH,HEIGHT))
root.bind('<Configure>', onResize)
The resize, makes the image change size, then call the resize again, having a window that infinitely resizes.
I have the same problem as this thread:
odd behavior with '<Configure>' callback
When you bind to the root window, that binding applies to every child of the root window, too, due to how tkinter uses binding tags.
Part of the solution is to change your onResize to only change the size of the image if the event.widget represents the root window. There may be other problems, but that's the first.
You also need to make sure you account for borders. If you make the image the same size of the window, but the label has a one pixel border, that will cause the label to grow, which will cause the root window to grow, which will start the process all over again.
Another answer related to bind tags is here: https://stackoverflow.com/a/2472992/7432

Categories

Resources