Subsequent to an earlier question (Scrolling canvas doesn't scroll) that I eventually managed to answer myself, I have hit upon another issue. I am drawing rectangles and placing images on a canvas. Everything works ok, except that the initial values for the x and y position seem to be ignored. It almost as if the canvas doesn't exist at that point until something has been drawn on i suspect I am misunderstanding how the canvas works.
Id be very grateful if someone could point out what i am doing wrong.
Everything gets drawn as it should but the origin of the whole thing seems to be at 0,0 not 95, 100 as specified.
thanks
class MapElements(Canvas):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.scroll_y = CTkScrollbar(master, orientation="vertical", command=self.yview)
self.scroll_y.grid(row=0, column=1, sticky="ns")
self.configure(yscrollcommand=self.scroll_y.set)
files = os.listdir(os.path.join(os.getcwd(), "uf_split", "uf_terrain"))
files.sort()
margin_x = 95
x = margin_x
y = 100
self.images = []
self.buttons = []
for i in range(0, len(files)):
self.create_rectangle(x, y, x + 50, y + 50, fill=None, outline="BLACK")
image = Image.open(
os.path.join(os.getcwd(), "uf_split", "uf_terrain", files[i])
)
self.images.append(ImageTk.PhotoImage(image))
self.create_image(x + 1, y + 1, image=self.images[i], anchor="nw")
x += 50
if x >= (50 * 8) + margin_x:
y += 50
x = margin_x
self.configure(scrollregion=self.bbox("all"))
Related
Im making one of my first programs, using python, tkinter and pillow.
This program is supposed to randomly select one of four tile images, create an image and repeat 25 times
Making a 5x5 Board using a grid to position the images in a square.
This is not what happened when i ran my code though
One or two columns and 4 rows are usually generated and not every coordinate is filled with an image.
And only the corner of the images are visible for some reason?
The images center seems to be placed in the top left corner of their grid.
A output might look something like this
X = coordinate filled with an image
O = coordinate filled with bg colour
X X
O X
O X
When i want it to look like this
X X X X X
X X X X X
X X X X X
X X X X X
X X X X X
The code thats supposed to place the images looks like this
while n < 25:
n = n + 1
number = random.randint(1,4)
if number == 1:
TILE_FLOWER.grid(row = x, column = y)
TILE_FLOWER.create_image(16,16, image = TILE_FLOWER_IMG)
elif number == 2:
TILE_GRASS.grid(row = x, column = y)
TILE_GRASS.create_image(16,16, image = TILE_GRASS_IMG)
elif number == 3:
TILE_STONE.grid(row = x, column = y)
TILE_STONE.create_image(16,16, image = TILE_STONE_IMG)
elif number == 4:
TILE_WATER.grid(row = x, column = y,)
TILE_WATER.create_image(16,16, image = TILE_WATER_IMG)
if x == 5:
x = 0
y = y + 1
else:
x = x + 1
win.mainloop()
The code defining my canvases looks like this
They're the same size as the images i want to draw on them.
TILE_FLOWER = Canvas(win, height = 80, width = 80)
TILE_GRASS = Canvas(win, height = 80, width = 80)
TILE_STONE = Canvas(win, height = 80, width = 80)
TILE_WATER = Canvas(win, height = 80, width = 80)
And lastly the code defining the images looks like this
TILE_FLOWER_IMG = PhotoImage(file = 'Path1.png')
TILE_GRASS_IMG = PhotoImage(file = 'Path2.png')
TILE_STONE_IMG = PhotoImage(file = 'Path3.png')
TILE_WATER_IMG = PhotoImage(file = 'Path4.png')
Hope what i've written makes sense!
And i would be super thankful if someone could help me fix this mess :)
You need to create new Label or Canvas in each cell of the 5x5 grid:
import tkinter as tk
import random
root = tk.Tk()
imagelist = [
tk.PhotoImage(file='Path1.png'),
tk.PhotoImage(file='Path2.png'),
tk.PhotoImage(file='Path3.png'),
tk.PhotoImage(file='Path4.png'),
]
for i in range(25):
image = random.choice(imagelist)
tk.Label(root, image=image, width=80, height=80, bg='white').grid(row=i//5, column=i%5)
'''
# or using Canvas
canvas = tk.Canvas(root, width=80, height=80, bg='white')
canvas.create_image(40, 40, image=image)
canvas.grid(row=i//5, column=i%5)
'''
root.mainloop()
Or using single Canvas:
canvas = tk.Canvas(root, width=400, height=400, bg='white')
canvas.pack()
for i in range(25):
image = random.choice(imagelist)
row = i // 5
col = i % 5
canvas.create_image(40+col*80, 40+row*80, image=image)
I am trying to draw a simple mesh with tkinter:
from tkinter import *
root=Tk()
root.title('Mesh simulator')
window_identifiers = {}
frame_identifiers = {}
canvas_identifiers = {}
mesh_identifiers = []
window_identifiers["main_window"] = root
SETTINGS_canvas_total_width = 2048
SETTINGS_canvas_total_height = 1536
SETTINGS_canvas_visible_width = 800
SETTINGS_canvas_visible_height = 600
SETTINGS_canvas_color = "black"
SETTINGS_grid_color = "white"
mesh_density = 50
frame=Frame(window_identifiers["main_window"],width=SETTINGS_canvas_visible_width,height=SETTINGS_canvas_visible_height)
frame_identifiers["body_holder"] = frame
frame.grid(row=0,column=0)
canvas=Canvas(frame,bd=-2, bg=SETTINGS_canvas_color,width=SETTINGS_canvas_visible_width,height=SETTINGS_canvas_visible_height,scrollregion=(0,0,SETTINGS_canvas_total_width,SETTINGS_canvas_total_height), highlightthickness=0)
canvas_identifiers["main_canvas"] = canvas
canvas.grid(row=0, column=0)
i = 0
while(i<=SETTINGS_canvas_total_height):
l = canvas_identifiers["main_canvas"].create_line(0, i, SETTINGS_canvas_total_width, i, width=1, fill=SETTINGS_grid_color)
mesh_identifiers.append(l)
i+=mesh_density
i = 0
while(i<=SETTINGS_canvas_total_width):
l = canvas_identifiers["main_canvas"].create_line(i, 0, i, SETTINGS_canvas_total_height, width=1, fill=SETTINGS_grid_color)
mesh_identifiers.append(l)
i+=mesh_density
root.mainloop()
But on the very end, when I measure up the distance between two lines, it seems it is not 50px, but about 62-64px. I don't have a clue what adds those 12 pixels per square. Can anyone explain me the root cause of this please, based on my upper snippet?
EDIT:
Interesting fact, I've just done the measurement on 2 different monitors (laptop and 22" one), and results are interesting. On 22" monitor, everything seems perfectly fine (image 1) while on laptop monitor, there's an offset (image 2)
And since this is not an HTML and web-design issue, I am even more confused now :)
Hello I got a problem with using the Tkinter package on python. I want to create a window containing two major widgets of which one is a canvas that will later show a grid with cells. When initializing the canvas I use "create_rectangle" to fill the canvas with the desired objects. Also when clicking on a cell (for testing reasons) the canvas should change its color in the area of the rectangle. However when initialy displaying the window at first no objects can be seen (the expected result would be only white colored rectangles) and only when a Click on the canvas is performed the area changes its color as desired.
While looking through the internet I tried several variations of the order of the pack()- and create_rectangle()-methods. This is the code:
from tkinter import *
from tkinter.ttk import *
import cells
GRID_WIDTH = 15
GRID_HEIGHT = 15
class Ui(Frame):
""" Class to represent the ui of conways game of life"""
def __init__(self, parent, grid):
self.parent = parent
self.grid = grid
Frame.__init__(self, parent)
self.__setup()
def __setup(self):
""" Method to setup the components of the ui """
self.parent.title("Conway's game of life")
self.pack()
#Setup a frame to hold control components
frame_cntrl = Frame(self)
frame_cntrl.pack(side = RIGHT, anchor="n")
self.__setup_cntrl_components(frame_cntrl)
#Setup a frame to hold the Grid
self.canvas = Canvas(self)
self.canvas.pack(side = LEFT)
self.__draw_grid()
self.canvas.bind("<Button-1>", self.__canvas_clicked)
def __setup_cntrl_components(self, parent):
""" Method to setup the control elements of the ui"""
#Setup a label for the generation
self.lb_generation = Label(parent, text="dummy")
self.lb_generation.pack(side = TOP)
#Setup a button for iteration
self.bt_iteration = Button(parent, text="Iterate")
self.bt_iteration.pack(side = TOP)
def __draw_cell(self, x, y):
""" Draws a cell on the canvas"""
width, height = self.canvas.winfo_width(), self.canvas.winfo_height()
color = "black" if self.grid.cell_alive(x, y) else "white"
x0 = width * x / self.grid.width + 1
x1 = width * (x + 1) / self.grid.width
y0 = height * y / self.grid.height + 1
y1 = height * (y + 1) / self.grid.height
self.canvas.create_rectangle(x0, y0, x1, y1, width=0, fill=color)
def __draw_grid(self):
""" Method to setup the grid itself"""
width, height = self.canvas.winfo_width(), self.canvas.winfo_height()
for i in range(0, self.grid.width):
for j in range(0, self.grid.height):
self.__draw_cell(i, j)
def __canvas_clicked(self, event):
""" Method for when the cell was clicked """
x, y, width, height = event.x, event.y, self.canvas.winfo_width(), self.canvas.winfo_height()
cell_x = int(x / width * self.grid.width)
cell_y = int(y / height * self.grid.height)
self.grid.switch_cell(cell_x, cell_y)
self.__draw_cell(cell_x, cell_y)
if __name__ == "__main__":
Ui(Tk(), cells.Grid(GRID_WIDTH, GRID_HEIGHT)).mainloop()
Problem 1:
Your main problem is that, before the canvas is actually displayed, canvas.winfo_width() and canvas.winfo_height() do not return the canvas width and height, but the value 1.
I suggest you create the canvas as follows:
# Define canvas and save dimensions
self.canvas_width = 300
self.canvas_height = 200
self.canvas = Canvas(self, width = self.canvas_width,
height = self.canvas_height)
Then, in your code, replace each instance of:
width, height = self.canvas.winfo_width(), self.canvas.winfo_height()
with
width, height = self.canvas_width, self.canvas_height
Problem 2:
When creating each cell I don't think you need to add 1 to x0 and y0. Instead, it should be:
x0 = width * x / self.grid.width
x1 = width * (x + 1) / self.grid.width
y0 = height * y / self.grid.height
y1 = height * (y + 1) / self.grid.height
I am trying to simulate a grid like what would be used for a game board using tkinter rectangles being drawn onto a canvas, however I am having trouble making a loop that does so correctly.
Pretty much I have a variable that contains a certain amount of rows for a grid and a variable for the amount of columns, and I need to create a grid of rectangles based off of those configurations which can change anytime the application is run, which is making it a bit tricky for me.
Anyways, I currently have a function to draw a rectangle to the screen like so:
def _place_empty_tiles(self):
self._canvas.update()
width = self._canvas.winfo_width()
height = self._canvas.winfo_height()
self.x = width / self._game.columns
self.y = height / self._game.rows
for i in range(self._game.columns):
click_point = point.from_pixel(self.x, self.y, width, height)
self._state.handle_click(click_point)
self.x += 60
def _redraw_game_pieces(self)->None:
'''Delete and redraw all the of game pieces'''
self._canvas.delete(tkinter.ALL)
canvas_width = self._canvas.winfo_width()
canvas_height = self._canvas.winfo_height()
for tile in self._state.all_tiles():
center_x, center_y = tile.center().pixel(canvas_width, canvas_height)
radius_x = tile.radius_frac() * canvas_width
radius_y = tile.radius_frac() * canvas_height
self._canvas.create_rectangle(
center_x - radius_x, center_y - radius_y,
center_x + radius_x, center_y + radius_y,
fill = '#006000', outline = '#000000')
You may noticed I have some custom coordinate conversion methods going on to compensate for the screen re sizing. However, my problem is under the function _place_empty_tiles(self) in the for loop. Let's say the amount of columns is 4, it will currently print out on the canvas the following:
However,
it is actually making a row, and
that is only one row.
How can I make it so I can create a grid with a similar style given the amount of rows and columns are stored in self._game.columns and self._game.rows?
UPDATE:
Tried basing it off The tkinter Knight's Tour Demo I rewrote the function like so:
def _place_empty_tiles(self):
self._canvas.update()
width = self._canvas.winfo_width()
height = self._canvas.winfo_height()
for r in range(self._game.rows -1,-1,-1):
for c in range(self._game.columns):
click_point = point.from_pixel(c*30+4, r*30+4, c*30+30, r*30+30)
self._state.handle_click(click_point)
self._redraw_game_pieces()
which is close, but still getting some unexpected results:
The Tk demos include a Knight's Tour demo that draws a chessboard. Someone converted it for tkinter and you can examine the _draw_board function to see an example of how to create such a layout using canvas rectangles:
def draw_board(canvas):
# draw checkerboard
for r in range(7, -1, -1):
for c in range(8):
if c&1 ^ r&1:
fill = 'tan3'
dfill = 'tan4'
else:
fill = 'bisque'
dfill= 'bisque3'
coords = (c*30+4, r*30+4, c*30+30, r*30+30)
canvas.create_rectangle(coords, fill=fill, disabledfill=dfill,
width=2, state='disabled')
Here's an example that draws a grid that fits the window as closely as possible. It is designed to redraw itself whenever the window resizes. If you don't want that behavior, you can hard-code the cell width and height.
The cells are stored in a dictionary indexed by row and column, to make it easy to reference each tile. Clicking on a tile will toggle it between blue and red. Note that when the window resizes it won't remember which cells were previously clicked on. That's easy to fix if you want.
import Tkinter as tk
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.canvas = tk.Canvas(self, width=500, height=500, borderwidth=0, highlightthickness=0)
self.canvas.pack(side="top", fill="both", expand="true")
self.rows = 20
self.columns = 20
self.tiles = {}
self.canvas.bind("<Configure>", self.redraw)
self.status = tk.Label(self, anchor="w")
self.status.pack(side="bottom", fill="x")
def redraw(self, event=None):
self.canvas.delete("rect")
cellwidth = int(self.canvas.winfo_width()/self.columns)
cellheight = int(self.canvas.winfo_height()/self.columns)
for column in range(self.columns):
for row in range(self.rows):
x1 = column*cellwidth
y1 = row * cellheight
x2 = x1 + cellwidth
y2 = y1 + cellheight
tile = self.canvas.create_rectangle(x1,y1,x2,y2, fill="blue", tags="rect")
self.tiles[row,column] = tile
self.canvas.tag_bind(tile, "<1>", lambda event, row=row, column=column: self.clicked(row, column))
def clicked(self, row, column):
tile = self.tiles[row,column]
tile_color = self.canvas.itemcget(tile, "fill")
new_color = "blue" if tile_color == "red" else "red"
self.canvas.itemconfigure(tile, fill=new_color)
self.status.configure(text="you clicked on %s/%s" % (row, column))
if __name__ == "__main__":
app = App()
app.mainloop()
I am running a creature simulator in python 2.7 using tkinter as my visualizer. The map is made up of squares, where colors represent land types, and a red square represents the creature. I use canvas.move to move that red square around the board. It has to move quite a lot. But I know exactly where it should start and where it should end. The problem is, instead of moving, most of the time it just disappears. In the code below I call move in Simulation's init, and it works. When I call it any time in sim.simulate, the creature just disappears. Can anyone explain why?
class Map():
def __init__(self,):
self.root = Tk()
self.canvas = Canvas(self.root, width=1200, height=1200)
self.canvas.pack()
self.colors = {
"Land": "grey",
"Food": "green",
"Water": "blue",
"Shelter": "black"
}
self.canvasDict = {} # the keys are (x,y, "type"), the data is the id so it can be grabbed for item config.
for i, row in enumerate(land.landMass):
for j, tile in enumerate(row):
color = self.colors[tile.__class__.__name__]
self.canvasDict[i, j, "tile"] = self.canvas.create_rectangle(50 * i, 50 * j, 50 * (i + 1), 50 * (j + 1),
outline=color, fill=color)
info = tile.elevation
if color == "green":
info = tile.vegitation
elif color == "black":
info = tile.quality
self.canvasDict[i, j, "text"] = self.canvas.create_text(50 * i + 3, 50 * j, anchor=NW, fill="white", text=info)
self.canvasDict["creature"] = self.canvas.create_rectangle(0, 0, 50, 50,
outline="red", fill="red")
self.canvas.pack(fill=BOTH, expand=1)
sim = Simulation([], 1, 2, self.root, self.canvas, self.canvasDict)
self.root.after(1000, sim.simulate)
...
other functions
...
def simulate(self):
self.canvas.move(self.canvasDict["creature"], 1, 1)
if self.generations > 0:
self.root.after(10000, self.canvas.move, self.canvasDict["creature"], 2 * 50, 2 * 50)
...
I finally realized what was happening. I made the mistake of thinking that .move would move the object to that location on the canvas, instead it is moving it by that much. So when my square 'disappears' it is really just moving of the visible canvas. I thought that the .after would stall the movements so that I could see that happen, but apparently not.