使用pymupdf开发的pdf查看器-tkinter篇

第三方库 PyMuPDF 在 python 环境下对 PDF 文件的操作,特别是图片和pdf之间相互转换比较方便,并且能较方便的执行一些如追加删除之类的功能。开发文档见:https://pymupdf.readthedocs.io/en/latest/。

    我写的一个查看 pdf文件demo已上传到我的资源中。界面左边栏为页面导航,点击缩略图后显示页面,按ctrl+鼠标滚轮后可缩放页面 ,使用了tkinter开发。CSDN资源下载:https://download.csdn.net/download/zhoury/12458793。欢迎下载参考。

Tkinter 是 Python 的标准 GUI 库。Python 使用 Tkinter 可以快速的创建 GUI 应用程序。

由于 Tkinter 是内置到 python 的安装包中、只要安装好 Python 之后就能 import Tkinter 库、而且 IDLE 也是用 Tkinter 编写而成、对于简单的图形界面 Tkinter 还是能应付自如。

与pyqt5相比,tkinter中如果想要构建一个GUI界面布局,就必须自己写代码,因为Tkinter没有提供一个图形界面设计器。这是tkinter中比较烦人的一个地方。还好本例界面还是比较简单的。

在本例中,我把界面部分和程序逻辑部分做了一些分离,界面类为class MyFrame(tk.Frame),主程序类为class PdfFrame(MyFrame),从MyFrame中继承界面元素后进行事件处理。下面分几部分简要介绍一下:

1、import库

import tkinter as tk
import tkinter.font as tf
from tkinter import messagebox, filedialog, simpledialog
from tkinter import ttk    
from PIL import Image, ImageTk

import sys
import os
import os.path
import time

import fitz    # pymupdf库
 

2、界面类

class MyFrame(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master) 
        self.root = master                 # root ,在主程序中 root = tk.Tk(),然后作为参数引入
        self.pack(fill=tk.BOTH, expand=True)
        self.font_en = tf.Font(self, size=14)

        self.createICO()   #初始化图标
        self.create_menu()   #创建菜单
        self.createToolbar()  #创建工具栏
        self.create_widgets()   #创建主界面

菜单、工具栏生成这里就不写了,主要介绍一下主界面中缩略图列表和内容查看两个部分吧。

2.1缩略图列表

        self.fm_left = tk.Frame(self)     # 左侧缩略图列表栏

        # LabelFrame本身不带滚动条,在LabelFrame中放置label,并带滚动条的方法:
        # 1) 先新建一个canvas,在cavas上放置滚动条,2) 然后在canvas中放置labelframe,frame的长宽,紧跟canvas。
        # 3) 最后canvas.create_window,根据labelframe创建canvas窗口
        self.fm_list_scb_ver = tk.Frame(self.fm_left,width=230)  # 缩略图列表窗口
        vsb1 = tk.Scrollbar(self.fm_list_scb_ver,width=20)    # canvas垂直滚动条
        vsb1.pack(side=tk.RIGHT,fill=tk.Y)
        self.listCanvas = tk.Canvas(self.fm_list_scb_ver,bg = 'white',yscrollcommand=vsb1.set,width=220)  # 缩略图放置canvas
        self.listCanvas.pack(side=tk.LEFT,fill=tk.BOTH, expand=True)
        vsb1.config(command=self.listCanvas.yview)

        self.labelframe = tk.LabelFrame(self.listCanvas)  # 把labelframe放在canvas里
        self.labelframe.pack(fill=tk.BOTH,expand=True)  # labelframe的长宽,和canvas差不多的

        self.fm_list_scb_ver.pack(side=tk.TOP,fill=tk.Y,expand=True)    #放置缩略图列表窗口
        self.fm_left.pack(side=tk.LEFT,fill=tk.Y)    # 放置左侧缩略图列表栏
以上只是建立了一个空的缩略图窗口,还需要在主程序中生成label列表并放置后才能显示。

2.2 图片显示组件

        self.fm_right = tk.Frame(self)    # 右侧查看窗口
        self.fm_canvas_scb_ver = tk.Frame(self.fm_right)  # canvas放置窗口
        vsb = tk.Scrollbar(self.fm_canvas_scb_ver)    # canvas滚动条
        vsb.pack(side=tk.RIGHT,fill=tk.Y) 
        hsb = tk.Scrollbar(self.fm_canvas_scb_ver,orient=tk.HORIZONTAL) 
        hsb.pack(side=tk.BOTTOM,fill=tk.X)
        # 创建canvas,并绑定滚动条
        self.cvCanvas = tk.Canvas(self.fm_canvas_scb_ver,bg = 'white',yscrollcommand=vsb.set,xscrollcommand=hsb.set,scrollregion=(0,0,1200,1200))
        self.cvCanvas.pack(fill=tk.BOTH, expand=True)
        self.cvCanvas.bind("<Control-MouseWheel>", self.canvasProcessWheel)   # 鼠标滚轮事件 CTRL + 滚轮
        vsb.config(command=self.cvCanvas.yview)
        hsb.config(command=self.cvCanvas.xview)
        self.fm_canvas_scb_ver.pack(fill=tk.BOTH, expand=True)  # 放置canvas放置窗口
        self.fm_right.pack(fill=tk.BOTH, expand=True)   # 放置右侧查看窗口

