使用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有明顯差距。

目前沒有找到好方法,如果哪位高手有解決方法,希望不吝賜教,多謝了!

 

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