I am trying to make a reactive scrollable list of items.
我正在尝试制作一个被动的可滚动的项目列表。
currently I have a canvas, scrollbar and item container defined as follows:
目前,我有一个画布、滚动条和项目容器,定义如下:
self.scroll_bar = tk.Scrollbar(self.container, orient=tk.VERTICAL)
self.scroll_bar.pack(side=tk.RIGHT, fill=tk.Y)
self.canvas = tk.Canvas(self.container, yscrollcommand=self.scroll_bar.set)
self.canvas.pack(fill=tk.BOTH, expand=True)
self.scroll_bar.config(command=self.canvas.yview)
self.item_container = tk.Frame(self.canvas)
self.canvas.create_window((0, 0), window=self.item_container, anchor=tk.NW)
when items (tk.Frame
s) are added or removed from item_container
it calls the update_canvas_scroll_region method:
在Item_Container中添加或删除项(tk.Frames)时,它会调用UPDATE_Canvas_Sscroll_Region方法:
def update_canvas_scrollregion(self):
self.item_container.update_idletasks()
self.canvas.config(scrollregion=self.canvas.bbox("all"))
this works fine for adding items but doesn't update properly when items are removed, I think its because the canvas.bbox("all")
doesn't shrink to fit when items are deleted.
这对于添加项目很有效,但在删除项目时不会正确更新,我认为这是因为canvar.bbox(“all”)在删除项目时不会缩小到合适的大小。
full code: https://github.com/harrisabdullah/tk_scrollable_list
完整代码:https://github.com/harrisabdullah/tk_scrollable_list
更多回答
No, the issue is not canvas.bbox('all')
. The problem is that when you delete the image
or frame
with ImageEditUI.delete()
there is no link with ScrollableList.delete_item()
and thus the scrollable region does not get adjusted.
不,问题不是canvar.bbox(‘all’)。问题是,当您使用ImageEditUI.ete()删除图像或帧时,没有与ScrollableList.Delete_Item()的链接,因此不会调整可滚动区域。
If you print child after deleting, you will see that they were not deleted. Probably because of the references to them from the class. I managed to delete them by name.
如果您在删除后打印子项,您将看到它们没有被删除。可能是因为类中对它们的引用。我设法删除了他们的名字。
优秀答案推荐
I took the liberty to change the class ImageEditUI
into ImageFrame
as that is what the class is doing, it makes a frame containing the image, input field and a delete button.
The ScrollableList
is the main UI and in order to make the link with ImageFrame
it creates the instance in the __init__
(self.image_frame = ImageFrame()
).
我擅自将ImageEditUI类更改为ImageFrame,因为这就是该类正在做的事情,它创建了一个包含图像、输入字段和一个删除按钮的框架。ScrollableList是主用户界面,为了与ImageFrame建立链接,它在__init__(self.Image_Frame=ImageFrame())中创建了实例。
To make the link with the delete button (made in ImageFrame
) bind this button to the function delete_item
of ScrollableList
and pass this function the newly created image_frame
. Deleting the frame is done by a call to ImageFrame.delete_item
要创建带有删除按钮的链接(在ImageFrame中制作),请将该按钮绑定到ScrollableList的函数DELETE_ITEM,并将新创建的IMAGE_FRAME传递给该函数。删除帧是通过调用ImageFrame.Delete_Item完成的
A working example is shown below.
下面显示了一个工作示例。
from functools import partial
import tkinter as tk
from PIL import Image, ImageTk
class ScrollableList:
def __init__(self):
self.item_container = None
self.canvas = None
self.scroll_bar = None
self.container = None
self.image_frame = ImageFrame()
def start(self, root):
self.container = tk.Frame(root)
self.container.pack(fill=tk.BOTH, expand=True)
self.scroll_bar = tk.Scrollbar(self.container, orient=tk.VERTICAL)
self.scroll_bar.pack(side=tk.RIGHT, fill=tk.Y)
self.canvas = tk.Canvas(self.container, yscrollcommand=self.scroll_bar.set)
self.canvas.pack(fill=tk.BOTH, expand=True)
self.scroll_bar.config(command=self.canvas.yview)
self.item_container = tk.Frame(self.canvas)
self.canvas.create_window((0, 0), window=self.item_container, anchor=tk.NW)
self.canvas.bind_all("<MouseWheel>", self.on_mousewheel)
def update_canvas_scrollregion(self):
self.item_container.update_idletasks()
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def on_mousewheel(self, event):
self.canvas.yview_scroll(event.delta, "units")
def add_item(self, image_path):
image_frame, delete_button = self.image_frame.add_item(
self.item_container, image_path
)
# link the delete button from the image_frame to the delete_item function of ImageContainer
delete_button.configure(
command=partial(self.delete_item, image_frame=image_frame)
)
self.update_canvas_scrollregion()
def delete_item(self, image_frame):
self.image_frame.delete_item(image_frame)
self.update_canvas_scrollregion()
class ImageFrame:
def __init__(self):
pass
def add_item(self, container, image_path):
PIL_image = Image.open(image_path)
PIL_image.thumbnail((800, 800))
image = ImageTk.PhotoImage(PIL_image)
image_frame = tk.Frame(container)
image_frame.pack()
image_label = tk.Label(image_frame, image=image)
image_label.image = image
image_label.pack(padx=5, pady=5)
label = tk.Label(image_frame, text="entre words in image: ")
label.pack()
input = tk.Entry(image_frame, width=50)
input.pack()
delete_button = tk.Button(image_frame, text="Delete")
delete_button.pack()
return image_frame, delete_button
def delete_item(self, container):
for child in container.winfo_children():
child.destroy()
container.destroy()
def export(self):
pass
def main():
scrollable_list = ScrollableList()
root = tk.Tk()
scrollable_list.start(root)
scrollable_list.add_item("pic3.jpg")
scrollable_list.add_item("pic4.jpg")
scrollable_list.add_item("pic5.jpg")
scrollable_list.add_item("pic6.jpg")
root.mainloop()
if __name__ == "__main__":
main()
更多回答
One suggestion instead of have a method start
, I would move all this to the __init__(self, root)
. Then start the app by calling scrollable_list = ScrollableList(root)
in main
.
一个建议是,我将把所有这些都移到__init__(self,根),而不是让方法开始。然后在Main中调用Scrollable_List=ScrollableList(根)来启动应用程序。
我是一名优秀的程序员,十分优秀!