3、主程序

3.1 继承界面类

class PdfFrame(MyFrame):
    def __init__(self, master=None):
        super(PdfFrame,self).__init__(master)    # 调用父类初始化

3.2 打开pdf文件后刷新缩略图列表

3.2.1 读pdf页面,生成label列表
        for i in range(0, self.nPages):
            page = self.docDoc[i]  # 当前页
            zoom = int(100)    # mupdf输出页面缩放,100表示默认大小
            rotate = int(0)
            trans = fitz.Matrix(zoom / 100.0, zoom / 100.0).preRotate(rotate)
            pix = page.getPixmap(matrix=trans, alpha=False)    # 生成pixmap图片
            # set the mode depending on alpha
            mode = "RGBA" if pix.alpha else "RGB"
            w = 210    
            h = 297 
            img = Image.frombytes(mode, [pix.width, pix.height], pix.samples).resize((w,h))    # 生成当前页图
            merge_img = Image.new('RGB', (210, 297), 0xffffff)     # 空白缩略图
            merge_img.paste(img, (0, 0))    # 将图片贴到空白缩略图中

            global My_Global_List_Imgs     # 一定要用global,否则canvas不显示图片
            My_Global_List_Imgs.append(ImageTk.PhotoImage(merge_img))          
            lb = tk.Label(self.labelframe, height=297, width=210 ,image=My_Global_List_Imgs[i])    # 产生缩略图label
            lb.bind("<Double-Button-1>",self.handlerAdaptor(self.clickList,curr=i))   # 绑定鼠标双击,传递参数curr为当前页码
            self.labelList.append(lb)

3.2.2  放置并显示

        canvasHeight = 0  # 列表高度
        self.lbpageList = []    # 缩略图label列表,显示页码
        for j in range(0, self.nPages):
            l = self.labelList[j]        # 放置缩略图label
            l.pack()
            canvasHeight = canvasHeight + 297   # 计算缩略图高度
            lbpage = tk.Label(self.labelframe,text="第"+ str(j+1) + "页")    # 缩略图页码
            lbpage.pack()
            self.lbpageList.append(lbpage)
            canvasHeight = canvasHeight + 28    # 计算页码显示高度(默认行高)

        self.listCanvas.config(scrollregion=(0,0,100,canvasHeight))    # 列表滚动区域
        self.listCanvas.create_window((110, int(canvasHeight/2)), window=self.labelframe)  # create_window
        self.fm_list_scb_ver.pack(side=tk.TOP,fill=tk.Y,expand=True)    # 只允许上下扩展
        self.btnShow.config(image=self.imgleft)       # 缩略图栏目缩放按钮图标
        self.listCanvas.update()  # force screen redraw/update
        time.sleep(0.25)    # pause, but don't block gui
3.3 显示当前页
        page = self.docDoc[self.nCurr]
        zoom = int(200)
        rotate = int(0)
        trans = fitz.Matrix(zoom / 100.0, zoom / 100.0).preRotate(rotate)
        pix = page.getPixmap(matrix=trans, alpha=False)  # 得到当前页

        # set the mode depending on alpha
        mode = "RGBA" if pix.alpha else "RGB"
        self.m_pixmap = Image.frombytes(mode, [pix.width, pix.height], pix.samples)
        self.pixmapw = pix.width
        self.pixmaph = pix.height
        global My_Global_CanVas_Pic    # 要用global变量,否则canvas不显示图片
        My_Global_CanVas_Pic = ImageTk.PhotoImage(self.m_pixmap.resize((self.pixmapw,self.pixmaph)))
        self.cvCanvas.create_image(int(self.pixmapw/2),int(self.pixmaph/2),image=My_Global_CanVas_Pic)
        self.cvCanvas.config(scrollregion=(0,0,self.pixmapw,self.pixmaph))  # canvas滚动范围
        self.fm_canvas_scb_ver.pack(fill=tk.BOTH, expand=True)  # 放置canvas放置窗口
        self.cvCanvas.update()  # force screen redraw/update
        time.sleep(0.25)    # pause, but don't block gui


4、tkinter的问题

在开发中,界面布局代码虽然繁琐,这种编码只是重复劳动,一旦熟练后问题不是很大,最大的问题是性能问题,在本例中主要是canvas刷新比较慢。

在本例中有一个功能是使用ctrl+滚轮缩放页面,前段时间使用pyqt5写了个pymupdf的demo(见https://blog.csdn.net/zhoury/article/details/90743357),相同功能两者比较,pyqt5缩放很流畅,而tkinter有很明显的拖沓,特别是打开文件页数一多、查看的图片放大后,刷新更是有数量级的差距,感觉鼠标滚轮的事件都堵在队列中了。使用self.cvCanvas.update() 强制刷新,效果稍好点,但仍不是很理想,和pyqt5有明显差距。

目前没有找到好方法,如果哪位高手有解决方法,希望不吝赐教,多谢了!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章