tkinter scrollbar with grid, it doesn't get linked - python-3.x

I'm studying GUI, so please understand my poor codes below.
I was trying to make a program which gets game-character's information. So if you press the 'search' button, the list would be shown below. But... it only shows about 11 names due to the window size. So i wanted to put a scrollbar for that area, but I just don't know how to link the scroll bar to control the area. I meant, the scroll bar itself has created, and it does scroll itself, but it doesn't scroll the window I wanted. I must have linked it wrong but... not sure.
Below is the minimized example code, but it's still quite long and crude. Sorry for that again.
If anyone can enlighten me, it would be really great help for this and my future works as well.
import tkinter as tk
import requests
from bs4 import BeautifulSoup
import webbrowser
import time
global var_dict, input_id, output
var_dict = {}
def enter_call_back(event=None):
output.grid(column = 0, row = 2, columnspan = 5 , sticky='w')
output.insert(tk.END,"Text_Something")
output.update()
search_chr()
def open_browse(url_list):
for url in url_list:
time.sleep(0.3)
webbrowser.open(url)
def search_inven(ch_id):
if ch_id == "ch1" or ch_id == "ch13" or ch_id == "ch15" :
num = 5
url_list = ["something.url","something2.url"]
self_count = 1
else:
num = 0
url_list = []
self_count = 0
masterset = []
masterset.append(num)
masterset.append(url_list)
masterset.append(self_count)
return masterset
def search_chr():
global var_dict, output
for things in var_dict.keys():
var_dict[things].destroy()
chr_list = ["ch1","ch2","ch3","ch4","ch5","ch6","ch7","ch8","ch9","ch9","ch10","ch11","ch12","ch13","ch14","ch15"]
output.insert(tk.END," Done! \n\n")
var_dict = {}
num = -1
for ch in chr_list:
num += 1
var_dict["output%s" %num] = tk.Entry(frame_buttons, width = 125)
result = search_inven(ch)
if result[0] == 0:
var_dict["output"+str(num)].insert(0, "Clean "+ch+"\n")
var_dict["output"+str(num)].grid(column = 0, row = num, sticky='w', padx=5, pady=5)
else:
url_list = result[1]
var_dict["o-button%s" %num] = tk.Button(frame_buttons, command=lambda url_list = url_list : open_browse(url_list))
var_dict["o-button"+str(num)].grid(column = 1, row = num, sticky='e')
var_dict["o-button"+str(num)].config(text="URL")
var_dict["output"+str(num)].insert(0, "Not Clean "+str(result[0])+" Self : "+str(result[2])+" Ch_id : "+ch+")\n")
var_dict["output"+str(num)].grid(column = 0, row = num, sticky='w', padx=5, pady=5)
vsb = tk.Scrollbar(frame_canvas, orient="vertical")
vsb.grid(row=0, column=1, sticky='ns')
vsb.config(command=canvas.yview)
canvas.configure(yscrollcommand=vsb.set)
frame_canvas.config(height = 300)
canvas.config(scrollregion=canvas.bbox("all"))
root = tk.Tk()
root.geometry("760x710")
root.resizable(width=False, height=False)
root.title("Minimum v.1.2")
root.grid_rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
frame_main = tk.Frame(root, bg="gray")
frame_main.grid(sticky='news')
intro = tk.Text(frame_main, height = 17, bg="#E3D5F3")
intro.option_add("*Font", "명조 10")
intro.insert(tk.CURRENT,"Text_something")
intro.config(state='disabled')
intro.grid(row=0, column=0, pady=(5, 0), columnspan = 5, sticky='nw')
input_id = tk.Entry(frame_main, width = 35)
input_id.option_add("*Font","명조 10")
input_id.insert(0,"Ch_name")
input_id.grid(row=1, column=0, pady=(5, 0), sticky='w')
search_btn = tk.Button(frame_main)
search_btn.config(text="Search")
search_btn.option_add("*Font","명조 10")
search_btn.config(width=5,height=1)
search_btn.grid(row = 1, column = 0, pady=(5, 0), sticky='e')
output = tk.Text(frame_main, height = 10)
output.option_add("*Font","명조 10")
output.grid(row = 2, column = 0,pady=(5,0),sticky='nw')
frame_canvas = tk.Frame(frame_main, width = 565)
frame_canvas.grid(row=3, column=0, pady=(5, 0), columnspan = 3 ,sticky='nw')
frame_canvas.grid_rowconfigure(0, weight=1)
frame_canvas.grid_columnconfigure(0, weight=1)
frame_canvas.grid_propagate(False)
canvas = tk.Canvas(frame_canvas, bg="gray", height=500,scrollregion=(0,0,500,1800))
canvas.grid(row=0, column=0, sticky="news")
frame_buttons = tk.Frame(canvas, bg="gray")
frame_buttons.grid(row = 0, column = 0,sticky='e')
root.bind('<Return>',enter_call_back)
search_btn.config(command = enter_call_back)
root.mainloop()

