How to call a function and pass variable in tkinter scale widget? - python

I am trying to use tkinter scale widget to increase or decrease a Label widget text size on the canvas. I am using the the scale widget variable value to do so. The text size changing when the button is clicked (my_btn) but I want it to change and I would like to increase or decrease the text size by dragging the scale widget left or right. When I am dragging the scale widget the program is prompting an error:
TypeError: <lambda>() takes 0 positional arguments but 1 was given.
The code is shown below. Please tell me how can I control the text size by dragging the scale widget directly.
##### Imports #####
from tkinter import *
from tkinter import ttk
###################################################################
# FUNCTIONS #
###################################################################
def slide(mainCanvas, textSlider):
slide.textLayer.place_forget()
slide.textLayer = Label(mainCanvas, text="TEST", font=('Arial', int(textSlider.get())), bg='white', fg='red')
slide.textLayer.place(relx=0.5, rely=0.5, anchor='center')
###################################################################
# MAIN #
###################################################################
def main():
root = Tk()
rootWidth = 500
rootHeight = 510
root.minsize(rootWidth, rootHeight)
mainFrameCanvas = Frame(root, bg='white', width=rootWidth, height=350)
mainFrameCanvas.pack(anchor='n')
mainCanvas = Canvas(mainFrameCanvas, width=rootWidth, height=350, bg='white', relief='solid', borderwidth=1)
mainCanvas.pack(fill='x', expand=1)
mainFrameSlider = Frame(root, bg='white', width=rootWidth, height=150, relief='solid', borderwidth=1)
mainFrameSlider.pack(anchor='s')
slide.textLayer = Label(mainCanvas, text="TEST", bg='white', fg='red')
slide.textLayer.place(relx=0.5, rely=0.5, anchor='center')
var = DoubleVar()
textSlider = Scale(mainFrameSlider, from_=10, to=30, variable=var, orient=HORIZONTAL, length=rootWidth*0.9, troughcolor='white', showvalue=1, bg='white', highlightbackground='white')
textSlider.bind("<Button-1>", lambda: slide(mainCanvas, textSlider))
textSlider.place(x=20, y=40)
my_btn = Button(root, text="Click Me!", command=lambda: slide(mainCanvas, textSlider)).pack()
root.mainloop()
###################################################################
# RUN #
###################################################################
def run():
print('\nStart script')
main()
print('Finished script')
run()

Callback for an event binding expects an argument, the Event object. So
textSlider.bind("<Button-1>", lambda: slide(mainCanvas, textSlider))
should be changed to:
textSlider.bind("<Button-1>", lambda e: slide(mainCanvas, textSlider))
Note that you don't need to remove and recreate the label inside slide(), just update the font for the label:
def slide(mainCanvas, textSlider):
slide.textLayer.config(font=('Arial', int(textSlider.get())))

Related

Tkinter frame, how to make it resize after destroying child widgets and repopulating with new widgets

