我正在尝试使用 flet 制作 janggi(国际象棋变体)游戏。由于我必须在多个地方使用该板,因此我创建了一个名为 janggiBoard 的类,它继承自 ft.Stack。我想对片段使用拖放操作,因此我按照 flet 文档中的 Solitaire 教程进行操作。当我尝试执行“move_on_top”部分时出现问题。在Solitaire代码中,move_on_top函数中有
page.update()
所以我很自然地在后面加上了
piece.board.update()
但是,当我这样做时,它会导致如下所示的断言错误。
Future exception was never retrieved
future: <Future finished exception=AssertionError('Control must be added to the page first.')>
Traceback (most recent call last):
File "C:\Users\andyp\AppData\Local\Programs\Python\Python312\Lib\concurrent\futures\thread.py", line 58, in run
result = self.fn(*self.args, **self.kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\andyp\AppData\Local\Programs\Python\Python312\Lib\site-packages\flet_core\page.py", line 950, in wrapper
handler(*args)
File "c:\limbaksa\janggi-v2\janggiBoard.py", line 25, in start_drag
e.control.update()
File "C:\Users\andyp\AppData\Local\Programs\Python\Python312\Lib\site-packages\flet_core\control.py", line 314, in update
assert self.__page, "Control must be added to the page first."
^^^^^^^^^^^
AssertionError: Control must be added to the page first.
当我搜索此错误时,他们说当您在未将控件添加到页面时调用更新时会发生这种情况。但是,该板显然已添加到页面中,因此我不明白为什么会发生这种情况。
import flet as ft
from janggiplayer import AI
import flet.canvas as cv
import janggibase
from db import db
def move_on_top(piece,controls):
controls.remove(piece)
controls.append(piece)
piece.board.update()
def start_drag(e:ft.DragStartEvent):
move_on_top(e.control,e.control.board.controls)
e.control.board.move_start_top=e.control.top
e.control.board.move_start_left=e.control.left
if e.control.piece.color == e.control.board.board.turn and (
not e.control.board.ai or e.control.piece.color != e.control.board.aiturn
):
for slot in e.control.board.slots:
if e.control.piece.isValidMove(e.control.board.slots.index(slot)):
slot.content = ft.Image(f"img/able.png")
e.control.board.controls.remove(slot)
e.control.board.controls.append(slot)
e.control.update()
slot.update()
def bounce_back(board,piece):
piece.top=board.move_start_top
piece.left=board.move_start_left
piece.update()
def drag(e: ft.DragUpdateEvent):
e.control.top = max(0, e.control.top + e.delta_y)
e.control.left = max(0, e.control.left + e.delta_x)
e.control.update()
def drop(e:ft.DragEndEvent):
for slot in e.control.board.slots:
slot.content=None
slot.update()
for slot in e.control.board.slots:
if (
abs(e.control.top - slot.top) < 20
and abs(e.control.left - slot.left) < 20
):
place(e.control, slot)
break
else:
bounce_back(e.control.board,e.control)
e.control.update()
def place(piece,slot):
piece.top=slot.top
piece.left=slot.left
piece.update()
class janggiPiece(ft.GestureDetector):
def __init__(self, piece: janggibase.Piece, board: "janggiBoard"):
super().__init__()
self.piece=piece
self.board=board
self.mouse_cursor=ft.MouseCursor.MOVE
self.drag_interval=5
self.on_pan_start=start_drag
self.on_pan_update=drag
self.on_pan_end=drop
self.left = 60 * (piece.location // 10)
self.top = 60 * (9 - piece.location % 10)
self.content=ft.Container(
width=60,
height=60,
border_radius=ft.border_radius.all(5),
content=ft.Image(f"img/{str(piece).upper()}{piece.color}.png"),
)
class Slot(ft.Container):
def __init__(self, top, left):
super().__init__()
self.width = 60
self.height = 60
self.left = left
self.top = top
class janggiBoard(ft.Stack):
def __init__(self, board: janggibase.Board, ai=False, aiturn=None):
super().__init__()
self.board = board
self.slots = []
self.controls = []
self.width = 540
self.height = 600
self.piecelist = []
self.move_start_top=None
self.move_start_left=None
self.ai = AI(self.board, aiturn) if ai else None
self.aiturn = aiturn
def did_mount(self):
cp = cv.Canvas(
[
cv.Path(
[
cv.Path.MoveTo(30, 30),
cv.Path.LineTo(30, 570),
cv.Path.MoveTo(90, 30),
cv.Path.LineTo(90, 570),
cv.Path.MoveTo(150, 30),
cv.Path.LineTo(150, 570),
cv.Path.MoveTo(210, 30),
cv.Path.LineTo(210, 570),
cv.Path.MoveTo(270, 30),
cv.Path.LineTo(270, 570),
cv.Path.MoveTo(330, 30),
cv.Path.LineTo(330, 570),
cv.Path.MoveTo(390, 30),
cv.Path.LineTo(390, 570),
cv.Path.MoveTo(450, 30),
cv.Path.LineTo(450, 570),
cv.Path.MoveTo(510, 30),
cv.Path.LineTo(510, 570),
cv.Path.MoveTo(30, 30),
cv.Path.LineTo(510, 30),
cv.Path.MoveTo(30, 90),
cv.Path.LineTo(510, 90),
cv.Path.MoveTo(30, 150),
cv.Path.LineTo(510, 150),
cv.Path.MoveTo(30, 210),
cv.Path.LineTo(510, 210),
cv.Path.MoveTo(30, 270),
cv.Path.LineTo(510, 270),
cv.Path.MoveTo(30, 330),
cv.Path.LineTo(510, 330),
cv.Path.MoveTo(30, 390),
cv.Path.LineTo(510, 390),
cv.Path.MoveTo(30, 450),
cv.Path.LineTo(510, 450),
cv.Path.MoveTo(30, 510),
cv.Path.LineTo(510, 510),
cv.Path.MoveTo(30, 570),
cv.Path.LineTo(510, 570),
cv.Path.MoveTo(210, 30),
cv.Path.LineTo(330, 150),
cv.Path.MoveTo(330, 30),
cv.Path.LineTo(210, 150),
cv.Path.MoveTo(210, 450),
cv.Path.LineTo(330, 570),
cv.Path.MoveTo(330, 450),
cv.Path.LineTo(210, 570),
],
paint=ft.Paint(
color="white",
stroke_width=2,
style=ft.PaintingStyle.STROKE,
),
),
],
width=float("inf"),
expand=True,
)
self.controls.append(cp)
for i in range(90):
self.slots.append(Slot(60 * (9 - i % 10), 60 * (i // 10)))
self.controls.extend(self.slots)
for color in range(2):
for piece in self.board.pieces[color]:
self.piecelist.append(janggiPiece(piece, self))
self.controls.extend(self.piecelist)
self.update()
return super().did_mount()
if __name__ == "__main__":
def main(page):
page.add(janggiBoard(janggibase.Board(15)))
ft.app(target=main)
这是该板的代码。完整的代码在我的 github 上。
在我评论了
piece.board.update()
之后,其他一切都按预期工作。我可以将棋子移动到不同的位置。然而,由于主板未更新,该作品并未位于其他作品之上。有没有办法更新主板而不导致错误?
这个错误是由于 Flet 框架的工作原理造成的。当你调用
piece.board.update()
的时候,
piece.board
(也就是你的
janggiBoard
实例) 还没有被正式添加到页面中。尽管你已经在
main
函数中调用了
page.add(janggiBoard(...))
,但这只是将控件加入到页面的更新队列中,而实际的页面更新是异步进行的。这意味着
piece.board.update()
的执行时机可能早于
janggiBoard
真正被添加到页面,所以就导致了这个错误。
解决这个问题的方法是确保
piece.board.update()
在
janggiBoard
被添加到页面之后才被调用。你可以利用 Flet 提供的
page.after_render
回调函数来实现:
def move_on_top(piece,controls,page):
controls.remove(piece)
controls.append(piece)
# 使用 page.after_render 确保更新在渲染完成后进行
def update_board():
piece.board.update()
page.after_render(update_board)
def start_drag(e:ft.DragStartEvent):
move_on_top(e.control,e.control.board.controls,e.page)
# ... 其他代码 ...
通过将
piece.board.update()
包裹在
page.after_render
回调函数中,你可以确保它会在页面渲染完成后才被执行,从而避免 "Control must be added to the page first" 的错误。
另外,你也可以尝试使用
async
和
await
来实现异步操作,这样可以更加优雅地处理页面更新问题。但是,这需要对 Flet 框架的异步机制有更深入的了解。