合并多个 PDF 文件。这款 PDF 合并工具允许用户浏览文件夹、选择 PDF 文件,并将其合并为一个新的 PDF 文件。我们将详细分析代码结构和如何一步步实现每个功能。
C:\pythoncode\new\PDFFileInFolderMergeToNewPDFFile.py
全部代码
python
import os
import wx
import PyPDF2
class PDFMergerFrame(wx.Frame):
def __init__(self, parent, title):
super(PDFMergerFrame, self).__init__(parent, title=title, size=(900, 600))
# Create main panel
self.panel = wx.Panel(self)
# 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()
# Bind toolbar events
self.Bind(wx.EVT_TOOL, self.on_browse, browse_tool)
self.Bind(wx.EVT_TOOL, self.on_merge, merge_tool)
# Create main sizer
main_sizer = wx.BoxSizer(wx.HORIZONTAL)
# Create left panel for folders
left_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 files
middle_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 panel
button_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 files
right_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 sizer
main_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 sizer
self.panel.SetSizerAndFit(main_sizer)
# # Set main sizer
# self.panel.SetSizerAndFit(main_sizer)
# Bind events
self.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 variables
self.root_path = ""
self.folders_with_files = []
# Center and show frame
self.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:
return
self.folders_with_files = []
self.folder_listbox.Clear()
for root, dirs, files in os.walk(self.root_path):
has_pdf = False
for file in files:
if file.lower().endswith('.pdf'):
has_pdf = True
break
if has_pdf:
self.folders_with_files.append(root)
folder_name = os.path.basename(root) or root
self.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:
return
selected_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:
return
selected_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 list
for i in range(self.selected_listbox.GetCount()):
if self.selected_listbox.GetClientData(i) == full_path:
return
self.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 file
with 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:
return
output_path = fileDialog.GetPath()
try:
merger = PyPDF2.PdfMerger()
# Add all selected files
for i in range(self.selected_listbox.GetCount()):
file_path = self.selected_listbox.GetClientData(i)
merger.append(file_path)
# Write to output file
merger.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 True
if __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:
return
self.folders_with_files = []
self.folder_listbox.Clear()
for root, dirs, files in os.walk(self.root_path):
has_pdf = False
for file in files:
if file.lower().endswith('.pdf'):
has_pdf = True
break
if has_pdf:
self.folders_with_files.append(root)
folder_name = os.path.basename(root) or root
self.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:
return
selected_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:
return
selected_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 list
for i in range(self.selected_listbox.GetCount()):
if self.selected_listbox.GetClientData(i) == full_path:
return
self.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 file
with 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:
return
output_path = fileDialog.GetPath()
try:
merger = PyPDF2.PdfMerger()
# Add all selected files
for i in range(self.selected_listbox.GetCount()):
file_path = self.selected_listbox.GetClientData(i)
merger.append(file_path)
# Write to output file
merger.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 文件合并并保存。
运行结果
