I'm trying to create a small program where I want to edit a big size table. See the example program below.
from tkinter import *
def table_generator(root):
size = 10
table_frame = Frame(root)
for i in range(size): # Rows
for j in range(size): # Columns
b = Text(table_frame, width=2, height=1)
b.grid(row=i, column=j)
table_frame.pack(side="left", anchor="nw")
if __name__ == '__main__':
application = Tk()
table_generator(application)
mainloop()
This works as I intended, but when I increase my size to 40, the program is slow to start and freezes if I try to move it. However, I want to increase this to around 200 at some point.
Does any of you have an alternative for this? I want to be able to enter a single character and change the background color in the end. Or some way of coding so that it does work with high numbers of Entry of Text fields?
Thanks
Related
I try to add image of dice to ma dice simulator but when i try to add image of it to my label, the label shrinks and image isn't there.
code is following(I hope you'll understand mi shitty coding style lol):
from tkinter import *
import random
from PIL import Image, ImageTk
window = Tk(className="dice simulator")
window.geometry("600x600")
picture_open = Image.open(r"/home/dan/dice1.png")
picture_resize = picture_open.resize((200, 200))
picture = ImageTk.PhotoImage(picture_resize)
def throw():
number = random.randint(1, 6)
if number == 1:
dice["image"] = picture
background = Label(window, bg="#000000", height=60, width=80)
background.pack()
background.pack_propagate(0)
buttonx1 = Button(window, text="throw 1x", command=throw)
buttonx1.pack()
buttonx1.pack_propagate(0)
buttonx1.place(x=255, y=475)
dice = Label(window, height=12, width=20, bg="#ffffff")
dice.pack()
dice.pack_propagate(0)
dice.place(x=220, y=150)
window.mainloop()
I tried many things as you can see I set the height and width for the label and I use pack_propagate function but it still doesn't work how it should:D
I recommend you to try to run it and I hope you'll understand everything, if not I will try to answer all questions:) Thanks for any answer
Stackoverflow still says that my post is mostly code and i dont know what more should i write here lol, I hope that will be enough.
I tried to make in smaller and it appeared in the label but the label got much smaller so the problem is shrinking of label
I noticed the issue (among other "issues" with your code (some of which I have already mentioned)), you set width and height for the label, while there is no image, those numbers are treated as characters of the font size and stuff, when you assign an image, those are treated as pixels so the easiest would be to remove width and height arguments from the Label, leave it like this (the simplest option):
dice = Label(window, bg="#ffffff")
Overall I would suggest these improvements (just compare to your code):
from tkinter import Tk, Label, Button
import random
from PIL import Image, ImageTk
window = Tk(className="dice simulator")
window.geometry("600x600")
picture_open = Image.open(r"pause_btn.png")
picture_resize = picture_open.resize((200, 200))
picture = ImageTk.PhotoImage(picture_resize)
def throw():
number = random.randint(1, 6)
if number == 1:
dice.config(image=picture)
background = Label(window, bg="#000000")
background.place(x=0, y=0, relwidth=1, relheight=1)
buttonx1 = Button(window, text="throw 1x", command=throw)
buttonx1.place(x=255, y=475)
dice = Label(window, bg="#ffffff")
dice.place(x=220, y=150)
window.mainloop()
Quickly go over:
Don't use .<manager>_propagate() on sth that is not a frame or a window
Use only one layout manager per widget
I added how to add a proper background image (for this case at least, notice the use of relwidth and relheight and other things in that .place() method)
Added the solved current issue
EDIT 1: I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
EDIT 2: this is how I would suggest storing images for this case:
image_dct = {1: picture_1, 2: picture_2}
Then you can access them like so:
def throw():
number = random.randint(1, 6)
dice['image'] = image_dct[number]
(beware key errors if you haven't yet completed the dictionary)
Another fancy thing would be to do sth like this:
image_dct = {key: ImageTk.PhotoImage(Image.open(value).resize((200, 200))) for key, value
in enumerate([f'path/to/image_{img_number}' for img_number in range(amount_of_images)])}
As #TheLizzard suggested you can also use tuples (since they are faster, less space consuming and there isn't really a need to modify these):
image_list = tuple(ImageTk.PhotoImage(Image.open(image).resize((200, 200))) for image
in (f"path/to/image_{img_number}" for img_number in range(amount_of_images)))
Another small note about tuples: for accessing you could use sth like this:
def throw():
number = random.randint(1, 6)
dice['image'] = image_list[number - 1] # -1 because first item has index 0 and the range starts from 1
and here you would store all images in one place (directory) and all of them would be named like so (as an example) (change the range accordingly of what is the first image name):
"image_1"
"image_2"
"image_3"
and that would make it very easy to add all of those image at runtime to the dictionary and assign the correct keys to them, making it super easy to add more images and stuff
If you have any questions, be sure to ask
I am making a board game with Tkinter. I create a grid:
def create_grid(self):
self.grid_frame = Frame(window)
self.grid_frame.grid(row=1, column=0)
self.grid_picture = PhotoImage(file="grid.PNG")
self.grid_label = Label(self.grid_frame, image=self.grid_picture)
self.grid_label.grid(row=0, column=0, columnspan=100, rowspan=10)
Then the pawns are placed based on their distance from start:
def green_grid_translation(self, green_position):
if green_position < 10:
self.green_grid_row = 9
self.green_grid_column = green_position*10+2
elif green_position < 20:
self.green_grid_row = 8
self.green_grid_column = 92 - (green_position - 10)*10
The pawns are placed on the same frame as the grid, the frame is created again with every move:
def position_interface(self):
self.grid_frame = Frame(window)
self.grid_frame.grid(row=1, column=0)
self.grid_picture = PhotoImage(file="grid.PNG")
self.grid_label = Label(self.grid_frame, image=self.grid_picture)
self.grid_label.grid(row=0, column=0, columnspan=100, rowspan=10)
self.green_picture = PhotoImage(file="green.png")
self.green_symbol = Label(self.grid_frame, image=self.green_picture)
self.green_symbol.grid(row=self.green_grid_row, column=self.green_grid_column)
self.blue_picture = PhotoImage(file="blue.png")
self.blue_symbol = Label(self.grid_frame, image=self.blue_picture)
self.blue_symbol.grid(row=self.blue_grid_row, column=self.blue_grid_column)
The following loops are used to make them go step by step:
for x in reversed(range(green_change[0])):
run_grid.green_grid_translation(green_change[1] - x)
run_grid.blue_grid_translation(blue_change[1])
run_grid.position_interface()
window.update()
sleep(1)
for x in reversed(range(blue_change[0])):
run_grid.green_grid_translation(green_change[1])
run_grid.blue_grid_translation(blue_change[1] - x)
run_grid.position_interface()
window.update()
sleep(1)
green_change[0] is the number of steps the pawn is supposed to move,
green_change[1] is its position on the grid
It works fine with a single pawn, but when there are two, it's like the number
of rows and columns changes and the pawns sometimes land in wrong positions:
Is there a way to fix it or do I need to take a completely different approach?
Your approach is wrong. There is plenty of stuff to improve, e.g the use of sleep in a GUI application is an absolute no-no.
But for the problem at hand, you simply use the wrong abstraction. Grids are for creating widgets in regular spaced layouts. But not for stacking/rearranging them. It CAN be done, but I would advise against it.
Use instead a canvas. This allows you to simply overlay graphical elements, and even move them around (smoothly if you are so inclined!).
Im currently testing around with python GUI and have made a script that takes 2 entered numbers from 2 textfields and upon a button press generates a block of labels (e.g. i enter 4 and 5 so it generates a 4x5 field of labels)
but now i want to do this: when i generate objects, i want to prevent them to
- move
- overlap
my current objects (buttons, textfields).
i can kind-of figure something for the overlapping, but every time i generate new stuff, everything moves around. Can i set a specific field in the grid to be "reserved" so that new stuff never goes in there?
this is my current attempt - as you can see, its not overlapping anymore, but if the snowflakes are generated, the textboxes and buttons still "jump" apart for a small distance
EDIT: the "jumps" are due to the font size of the added snowflakes - that still leaves my question on how i prevent this, as i dont want to be limited to small font sizes
from tkinter import *
wide = 0
deep = 0
entrytext = "test"
window = Tk()
window.title("test")
window.geometry('1000x1000')
ent = Entry(window)
ent.grid(column=0, row=1)
def GetClicked():
global wide
wide = ent.get()
wide = int(wide)
btn2 = Button(window, text="Width", command=GetClicked)
btn2.grid(column=0, row=2)
ent2 = Entry(window)
ent2.grid(column=0, row=3)
def GetClicked2():
global deep
deep = ent2.get()
deep = int(deep)
btn = Button(window, text="Depth", command=GetClicked2)
btn.grid(column=0, row=4)
def WingBut(column,row):
lbl = Label(window, text="T", font=("Wingdings", 15))
lbl.grid(column=column, row=row)
def clicked(wide,deep):
h = 0
j = 0
while h in range (deep):
i = 0
h += 1
while i in range(wide):
if i > 2 or j > 5:
WingBut(i,j)
i += 1
if i == wide:
j += 1
btn = Button(window, text="Buttonspam",font=("Arial", 10),command=lambda: clicked(wide,deep))
btn.grid(column=0, row=0)
window.mainloop()
the textboxes and buttons still "jump" apart for a small distance
This is due to the resulting size of the dynamically added labels (those labelled "T") being taller than the current row height for each row. Because the row size must increase to accommodate the new label, the other widgets in the same row are also resized so that the overall height for the row is consistent. That resize is causing the jumping effect.
One way to correct it would be to reduce the font size of the "T" labels. Try setting it to 10 and the problem should go away.
Another way to solve it would be to set the minsize for each row to be the height of the tallest widget in the row, e.g. the "T" label widget height.
for row in range(5):
window.rowconfigure(row, minsize=36)
You can add the above code before you call window.mainloop().
I selected 36 because this makes the rows a minimum of 36 pixels high, and this is sufficient on my system to display the "T" without causing the row to resize.
If you don't want to hardcode the minsize you could calculate it dynamically.
dummy = Label(window, text="T", font=("Wingdings", 20))
dummy.grid(row=0, column=0)
dummy.update_idletasks() # seems to be required to get rendered size
height = dummy.winfo_height()
dummy.grid_forget() # we don't want users seeing this widget
for row in range(5):
window.rowconfigure(row, minsize=height)
That's one way to do it. Possibly there is a better, more direct, way using the font itself, but you can research that if you're interested.
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.
i am reading the width of a label at three different times and only one of them is producing the correct output.. code:
from tkinter import *
def getwidth(string):
print(string+str(lbl1.winfo_width()))
root = Tk()
lbl1 = Checkbutton(root, text="test text")
lbl1.grid(row=0,rowspan=2)
print("first "+str(lbl1.winfo_width()))
getwidth("second ")
btn = Button(root, text="GO", command=lambda x="third ": getwidth(x))
btn.grid(row=2)
root.mainloop()
How can i read the correct width (69) during the first two outputs without having to rely on the button command? Thanks
current outputs are:
first 1
second 1
third 69
Well, unfortunately, you can't. The first two times are done before the window is loaded (which causes it to return the default value of 1 since the label isn't drawn yet). The third time is done after the window is loaded (the label is drawn), so it returns the correct number.
You have to remember that, until you call root.mainloop and load the window, the widgets are not placed on the screen. Sure, they exist behind the scenes (otherwise a NameError would be thrown), but they are not on the screen and taking up space yet. Thus, when you try to see how much space they are taking up, you get the default number of 1.