使用image_create更新Tkinter文本组件的优化及缩放改进需求
问题
当前PDF查看器功能正常,但处理大型PDF时,加载新文件或使用缩放按钮会出现明显闪烁。目前实现是通过fitz库将PDF页面转为图片,再用image_create方法插入到Tkinter文本组件中;另外当前缩放依赖销毁重建文本组件,希望找到更高效的替代方案。
原代码
import tkinter as tk from tkinter import filedialog import ttkbootstrap as tb import fitz import os import shutil #---------------------------------------------------------- class PDFExample(tb.Frame): #------------------------------------------------------ def __init__(self, root, filename=None): super().__init__(root) self.root = root self.filename = filename self.pack(fill='both', expand=True) self.dpi = 100 # Setup self.setupViewer() self.checkSelection() #------------------------------------------------------ def setupViewer(self): # Create 2 Frames self.pdf_frame = tb.Frame(self) self.pdf_frame.pack(side='left', fill='both', expand=1) self.button_frame = tb.Frame(self) self.button_frame.pack(side='right', fill='y') # Create PDF Viewer self.pdfViewer() # Create Buttons self.open_button = tb.Button(self.button_frame, text='Open PDF', command=self.open) self.open_button.grid(row=0, column=0, padx=5, pady=5, sticky='nsew') self.save_button = tb.Button(self.button_frame, text='Save PDF', command=self.save, state='disabled') self.save_button.grid(row=1, column=0, padx=5, pady=5, sticky='nsew') self.zoom_in = tb.Button(self.button_frame, text='Zoom In', command=self.zoomIn, state='disabled') self.zoom_in.grid(row=2, column=0, padx=5, pady=5, sticky='nsew') self.zoom_out = tb.Button(self.button_frame, text='Zoom Out', command=self.zoomOut, state='disabled') self.zoom_out.grid(row=3, column=0, padx=5, pady=5, sticky='nsew') #------------------------------------------------------ def checkSelection(self): if self.filename is not None and os.path.exists(self.filename): self.save_button.config(state='enable') self.zoom_in.config(state='enable') self.zoom_out.config(state='enable') # Show PDF self.addImage() #------------------------------------------------------ def pdfViewer(self): scroll_y = tk.Scrollbar(self.pdf_frame, orient='vertical', width=16) scroll_x = tk.Scrollbar(self.pdf_frame, orient='horizontal', width=16) scroll_x.pack(side='bottom', fill='x') scroll_y.pack(side='right', fill='y') self.text = tb.Text(self.pdf_frame, yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set) self.text.pack(side='top', fill='both', expand=True) scroll_x.config(command=self.text.xview) scroll_y.config(command=self.text.yview) #------------------------------------------------------ def open(self): files = [('PDF', '*.pdf'), ('All Files', '*.*')] ask_where = filedialog.askopenfile(filetypes=files, defaultextension='.pdf') self.filename = ask_where.name self.addImage() #------------------------------------------------------ def save(self): files = [('PDF', '*.pdf'), ('All Files', '*.*')] ask_where = filedialog.asksaveasfile(filetypes=files, defaultextension='.pdf') shutil.copy(self.filename, ask_where.name) #------------------------------------------------------ def zoomIn(self): self.dpi += 10 self.addImage() #------------------------------------------------------ def zoomOut(self): self.dpi -= 10 self.addImage() #------------------------------------------------------ def addImage(self): self.img_object_li = [] with fitz.open(self.filename) as open_pdf: for page in open_pdf: pix = page.get_pixmap(dpi=self.dpi) pix1 = fitz.Pixmap(pix,0) if pix.alpha else pix img = pix1.tobytes('ppm') timg = tb.PhotoImage(data=img) self.img_object_li.append(timg) # Destroy PDF Viewer and Recreate in order to Zoom # There has to be a better way but this is all I # could figure out for now for widget in self.pdf_frame.winfo_children(): widget.destroy() self.pdfViewer() for i in self.img_object_li: self.text.image_create('end', image=i) self.text.insert('end', '\n\n') self.text.configure(state='disabled') #---------------------------------------------------------- if __name__ == '__main__': app = tb.Window(title='Test') p = PDFExample(app) app.position_center() app.mainloop()
优化后的代码及说明
import tkinter as tk from tkinter import filedialog import ttkbootstrap as tb import fitz import os import shutil #---------------------------------------------------------- class PDFExample(tb.Frame): #------------------------------------------------------ def __init__(self, root, filename=None): super().__init__(root) self.root = root self.filename = filename self.pack(fill='both', expand=True) self.dpi = 100 self.img_cache = {} # 缓存已生成的图片,键为(文件名, DPI) # Setup self.setupViewer() self.checkSelection() #------------------------------------------------------ def setupViewer(self): # Create 2 Frames self.pdf_frame = tb.Frame(self) self.pdf_frame.pack(side='left', fill='both', expand=1) self.button_frame = tb.Frame(self) self.button_frame.pack(side='right', fill='y') # Create PDF Viewer self.pdfViewer() # Create Buttons self.open_button = tb.Button(self.button_frame, text='Open PDF', command=self.open) self.open_button.grid(row=0, column=0, padx=5, pady=5, sticky='nsew') self.save_button = tb.Button(self.button_frame, text='Save PDF', command=self.save, state='disabled') self.save_button.grid(row=1, column=0, padx=5, pady=5, sticky='nsew') self.zoom_in = tb.Button(self.button_frame, text='Zoom In', command=self.zoomIn, state='disabled') self.zoom_in.grid(row=2, column=0, padx=5, pady=5, sticky='nsew') self.zoom_out = tb.Button(self.button_frame, text='Zoom Out', command=self.zoomOut, state='disabled') self.zoom_out.grid(row=3, column=0, padx=5, pady=5, sticky='nsew') #------------------------------------------------------ def checkSelection(self): if self.filename is not None and os.path.exists(self.filename): self.save_button.config(state='normal') self.zoom_in.config(state='normal') self.zoom_out.config(state='normal') self.addImage() #------------------------------------------------------ def pdfViewer(self): scroll_y = tk.Scrollbar(self.pdf_frame, orient='vertical', width=16) scroll_x = tk.Scrollbar(self.pdf_frame, orient='horizontal', width=16) scroll_x.pack(side='bottom', fill='x') scroll_y.pack(side='right', fill='y') # 启用双缓冲减少闪烁 self.text = tb.Text(self.pdf_frame, yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set, borderwidth=0, highlightthickness=0) self.text.pack(side='top', fill='both', expand=True) scroll_x.config(command=self.text.xview) scroll_y.config(command=self.text.yview) #------------------------------------------------------ def open(self): files = [('PDF', '*.pdf'), ('All Files', '*.*')] ask_where = filedialog.askopenfile(filetypes=files, defaultextension='.pdf') if not ask_where: # 处理用户取消选择的情况 return self.filename = ask_where.name self.save_button.config(state='normal') self.zoom_in.config(state='normal') self.zoom_out.config(state='normal') self.addImage() #------------------------------------------------------ def save(self): files = [('PDF', '*.pdf'), ('All Files', '*.*')] ask_where = filedialog.asksaveasfile(filetypes=files, defaultextension='.pdf') if not ask_where: # 处理用户取消保存的情况 return shutil.copy(self.filename, ask_where.name) #------------------------------------------------------ def zoomIn(self): if self.dpi < 300: # 限制最大缩放比例,避免性能问题 self.dpi += 10 self.addImage() #------------------------------------------------------ def zoomOut(self): if self.dpi > 50: # 限制最小缩放比例,避免显示过小 self.dpi -= 10 self.addImage() #------------------------------------------------------ def addImage(self): cache_key = (self.filename, self.dpi) # 优先从缓存取图片,避免重复转换PDF if cache_key in self.img_cache: self.img_object_li = self.img_cache[cache_key] else: self.img_object_li = [] with fitz.open(self.filename) as open_pdf: for page in open_pdf: pix = page.get_pixmap(dpi=self.dpi) pix1 = fitz.Pixmap(pix,0) if pix.alpha else pix img = pix1.tobytes('ppm') timg = tb.PhotoImage(data=img) self.img_object_li.append(timg) self.img_cache[cache_key] = self.img_object_li # 清空文本内容而非销毁组件,彻底解决闪烁 self.text.config(state='normal') self.text.delete(1.0, tk.END) for i in self.img_object_li: self.text.image_create('end', image=i) self.text.insert('end', '\n\n') self.text.config(state='disabled') #---------------------------------------------------------- if __name__ == '__main__': app = tb.Window(title='Test') p = PDFExample(app) app.position_center() app.mainloop()
核心优化点
- 移除组件销毁逻辑:不再重建滚动条和文本组件,改为直接清空内容,彻底消除闪烁根源
- 图片缓存机制:对相同文件和DPI的图片进行缓存,避免重复转换PDF页面,大幅提升缩放和重复加载速度
- DPI范围限制:设置缩放上下限,防止过度缩放导致性能下降或显示异常
- 增加异常判断:处理用户取消文件选择/保存的操作,避免程序报错
- 启用双缓冲:通过文本组件参数配置,减少绘制过程中的闪烁
内容的提问来源于stack exchange,提问作者blaufer
相关产品推荐
相关产品推荐

