我正在制作一个用于介绍 CS 课程的图像处理类,使用 PIL 进行图像处理,使用 Tkinter 显示图片。为了让用户在操作图片时能够看到图片,我让图形操作在单独的线程上运行,使用类似于这个问题的代码。这似乎有效(即没有崩溃),但我无法显示图像——Tk 正在启动,但没有出现任何窗口。代码如下所示:
self.root = Tk.Toplevel()
self.frame = tk.Frame(self.root, width=self.image.size[0], height=self.image.size[1])
img = ImageTk.PhotoImage(self.image)
self.label = tk.Label(self.frame, image=img)
self.label.image = img
self.label.pack()
self.frame.pack()
tick()
self.root.mainloop()
该tick
功能类似于链接问题中的功能。我怀疑我的问题是由于对 Tkinter 的误解,但我真的不知道。
此外,我似乎无法让程序很好地退出——即使我daemon=True
在构建Thread
运行它的程序时进行了设置,我仍然必须C-c
在完成时点击。这看起来有点难看,我不想用虚假的错误信息来打扰学生。
编辑:这是更多代码。
class Picture():
##
# Constructor. Creates Picture either by passing in the path of an image file as param
# or by passing in a tuple in the format of (x, y) to indicate the size of a blank image.
# Example 1: Picture("image.jpg") constructs Picture with image.jpg
# Example 2: Picture((500, 500)) constructs Picture with a blank image of size 500*500
def __init__(self, param):
# Check if parameter is the right type, because we can't
# overload functions
if isinstance(param, tuple) and len(param) == 2:
self.image = Image.new('RGB', (param))
elif isinstance(param, str):
self.image = Image.open(param)
else:
raise TypeError('Parameter to Picture() should be a string or 2-tuple!')
# Default values for pen
self.pen_color = (0, 0, 0)
self.pen_position = (0, 0)
self.pen_width = 1.0
self.pen_rotation = 0
# Pixel data of the image
self.pixel = self.image.load()
# Draw object of the image
self.draw = ImageDraw.Draw(self.image)
# The main window, and associated widgets.
self.root = None
self.label = None
self.frame = None
# Threading support, so that we can show the image while
# continuing to draw on it.
self.request_queue = queue.Queue()
self.result_queue = queue.Queue()
self.thread = threading.Thread(target=self._thread_main)
self.thread.start()
def _thread_main(self):
"""
Runs the main Tkinter loop, as well as setting up all the
necessary GUI widgets and whatnot. By running Tkinter on
a separate thread, we can keep the picture displaying
even after the user's program is finished drawing on it.
"""
def tick():
"""
Called whenever Tk's main loop is idle. This lets us perform
drawing operations on the right thread.
"""
try:
f, args, kwargs = self.request_queue.get_nowait()
except queue.Empty:
pass
else:
value = f(*args, **kwargs)
self.result_queue.put(value)
self.root.after_idle(tick)
self.root = tk.Toplevel()
self.frame = tk.Frame(self.root, width=self.image.size[0], height=self.image.size[1])
img = ImageTk.PhotoImage(self.image)
self.label = tk.Label(self.frame, image=img)
# This line ensures that Python doesn't try to garbage collect
# our photo, due to a bug in Tk.
self.label.image = img
self.label.pack()
self.frame.pack()
tick()
self.root.mainloop()
def _submit_operation(self, f, *args, **kwargs):
"""
Submits an operation to the request queue. The arguments
should consist of a function, any positional arguments
to said function, and any keyword arguments to the function.
If f returns a value, that value will be returned.
Any function that does something with the picture (i.e.,
saving it, drawing to it, reading from it, etc.) should
be called only by submitting it to the queue.
"""
self.request_queue.put((f, args, kwargs))
return self.result_queue.get()
##
# Display the picture.
def display(self):
def display_func():
img = ImageTk.PhotoImage(self.image)
self.label.configure(image=img)
self.label.image = img
self.label.pack()
self.frame.pack()
self._submit_operation(display_func)