合并多个 PDF 文件。这款 PDF 合并工具允许用户浏览文件夹、选择 PDF 文件,并将其合并为一个新的 PDF 文件。我们将详细分析代码结构和如何一步步实现每个功能。
C:\pythoncode\new\PDFFileInFolderMergeToNewPDFFile.py
全部代码
python">import os
import wx
import PyPDF2class PDFMergerFrame(wx.Frame):def __init__(self, parent, title):super(PDFMergerFrame, self).__init__(parent, title=title, size=(900, 600))# Create main panelself.panel = wx.Panel(self)# Create toolbarself.toolbar = self.CreateToolBar()# Add toolbar buttonsbrowse_tool = self.toolbar.AddTool(wx.ID_ANY, "浏览", wx.ArtProvider.GetBitmap(wx.ART_FOLDER_OPEN), "浏览文件夹")merge_tool = self.toolbar.AddTool(wx.ID_ANY, "合成", wx.ArtProvider.GetBitmap(wx.ART_NEW), "合成PDF文件")self.toolbar.Realize()# Bind toolbar eventsself.Bind(wx.EVT_TOOL, self.on_browse, browse_tool)self.Bind(wx.EVT_TOOL, self.on_merge, merge_tool)# Create main sizermain_sizer = wx.BoxSizer(wx.HORIZONTAL)# Create left panel for foldersleft_panel = wx.Panel(self.panel)left_sizer = wx.BoxSizer(wx.VERTICAL)folder_label = wx.StaticText(left_panel, label="文件夹:")self.folder_listbox = wx.ListBox(left_panel, size=(200, -1), style=wx.LB_SINGLE)left_sizer.Add(folder_label, 0, wx.ALL, 5)left_sizer.Add(self.folder_listbox, 1, wx.EXPAND | wx.ALL, 5)left_panel.SetSizer(left_sizer)# Create middle panel for filesmiddle_panel = wx.Panel(self.panel)middle_sizer = wx.BoxSizer(wx.VERTICAL)file_label = wx.StaticText(middle_panel, label="子文件:")self.file_listbox = wx.ListBox(middle_panel, size=(200, -1), style=wx.LB_SINGLE)middle_sizer.Add(file_label, 0, wx.ALL, 5)middle_sizer.Add(self.file_listbox, 1, wx.EXPAND | wx.ALL, 5)middle_panel.SetSizer(middle_sizer)# Create button panelbutton_panel = wx.Panel(self.panel)button_sizer = wx.BoxSizer(wx.VERTICAL)add_button = wx.Button(button_panel, label=">")remove_button = wx.Button(button_panel, label="<")button_sizer.Add(add_button, 0, wx.ALL, 5)button_sizer.Add(remove_button, 0, wx.ALL, 5)button_panel.SetSizer(button_sizer)# Create right panel for selected filesright_panel = wx.Panel(self.panel)right_sizer = wx.BoxSizer(wx.VERTICAL)selected_label = wx.StaticText(right_panel, label="已选文件:")self.selected_listbox = wx.ListBox(right_panel, size=(200, -1), style=wx.LB_SINGLE)right_sizer.Add(selected_label, 0, wx.ALL, 5)right_sizer.Add(self.selected_listbox, 1, wx.EXPAND | wx.ALL, 5)right_panel.SetSizer(right_sizer)# Add panels to main sizermain_sizer.Add(left_panel, 1, wx.EXPAND | wx.ALL, 5)main_sizer.Add(middle_panel, 1, wx.EXPAND | wx.ALL, 5)main_sizer.Add(button_panel, 0, wx.EXPAND | wx.ALL, 5)main_sizer.Add(right_panel, 1, wx.EXPAND | wx.ALL, 5)# Set main sizerself.panel.SetSizerAndFit(main_sizer)# # Set main sizer# self.panel.SetSizerAndFit(main_sizer)# Bind eventsself.folder_listbox.Bind(wx.EVT_LISTBOX, self.on_folder_select)add_button.Bind(wx.EVT_BUTTON, self.on_add_file)remove_button.Bind(wx.EVT_BUTTON, self.on_remove_file)# Initialize variablesself.root_path = ""self.folders_with_files = []# Center and show frameself.Centre()self.Show()def on_browse(self, event):"""Handle browse button click"""dlg = wx.DirDialog(self, "选择文件夹", style=wx.DD_DEFAULT_STYLE)if dlg.ShowModal() == wx.ID_OK:self.root_path = dlg.GetPath()self.scan_folders()dlg.Destroy()def scan_folders(self):"""Scan folders and list those with PDF files"""if not self.root_path:returnself.folders_with_files = []self.folder_listbox.Clear()for root, dirs, files in os.walk(self.root_path):has_pdf = Falsefor file in files:if file.lower().endswith('.pdf'):has_pdf = Truebreakif has_pdf:self.folders_with_files.append(root)folder_name = os.path.basename(root) or rootself.folder_listbox.Append(folder_name)def on_folder_select(self, event):"""Handle folder selection"""selected_index = self.folder_listbox.GetSelection()if selected_index == wx.NOT_FOUND:returnselected_folder = self.folders_with_files[selected_index]self.file_listbox.Clear()for file in os.listdir(selected_folder):if file.lower().endswith('.pdf'):self.file_listbox.Append(file)def on_add_file(self, event):"""Add selected file to the list of files to merge"""selected_file_index = self.file_listbox.GetSelection()selected_folder_index = self.folder_listbox.GetSelection()if selected_file_index == wx.NOT_FOUND or selected_folder_index == wx.NOT_FOUND:returnselected_file = self.file_listbox.GetString(selected_file_index)selected_folder = self.folders_with_files[selected_folder_index]full_path = os.path.join(selected_folder, selected_file)# Check if already in the listfor i in range(self.selected_listbox.GetCount()):if self.selected_listbox.GetClientData(i) == full_path:returnself.selected_listbox.Append(selected_file, full_path)def on_remove_file(self, event):"""Remove selected file from the list of files to merge"""selected_index = self.selected_listbox.GetSelection()if selected_index != wx.NOT_FOUND:self.selected_listbox.Delete(selected_index)def on_merge(self, event):"""Merge selected PDF files"""if self.selected_listbox.GetCount() == 0:wx.MessageBox("请选择至少一个PDF文件", "警告", wx.OK | wx.ICON_WARNING)return# Ask for output filewith wx.FileDialog(self, "保存合并的PDF", wildcard="PDF files (*.pdf)|*.pdf",style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as fileDialog:if fileDialog.ShowModal() == wx.ID_CANCEL:returnoutput_path = fileDialog.GetPath()try:merger = PyPDF2.PdfMerger()# Add all selected filesfor i in range(self.selected_listbox.GetCount()):file_path = self.selected_listbox.GetClientData(i)merger.append(file_path)# Write to output filemerger.write(output_path)merger.close()wx.MessageBox(f"PDF文件已成功合并为: {output_path}", "成功", wx.OK | wx.ICON_INFORMATION)except Exception as e:wx.MessageBox(f"合并PDF时出错: {str(e)}", "错误", wx.OK | wx.ICON_ERROR)class PDFMergerApp(wx.App):def OnInit(self):frame = PDFMergerFrame(None, "PDF合并工具")self.SetTopWindow(frame)return Trueif __name__ == "__main__":app = PDFMergerApp()app.MainLoop()
背景
wxPython
是一个基于 wxWidgets
的 Python GUI 库,它提供了创建跨平台桌面应用程序的强大工具。PyPDF2
则是一个 Python 库,用于处理 PDF 文件的操作,如合并、拆分、提取文本等。
本示例程序结合了 wxPython
用于构建图形界面和 PyPDF2
用于处理 PDF 合并的功能。
主要功能
- 浏览文件夹:用户可以选择一个文件夹,程序会扫描该文件夹下所有的 PDF 文件。
- 选择文件:用户可以从文件夹中选择需要合并的 PDF 文件。
- 合并文件:用户选择多个 PDF 文件后,程序会将其合并成一个新的 PDF 文件。
接下来,让我们详细分析代码。
代码解析
1. 初始化 wx.Frame
窗口
python">class PDFMergerFrame(wx.Frame):def __init__(self, parent, title):super(PDFMergerFrame, self).__init__(parent, title=title, size=(900, 600))
我们创建了一个继承自 wx.Frame
的类 PDFMergerFrame
,这是应用程序的主窗口。窗口的大小为 900x600
。
2. 创建工具栏
python"># Create toolbar
self.toolbar = self.CreateToolBar()# Add toolbar buttons
browse_tool = self.toolbar.AddTool(wx.ID_ANY, "浏览", wx.ArtProvider.GetBitmap(wx.ART_FOLDER_OPEN), "浏览文件夹")
merge_tool = self.toolbar.AddTool(wx.ID_ANY, "合成", wx.ArtProvider.GetBitmap(wx.ART_NEW), "合成PDF文件")
self.toolbar.Realize()
在窗口中添加了一个工具栏,并且为工具栏添加了两个按钮:
- 浏览:用来选择文件夹,浏览文件夹并列出包含 PDF 文件的文件夹。
- 合成:用来合并用户选择的 PDF 文件。
按钮的图标通过 wx.ArtProvider.GetBitmap
方法加载。
3. 创建布局和界面控件
我们使用 wx.BoxSizer
创建了主界面布局,并将不同的控件添加到面板中。
python"># Create main sizer
main_sizer = wx.BoxSizer(wx.HORIZONTAL)
main_sizer
是一个水平的 BoxSizer
,用于将控件横向排列。接下来,我们定义了四个面板,分别用于显示:
- 文件夹选择:显示文件夹列表。
- 文件选择:显示当前文件夹中的 PDF 文件。
- 按钮面板:显示添加和移除文件的按钮。
- 已选文件:显示用户选择的文件列表。
每个面板使用 wx.Panel
创建,并且通过 wx.ListBox
控件展示文件夹或文件列表。
4. 文件夹扫描功能
python">def scan_folders(self):"""Scan folders and list those with PDF files"""if not self.root_path:returnself.folders_with_files = []self.folder_listbox.Clear()for root, dirs, files in os.walk(self.root_path):has_pdf = Falsefor file in files:if file.lower().endswith('.pdf'):has_pdf = Truebreakif has_pdf:self.folders_with_files.append(root)folder_name = os.path.basename(root) or rootself.folder_listbox.Append(folder_name)
scan_folders
函数扫描用户选择的文件夹并列出其中包含 PDF 文件的子文件夹。使用 os.walk
递归遍历文件夹,并筛选出后缀为 .pdf
的文件。如果文件夹中包含 PDF 文件,则将文件夹添加到 folder_listbox
。
5. 选择文件夹后的操作
python">def on_folder_select(self, event):"""Handle folder selection"""selected_index = self.folder_listbox.GetSelection()if selected_index == wx.NOT_FOUND:returnselected_folder = self.folders_with_files[selected_index]self.file_listbox.Clear()for file in os.listdir(selected_folder):if file.lower().endswith('.pdf'):self.file_listbox.Append(file)
用户在 folder_listbox
中选择一个文件夹后,on_folder_select
方法会被触发。此时,程序会列出该文件夹中的所有 PDF 文件,并显示在 file_listbox
中供用户选择。
6. 添加和移除文件
python">def on_add_file(self, event):"""Add selected file to the list of files to merge"""selected_file_index = self.file_listbox.GetSelection()selected_folder_index = self.folder_listbox.GetSelection()if selected_file_index == wx.NOT_FOUND or selected_folder_index == wx.NOT_FOUND:returnselected_file = self.file_listbox.GetString(selected_file_index)selected_folder = self.folders_with_files[selected_folder_index]full_path = os.path.join(selected_folder, selected_file)# Check if already in the listfor i in range(self.selected_listbox.GetCount()):if self.selected_listbox.GetClientData(i) == full_path:returnself.selected_listbox.Append(selected_file, full_path)
当用户选择文件并点击 >
按钮时,选中的文件将被添加到 selected_listbox
中。在添加之前,程序会检查该文件是否已经在列表中,防止重复添加。
移除文件的操作也是类似的,用户可以通过 <
按钮从 selected_listbox
中删除已选文件。
7. 合并PDF文件
python">def on_merge(self, event):"""Merge selected PDF files"""if self.selected_listbox.GetCount() == 0:wx.MessageBox("请选择至少一个PDF文件", "警告", wx.OK | wx.ICON_WARNING)return# Ask for output filewith wx.FileDialog(self, "保存合并的PDF", wildcard="PDF files (*.pdf)|*.pdf",style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as fileDialog:if fileDialog.ShowModal() == wx.ID_CANCEL:returnoutput_path = fileDialog.GetPath()try:merger = PyPDF2.PdfMerger()# Add all selected filesfor i in range(self.selected_listbox.GetCount()):file_path = self.selected_listbox.GetClientData(i)merger.append(file_path)# Write to output filemerger.write(output_path)merger.close()wx.MessageBox(f"PDF文件已成功合并为: {output_path}", "成功", wx.OK | wx.ICON_INFORMATION)except Exception as e:wx.MessageBox(f"合并PDF时出错: {str(e)}", "错误", wx.OK | wx.ICON_ERROR)
最后,合并 PDF 的操作由 on_merge
方法实现。用户选择了要合并的 PDF 文件后,程序会弹出文件保存对话框,询问用户保存合并后文件的位置。使用 PyPDF2.PdfMerger
将所有选择的 PDF 文件合并并保存。