How can I add Unscrollable Image to a frame/Canvas? - python

In Tkinter Canvas how can I place a image on a canvas/Frame that has Scrollbar attached to it. But on Scrolling the scrollbar the Image shouldn't scroll, Rather the other contents like(Label) should move while scrolling making image unscrollable and below label.

I do it by using new yview function for scrollbar, update position of background image as the scrollbar.
from PIL import Image
from tkinter import Tk, Label, Frame, Canvas, Scrollbar, PhotoImage
def yview(action, fraction):
canvas.yview(action, fraction)
x, y = canvas.coords(bg)
dy = canvas.canvasy(0)-y
canvas.move(bg, 0, dy)
image_file = "d:/chess.png"
width, height = Image.open(image_file).size
root = Tk()
vscrollbar = Scrollbar(root, orient='vertical')
vscrollbar.pack(side='right', fill='y', expand=0)
canvas = Canvas(root, width=width, height=height, bg='green', yscrollcommand=vscrollbar.set)
canvas.pack(side='left', padx=0, pady=0, ipadx=0, ipady=0)
vscrollbar.configure(command=yview)
image = PhotoImage(file=image_file)
bg = canvas.create_image(0, 0, image=image, anchor='nw')
# ======================== Block ======================================
frame = Frame(canvas)
frame_id = canvas.create_window(width//2, 0, window=frame, anchor="n")
for i in range(30):
Label(frame, text=f'Label {i+1:0>2d}', padx=5, pady=5).pack()
# ==================================================================
canvas.update()
canvas.configure(scrollregion=canvas.bbox('all'))
root.mainloop()
[Update] frame used as container of labels will mask background image, to avoid this, need to put each widget under canvas directly.
Code for Block replaced by
label_ids = []
for i in range(30):
label = Label(canvas, text=f'Label {i+1:0>2d}', padx=5, pady=5)
h = label.winfo_reqheight()
label_id = canvas.create_window(width//2, int(i*(h*1.5)), window=label, anchor="n")

Related

How to resize the width of the canvas dynamically

I have this code here. Like you see if the text in one of the button is long, it will not all apper. Is there a solution to resize the width of the canvas which is now defined equal to 300 so that it will show all the text in in the buttons. I don't want to do it manually because in the futur the text will be get from an external file and the data can change.
Any ideas
from tkinter import *
from tkinter.ttk import *
from tkinter import messagebox
from time import strftime
import csv
import xmlrpc.client as xmlrpclib
root = Tk()
frame = Frame(root)
container = Frame(root)
canvas = Canvas(container, borderwidth = 0, highlightthickness = 0, width=300, height=500, bg = "white")
#scrollbar = Scrollbar(container, orient="vertical", command=canvas.yview)
vertibar=Scrollbar(
container,
orient=VERTICAL
)
vertibar.pack(side=RIGHT,fill=Y)
vertibar.config(command=canvas.yview)
horibar=Scrollbar(
container,
orient=HORIZONTAL
)
horibar.pack(side=BOTTOM,fill=X)
horibar.config(command=canvas.xview)
scrollable_frame = Frame(canvas)
# setting the minimum size of the root window
root.geometry("300x650")
# Adding widgets to the root window
Label(root, text='my app',
font=('Verdana', 15)).pack(pady=10)
# tell the canvas how large the frame will be, so that it knows how much it can scroll:
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(
scrollregion=canvas.bbox("all")
)
)
frame_id =canvas.create_window((0, 0), window=scrollable_frame, anchor='nw')
canvas.bind("<Configure>",canvas.itemconfig(frame_id, width=canvas.winfo_reqwidth()))
#canvas.configure(yscrollcommand=scrollbar.set)
canvas.config(
xscrollcommand=horibar.set,
yscrollcommand=vertibar.set
)
# rechner_full = str(rechner[row])
# print(rechner_full)
Button(scrollable_frame, text="text1").pack(fill="both", side="top")
Button(scrollable_frame, text="text2").pack(fill="both", side="top")
Button(scrollable_frame, text="text3").pack(fill="both", side="top")
Button(scrollable_frame, text="longtexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxt").pack(fill="both", side="top")
# print(button.cget('text'))
container.pack()
canvas.pack(side="left", fill="both")
#scrollbar.pack(side="right", fill="both")
# Button for closing
exit_button = Button(root, text="Beenden", command=root.destroy)
exit_button.pack(pady=20)
root.mainloop()
Since scrollable_frame will be resized to show the longest button, so you can expand canvas inside the callback bound to <Configure> event on scrollable_frame:
...
scrollable_frame = Frame(canvas)
frame_id =canvas.create_window((0, 0), window=scrollable_frame, anchor='nw')
def on_frame_resized(event):
canvas_width = canvas.winfo_reqwidth()
if event.width > canvas_width:
# expand canvas to the width of scrollable_frame
canvas.configure(width=event.width)
else:
# expand scrollable_frame to the width of canvas
canvas.itemconfigure(frame_id, width=canvas_width)
canvas.configure(scrollregion=canvas.bbox("all"))
# tell the canvas how large the frame will be, so that it knows how much it can scroll:
scrollable_frame.bind("<Configure>", on_frame_resized)
...
However you need to remove root.geometry(...) because it will block the main window from expanding due to resize of canvas. Also you need to remove canvas.bind("<Configure>", ...) because it will mess up the work done by on_frame_resized().
...
#root.geometry("300x650")
...
#canvas.bind("<Configure>",canvas.itemconfig(frame_id, width=canvas.winfo_reqwidth()))
...

Why isn't this frame in tkinter centered correctly?

I want this entry bar and other contents I'll add to the frame later to be centred correctly, I received this code that supposedly should work but it isn't.
import tkinter as tk
import math
import time
root = tk.Tk()
root.geometry()
root.attributes("-fullscreen", True)
exit_button = tk.Button(root, text = "Exit", command = root.destroy)
exit_button.place(x=1506, y=0)
frame = tk.Frame(root)
main_entry = tk.Entry(root, width = 100, fg = "black")
main_entry.place(x=50, y=50)
frame.place(relx=.5,rely=.5, anchor='center')
root.mainloop()
As you can see the frame isn't centred so how can I fix this?
In order to achieve widget centering on a fullscreen I've had to use grid manager.
The code below works but the exact positioning requires some fiddling with frame padding.
frame padx = w/2-300 and pady = h/2-45 are arbitrary values found using a bit of trial and error.
import tkinter as tk
root = tk.Tk()
root.attributes( '-fullscreen', True )
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
frame = tk.Frame( root )
main_entry = tk.Entry( frame, width = 100 )
main_entry.grid( row = 0, column = 0, sticky = tk.NSEW )
frame.grid( row = 0, column = 0, padx = w/2-300, pady = h/2-45, sticky = tk.NSEW )
exit_button = tk.Button( frame, text = 'Exit', command = root.destroy )
exit_button.grid( row = 1, column = 0, sticky = tk.NSEW )
tk.mainloop()
Frame automatically changes size to size of objects inside Frame (when you use pack()) but you have nothing inside Frame. You put all widgets directly in root - so Frame has no size (width zero, height zero) and it is not visible.
When I use tk.Frame(root, bg='red', width=100, height=100) then I see small red frame in the center.
You have two problems:
(1) you put Entry in wrong parent - it has to be frame instead of root,
(2) you use place() which doesn't resize Frame to its children and it has size zero - so you don't see it. You would have to set size of Frame manully (ie. tk.Frame(..., width=100, height=100)) or you could use pack() and it will resize it automatically.
I add colors for backgrounds to see widgets. blue for window and red for frame.
import tkinter as tk
root = tk.Tk()
root['bg'] = 'blue'
root.attributes("-fullscreen", True)
exit_button = tk.Button(root, text="Exit", command=root.destroy)
exit_button.place(x=1506, y=0)
frame = tk.Frame(root, bg='red')
frame.place(relx=.5, rely=.5, anchor='center')
main_entry = tk.Entry(frame, width=100, fg="black")
main_entry.pack(padx=50, pady=50) # with external margins 50
root.mainloop()

How do I change the width of Tkinter widgets in smaller increments (Python)?

The left side of the box is aligned with the left side of some_label. I am unable to set the width so that the right side of some_label also aligns with the right side of the box because the increments between different width values are too large. A width of 35 puts the right end of some_label too far left and a width of 36 puts it too far right.
from tkinter import *
root = Tk()
root.geometry('500x500')
box = Frame(root, width=383, height=246, bg='black')
box.place(x=241, y=65, anchor=CENTER)
some_label = Label(root, text='Some Label', borderwidth=1, relief='solid')
some_label.place(x=50, y=210)
some_label.config(width=35, font=('TkDefaultFont', 15)) # whether width is 35 or 36, the label never aligns with the box
mainloop()
Since you use place(), you can specify the width directly:
import tkinter as tk
root = tk.Tk()
root.geometry('500x500')
box = tk.Frame(root, width=383, height=246, bg='black')
box.place(x=241, y=65, anchor='c')
some_label = tk.Label(root, text='Some Label', borderwidth=1, relief='solid')
some_label.place(x=241, y=210, width=383, anchor='c') # set width to same as 'box'
some_label.config(font=('TkDefaultFont', 15))
root.mainloop()
To set the width of a Label in pixels you have to include an image. Easiest would to use a transparent pixel and present it in the label with compound='center' which does not offset the text.
Alternatively you could simply use a containing frame to control the widget sizes.
I have included both ways in my example.
from tkinter import *
root = Tk()
root.geometry('500x500')
box = Frame(root, width=383, height=246, bg='black')
box.place(x=241, y=65, anchor=CENTER)
some_label = Label(root, text='Some Label', borderwidth=1, relief='solid')
some_label.place(x=50, y=210)
img = PhotoImage(file='images/pixel.gif') # Create image
some_label.config(image=img, compound='center') # Set image in label
some_label.config(width=379, font=('TkDefaultFont', 15)) # Size in pixels
# Alternateivly control size by an containing widget:
container = Frame(root, bg='tan') # Let frame adjust to contained widgets
container.place(x=241, y=360, anchor=CENTER)
# Let the contained widget set width
other_box = Frame(container, height=100, width=383, bg='black') # Set width
other_box.pack()
other_label = Label(container, text='Some Label', borderwidth=1, relief='solid')
other_label.pack(expand=True, fill=X, pady=(20,0)) # Expand to fill container
other_label.config(font=('TkDefaultFont', 15))
mainloop()
If you are going to make complex GUI designs, grid() is almost always easier to use.

How to resize scrollable frame when the window is expanded or maximized?

When the window is expanded or maximized, the frame is just constant. The size is constant. I want the over all frame to move as I expand or maximize the window. How can this be done?
from tkinter import *
def data():
for i in range(1000):
if (i % 2) == 0:
l4 = Label(frame, text="Size of rectangle:")
l4.grid(row=i, column=0)
en = Entry(frame)
en.grid(row=i, column=1)
b3 = Button(frame, text="Save")
b3.grid(row=1001, column=0)
b4 = Button(frame, text="Back")
b4.grid(row=1001, column=1)
def myfunction(event):
canvas.configure(scrollregion=canvas.bbox("all"),width=250,height=700)
def _on_mousewheel(event):
canvas.yview_scroll(-1*(event.delta/120), "units")
root=Tk()
sizex = 272
sizey = 707
posx = 100
posy = 100
root.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))
myframe=Frame(root)
myframe.place(x=0,y=0)
canvas=Canvas(myframe)
frame=Frame(canvas)
myscrollbar=Scrollbar(myframe,orient="vertical",command=canvas.yview)
canvas.configure(yscrollcommand=myscrollbar.set)
canvas.bind_all('<MouseWheel>', lambda event: canvas.yview_scroll(int(-1*(event.delta/120)), "units"))
myscrollbar.pack(side="right",fill="y")
canvas.pack(side="left")
canvas.create_window((0,0),window=frame,anchor='nw')
frame.bind("<Configure>",myfunction)
data()
root.bind("<MouseWheel>", myfunction)
root.mainloop()
The canvas will generate a <Configure> event when it is resized. You can bind to this event and reset the size of the inner frame to match the width of the canvas when this happens.
However, in your specific case you've got a series of stacked widgets, none of which will grow when you resize the window. Because of that, even when you resize the window, the canvas doesn't grow. Because the canvas doesn't grow, the event won'g fire.
You'll need to use the appropriate options to the geometry managers to make sure the entire hierarchy of windows grows and shrinks appropriately, and then add the binding to set the size of the inner window.
Here's a very basic example. It starts by creating a canvas and packing it so that it fills the root window. It then adds a frame inside the canvas, and arranges for the width of the frame to change whenever the canvas is resized.
import tkinter as tk
def handle_canvas_resize(event):
canvas.itemconfigure(window_id, width=event.width)
def handle_frame_resize(event):
canvas.configure(scrollregion=canvas.bbox("all"))
root = tk.Tk()
canvas = tk.Canvas(root, width=200, height=200)
scrollbar = tk.Scrollbar(root, command=canvas.yview)
canvas.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
canvas.pack(fill="both", expand=True)
inner_frame = tk.Frame(canvas, background="bisque")
window_id = canvas.create_window(2,2, window=inner_frame)
for i in range(1, 101):
label = tk.Label(inner_frame, text="label #{}".format(i))
label.pack(side="top", fill="x", padx=1, pady=1)
inner_frame.bind("<Configure>", handle_frame_resize)
canvas.bind("<Configure>", handle_canvas_resize)
root.after_idle(canvas.yview_moveto, 0)
root.mainloop()

