Я кодирую игру на Python с помощью tkinter. Основная функциональность заключается в том, что изображение отображается, но оно покрыто сеткой блоков, которые можно уничтожить. Я написал линейную версию кода, которая максимально упрощена:
from tkinter import *
from PIL import ImageTk, Image
class Box:
def __init__(self, parent, row, column, width, height):
self.row = row
self.column = column
self.canvas = Canvas(parent, bg='black', width=width, height=height)
self.canvas.grid(row=row, column=column, sticky=NSEW)
self.canvas.bind('<Button-1>', self.destroy)
def destroy(self, event):
print('Destroyed box at', self.row, self.column)
self.canvas.destroy()
root = Tk()
root.title('Image Reveal')
root.state('zoomed')
image_raw = None
# Create a frame for the image
image_frame = Frame(root, highlightbackground = "blue", highlightthickness=5)
image_frame.pack(expand=True, anchor=CENTER, fill=BOTH, padx=0, pady=0)
# Load image and put it in a label
image_raw = Image.open('image.png')
image = ImageTk.PhotoImage(image_raw)
image_label = Label(image_frame, image=image, borderwidth=5, relief='ridge')
image_label.pack(anchor=CENTER, expand=True)
# Calculate the size of the boxes
image_label.update()
width = image.width()
height = image.height()
column_amount = 10
row_amount = 10
box_width = width / column_amount
box_height = height / row_amount
# Create boxes that fill the grid
for row in range(row_amount):
for column in range(column_amount):
Box(image_label, row, column, box_width, box_height)
root.mainloop()
Вы можете щелкнуть по ящику, чтобы уничтожить его, и он также зарегистрирует координаты уничтоженного ящика.
Желаемое поведение: Вы можете нажимать на ящики, чтобы уничтожать их, постепенно открывая изображение. Остальные прямоугольники должны оставаться на прежнем месте (в тех же ячейках сетки), чтобы форма сетки не менялась.
Неожиданное поведение: уничтожение одной коробки приводит к тому, что остальные коробки каким-то образом исчезают. Тем не менее, вы все еще можете щелкнуть их (он все еще регистрирует сообщение об уничтожении). В целом, это самое близкое к моему желаемому поведению, но я не могу понять, почему это происходит или как лучше подойти к этой функциональности.
РАБОЧИЙ КОД:
Лукас Крабихлер помог добиться большей части желаемого поведения. Вот обновленный код. Однако уничтожение ящиков приводило к изменению размера сетки, когда уничтожалась полная строка или столбец ящиков. Чтобы исправить это, я вместо этого прячу коробку, опуская ее за изображение. Сначала это не сработало, но потом я нашел это - Получается опустить ВСЁ полотно, прямого метода для этого нет. Окончательный код:
from tkinter import *
from PIL import ImageTk, Image
class Box:
def __init__(self, root, image_frame, image_label, row, column):
self.root = root
self.row = row
self.column = column
self.image_label = image_label
self.image_frame = image_frame
self.visible = True
self.canvas = Canvas(image_frame, bg='black')
self.canvas.grid(row=row, column=column, sticky=NSEW, padx=5, pady=5)
self.canvas.bind('<Button-1>', self.hide)
def hide(self, event):
# Lower the box under the image to hide it
self.canvas.tk.call('lower', self.canvas._w, None)
self.visible = False
print('Hid box at', self.row, self.column)
root = Tk()
root.title('Image Reveal')
root.state('zoomed')
image_raw = None
column_amount = 10
row_amount = 10
boxes = []
# Create a frame for the image
image_frame = Frame(root, highlightbackground = "blue", highlightthickness=0)
image_frame.pack(anchor=CENTER, fill=None, padx=0, pady=0)
# Load image and put it in a label
image_raw = Image.open('image.png')
image = ImageTk.PhotoImage(image_raw)
width = image.width()
height = image.height()
# Create a label for the image
image_label = Label(image_frame, image=image, borderwidth=0, relief='ridge')
# Configure the image frame so it doesn't resize <----- UPDATED
image_frame.config(width=width, height=height)
image_frame.grid_propagate(False)
image_label.grid_propagate(False)
# Place the image label in the frame
image_label.grid(rowspan=row_amount, columnspan=column_amount, sticky = "NSEW")
# Configure the grid weights <----- UPDATED
for row in range(10):
image_frame.rowconfigure(row, weight=1)
for column in range(10):
image_frame.columnconfigure(column, weight=1)
# Calculate the size of the boxes
image_label.update()
# Create boxes that fill the grid
for row in range(row_amount):
for column in range(column_amount):
box = Box(root, image_frame, image_label, row, column)
boxes.append(box)
root.mainloop()
@BryanOakley Вот это интересно. Я уверен, что именно этот код демонстрирует такое поведение. Но я заметил еще одну странную вещь - изменение размера заставляет поля снова появляться (с удаленными щелкнутыми). И даже простое перемещение окна (пока поля все еще отображаются) приводит к тому, что поля появляются и исчезают несколько случайным образом.
В tkinter вы не должны размещать другие виджеты (например, Canvas) в метке. Если вы сеткаете все в «image_frame» и даете «image_label» столбец и диапазон строк, это должно работать.
from tkinter import *
from PIL import ImageTk, Image
class Box:
def __init__(self, parent, row, column, width, height):
self.row = row
self.column = column
self.canvas = Canvas(parent, bg='black', width=width, height=height)
self.canvas.grid(row=row, column=column, sticky=NSEW)
self.canvas.bind('<Button-1>', self.destroy)
def destroy(self, event):
print('Destroyed box at', self.row, self.column)
self.canvas.destroy()
root = Tk()
root.title('Image Reveal')
root.state('zoomed')
image_raw = None
# Create a frame for the image
image_frame = Frame(root, highlightbackground = "blue", highlightthickness=5)
image_frame.pack(expand=True, anchor=CENTER, fill=BOTH, padx=0, pady=0)
# Define row and column amount
column_amount = 10
row_amount = 10
# Load image and put it in a label
image_raw = Image.open('image.png')
image = ImageTk.PhotoImage(image_raw)
image_label = Label(image_frame, image=image, borderwidth=5, relief='ridge')
image_label.grid(rowspan=row_amount, columnspan=column_amount, sticky = "NSEW")
# Calculate the size of the boxes
image_label.update()
width = image.width()
height = image.height()
box_width = width / column_amount
box_height = height / row_amount
# Create boxes that fill the grid
for row in range(row_amount):
for column in range(column_amount):
Box(image_frame, row, column, box_width, box_height)
root.mainloop()
На самом деле нет ничего плохого в размещении виджетов внутри метки. Это не то, для чего предназначены метки, но это ничем не отличается от размещения виджета во фрейме.
О, я не знал о rowspan и columnspan. Это кажется весьма полезным, спасибо! Ваш код действительно работает для меня с точки зрения достижения желаемого поведения. Я реализовал этот подход, но мне пришлось внести несколько дополнений, чтобы изображение оставалось в центре, а поля идеально соответствовали изображению, и, к сожалению, теперь у него другое неожиданное поведение... Я добавлю обновленный код в сообщение
Кажется, я во всем этом разобрался. Добавил рабочий код в пост. Большое спасибо!
Когда я запускаю ваш код, щелчок по одному полю удаляет только это поле. Вы уверены, что этот код демонстрирует описанное вами поведение?