First, using grid() to put frame_buttons into the canvas will not affect the scrollregion of the canvas. Use canvas.create_window() instead.
Second, it is better to bind <Configure> event on frame_buttons and update canvas' scrollregion inside the bind callback. Then adding widgets to frame_buttons will automatically update the scrollregion.
Also note that you have created new scrollbar and put it at same position whenever search_chr() is executed. Better create the scrollbar only once outside the function.

Related

How can I scroll multiple frames in canvas?

I want to create a list of frames with further features like a button, label e.g.. but my issues are the size of the LabelFrame. If I put the LabelFrame in container it fits like I want to but it isn't scrollable any more. Any ideas?
from tkinter import ttk
import tkinter as tk
root = tk.Tk()
container = ttk.Frame(root)
canvas = tk.Canvas(container)
scrollbar = ttk.Scrollbar(container, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(
scrollregion=canvas.bbox("all")
)
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
for i in range(50):
lf = ttk.Frame(scrollable_frame, text=i).grid(column=1, row=i)
frame_ip = tk.LabelFrame(lf, bg="white", text=i)
frame_ip.place(relwidth=0.95, relheight=0.2, relx=0.025, rely=0)
button_scroll1 = tk.Button(frame_ip, text="Start", bg="grey")
button_scroll1.place(relwidth=0.15, relx=0.025, relheight=0.15, rely=0.1)
container.pack()
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
root.mainloop()
Here is your updated code with a Button, Canvas and Scrollbar inserted into each LabelFrame with grid manager.
I've also made the container resizable with row|column configure.
Seems to work fine.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.rowconfigure(0, weight = 1)
root.columnconfigure(0, weight = 1)
root.geometry("341x448")
container = ttk.Frame(root)
container.rowconfigure(0, weight = 1)
container.columnconfigure(0, weight = 1)
canvas = tk.Canvas(container)
scrollbar = ttk.Scrollbar(container, orient = tk.VERTICAL, command = canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(
scrollregion=canvas.bbox("all")))
canvas.create_window((0, 0), window = scrollable_frame, anchor = tk.NW)
canvas.configure(yscrollcommand = scrollbar.set)
for i in range(15):
L = ttk.LabelFrame(scrollable_frame, text = "Sample scrolling label")
L.grid(row = i, column = 0, sticky = tk.NSEW)
B = ttk.Button( L, text = f"Button {i}")
B.grid(row = 0, column = 0, sticky = tk.NW)
K = tk.Canvas(
L, width = 300, height = 100,
scrollregion = "0 0 400 400", background = "#ffffff")
K.grid(row = 1, column = 0, sticky = tk.NSEW)
S = ttk.Scrollbar( L, command = K.yview)
S.grid(column = 1, row = 1, sticky = tk.NSEW)
K["yscrollcommand"] = S.set
container.grid(row = 0, column = 0, sticky = tk.NSEW)
canvas.grid(row = 0, column = 0, sticky = tk.NSEW)
scrollbar.grid(row = 0, column = 1, sticky = tk.NS)
root.mainloop()

tkinter key binding causes image to disappear

I'm trying to bind arrow keys to buttons or at least the functions of the buttons (button_forward and button_back). The function of the buttons works, however, when I bind a key to the button the image just disappears.
It would also be awesome if someone could help me figure out how to create a loop to define the images and put them into a list. I'm just so lost when it comes to that.
The main purpose of the code is to be an image viewer that flashes an LED strip when an image changes.
I want to be able to control it using arrow keys to move forward and back between the images.
from tkinter import Tk, Button,Label, DISABLED
from PIL import ImageTk,Image
import board
import time
import neopixel
pixels = neopixel.NeoPixel(board.D18, 30)
root= Tk()
root.configure(bg='black')
root.title("please work")
# define, load, show
my_img1 = ImageTk.PhotoImage(Image.open("1.bmp"))
my_img2 = ImageTk.PhotoImage(Image.open("2.bmp"))
my_img3 = ImageTk.PhotoImage(Image.open("3.bmp"))
my_img4 = ImageTk.PhotoImage(Image.open("4.bmp"))
my_img5 = ImageTk.PhotoImage(Image.open("5.bmp"))
my_img6 = ImageTk.PhotoImage(Image.open("6.bmp"))
my_img7 = ImageTk.PhotoImage(Image.open("7.bmp"))
my_img8 = ImageTk.PhotoImage(Image.open("8.bmp"))
image_list = [my_img1, my_img2, my_img3, my_img4, my_img5, my_img6, my_img7, my_img8]
my_label = Label(image=my_img1)
my_label.grid(row = 0, column = 0, columnspan= 3, rowspan = 25, padx=440, pady= 5)
def forward(image_number, event = None):
global my_label
global button_forward
global button_back
my_label.grid_forget()
my_label = Label(image = image_list[image_number-1])
button_forward = Button(root, text = "next", command=lambda: forward(image_number+1))
button_back = Button(root, text = "previous", command = lambda: back(image_number-1))
if image_number == 7:
button_forward = Button(root, text = "next", state = DISABLED)
my_label.grid(row = 0, column = 0, columnspan = 3, rowspan = 25, padx=440, pady= 5)
button_back.grid(row = 23, column = 0)
button_forward.grid(row = 23, column = 2)
pixels.fill((255,0,0))
time.sleep(0.1)
pixels.fill((0,0,0))
time.sleep(0.5)
def back(image_number,):
global my_label
global button_forward
global button_back
my_label.grid_forget()
my_label = Label(image = image_list[image_number-1])
button_forward = Button(root, text = "next", command=lambda: forward(image_number+1))
button_back = Button(root, text = "previous", command = lambda: back(image_number-1))
my_label.grid(row = 0, column = 0, columnspan = 3, rowspan = 25, padx=440, pady= 5)
if image_number == 1:
button_back = Button(root, text = "previous", state = DISABLED)
button_back.grid(row=23, column = 0 )
button_exit.grid(row=23, column = 1 )
button_forward.grid(row=23, column = 2)
pixels.fill((255,0,0))
time.sleep(0.1)
pixels.fill((0,0,0))
time.sleep(0.5)
button_back = Button(root, text = "previous", command = back)
button_exit = Button(root, text = "Exit", command = root.quit)
button_forward = Button(root, text = "next", command =lambda:forward(2))
root.bind('<Left>', back)
root.bind('<Right>', forward)
button_back.grid(row=23, column = 0 )
button_exit.grid(row=23, column = 1 )
button_forward.grid(row=23, column = 2)
root.mainloop()
Your bindings cannot work because the event corresponding to the keypress will be passed instead of the image_number argument in back() and forward().
Also, you are recreating the widgets every time while you should only reconfigure them. So to change the label's image, you can do: my_label.configure(image=<new image>). Also instead of passing image_number as an argument, I would rather use a global variable, so that it will be easier to use the functions in the key bindings:
from tkinter import Tk, Button, Label, DISABLED, NORMAL
from PIL import ImageTk, Image
import board
import time
import neopixel
pixels = neopixel.NeoPixel(board.D18, 30)
root = Tk()
root.configure(bg='black')
root.title("please work")
# define, load, show
my_img1 = ImageTk.PhotoImage(Image.open("1.bmp"))
my_img2 = ImageTk.PhotoImage(Image.open("2.bmp"))
my_img3 = ImageTk.PhotoImage(Image.open("3.bmp"))
my_img4 = ImageTk.PhotoImage(Image.open("4.bmp"))
my_img5 = ImageTk.PhotoImage(Image.open("5.bmp"))
my_img6 = ImageTk.PhotoImage(Image.open("6.bmp"))
my_img7 = ImageTk.PhotoImage(Image.open("7.bmp"))
my_img8 = ImageTk.PhotoImage(Image.open("8.bmp"))
image_list = [my_img1, my_img2, my_img3, my_img4, my_img5, my_img6, my_img7, my_img8]
image_number = 1
# create the label only once
my_label = Label(root, image=image_list[image_number - 1])
my_label.grid(row=0, column=0, columnspan= 3, rowspan=25, padx=440, pady= 5)
def forward(event=None):
global image_number # global variable to keep track of the displayed image
image_number += 1
# change image in label
my_label.configure(image=image_list[image_number - 1])
if image_number == 8:
# last image, disable forward button
button_forward.configure(state=DISABLED)
elif image_number == 2:
# no longer first image, re-enable back button
button_back.configure(state=NORMAL)
pixels.fill((255,0,0))
time.sleep(0.1)
pixels.fill((0,0,0))
time.sleep(0.5)
def back(event=None):
global image_number
image_number -= 1
my_label.configure(image=image_list[image_number - 1])
if image_number == 1:
# first image, disable back button
button_back.configure(state=DISABLED)
elif image_number == 7:
# no longer last image, re-enable forward button
button_forward.configure(state=NORMAL)
pixels.fill((255,0,0))
time.sleep(0.1)
pixels.fill((0,0,0))
time.sleep(0.5)
button_back = Button(root, text="previous", command=back, state=DISABLED)
button_exit = Button(root, text="Exit", command=root.quit)
button_forward = Button(root, text="next", command=forward)
root.bind('<Left>', back)
root.bind('<Right>', forward)
button_back.grid(row=23, column=0)
button_exit.grid(row=23, column=1)
button_forward.grid(row=23, column=2)
root.mainloop()

tkinter frame not working properly

Can anyone help me why columnspan not having any effect in this code:
import tkinter as tk
root = tk.Tk()
frametop = tk.Frame(root).grid(row = 0, column = 0, columnspan = 2)
labeltest1 = tk.Label(frametop, text="A").grid(row = 0, column = 0)
labeltest2 = tk.Label(frametop, text="B").grid(row = 0, column = 1)
labeltest3 = tk.Label(frametop, text="C").grid(row = 0, column = 2)
framebottom = tk.Frame(root).grid(row = 1, column = 0)
labeltest4 = tk.Label(framebottom, text="Hello World").grid(row = 1, column = 0)
labeltest5 = tk.Label(framebottom, text="Hello World").grid(row = 1, column = 1)
labeltest6 = tk.Label(framebottom, text="Hello World").grid(row = 1, column = 2)
root.mainloop()
labeltest1, labeltest2, labeltest3 are being distributed in column similar to the widgets of framebottom. What I wanted is a grid of 3 columns inside a frame with a columnspan = 2 with the same row.
Something similar to this:
|| A | B | C ||
| Hello World | Hello World | Hello World |
The way your code is organized, the Frames have no effect; when you grid on the same line as the widget creation, None gets returned and stored in the variables you are using for the Frames, and Label. A consequence is that the labels are inserted directly into root.
The following does the same, with a placement of the labels per your request.
import tkinter as tk
root = tk.Tk()
tk.Label(root, text="A").grid(row=0, column=0, columnspan=2)
tk.Label(root, text="B").grid(row=0, column=2, columnspan=2)
tk.Label(root, text="C").grid(row=0, column=4, columnspan=2)
tk.Label(root, text="Hello World").grid(row=1, column=0, columnspan=3)
tk.Label(root, text="Hello World").grid(row=1, column=3, columnspan=3)
tk.Label(root, text="Hello World").grid(row=1, column=6, columnspan=3)
root.mainloop()
GUI appearance (osX):

Cell formatting in Grid Geometry manager

I have this code:
from tkinter import *
from tkinter import ttk
class Application:
def __init__(self, master):#Change to game class when combining code
self.master = master#remove when combining code
self.frame_Canvas = ttk.Frame(self.master, width = 600, height = 600)
self.frame_Canvas.pack(side = 'left')
self.frame_Canvas.pack_propagate(False)
self.frame_Canvas.grid_propagate(False)
self.hangman = Canvas(self.frame_Canvas, width = 600, height = 600,
background = 'white').pack()
self.FullName = ttk.Label(self.frame_Canvas, text = "Full Name", background = 'white')#full name will be entered here
self.FullName.config(font=("TkDefaultFont", 20))
self.FullName.place(x = 10, y = 10)
self.frame_Interact = ttk.Frame(self.master, width = 200, height = 600)
self.frame_Interact.pack(side = 'right')
self.frame_Interact.pack_propagate(False)
self.frame_Interact.grid_propagate(False)
self.QuestionLabel = ttk.Label(self.frame_Interact, text = "Question:")
self.QuestionLabel.config(font=("TkDefaultFont", 20))
self.QuestionLabel.grid(column = 0, row = 0)
self.QuestionShow = Text(self.frame_Interact, height=1, width=8)#input question here
self.QuestionShow.config(font=("TkDefaultFont", 20))
self.QuestionShow.grid(column = 0, row = 1)#FIX THE FORMATTING OF QUESTION, GRID CELL TO LEFT, NOT BIG ENOUGTH?
self.AnswerEntry = ttk.Entry(self.frame_Interact, width = 10)#do later
def main():
root = Tk()
root.wm_title("Hangman")
Menu = Application(root)
root.resizable(width=False, height=False)
root.iconbitmap("windowicon.ico")
root.mainloop()
if __name__ == "__main__": main()
I dont know why, but the Answer box and label on the right side of my tkinter GUI is on the left side of its frame. I want it in the center. Does anyone know a way to frix it, or any improvements for the code so far. Thanks :)
There are two ways you can do this, first way will to be to use .pack() instead of .grid() since pack is very easy to use and does the aligning for you automatically.
So you can just replace:
self.QuestionLabel.grid(column = 0, row = 0)
# and
self.QuestionShow.grid(column = 0, row = 1)
With:
self.QuestionLabel.pack()
# and
self.QuestionLShow.pack()
This way isn't recommended for your situation whatsoever, since that will involve mixing pack and grid together which could cause future errors in your code.
As Bryan Oakley said:
it will cause errors immediately, if the widgets share the same parent. It won't ever cause problems if the widgets have different parents.
You should instead do this:
from tkinter import *
from tkinter import ttk
class Application:
def __init__(self, master):#Change to game class when combining code
self.master = master#remove when combining code
self.frame_Canvas = ttk.Frame(self.master, width = 600, height = 600)
self.frame_Canvas.pack(side = 'left')
self.frame_Canvas.pack_propagate(False)
self.frame_Canvas.grid_propagate(False)
self.hangman = Canvas(self.frame_Canvas, width = 600, height = 600,
background = 'white').pack()
self.FullName = ttk.Label(self.frame_Canvas, text = "Full Name", background = 'white')#full name will be entered here
self.FullName.config(font=("TkDefaultFont", 20))
self.FullName.place(x = 10, y = 10)
self.frame_Interact = ttk.Frame(self.master, width = 200, height = 600)
self.frame_Interact.pack(side = 'right')
self.frame_Interact.pack_propagate(False)
self.frame_Interact.grid_propagate(False)
Grid.columnconfigure(self.frame_Interact, 0, weight=1) # NOTE, THIS CHANGED
self.QuestionLabel = ttk.Label(self.frame_Interact, text = "Question:")
self.QuestionLabel.config(font=("TkDefaultFont", 20))
self.QuestionLabel.grid(column = 0, row = 0)
self.QuestionShow = Text(self.frame_Interact, height=1, width=8)
self.QuestionShow.config(font=("TkDefaultFont", 20))
self.QuestionShow.grid(column = 0, row = 1)
def main():
root = Tk()
root.wm_title("Hangman")
Menu = Application(root)
root.resizable(width=False, height=False)
root.iconbitmap("windowicon.ico")
root.mainloop()
if __name__ == "__main__":
main()
By adding Grid.columnconfigure(self.frame_Interact, 0, weight=1) it will help grid know how to allocate extra space. so that the label will try to take up the whole column. The same goes for rows if you're wondering, you can add Grid.rowconfigure(self.frame_Interact, 0, weight=1) to make the widgets fill the whole row.
And for some improvements to your code, you should change this line:
self.hangman = Canvas(self.frame_Canvas, width = 600, height = 600,
background = 'white').pack()
# to
self.hangman = Canvas(self.frame_Canvas, width = 600, height = 600, background = 'white')
self.hangman.pack()
Or else self.hangman will be None, as it is in your code.

