How to remove widgets from grid in tkinter? - python

I have a grid in a tkinter frame which displays query results. It has a date field that is changed manually and then date is used as a parameter on the query for those results. every time the date is changed, obviously the results change, giving a different amount of rows. The problem is that if you get less rows the second time you do it, you will still have results from the first query underneath those and will be very confusing.
My question is, how can i remove all rows from the frame that have a row number greater than 6 (regardless of what's in it)?
By the way, I'm running Python 3.3.3. Thanks in advance!

Calling the method grid_forget on the widget will remove it from the window -
this example uses the call grid_slaves on the parent to findout all
widgets mapped to the grid, and then the grid_info call to learn about
each widget's position:
>>> import tkinter
# create:
>>> a = tkinter.Tk()
>>> for i in range(10):
... label = tkinter.Label(a, text=str(i))
... label.grid(column=0, row=i)
# remove from screen:
>>> for label in a.grid_slaves():
... if int(label.grid_info()["row"]) > 6:
... label.grid_forget()

Related

Is there a way to get the row number at the insertion point of a Tkinter text widget?

I'm trying to change the color of text in a text widget similar to how a VS code changes the code colors:
Like this:
To do this I have a Text widget and I have a function handleCodeEditor bound to <KeyRelease> that will split the text widget's contents by row and then conditionally modify text colors. To make the process less exhaustive I would like to only modify the current row, but I am not aware of any method that would give me the row number.
Any ideas?
def getrow(event):
index = textbox.index(INSERT)
row = index.split(".")[0]
print(row)
root = Tk()
textbox = Text(root)
textbox.bind("<KeyRelease>",getrow)
textbox.pack()
root.mainloop()
This function will print the current line number whenever a key is released.
Hope this is what you're after.

How to know in what row is a label on a grid in Tkinter?

I have a table (grid) I'm creating, constituted of labels.
These labels are showing elements I'm adding to a list, therefore, when I add a new obj to the list, the table will grow, showing more labels.
My intent is that I can click a label and have it print the row of the table that that label is in.
import tkinter as tk
phrasesdb = []
def debug(event):
#this is where I'd have it print the row
#but how do I get it?
#for example, it the label I pressed was in the row 2, it'd print 2
print( ??? )
#Add obj to list
def addline():
##This creates new obj with values from the input fields, and inserts it in the list
newobj = {"text": newtext.get()} #This is getting the text from an Entery
phrasesdb.append(newobj)
##This shows new obj on the table
newesttext = tk.Label(tableframe, text=newobj["text"])
newesttext.grid(row=len(phrasesdb), column=1, sticky=tk.W)
newesttext.bind("<Double-Button-1>", debug)
I'm already able to show them in the table, and to have it recognize I'm pressing the correct label (tested with a simple print("yup, this is it") ), but I'm not being able to figure out how to access the row of the label I'm clicking.
I'm kinda new to python and especially tkinter, so sorry if this is a really easy question, but I'm not finding the answer anywhere else...
You can use the grid_info method which will return a dictionary of the item's grid attributes.
def debug(event):
widget = event.widget
info = widget.grid_info()
row = info['row']
If I understand your problem correctly using .grid_info()['row'] on the label you already received after clicking should return the result you need.

Programatically add and remove tkinter python labels causes IndexError: list index out of range

Sorry for the vague title but I didn't know how to explain myself better. Basically what I try to do in tkinter here is adding and removing labels. The label value gets updated so that I always have an increment of 1 even though I deleted a label in the beginning. If I generate labels and delete them from the bottom up I have no problems but it I delete one from the middle and then try to clean my list I get an error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1536, in __call__
return self.func(*args)
File "/Users/XXXX/Helper/development/dynamicListLabels.py", line 21, in <lambda>
labelList[index].append(ttk.Button(root, text="Remove", command=lambda: removeLabel(labelList[index][0], index)))
IndexError: list index out of range
My python code looks like this:
#!/usr/bin/python
from Tkinter import *
import ttk
def removeLabel(labelToRemove, bla):
labelList[labelToRemove.get()][1].destroy()
labelList[labelToRemove.get()][2].destroy()
del labelList[labelToRemove.get()]
for label in labelList:
index = labelList.index(label)
label[0].set(index)
def addNewLabel():
labelList.append([IntVar()])
index = len(labelList) - 1
labelList[index][0].set(index)
labelList[index].append(ttk.Label(root, textvariable=labelList[index][0]))
labelList[index].append(ttk.Button(root, text="Remove", command=lambda: removeLabel(labelList[index][0], index)))
labelList[index][1].grid(column=0)
labelList[index][2].grid(column=1, row=labelList[index][1].grid_info()['row'])
root = Tk()
labelList = []
ttk.Button(root, text="add label", command=addNewLabel).grid(column=1, row=0)
root.mainloop()
And my GUI looks like this:
Thanks for your help!
Design
The main problem comes when dealing with different indexes. Trying to manipulate them carefully leads to complicated operations resulting in a long and inefficient code. To remedy to this problem, we simply get rid of them and take advantage of the label class variable Tkinter.IntVar() you already are using. This gives us full control of the labels and associated widgets.
An other efficient decision to take that prevents from getting lot of headache is to attach each (label, button) couple widgets to a unique Tkinter.Frame() instance. This offers the advantage of deleting the frame using destroy() method leading automatically to the destruction of the widgets it contains. In the same time, this keeps the look of your GUI and makes your it scalable as it offers you the possibility to add more widgets.
Designing addNewLabel()
There is nothing new here compared to your original code except, as I said in 2. each (label, button) couple will be drawn into a single and unique Tkinter.Frame() instance. Of course, the list frames must be declared global in this method.
Designing removeLabel()
From 1. the only argument we need to pass to removeLabel() is the Tkinter variable (var in the code below) inherent to the label we want to get rid of.
We need then to loop over list of frames (frames in the code below) using winfo_children() to seek for the label which has the text variable we are looking for.
Note that because I draw the label before the button inside individual frames, winfo_children() returns as first widget list element the label
winfo_children():
Returns a list containing the path names of all the children of window. Top-level windows are returned as children of their logical
parents. The list is in stacking order, with the lowest window first,
except for Top-level windows which are not returned in stacking order.
Use the wm stackorder command to query the stacking order of Top-level
windows.
This is why it is correct to write : if frame.winfo_children()[0].var == var and destroy the frame that contains the label which satisfies this condition.
Solution
Here is the program. I commented the lines which I think deserve to be commented:
'''
Created on Jun 25, 2016
#author: billal begueradj
'''
from Tkinter import *
import ttk
def removeLabel(var):
global frames
z = -1
# Loop over the list of rames
for frame in frames:
z = z + 1
# Check the text variable of the label of this frame
if frame.winfo_children()[0].var == var:
# Destroy the related frame
frame.destroy()
# Update the size of the list of frames
frames = frames[:z] + frames[z+1:]
# Do not forget to always rest this flag back to -1
z = -1
# Update the labels' numbers
r = 0
for frame in frames:
frame.winfo_children()[0].var.set(r)
r = r + 1
def addNewLabel():
global frames, i
var = IntVar()
frame = Frame(root)
i = i + 1
frame.grid(row=i, column=0)
var.set(len(frames))
l = ttk.Label(frame, textvariable=var)
l.grid(row=0, column=0)
l.var = var
b = ttk.Button(frame, text="Remove", command=lambda: removeLabel(var))
b.grid(row=0, column=1)
frames.append(frame)
if __name__ == '__main__':
root = Tk()
frames = []
i = 1
ttk.Button(root, text="add label", command=addNewLabel).grid(column=0, row=0)
root.mainloop()
Demo
Let us create 6 labels:
Now let us delete the label number 3. You can see that the numbering of the labels is automatically updated:
Now let us add a new label. You can see the newly added label has a number which is consecutive to the last existing label number in the list:
Note that the length of the list is updated all the time as you wanted.

Why does a frame keep a non null size when emptied?

I have a list of tasks to do. That list has several subgroups, that I want to display together. To do so I created a frame for each subgroup and display each tasks in the corresponding frame with grid(). Once a task is done, I destroy the corresponding label.
When a subgroup is empty from the start, tkinter reduce the size of the frame to 0, and I don't see it. But when a subgroup that had tasks becomes empty it seems to have a minimum size and does not disappear. Is there a way to prevent that?
Here is an example of my problem: the frame0 does not appear because it is empty (which is good). But even after destroying the labels of frame1 (with the buttons) the frame1 keeps one row for some reason.
import tkinter as tk
window=tk.Tk()
frame0=tk.Frame(window,bg='green')
frame1=tk.Frame(window,bg='red')
frame2=tk.Frame(window,bg='blue')
window.grid_columnconfigure(0,weight=1)
frame0.grid(sticky='ew')
frame1.grid(sticky='ew')
frame2.grid(sticky='ew')
labelList1=[]
for i in range(2):
labelList1.append(tk.Label(frame1,text='Task type 1'))
labelList1[-1].grid(sticky='ew',pady=5)
tk.Label(frame2,text='Task type 2: 1').grid(sticky='ew',pady=5)
tk.Label(frame2,text='Task type 2: 2').grid(sticky='ew',pady=5)
for i in range(2):
tk.Button(window,text='Destroy {}'.format(i),command=labelList1[i].destroy).grid()
window.mainloop()
I have found a way around it by using only one frame and using rows 1 to 100 for subgroup 0, 101 to 200 to subgroup 1, etc But I don't find this elegant.
Tkinter will not resize a Frame if it contains no child widgets. So if you had a callback that removed all your labels at once the Frame wouldn't resize at all. A simple (if somewhat kludgy) workaround is to add a dummy Frame widget to frame1. Eg, add this line
tk.Frame(frame1).grid()
before your labelList1 loop.
FWIW, here's a variation of your code that destroys both the label and the corresponding button.
import tkinter as tk
window=tk.Tk()
frame0=tk.Frame(window,bg='green')
frame1=tk.Frame(window,bg='red')
frame2=tk.Frame(window,bg='blue')
window.grid_columnconfigure(0,weight=1)
frame0.grid(sticky='ew')
frame1.grid(sticky='ew')
frame2.grid(sticky='ew')
tk.Frame(frame1).grid()
num_tasks = 3
labelList1=[]
for i in range(num_tasks):
labelList1.append(tk.Label(frame1,text='Task type 1: {}'.format(i)))
labelList1[-1].grid(sticky='ew',pady=5)
tk.Label(frame2,text='Task type 2: 1').grid(sticky='ew',pady=5)
tk.Label(frame2,text='Task type 2: 2').grid(sticky='ew',pady=5)
def kill_label_and_button(l, b):
l.destroy()
b.destroy()
for i in range(num_tasks):
b = tk.Button(window,text='Destroy {}'.format(i))
b.config(command=lambda l=labelList1[i], b=b: kill_label_and_button(l, b))
b.grid()
window.mainloop()

How to use tkinter slider `Scale` widget with discrete steps?

Is it possible to have a slider (Scale widget in tkinter) where the possible values that are displayed when manipulating the slider are discrete values read from a list?
The values in my list are not in even steps and are situation dependent.
From all the examples I've seen, you can specify a minimum value, a maximum value and a step value (n values at a time), but my list might look like this:
list=['0', '2000', '6400', '9200', '12100', '15060', '15080']
Just as an example. To reiterate, I want it go from for instance list[0] to list[1] or list[6] to list[5] when pulling the slider.
If anyone has any other suggestion for easily being able to pick a value from hundreds of items in a list, I'm all ears. I tried the OptionMenu widget but it gets to extensive and hard get a view of.
Edit you could set the command of the slider to a callback, have that callback compare the current value to your list and then jump to the nearest by calling set() on the slider
so:
slider = Slider(parent, from_=0, to=100000, command=callback)
and:
def callback(event):
current = event.widget.get()
#compare value here and select nearest
event.widget.set(newvalue)
Edit:
to show a complete (but simple example)
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
valuelist = [0,10,30,60,100,150,210,270]
def valuecheck(value):
newvalue = min(valuelist, key=lambda x:abs(x-float(value)))
slider.set(newvalue)
root = tk.Tk()
slider = tk.Scale(root, from_=min(valuelist), to=max(valuelist), command=valuecheck, orient="horizontal")
slider.pack()
root.mainloop()
i've tested this in python 2.7.6 and 3.3.2, even when dragging the slider this jumps to the nearest value to where the mouse is currently as opposed to only jumping when you let go of the slider.

Categories