Tkinter: Overlaying a label on top of a background image

I'm trying to create my first GUI, using Python and Tkinter. I want to have a background image that resizes accordingly with the window size, along with two labels on top of the background, both placed about midway on the window. The two labels are "Full Name" and "Education", as shown in my code below.
Currently, I'm using the pack() method, and I've been using the window resizing code from here.
My question is: How do I get the labels to overlap the background image (also a label in my code)? With my current code, the background image seems to be on top of the frame and labels.
Attached is a picture of the output/GUI I'm looking for, except I want my image to be in the background.
GUI
#Resize using label
from tkinter import *
from tkinter import ttk
from PIL import Image, ImageTk
root = Tk()
root.title("Title")
root.geometry('600x600')
def resize_image(event):
new_width = event.width
new_height = event.height
image = copy_of_image.resize((new_width, new_height))
photo = ImageTk.PhotoImage(image)
label.config(image = photo)
label.image = photo #avoid garbage collection
#Background image
image = Image.open("filepath.jpg")
copy_of_image = image.copy()
photo = ImageTk.PhotoImage(image)
label = Label(root, image = photo)
label.bind('<Configure>', resize_image)
label.place(x=0, y=0, relwidth=1, relheight=1)
label.pack(fill=BOTH, expand = YES)
label.lower()
frame = Frame(root, width=600, height=600, relief='raised', borderwidth=2)
frame.pack(fill="both", expand=True)
frame.pack_propagate(False)
#Top Frame
top_frame = Frame(frame,width=600, height=350)
top_frame.pack(side = TOP)
#Various Labels
Label(frame, text = 'Full Name', width = 8).pack()
Label(frame, text = 'Education', width = 8).pack()
root.mainloop()
Some rearrangement is in order. Your large frame is sitting atop the background image, covering it completely. So, let's make the background Label part of frame, not root. You should probably either place() or pack() but not both. To get the other labels to the center, I created a centered frame and packed them in to it. There are probably other ways to do all of this:
from tkinter import *
from PIL import Image, ImageTk
def resize_image(event):
new_width = event.width
new_height = event.height
image = copy_of_image.resize((new_width, new_height))
photo = ImageTk.PhotoImage(image)
label.config(image=photo)
label.image = photo # avoid garbage collection
root = Tk()
root.title("Title")
root.geometry('600x600')
frame = Frame(root, relief='raised', borderwidth=2)
frame.pack(fill=BOTH, expand=YES)
frame.pack_propagate(False)
copy_of_image = Image.open("filepath.jpg")
photo = ImageTk.PhotoImage(copy_of_image)
label = Label(frame, image=photo)
label.place(x=0, y=0, relwidth=1, relheight=1)
label.bind('<Configure>', resize_image)
center_frame = Frame(frame, relief='raised', borderwidth=2)
center_frame.place(relx=0.5, rely=0.5, anchor=CENTER)
Label(center_frame, text='Full Name', width=8).pack()
Label(center_frame, text='Education', width=8).pack()
root.mainloop()

Categories

Resources