tkinter report frame as well as mouse coordinates

def mouse_click(event):
#'reports' to both terminal and results text box
parent_name = Frame.winfo_parent(root)
parent = Frame._nametowidget(parent_name)
result = ("{0}: {1},{2}\n ".format('Clicked at', event.x, event.y))
print('clicked at',parent, event.x, event.y)
from tkinter import *
root = Tk()
root.title("Change Text")
root.geometry('700x500')
top=root.winfo_toplevel()
for rn in range(0,9): # rn = row number
top.rowconfigure(rn, weight=1)
top.rowconfigure(rn, weight=0)
for cn in range(0,5): # cn = column number
top.columnconfigure(cn, weight=1)
# FRAME 1
frame1 = Frame(root, borderwidth = 2, background = '#EFE0CD', relief = RIDGE,width = 25, height = 20)
frame1.bind("<Button-1>", mouse_click)
frame1.grid(column = 0, row = 0, columnspan = 2, rowspan = 3, sticky = N+S+E+W)
frame1_lbl = Label(frame1, text='Frame 1', font='comic-sans-MS 10 ', fg ='red', bg = '#EFE0CD')
frame1_lbl.grid(row=0, column =0)
# FRAME 2
frame2 = Frame(root, borderwidth = 2, background = '#CCC6B0', relief = RIDGE,width = 25, height = 20)
frame2.bind("<Button-1>", mouse_click)
frame2.grid(column = 0, row = 3, columnspan = 2, rowspan = 3, sticky = N+S+E+W)
frame2_lbl = Label(frame2, text='Frame 2', font='comic-sans-MS 10', fg ='red', bg = '#CCC6B0')
frame2_lbl.grid(row=0, column =0,)
root.mainloop()
I know that this is a really dumb question. How do I identify the Frame in which the mouse is clicked so that report prints clicked frame1 or frame2 at x y coordinates?. I found a bit about using winfo_parent but have obviously not used it properly.
Thank you
The event object that is passed in to the callback has a widget attribute which is a reference to the widget that got the event. So, event.widget is what you want to print.

Resources