My app displays data from a sqlite database. After displaying the initial data, say 50 records when my program destroys the widgets associated with that initial display and repopulates based on a search function the frame container no longer adapts to the size of the new widgets placed in it.
To make this simpler I've created smaller simpler version of the problem. My initial version of this post was from that actual app and difficult understand. Lesson learned. Running this code defaults to showin 50 rows initially, Then try entering 100 in the search field and click the button and see that the window does not expand to fit. Then can then try entering 40 and see that the window doesn't shrink. I'm using Python v3.11.
Here's the simplified code that captures the essence of my problem:
from tkinter import *
from tkinter import ttk
def displayData():
for widgets in second_frame.winfo_children():
widgets.destroy()
Count = Search.get()
print("displayData entered with value of " + str(Count))
nRows = int(Count)
rows = []
for i in range(nRows):
label_list = Label(second_frame, text='Row '+str(i), relief=GROOVE, font=("Arial 11"), width=17, anchor='w', justify=LEFT)
label_list.grid(row=i, column=0, sticky=NSEW)
rows.append(label_list)
def _on_mousewheel(event):
my_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# Set up tkinter GUI
root = Tk()
root.geometry("1250x810+100+0")
root.title("Test")
# Control Frame
control_frame = Frame(root, height=10, highlightbackground="blue", highlightthickness=2, pady=3, padx=3)
control_frame.pack(fill=X)
Search = StringVar()
Search.set('50')
ent_search = ttk.Entry(control_frame, width=15, textvariable=Search)
ent_search.pack(padx=5, pady=5, side=RIGHT)
# Create a Main Frame
main_frame = Frame(root, highlightbackground="yellow", highlightthickness=2)
main_frame.pack(fill=BOTH, expand=1)
# Create a Canvas
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_canvas.bind_all("<MouseWheel>", _on_mousewheel)
# Add a Scrollbar to the Canvas
my_scrollbar = ttk.Scrollbar(main_frame, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
# Configure the Canvas
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion = my_canvas.bbox('all')))
# Create Another Frame inside the Canvas
second_frame = Frame(my_canvas)
# Add the New Frame to a Window inside the Canvas
my_canvas.create_window((0,100), window=second_frame, anchor='nw')
btn_search = Button(control_frame, text='Search', command=displayData)
btn_search.pack(padx=5, pady=5, side=RIGHT)
rows = []
displayData()
root.mainloop()
```
FYI: my actual app is displaying database records based on search parameters. The programs works fine except for the fact once the initial size of 'second_frame' is set up it never changes. So if a search happens to display more records than that initial display, those records will be hidden. e.g. Initial display shows 50 records, if a search asks to display 75 records, 25 of them will not be visible...So the second_frame doesn't resize to show the added widgets in the search.
My workaround for now is just to initially display more records then I anticipate most searches will need to display.
How can I make 'second_frame' adapt to new amounts of widgets on new searches? The simplified code above emulates my issue.
Yahoo, found a solution to this issue. Essentially one must first destroy and recreate the canvas used to display the records. Some complications due to the scrollbar widget being linked to the frame containing the canvas. I solved this by creating an intermediate frame to contain the frame referenced by the scrollbar and the used the methods winfo_childred() and destroy() to delete the frames/canvas/widgets before recreating them anew to display the new data.
Here's the modified example code that now works correctly:
from tkinter import *
from tkinter import ttk
def makeCanvas():
# Create a Canvas
global my_canvas
global second_frame
global my_scrollbar
global main_frame
mid_frame = Frame(main_frame)
mid_frame.pack(fill=BOTH, expand=1)
my_canvas = Canvas(mid_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_canvas.bind_all("<MouseWheel>", _on_mousewheel)
# Add a Scrollbar to the Canvas
my_scrollbar = ttk.Scrollbar(mid_frame, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
# Configure the Canvas
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion = my_canvas.bbox('all')))
# Create Another Frame inside the Canvas
second_frame = Frame(my_canvas)
# Add the New Frame to a Window inside the Canvas
my_canvas.create_window((0,0), window=second_frame, anchor='nw')
def displayData():
global displayCnt
print("displayData entered with displayCnt of " + str(displayCnt))
if displayCnt != 0:
for widgets in main_frame.winfo_children():
widgets.destroy()
Count = Search.get()
makeCanvas()
print("displayData entered with value of " + str(Count))
nRows = int(Count)
rows = []
for i in range(nRows):
label_list = Label(second_frame, text='Row '+str(i), relief=GROOVE, font=("Arial 11"), width=17, anchor='w', justify=LEFT)
label_list.grid(row=i, column=0, sticky=NSEW)
rows.append(label_list)
displayCnt += 1
def _on_mousewheel(event):
my_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
# Set up tkinter GUI
root = Tk()
root.geometry("1250x810+100+0")
root.title("Test")
# Control Frame
control_frame = Frame(root, height=10, highlightbackground="blue", highlightthickness=2, pady=3, padx=3)
control_frame.pack(fill=X)
Search = StringVar()
Search.set('50')
ent_search = ttk.Entry(control_frame, width=15, textvariable=Search)
ent_search.pack(padx=5, pady=5, side=RIGHT)
# Create a Main Frame
main_frame = Frame(root, highlightbackground="yellow", highlightthickness=2)
main_frame.pack(fill=BOTH, expand=1)
# Create a Canvas
#makeCanvas()
btn_search = Button(control_frame, text='Search', command=displayData)
btn_search.pack(padx=5, pady=5, side=RIGHT)
rows = []
displayCnt = 0
displayData()
root.mainloop()
I ended up creating a separate function makeCanvas() to recreate the frame/canvas structure upon each new display of data. For this much simplified example the data consists simply of numbered Label widgets.

Buttons and tables not visible in tkinter Python

I have tried to add a scrollbar through canvas and added a frame named frametwo in canvas. I am adding a few buttons and a table in that frame but nothing is visible. If I add all these things in the root then they become visible. I have tried different things but nothing worked.
Here is the code that I wrote
import myvariant
from tkinter import ttk
from tkinter import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from pandas import DataFrame
from PIL import ImageTk, Image
rsid_list=[8,9,5,5]
mv = myvariant.MyVariantInfo()
def main():
main_window = Tk()
app = info(main_window)
main_window.mainloop()
class info:
def __init__(self, root):
self.root = root
self.root.title('VCESS-ExAC')
self.root.geometry('1600x800+0+0')
self.root.configure(background='light grey')
main_frame = Frame(self.root)
main_frame.pack(fill=BOTH, expand=1, padx=0, pady=0)
main_frame.place(x=0, y=0, width=1600, height=800)
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_scroll = ttk.Scrollbar(main_frame, orient=VERTICAL, command=my_canvas.yview)
my_scroll.pack(side=RIGHT, fill=Y)
my_canvas.configure(yscrollcommand=my_scroll.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.config(scrollregion=my_canvas.bbox(ALL)))
self.frametwo = Frame(my_canvas)
my_canvas.create_window((0, 0), window=self.frametwo, anchor='nw')
table1 = LabelFrame(self.root, text="Retreived Data") ################
table1.pack(fill="both", expand="yes", padx=0, pady=0) ###################
table1.place(x=40, y=250, width=250, height=380)
table = ttk.Treeview(table1, height="8") #################
table['columns'] = ['rsID']
table.column('#0', width=120, minwidth=25)
table.column('rsID', anchor=W, width=120)
table.heading('#0', text='Serial No.', anchor=W)
table.heading('rsID', text='rsID', anchor=W)
for i in range(len(rsid_list)):
table.insert(parent='', index='end', iid=i, text=i + 1,
values=(rsid_list[i]))
table.place(x=0, y=0) ##########################
# VERTICAL SCROLLBAR
yscrollbar = ttk.Scrollbar(table1, orient=VERTICAL, command=table.yview) #############
yscrollbar.pack(side=RIGHT, fill='y') ##################
# HORIZONTAL SCROLLBAR
xscrollbar = ttk.Scrollbar(table1, orient=HORIZONTAL, command=table.xview) ###################
xscrollbar.pack(side=BOTTOM, fill='x') #######################
table.configure(yscrollcommand=yscrollbar.set, xscrollcommand=xscrollbar.set) ##############
table.pack(side=LEFT)
btn_download = Button(self.frametwo, text='Save File',
font=("Times New Roman", 14, 'bold'), bd=3, relief=RIDGE,
cursor='hand2', bg='#154857', fg='white', activeforeground='white',
activebackground='#154857')
btn_download.place(x=190, y=640, width=120)
btn_graph = Button(self.frametwo, text='Graph',
font=("Times New Roman", 14, 'bold'), bd=3, relief=RIDGE,
cursor='hand2', bg='#154857', fg='white', activeforeground='white',
activebackground='#154857')
btn_graph.place(x=530, y=640, width=120)
if __name__ == '__main__':
main()
Looking forward for any possible solution.
You have created the buttons inside self.frametwo. You are using place, which means that the buttons don't affect the size of the frame. Since you don't give self.frametwo a size, it defaults to one pixel wide and one pixel tall. Therefore, the frame is essentially invisible and thus all buttons inside the frame are invisible.
You can easily see this by switching to using pack or grid for the buttons. When you use pack or grid, the parent frame by default will grow or shrink to fit its children. Thus, using either of these for the buttons will cause the frame to grow just big enough to show the buttons.
I am adding ... a table in that frame but nothing is visible.
You are not adding the table to the frame, you are adding it to the root window. If you want to add it to the frame, you must use the frame as its parent. And again, you should probably not use place. place is almost never the right choice unless you are prepared to do a lot of extra work to make sure widgets are visible and responsive to changes in widget size, font size, display resolution, etc.

Tkinter - Scrollbar does not resize when number of labels inside a frame changes

I am trying to create a GUI where left hand side is a Listbox (contained inside a frame f2) that displays employee ID's and right side is another frame second_frame (contained inside canvas and outer frame f3) that shows transaction details of each selected employee in the form of labels.
Each employee can have multiple transactions. So, The number of labels had to be dynamic, i.e. for first selected item in listbox, there could be two labels and for second selected item in listbox, it could be hundred. For every selection, I am calling two functions to destroy old labels and create new labels. While the code works fine, I am having trouble resizing the scrollbar according to the selected listbox entry. I am new to Tkinter, Please advise. Below is my code.
Also note, the test() function when called from outside any function displays the scroll bar, but does not display anything when called from within any function.
# -*- coding: utf-8 -*-
from tkinter import *
'''def test():
for i in range(0,50):
for j in range (0,7):
Label(second_frame, text=f'{i}{j}', width=20).grid(row=i, column=j, pady=5,padx=5)
'''
# --- function ---
def destroy_frame():
#f1.grid_forget()
print("destroying frame")
for label in second_frame.winfo_children():
label.destroy()
def create_frame(val):
print("creating new frame")
for i in range(0,val):
for j in range (5):
Label(second_frame, text=f'{i} {j} ', relief=GROOVE, width=10).grid(row=i, column=j, pady=5,padx=5)
def on_selection(event):
# here you can get selected element
print('previous:', listbox.get('active'))
print(' current:', listbox.get(listbox.curselection()))
# or using `event`
print('(event) previous:', event.widget.get('active'))
print('(event) current:', event.widget.get(event.widget.curselection()))
print (listbox.get(listbox.curselection()))
if (listbox.get(listbox.curselection()) == "Eid 1"):
destroy_frame()
create_frame(100)
elif (listbox.get(listbox.curselection()) == "Eid 2"):
destroy_frame()
create_frame(200)
print('---')
root = Tk()
root.geometry('800x500')
#Create base Frames
f1 = Frame(width=800, height=50, bg="yellow", colormap="new")
f1.grid(row=0, columnspan=2)
f1.grid_propagate(False)
f2 = Frame(width=200, height=425, bg="light blue", colormap="new")
f2.grid(row=1, column=0)
f2.grid_propagate(False)
f3 = Frame(width=600, height=425, bg="light green", colormap="new")
f3.grid(row=1, column=1)
f3.grid_propagate(False)
#Create header Label
l1_f1 = Label(f1, text="Employee Purchase Entries:", bg="yellow")
l1_f1.grid(row=0, column=0)
#Create Listbox
listbox = Listbox(f2, bg="light blue", width=40, height=400)
listbox.grid(row=0, column=0)
#Add Scrollbar to ListBox
list_scrollbar = Scrollbar(f2)
list_scrollbar.grid(row=0, column=1, sticky=NSEW)
#Enter Listbox Data
listbox.insert(1, 'Eid 1')
listbox.insert(2, 'Eid 2')
listbox.bind('<<ListboxSelect>>', on_selection)
#configure the Listbox and Scrollbar
listbox.config(yscrollcommand = list_scrollbar.set)
list_scrollbar.config(command = listbox.yview)
#Create a Canvas
my_canvas = Canvas(f3, width=580, height=425, bg="light green")
#my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
my_canvas.grid(row=0, column=0)
#Add a Scrollbar to the canvas
my_scrollbar = Scrollbar(f3, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.grid(row=0, column=1, sticky=NSEW)
#configure the canvas
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e : my_canvas.configure(scrollregion = my_canvas.bbox("all")))
#Create another frame inside the canvas
second_frame = Frame(my_canvas)
#Add the new frame to a window in the canvas
my_canvas.create_window((0,0), window=second_frame, anchor="nw")
#test()
root.mainloop()
Your canvas isnt trigger the configure event when you add widgets to your frame. Instead your frame is been triggerd.
So you need to put this line:
second_frame.bind('<Configure>', lambda e : my_canvas.configure(scrollregion = my_canvas.bbox("all")))
after creating second_frame

Tkinter: Get actual canvas size during init (including auto-stretching by "sticky")

I am trying to center some text on a canvas, during program initialization. However, winfo_width/height don't return the correct values for me in this case, so I cannot properly place text using Canvas method create_text(), since I cannot calculate the correct center position. I can only get the correct dimensions post-init, say if I query the size in a button callback.
How to solve this? Here's the code:
try:
from Tkinter import *
except ImportError:
from tkinter import *
class GUI:
def __init__(self):
# root window of the whole program
self.root = Tk()
self.root.minsize(800, 600)
# canvas/viewport for displaying the image and drawing vectors on it
self.viewport = Canvas(self.root, bd=2, relief='ridge', highlightthickness=0)
# define master buttons for audio preview, render to file and clear all vectors
btn_preview = Button(self.root, text='Preview', command=self.Preview)
# layout managing
self.viewport.grid(columnspan=3, padx=5, pady=5, sticky=N+S+W+E)
btn_preview.grid(row=1, padx=85, pady=10, ipadx=5, ipady=5, sticky=W)
# position text on canvas to notify user he can load the image by clicking it
self.viewport.update_idletasks()
textpos = (self.viewport.winfo_width(),self.viewport.winfo_height())
print(textpos)
self.viewport.create_text(textpos[0] / 2, textpos[1] / 2, text="Click here to load an image!", justify='center', font='arial 20 bold')
# weights of rows and columns
self.root.rowconfigure(0, weight=1)
self.root.columnconfigure(0, weight=1)
def Preview(self, event=None):
textpos = (self.viewport.winfo_width(),self.viewport.winfo_height())
print(textpos)
if __name__ == '__main__':
mainwindow = GUI()
mainloop()
Compare the dimensions returned on init to dimensions after you click the Preview button. They're different!
OK haha, I managed to solve it after checking this answer. I needed to bind <Configure> event to the canvas, and define a function that does stuff when window is resized. It's working now!
try:
from Tkinter import *
except ImportError:
from tkinter import *
class GUI:
textid = 0
def __init__(self):
# root window of the whole program
self.root = Tk()
self.root.minsize(800, 600)
# canvas/viewport for displaying the image and drawing vectors on it
self.viewport = Canvas(self.root, bd=2, relief='ridge', highlightthickness=0)
# define master buttons for audio preview, render to file and clear all vectors
btn_preview = Button(self.root, text='Preview', command=self.Preview)
# layout managing
self.viewport.grid(columnspan=3, padx=5, pady=5, sticky=N+S+W+E)
btn_preview.grid(row=1, padx=85, pady=10, ipadx=5, ipady=5, sticky=W)
# weights of rows and columns
self.root.rowconfigure(0, weight=1)
self.root.columnconfigure(0, weight=1)
# bind mouse actions for the canvas
self.viewport.bind('<Configure>', self.ResizeCanvas)
def Preview(self, event=None):
textpos = (self.viewport.winfo_width(),self.viewport.winfo_height())
print(textpos)
def ResizeCanvas(self, event):
if self.textid != 0:
event.widget.delete('openfiletext')
# position text on canvas to notify user he can load the image by clicking it
textpos = (self.viewport.winfo_width(), self.viewport.winfo_height())
self.textid = self.viewport.create_text(textpos[0] / 2, textpos[1] / 2, text="Click here to load an image!", justify='center', font='arial 20 bold', tag='openfiletext')
if __name__ == '__main__':
mainwindow = GUI()
mainloop()

Is there a way to gray out (disable) a tkinter Frame?

I want to create a GUI in tkinter with two Frames, and have the bottom Frame grayed out until some event happens.
Below is some example code:
from tkinter import *
from tkinter import ttk
def enable():
frame2.state(statespec='enabled') #Causes error
root = Tk()
#Creates top frame
frame1 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame1.grid(column=0, row=0, padx=10, pady=10)
button2 = ttk.Button(frame1, text="This enables bottom frame", command=enable)
button2.pack()
#Creates bottom frame
frame2 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame2.grid(column=0, row=1, padx=10, pady=10)
frame2.state(statespec='disabled') #Causes error
entry = ttk.Entry(frame2)
entry.pack()
button2 = ttk.Button(frame2, text="button")
button2.pack()
root.mainloop()
Is this possible without having to individually gray out all of the frame2's widgets?
I'm using Tkinter 8.5 and Python 3.3.
Not sure how elegant it is, but I found a solution by adding
for child in frame2.winfo_children():
child.configure(state='disable')
which loops through and disables each of frame2's children, and by changing enable() to essentially reverse this with
def enable(childList):
for child in childList:
child.configure(state='enable')
Furthermore, I removed frame2.state(statespec='disabled') as this doesn't do what I need and throws an error besides.
Here's the complete code:
from tkinter import *
from tkinter import ttk
def enable(childList):
for child in childList:
child.configure(state='enable')
root = Tk()
#Creates top frame
frame1 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame1.grid(column=0, row=0, padx=10, pady=10)
button2 = ttk.Button(frame1, text="This enables bottom frame",
command=lambda: enable(frame2.winfo_children()))
button2.pack()
#Creates bottom frame
frame2 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame2.grid(column=0, row=1, padx=10, pady=10)
entry = ttk.Entry(frame2)
entry.pack()
button2 = ttk.Button(frame2, text="button")
button2.pack()
for child in frame2.winfo_children():
child.configure(state='disable')
root.mainloop()
Based on #big Sharpie solution here are 2 generic functions that can disable and enable back a hierarchy of widget (frames "included"). Frame do not support the state setter.
def disableChildren(parent):
for child in parent.winfo_children():
wtype = child.winfo_class()
if wtype not in ('Frame','Labelframe','TFrame','TLabelframe'):
child.configure(state='disable')
else:
disableChildren(child)
def enableChildren(parent):
for child in parent.winfo_children():
wtype = child.winfo_class()
print (wtype)
if wtype not in ('Frame','Labelframe','TFrame','TLabelframe'):
child.configure(state='normal')
else:
enableChildren(child)
I think you can simply hide the whole frame at once.
If used grid
frame2.grid_forget()
If used pack
frame2.pack_forget()
In your case the function would be
def disable():
frame2.pack_forget()
To enable again
def enable():
frame2.pack()
grid_forget() or pack_forget() can be used for almost all tkinter widgets
this is a simple way and reduces the length of your code, I'm sure it works

Categories

Resources