Custom controls appeared "ValueError: Circular reference detected" in version 0.24.1 #4281
-
Duplicate Check
Describe the bugMy custom component ran normally in version 0.23.2, but when I upgraded to version 0.24. x, there was a "ValueError: Circular reference detected" error, and the component was completely unusable. I can't find the cause of the error, but I don't think it's a problem with my code. I think a component of Flet may have been cyclically referenced. (From machine translation, if the expression is not clear, please understand.) Code sampleCodeimport flet as ft
import os
from PIL import Image
from base64 import b64encode, b64decode
class ImageViewer(ft.Container):
def __init__(self, only_show: bool = False, save_path: str = 'downloads', upload_dir: str = 'uploads'):
"""
图片展示组件,可方便切换图片及取消
:param only_show: 是否仅展示图片,如果设置为True则无法自定义选择图片
:param save_path: 图片的默认保存路径,仅在web端生效
:param upload_dir: 图片的默认上传路径,仅在web端生效
"""
super().__init__()
self.only_show = only_show
self.save_path = save_path
self.upload_dir = upload_dir
self.alignment = ft.Alignment(0, 0)
self.width = 300
self.height = 400
self.border = ft.border.all(1, ft.colors.SECONDARY)
self.border_radius = 5
self.ink = True
self.on_hover = self.container_hover_event
if not self.only_show:
self.on_click = self.container_click_event
else:
self.on_click = lambda _: ...
self._init_widget()
def _init_widget(self):
"""初始化组件"""
self.txt = ft.Text('空空如也~' if self.only_show else '➕点击选择图片', size=16)
self.content = self.txt
self.image = ft.Image(filter_quality=ft.FilterQuality.MEDIUM)
self.remove_btn = ft.IconButton(ft.icons.CLOSE, left=0, top=0, icon_size=16, tooltip='取消图像显示',
style=ft.ButtonStyle(color={'hovered': 'primary', '': 'secondary'}),
on_click=self.remove_btn_click_event)
self.save_btn = ft.IconButton(ft.icons.DOWNLOAD, icon_size=16, bottom=0, right=0, tooltip='保存至本地',
style=ft.ButtonStyle(color={'hovered': 'primary', '': 'secondary'}),
on_click=self.save_btn_click_event)
self.view_btn = ft.IconButton(ft.icons.REMOVE_RED_EYE, icon_size=16, bottom=0, left=0, tooltip='查看图像',
style=ft.ButtonStyle(color={'hovered': 'primary', '': 'secondary'}),
on_click=self.view_btn_click_event)
self.image_stack = ft.Stack([self.image, self.remove_btn, self.save_btn, self.view_btn],
fit=ft.StackFit.EXPAND, alignment=ft.Alignment(0, 0))
def container_click_event(self, e: ft.ControlEvent):
"""图片查看器被点击的事件"""
self.remove_btn.visible = False
self.save_btn.visible = False
self.view_btn.visible = False
dialog = ft.FilePicker(on_result=self.pick_files_finished, on_upload=self.upload_file_event)
self.page.overlay.append(dialog)
self.page.update()
dialog.pick_files('选择图片', allowed_extensions=['jpg', 'jpeg', 'png', 'webp'])
def container_hover_event(self, e: ft.ControlEvent):
"""鼠标进入或退出图片查看器的事件"""
if self.content == self.txt:
return
if e.data == 'true':
self.remove_btn.visible = True
self.save_btn.visible = True
self.view_btn.visible = True
else:
self.remove_btn.visible = False
self.save_btn.visible = False
self.view_btn.visible = False
self.image_stack.update()
def pick_files_finished(self, e: ft.FilePickerResultEvent):
"""文件选择对话框关闭时触发的事件"""
if e.files is None:
self.page.overlay.remove(e.control)
self.page.update()
return
if self.page.web:
self.image.data = f'{self.upload_dir}/{e.files[0].name}'
upload_way = ft.FilePickerUploadFile(e.files[0].name, self.page.get_upload_url(e.files[0].name, 600))
e.control.upload([upload_way])
return
img_obj = e.files[0]
self.image.src = img_obj.path
self.image.data = img_obj.path
if self.content == self.txt:
self.content = self.image_stack
self.page.update()
self.page.overlay.remove(e.control)
def upload_file_event(self, e: ft.FilePickerUploadEvent):
"""文件上传事件"""
if e.progress == 1:
with open(self.image.data, 'rb') as f:
img_data = f.read()
img_b64 = b64encode(img_data).decode('utf-8')
self.image.src_base64 = img_b64
if self.content == self.txt:
self.content = self.image_stack
self.page.overlay.remove(e.control)
self.page.update()
def remove_btn_click_event(self, e: ft.ControlEvent):
"""关闭图像按钮的点击事件"""
self.image.src = None
self.image.src_base64 = None
self.image.data = None
self.content = self.txt
self.update()
def save_btn_click_event(self, e: ft.ControlEvent):
"""保存图像按钮的点击事件,如果通过浏览器打开,将保存到设定的save_path目录下"""
if not os.path.exists(self.save_path):
os.makedirs(self.save_path, exist_ok=True)
if self.page.web:
img_data = b64decode(self.image.src_base64)
img_name = os.path.split(self.image.data)[1]
with open(f'{self.save_path}/{img_name}', 'wb') as f:
f.write(img_data)
return
dialog = ft.FilePicker(on_result=self.save_file_finished)
self.page.overlay.append(dialog)
self.page.update()
img_name = os.path.split(self.image.data)[-1]
li = img_name.rsplit('.', 1)
img_format = 'jpg' if len(li) == 1 else li[1]
dialog.save_file('保存图片', file_name=img_name, allowed_extensions=[img_format])
def save_file_finished(self, e: ft.FilePickerResultEvent):
"""保存文件对话框关闭时触发的事件"""
save_path = e.path
if save_path:
if '.' not in os.path.split(save_path)[1]:
save_path = f'{save_path}.{e.control.allowed_extensions[0]}'
if self.image.data != save_path:
with open(self.image.data, 'rb') as f, open(save_path, 'wb') as f2:
f2.write(f.read())
self.page.overlay.remove(e.control)
self.page.update()
def view_btn_click_event(self, e: ft.ControlEvent):
"""查看图像按钮的点击事件"""
window_width, window_height = self.page.width, self.page.height
img = Image.open(self.image.data)
img_width, img_height = img.width, img.height
fw, fh = window_width / img_width, window_height / img_height
if fw > fh:
dialog_height = window_height * 0.8
dialog_width = img_width / img_height * dialog_height
else:
dialog_width = window_width * 0.8
dialog_height = img_width / img_height * dialog_width
dialog_content = ft.Image(filter_quality=ft.FilterQuality.MEDIUM, width=dialog_width,
height=dialog_height)
if self.page.web:
with open(self.image.data, 'rb') as f:
img_data = f.read()
img_b64 = b64encode(img_data).decode('utf-8')
dialog_content.src_base64 = img_b64
else:
dialog_content.src = self.image.data
dialog = ft.AlertDialog(content=dialog_content, shape=ft.RoundedRectangleBorder(),
open=True, content_padding=0, actions_padding=0, inset_padding=0, modal=False,
on_dismiss=self.dialog_close_event)
self.page.overlay.append(dialog)
self.page.update()
def dialog_close_event(self, e: ft.ControlEvent):
"""图像显示窗口关闭的事件"""
self.page.overlay.remove(e.control)
self.page.update()
def main(page: ft.Page):
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.vertical_alignment = ft.MainAxisAlignment.CENTER
image_picker = ImageViewer()
page.add(image_picker)
if __name__ == '__main__':
os.environ['FLET_SECRET_KEY'] = 'lasifea'
ft.app(main)
# ft.app(main, view=ft.AppView.WEB_BROWSER, upload_dir='uploads', port=5201) To reproduceNote that the code requires additional installation of the pillow module. After running, selecting an image and clicking again will result in an error. Expected behaviorI hope to find the problem because I cannot determine which specific location went wrong, so I can only display the entire component code(I don't want to either). Screenshots / VideosOperating SystemWindows Operating system detailsWindows10 Flet version0.24.1 RegressionYes, it used to work in a previous Flet version (please specify the version in additional details) SuggestionsNo response LogsLogs[Paste your logs here] Additional detailsNo response |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
|
Beta Was this translation helpful? Give feedback.
Container.image
is of typeDecorationImage
and notImage
.