You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 15:50:32