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
Related
I need to make a program where the user enters the value that he wants the radius or side of the figure to have, and then chooses one figure from the possible ones from a list so that it is drawn with the previously entered value, and then the same with a second figure. For this I decided to use tkinter and turtle in python.
The problem is that when I want to store the value in a variable, I get this error: Entry.get() takes 1 positional argument but 2 were given"
Also, the code I use to trigger the select function and draw the circle, in this case, when that option is selected, ComboboxSelected doesn't seem to trigger anything. I don't know if I should put the option in another way inside the function. In this case, I imported turtle for drawing the figures, since it seemed less complex to me.
Obviously, the other figures still need to be programmed and the other list for the second figure, but I want it to work before continuing. All kind of advice is welcomed. Here is my code. Some parts are in spanish, but they are mostly the functions' and variable names and text displayed as titles, so I hope they dont become much of an obstacle.
from turtle import *
from tkinter import *
from tkinter import ttk
inicio = Tk()
inicio.geometry("600x500")
inicio.title("Dibujos de figuras")
texto = Entry(inicio, font = "Helvetica 15")
texto.place(relx=0, rely=0.1, relwidth=1, relheight=0.05)
m = None
radiolado = Label(inicio, text = "Radio o lado en cm")
radiolado.place(relx=0, rely=0.05)
def guardar_Valor():
global m
valor = Entry.get(1.0, "end-1c")
m = valor
def select(event):
if lista_1.get() == "Círculo":
c = Turtle()
c.circle(m)
figura_1 = Label(inicio, text = "Figura 1")
figura_1.place(relx=0.43,rely=0.25)
figura_2 = Label(inicio, text = "Figura 2")
figura_2.place(relx=0.43,rely=0.6)
lista_1 = ttk.Combobox(inicio, state="readonly",values=["Círculo", "Cuadrado", "Triángulo", "Pentágono", "Hexágono"])
lista_1.current(0)
lista_1.place(relx= 0.35,rely=0.3)
lista_1.bind("«ComboboxSelected»", select)
Boton1= Button(inicio, text = "Guardar", command = guardar_Valor)
Boton1.place(relx= 0.42,rely=0.18)
lista_2 = ttk.Combobox(inicio, state="readonly",values=["Círculo", "Cuadrado", "Triángulo", "Pentágono", "Hexágono"])
lista_2.current(1)
lista_2.place(relx= 0.35,rely=0.7)
inicio.mainloop()
Is there anyway I can plug my list of integers into my list of entry boxes? The list of integers is constantly changing..
This would not be a problem if the list of integers and list of entry boxes had the same number of data points, however I can't determine that initially because I want user input to determine this entry list length in future code. I've tried using Insert to solve this problem, to no avail, given that I couldn't use the index of entry to configure its text option.
from tkinter import *
def entry_list_extender():
entrylist.extend(number)
gx=10
number=0
root=Tk()
frame=Frame(root)
frame.pack()
entry=[]
entrylist=[1,2,3,4]
var = []
entrybox=Entry(frame,bg='blue',textvariable=number)
entrybox.pack()
button=Button(frame,bg='red',command=entry_list_extender)
button.pack()
for i in range(gx):
entry.append(Entry(frame, textvariable=entrylist[i]))
entry[-1].pack()
root.mainloop()
A solution or path I could take to get the results I want would be appreciated.
Edit: my original question was quite ambiguous. This should make it more clear
UPDATE:
I am going to have to make an assumption here to make this work.
I am assuming that gx is the user defined variable you want to use down the road.
If that is the case then you need to change you your code a bit to re-create the entry fields when you press the button and also use the value of gx to decide on how many entry fields you should use.
Let me know if this is closer to what you are trying to do as it is still not very clear what your goal is.
from tkinter import *
root=Tk()
gx=10
number=0
entry=[]
entrylist=[1, 2, 3, 4]
var = []
def entry_list_extender():
global frame, entrylist, entry
entry = []
entrylist = []
for i in range(gx):
entrylist.append(i)
frame.destroy()
create_entry_fields()
entrybox=Entry(root, bg='blue', textvariable = number)
entrybox.pack()
button=Button(root, bg='red', command = entry_list_extender)
button.pack()
def create_entry_fields():
global frame, entrylist, entry
frame = Frame(root)
frame.pack()
print (len(entrylist))
for i in range(len(entrylist)):
entry.append(Entry(frame, textvariable = i))
entry[-1].pack()
create_entry_fields()
root.mainloop()
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've looked everywhere for a fix to this. I stumbled across this:
How to fit Tkinter listbox to contents
But this question is asking the reverse of what I want. I want the box to remain the size I've set it to, but the text runs off of the side like in the screenshot from the above linked question. Is there anyway to force a \n to be added to the string once its character count reaches the length of the listbox?
Also, I apologize if something is wrong with the format of my question, I've never posted here before.
class Stars(Tk):
def __init__(self):
Tk.__init__(self)
self.feed = Listbox(self, width = 55 , height = 31, relief = SUNKEN, borderwidth = 3)
self.feed.grid(row = 1, column = 2, columnspan = 2)
def simulate(self):
self.mass = eval(self.massEntry.get())
self.feed.insert(END, 'Star to be created with mass of {} * 10^30 kg; {} solar masses.'.format(1.98855 * self.mass, self.mass))
self.feed.insert(END, '0 years: Protostar formed in an interstellar gas cloud, and begins to compress due to gravity.')
This is all of the relevant code (trying to make a stellar evolution simulation). This is what it looks like when run, with the problem circled in red:
http://imgur.com/dZCYe6s
No, there is no way to have a Listbox wrap the text. If you want to support wrapping, use a Text widget. If you want to select items like in a listbox, you can add some custom bindings to do that.
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.