Im working on a GUI for a board game. So I made some objects on the Canvas and I know they are somehow saved as integers because if I create it, it returns the int and if I canvas.delete(i) it gets removed. Summed up I have a mxn board and the squares are $i\in {1,__,m*n}$.
How can I configure the cells now, by only knowing the integers?
For all squares I set the tag: 'cells', therefore I can get the integers and change stuff:
items = canvas.find_withtag('cells')
canvas.itemconfig('cells', ...)
Do I have to set the (i,j) as a tag when I create the squares?
Thanks for reading and good evening.
I don't really often use Canvas quite often but this should be a workaround for what you have asking for:
import tkinter as tk
# Defining all of your constants
width = 10
height = 10
spacing = 2
countX = 20
countY = 10
# Dictionary which will contains all rectangles
_objects = {}
# Creating the window
root = tk.Tk()
root.geometry("200x100")
canvas = tk.Canvas(root, width = 200, height = 100, bg = "white")
canvas.pack()
# Creating all fields and naming than like coordinates
for y in range(countY):
for x in range(countX):
_objects["{0}/{1}".format(x,y)] = canvas.create_rectangle(0+(x*width),
0+(y*height),
width+(x*width) - spacing,
height+(y*height) - spacing)
# Call a ractangle [X/Y]
canvas.itemconfig(_objects["0/2"], fill = "red")
canvas.itemconfig(_objects["0/9"], fill = "yellow")
canvas.itemconfig(_objects["19/9"], fill = "green")
print(_objects)
root.mainloop()
So the idea is to use a simple Dictionary where you store the id corresponding to the correct coordinate. You can address a specific rectangle by using _objects[<X>/<Y>]. And if you need to know the coordinate you can simply iterate over the dic with:
def getCoordById(oid):
# Search with id
for pairs in _objects.items():
if pairs[1] == oid:
return pairs[0]
canvas.itemconfig(_objects[getCoordById(1)], fill = "purple")
Ps.: If you always use the ID to identfy a coordinate I would use the canvas.create(...) as key and the name as value.
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()
Here is a rough example of what I want to do(my code is too long and messy to share here): -
import tkinter as tk
app = tk.Tk()
w, h = 600, 600
canvas = tk.Canvas(app, width = w, height = h)
canvas.pack()
Rec1 = canvas.create_rectangle(0, 0, 100, 100, fill = 'blue', tag = 'move_to_next_window')
Rec2 = canvas.create_rectangle(100, 100, fill='green', tag = 'dont_move_to_next_window')
app.mainloop()
I am sorry if I messed up a couple lines but this program should run by creating 2 rectangles. What I need help with is if I initiate a brand new window which is running off different code, how would I move Rec1 and its position to the other window. If its possible, could I copy all of the object's properties in the second window? Thank you for taking the time to read this (the second window can also use the tkinter canvas).
What I need help with is if I initiate a brand new window which is running off different code, how would I move Rec1 and its position to the other window.
You can't move canvas items from one canvas to another. Your only option is to delete the item in the first canvas and add a new item in the other canvas.
If its possible, could I copy all of the object's properties in the second window?
Yes, you can use the itemconfigure method to get all of the properties of a canvas object, you can use coords to get the coordinates, and you can get the type with type method.
Here's an example function that copies a rectangle from one canvas to another.
def copy_canvas_item(source, item_id, destination):
item_type = source.type(item_id)
coords = source.coords(item_id)
# N.B. 'itemconfigure' returns a dictionary where each element
# has a value that is a list. The currently configured value
# is at index position 4
attrs = {x[0]: x[4] for x in source.itemconfigure(item_id).values()}
if item_type == "rectangle":
item_id = destination.create_rectangle(*coords, attrs)
return item_id
I have made an application and part of it involves entering a question and answer. I have this code:
import tkinter as tk
root = tk.Tk()
root.geometry("500x250")
#Main question/answer frame
createFrm = tk.Frame(root)
createFrm.pack(expand = True) #To centre the contents in the window
#Create question entry area
cnqFrm = tk.Frame(createFrm)
cnqFrm.pack()
cnqFrm.pack_propagate(False)
#Question entry
cnqLabQ = tk.Label(cnqFrm, text = "Question")
cnqLabQ.grid(column = 0, row = 0)
#Frame for question Text
cnqTxtQFrm = tk.Frame(cnqFrm, height = 100, width = 100)
cnqTxtQFrm.grid(column = 0, row = 1)
cnqTxtQFrm.grid_propagate(False)
#Question Text
cnqTxtQ = tk.Text(cnqTxtQFrm)
cnqTxtQ.pack()
cnqTxtQ.pack_propagate(False)
#Answer entry
cnqLabA = tk.Label(cnqFrm, text = "Answer")
cnqLabA.grid(column = 1, row = 0)
#Frame for answer text
cnqTxtAFrm = tk.Frame(cnqFrm, height = 100, width = 100)
cnqTxtAFrm.grid(column = 1, row = 1)
cnqTxtAFrm.grid_propagate(False)
#Answer Text
cnqTxtA = tk.Text(cnqTxtAFrm)
cnqTxtA.pack()
cnqTxtA.pack_propagate(False)
Despite the fact the Text widget is in a Frame with grid_propagate(False) and a fixed height and width, and the Text widget itself has pack_propagate(False), it still expands to far larger than it should be. Why is this and how can I fix it?
You don't give the text widget an explicit size, so it defaults to 40x80 average-sized characters. The most common way to force it to a specific size that is determined by its parent is to give it a size that is smaller than the containing widget, and then let grid or pack expand it to fit the space given to it. So, start by giving the text widget a width and height of 1 (one).
Next, in this specific case you are calling grid_propagate(False) on the containing frame, but you are using pack to manage the window. You should call pack_propagate if you're using pack. You also need to tell pack to expand the text widget to fill its frame.
Finally, there's no point in calling cnqTxtQ.pack_propagate(False) since that only affects children of the text widget and you've given it no children.
All of that being said, I strongly encourage you to not use grid_propagate(False) and pack_propagate(False). Tkinter is really good at arranging widgets. Instead of trying to force the text widget to a specific pixel size, set the text widget to the desired size in lines and characters, and let tkinter intelligently arrange everything else to line up with them.
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.
Edit: Sorry I can't answer my own post since I'm new but I figured it out: If you remove the line "tki.Button(master,..." (2nd to last code line), then the code runs perfectly fine. I guess the grid and the button don't work the way I put it.
sorry to bother but I'm having a little trouble figuring out what's off here. Basically I have an array that I want to loop through and set each of the values as a radiobutton IN A GRID. Later I'm going to loop through several arrays to generate a larger grid menu, but I can probably figure that out once I get this first loop working.
Here is my code:
import Tkinter as tki
master = tki.Tk()
frm = tki.Frame(master, bd = 16, relief = "sunken")
frm.grid()
tType = tki.StringVar()
tColumn = tki.IntVar()
tRow = tki.IntVar()
compType = ["iMac ", "Mac Mini ", "Mac Pro ", "Macbook ", "Macbook Air ", "Macbook Pro "]
tColumn.set(0)
tRow.set(0)
def radioCreate(typeArray):
for t in typeArray:
b = tki.Radiobutton(frm, text = t, variable = tType)
b.config(indicatoron = 0, bd = 4, width = 16, value = t)
b.grid(row = tRow.get(), column = tColumn.get())
tRow.set((tRow.get() + 1)) #increment tRow for next run-through
def p():
print tType.get()
radioCreate(compType)
tki.Button(master, command = p, text = "Display").pack()
master.mainloop()
Now remember, I'm trying to get this working in a grid, because I'm going to populate other columns with other data from different arrays.
The problem is i. These two lines:
frm.grid()
...
tki.Button(...).pack()
While it's perfectly acceptable to use pack and grid in the same application, you can't use them on two widgets that share the same master.
Grid will potentially change the size of a widget or its master depending on its options. Pack will notice the change, and may itself try to resize one or more widgets and/or the master based on its options. Grid will notice the change, and may resize... Pack will notice the change and resize, ...