섭섭한 개발일지

[VB] Visual Basic으로 PDF 병합 프로그램 만들기 (3 - BE) 본문

Project/PDF병합프로그램

[VB] Visual Basic으로 PDF 병합 프로그램 만들기 (3 - BE)

Seop 2023. 1. 12. 11:20

[PDF병합프로그램 설치파일 다운로드]

 

 

 

앞서 UI에서 구성한 요소들의 동작과 PDFBox lib를 통해 PDF를 병합하는 방법을 알아야 한다.

병합하는 방법은 라이브러리를 찾을 때 간단하게 본 내용이 있어 이해한 내용을 토대로 UI를 만들었고

이제는 더 확인해 볼 필요가 있을 것 같다.

 

https://www.tutorialspoint.com/pdfbox/pdfbox_merging_multiple_pdf_documents.htm

 

 

pdf를 병합하는 로직은 간단하다.

 

PDFMergerUtility 클래스를 사용하고 합칠 파일들의 경로를 soruce 에 넣어 merge만 하면 된다.

 

파일을 선택하고 경로를 가져오기 위해 OpenFileDialog를 사용하고

합친 PDF를 저장할 경로와 파일명을 설정하기 위해 SaveFileDialog 를 사용할 거다.

 

 

 

 

OK. 이제 전체적인 프로세스 설계도를 그려보자

 

 

 

ListBox에 파일 목록을 보여주고 순서를 설정하거나 파일을 삭제할 수 있게 할 거다.

실제 PDF 합치기에 들어가면 ListBox에 있는 순서대로 파일을 합칠 것이라 key와 value로 이루어진 Map 형태의 객체 배열이 필요했다.

.net을 잘 몰라 찾아보니 Dictionary가 Java의 Map과 같은 형태의 객체 배열이다.

이것을 사용할 것이고 아래와 같은 코드로 병합할 PDF 순서를 맞추려고 한다.

For index = 0 To AddFileList.Items.Count - 1
    findName = AddFileList.Items(index)
    util.addSource(files(findName))
Next

 

그럼 이제 코드를 작성하자.

 

 

기본 설정

우선 App이 실행되면 "파일추가" 버튼 이외에 모든 버튼을 비활성화를 한다.

Public Class Form1
    Private Sub PdfMerge_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Btn_Enabled()
    End Sub
    
    Private Sub Btn_Enabled()
        FileCleanBtn.Enabled = False
        FileDeleteBtn.Enabled = False
        FileMergeBtn.Enabled = False
        FileIndexUp.Enabled = False
        FileIndexDown.Enabled = False
    End Sub
End Class

 

코드를 작성하다 보면 복잡해질 것 을 고려하여

버튼 활성화 관련된 코드를 다른 클래스로 분리하자

 

Public Class PdfMerge
    Private PDFFunction As PDFFunction = New PDFFunction
    Private Sub PdfMerge_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        PDFFunction.App_All_Btn_Enabled()
    End Sub
End Class


Public Class PDFFunction
    Public Sub App_All_Btn_Enabled()
        PdfMerge.FileCleanBtn.Enabled = False
        PdfMerge.FileDeleteBtn.Enabled = False
        PdfMerge.FileMergeBtn.Enabled = False
        PdfMerge.FileIndexUp.Enabled = False
        PdfMerge.FileIndexDown.Enabled = False
    End Sub
End Class

 

 

 

파일 추가

다음은 파일 추가에 대한 기능이다.

Public Class PdfMerge
	Private PDFFunction As PDFFunction = New PDFFunction
    
	Private Const ERROR_MSG As String = "ERROR"
	Private Const PDF As String = ".pdf"
	Private Const COMP_MSH As String = "COMPLETE"

	
	Private Sub SelectFileBtn_Click(sender As Object, e As EventArgs) Handles SelectFileBtn.Click
		Dim selectFile As OpenFileDialog = New OpenFileDialog
		Dim selectFileResult As DialogResult
		Dim filePath As String = ""
		Dim fileName As String = ""
		Dim fileExtension As String = ""

		selectFile.Filter = "PDF (.pdf)|*.pdf" ' PDF만 선택할 수 있도록 Filter 설정
		selectFileResult = selectFile.ShowDialog()

		If selectFileResult = DialogResult.OK Then ' 파일을 선택했는지 확인
			filePath = selectFile.FileName
			fileName = selectFile.SafeFileName
			fileExtension = IO.Path.GetExtension(filePath)

		ElseIf selectFileResult = DialogResult.Cancel Then
			MsgBox("파일을 선택하지 않았습니다.",, ERROR_MSG)
			Return
		End If

		If Files.ContainsKey(fileName) Then ' 목록에 동일한 파일명이 존재할 경우 추가 불가
			MsgBox("'" + fileName + "'과 동일한 파일명이 존재합니다.
파일명을 다르게 설정해 주세요.",, ERROR_MSG)
			Return
		End If

		If fileExtension.Equals(PDF) Then ' PDF 형식의 파일인지 확인
			AddFileList.Items.Add(fileName)
			Files.Add(fileName, filePath)
			PDFFunction.List_Add_Item_Btn_Enabled()
			MsgBox("'" + fileName + "'이(가) 추가되었습니다.",, COMP_MSH)
		Else
			MsgBox("PDF 형식의 파일만 선택이 가능합니다.",, COMP_MSH)
			Return
		End If

		PDFFunction.List_Add_Two_Item_Btn_Enabled()
	End Sub
End Class


Public Class PDFFunction
    Public Sub List_Add_Item_Btn_Enabled()
        PdfMerge.FileCleanBtn.Enabled = True
    End Sub

    Public Sub List_Add_Two_Item_Btn_Enabled()
        If PdfMerge.AddFileList.Items.Count >= 2 Then
            PdfMerge.FileMergeBtn.Enabled = True
            PdfMerge.FileIndexUp.Enabled = True
            PdfMerge.FileIndexDown.Enabled = True
        End If
    End Sub
End Class

파일추가 버튼을 눌렀을 때 OpenFileDialog 객체를 통해 이용자가 직접 파일을 선택하도록 할 수 있다.

이용자가 파일을 추가할 때 오직 PDF만을 선택하도록 Filter를 설정하였다.

 

파일을 선택하면 filePath와 fileName 그리고 확장자 정보를 변수에 적용하고

동일한 파일명이 이미 목록에 있는지와 PDF 형식인지 한 번 더 검증 절차를 밟는다.

 

문제가 없다면 ListBox와 Dictionary 배열에 값을 저장한다.

 

 

파일 삭제

파일 삭제는 단순히 ListBox에서 선택한 Item 값을 가져와  ListBox 와 Dictionary 배열에서 제거를 해주면 된다.

만약 목록에 파일이 없거나 삭제를 함으로 인하여 파일이 더 이상 없을 경우 파일 삭제 버튼과 초기화 버튼을 비활성화해주면 된다.

Public Class PdfMerge
	Private Sub FileDeleteBtn_Click(sender As Object, e As EventArgs) Handles FileDeleteBtn.Click
		Dim selectItem As Integer = AddFileList.SelectedIndex
		MsgBox("'" + AddFileList.Items(selectItem) + "' 파일을 목록에서 제거합니다.",, "COMP")
		Files.Remove(AddFileList.Items(selectItem))
		AddFileList.Items.RemoveAt(selectItem)
		PDFFunction.Delete_Other_Btn_Enabled()
	End Sub

	Private Sub AddFileList_SelectedIndexChanged(sender As Object, e As EventArgs) Handles AddFileList.SelectedIndexChanged
		PDFFunction.Delete_Btn_Enabled()
	End Sub
End Class

Public Class PDFFunction
    Public Sub Delete_Other_Btn_Enabled()
        If PdfMerge.AddFileList.Items.Count = 1 Then
            PdfMerge.FileMergeBtn.Enabled = False
            PdfMerge.FileIndexUp.Enabled = False
            PdfMerge.FileIndexDown.Enabled = False
        End If

        If PdfMerge.AddFileList.Items.Count = 0 Then
            PdfMerge.FileCleanBtn.Enabled = False
            PdfMerge.FileMergeBtn.Enabled = False
        End If
    End Sub
End Class

 

파일 삭제 버튼을 비활성화 시키는 기능은 ListBox를 선택할 경우 발생되는 Event에서 처리를 했다.

파일 삭제 버튼을 클릭하면 ListBox와 Dictionary 배열에서 삭제를 하는 것으로 끝난다.

 

 

목록 초기화
Public Class PdfMerge
	Private Sub FileCleanBtn_Click(sender As Object, e As EventArgs) Handles FileCleanBtn.Click
		Files.Clear()
		AddFileList.Items.Clear()
		MsgBox("목록을 초기화 하였습니다.",, "COMP")
		PDFFunction.App_All_Btn_Enabled()
	End Sub
End Class

목록을 초기화하는 버튼은 ListBox와 Dictionary를 모두 초기화한 후 App 실행 시 호출한 Btn_Enabled 메소드를 호출해 주면 끝이다.

 

 

목록 순서 조정

ListBox의 정렬되어 있는 순서대로 병합이 이루어지기에 목록 순서를 조정은 꽤나 중요하다.

파일 삭제에서 ListBox를 클릭할 때 발생한 Event도 수정한다.

Public Class PdfMerge
	' Change Index Up
	Private Sub FileIndexUp_Click(sender As Object, e As EventArgs) Handles FileIndexUp.Click
		If (AddFileList.SelectedIndex <= 0) Then
			Return
		End If

		Dim upItemIndex As Integer = AddFileList.SelectedIndex
		Dim downItemIndex As Integer = AddFileList.SelectedIndex - 1

		Dim saveUpItem As String = AddFileList.Items(upItemIndex)
		Dim saveDownItem As String = AddFileList.Items(downItemIndex)

		AddFileList.Items(downItemIndex) = saveUpItem
		AddFileList.Items(upItemIndex) = saveDownItem

		AddFileList.SelectedIndex = downItemIndex
        FileIndexUp.Focus()
	End Sub

	' Change Index Down
	Private Sub FileIndexDown_Click(sender As Object, e As EventArgs) Handles FileIndexDown.Click
		If (AddFileList.Items.Count <= AddFileList.SelectedIndex + 1) Then
			Return
		End If

		Dim downItemIndex As Integer = AddFileList.SelectedIndex
		Dim upItemIndex As Integer = AddFileList.SelectedIndex + 1

		Dim saveDownItem As String = AddFileList.Items(downItemIndex)
		Dim saveUpItem As String = AddFileList.Items(upItemIndex)

		AddFileList.Items(upItemIndex) = saveDownItem
		AddFileList.Items(downItemIndex) = saveUpItem

		AddFileList.SelectedIndex = upItemIndex
        FileIndexDown.Focus()
	End Sub
End Class


Public Class PDFFunction
' 범용성 있도록 수정 Delete_Btn_Enabled -> Lsit_Select_Btn_Enabled
    Public Sub Lsit_Select_Btn_Enabled()
        If PdfMerge.AddFileList.SelectedIndex = -1 Then
            PdfMerge.FileDeleteBtn.Enabled = False
            PdfMerge.FileIndexUp.Enabled = False
            PdfMerge.FileIndexDown.Enabled = False
        Else
            PdfMerge.FileDeleteBtn.Enabled = True
            PdfMerge.FileIndexUp.Enabled = True
            PdfMerge.FileIndexDown.Enabled = True
        End If
    End Sub
End Class

 

파일 병합

이 프로그램의 가장 핵심 부분인 파일 병합이다. 

 

Public Class PdfMerge
    Private Sub FileMergeBtn_Click(sender As Object, e As EventArgs) Handles FileMergeBtn.Click
		PDFFunction.Merge_PDF(Files)
	End Sub
End Class

Imports org.apache.pdfbox.util

Public Class PDFFunction
	Public Sub Merge_PDF(ByVal Files As Dictionary(Of String, String))
		Dim util As New PDFMergerUtility
		Dim saveFilePath As New DialogResult

		Dim File_Validate As Boolean = PDF_Merge_Source(util, Files)

		If Not (File_Validate) Then
			Return
		End If

		Dim savePath As SaveFileDialog = Save_File_Setup()
		saveFilePath = savePath.ShowDialog()

		If Not (saveFilePath = DialogResult.OK) Then
			MsgBox("취소",, "CANCEL")
			Return
		End If

		util.setDestinationFileName(savePath.FileName.ToString)
		util.mergeDocuments()
		MsgBox("PDF 합치기가 완료되었습니다.",, "COMP")
	End Sub

	' SaveFileDialog의 기본 값 설정
	Private Function Save_File_Setup() As SaveFileDialog
		Dim savePath As SaveFileDialog = New SaveFileDialog()
		savePath.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
		savePath.DefaultExt = "pdf"
		savePath.Filter = "PDF (.pdf)|*.pdf"
		Return savePath
	End Function

	' 병합할 소스들을 순차적으로 source로 삽입
	Private Function PDF_Merge_Source(ByRef util As PDFMergerUtility, ByVal Files As Dictionary(Of String, String)) As Boolean
		Dim findName As String = ""
		Try
			For index = 0 To PdfMerge.AddFileList.Items.Count - 1
				findName = PdfMerge.AddFileList.Items(index)
				util.addSource(Files(findName))
			Next
			Return True
		Catch e_runtime As java.lang.RuntimeException
			MsgBox("'" + findName + "' 파일을 확인해 주세요.", , "ERROR")
			Return False
		Catch e_all As Exception
			MsgBox("[ERROR] 예상치 못한 에러가 발생하였습니다.
관리자에게 문의해 주세요.
" + e_all.ToString,, "ERROR")
			Return False
		End Try
	End Function
End Class

병합 기능을 나름 읽기 편할 정도로 기능을 쪼개기는 했는데 그래도 복잡한 듯싶다... (러프하게 한다는 의도로 안도를..)

 

우선 Save_file_Setup 기능을 보면 이용자가 병합 버튼을 눌렀을 때 저장 경로와 저장할 이름을 선택하는 창에 대한 속성을 설정한다.

창이 뜰 때 기본 경로를 바탕화면으로 설정하였고 PDF 형식의 파일로 저장이 되도록 설정한 것이다.

 

 

다음 PDF_Merge_Source 기능은 파일들의 path 정보를 가지고 있는 Dictionary 배열을 파람으로 받는다.

반복문을 통해 순차적으로 ListBox에서 값을 꺼내고 꺼낸 값으로 Dictionary의 키를 넣어 value를 뽑아내 병합 소스로 넣는다.

 

만약 여기서 PDF 파일이 아닌 파일이 있을 경우 RuntimeException 이 발생하여 문제의 파일명을 알려주고 병합을 멈추게 했다.

외에는 예상치 못한 오류로 묶어버렸다.

 

정상적으로 실행이 되면 true를 반환하고 아닐 경우 false을 반환하게 했다.

 

 

마지막으로 위 두 기능을 호출하여 PDF를 병합해 줄 Merge_PDF를 보자

실제 위 기능들을 호출한 뒤 문제가 발생하면 기능 실행을 멈추고 Return을 시켜버리고

 

정상이라면 saveFileDialog를 통해 유저가 선택한 경로와 저장할 파일명을 setDestinationFileName()를 통해 util에 값을 설정한다.

mergeDocuments() 메소드를 호출하면 병합이 된다.

 

 

 

 

이것으로 러프하게 만들어본 PDF 병합 프로그램 끝  !

 

 

 

 

전체 소스코드 보기
더보기

GIt (https://github.com/tseop/VB_PDFMergeSolution/tree/dev)

Build 참고자료 (https://blog.naver.com/goldrushing/130166294554)

 

[Main]

Imports org.apache.pdfbox.util

Public Class PdfMerge
	Private PDFFunction As PDFFunction = New PDFFunction
	Private Files As Dictionary(Of String, String) = New Dictionary(Of String, String)

	Private Const ERROR_MSG As String = "ERROR"
	Private Const PDF As String = ".pdf"
	Private Const COMP_MSH As String = "COMPLETE"

	Private Sub PdfMerge_Load(sender As Object, e As EventArgs) Handles MyBase.Load
		PDFFunction.App_All_Btn_Enabled()
	End Sub

	' Add file
	Private Sub SelectFileBtn_Click(sender As Object, e As EventArgs) Handles SelectFileBtn.Click
		Dim selectFile As OpenFileDialog = New OpenFileDialog
		Dim selectFileResult As DialogResult
		Dim filePath As String = ""
		Dim fileName As String = ""
		Dim fileExtension As String = ""

		selectFile.Filter = "PDF (.pdf)|*.pdf" ' PDF만 선택할 수 있도록 Filter 설정
		selectFileResult = selectFile.ShowDialog()

		If selectFileResult = DialogResult.OK Then ' 파일을 선택했는지 확인
			filePath = selectFile.FileName
			fileName = selectFile.SafeFileName
			fileExtension = IO.Path.GetExtension(filePath)

		ElseIf selectFileResult = DialogResult.Cancel Then
			MsgBox("파일을 선택하지 않았습니다.",, ERROR_MSG)
			Return
		End If

		If Files.ContainsKey(fileName) Then ' 목록에 동일한 파일명이 존재할 경우 추가 불가
			MsgBox("'" + fileName + "'과 동일한 파일명이 존재합니다.
파일명을 다르게 설정해 주세요.",, ERROR_MSG)
			Return
		End If

		If fileExtension.Equals(PDF) Then ' PDF 형식의 파일인지 확인
			AddFileList.Items.Add(fileName)
			Files.Add(fileName, filePath)
			PDFFunction.List_Add_Item_Btn_Enabled()
			MsgBox("'" + fileName + "'이(가) 추가되었습니다.",, COMP_MSH)
		Else
			MsgBox("PDF 형식의 파일만 선택이 가능합니다.",, COMP_MSH)
			Return
		End If

		PDFFunction.List_Add_Two_Item_Btn_Enabled()
	End Sub

	' Delete file
	Private Sub FileDeleteBtn_Click(sender As Object, e As EventArgs) Handles FileDeleteBtn.Click
		Dim selectItem As Integer = AddFileList.SelectedIndex
		MsgBox("'" + AddFileList.Items(selectItem) + "' 파일을 목록에서 제거합니다.",, "COMP")
		Files.Remove(AddFileList.Items(selectItem))
		AddFileList.Items.RemoveAt(selectItem)
		PDFFunction.Delete_Other_Btn_Enabled()
	End Sub

	' Select ListBox
	Private Sub AddFileList_SelectedIndexChanged(sender As Object, e As EventArgs) Handles AddFileList.SelectedIndexChanged
		PDFFunction.List_Select_Btn_Enabled()
	End Sub

	' Clear file
	Private Sub FileCleanBtn_Click(sender As Object, e As EventArgs) Handles FileCleanBtn.Click
		Files.Clear()
		AddFileList.Items.Clear()
		MsgBox("목록을 초기화 하였습니다.",, "COMP")
		PDFFunction.App_All_Btn_Enabled()
	End Sub

	' Change Index Up
	Private Sub FileIndexUp_Click(sender As Object, e As EventArgs) Handles FileIndexUp.Click
		If (AddFileList.SelectedIndex <= 0) Then
			Return
		End If

		Dim upItemIndex As Integer = AddFileList.SelectedIndex
		Dim downItemIndex As Integer = AddFileList.SelectedIndex - 1

		Dim saveUpItem As String = AddFileList.Items(upItemIndex)
		Dim saveDownItem As String = AddFileList.Items(downItemIndex)

		AddFileList.Items(downItemIndex) = saveUpItem
		AddFileList.Items(upItemIndex) = saveDownItem

		AddFileList.SelectedIndex = downItemIndex
		FileIndexUp.Focus()
	End Sub

	' Change Index Down
	Private Sub FileIndexDown_Click(sender As Object, e As EventArgs) Handles FileIndexDown.Click
		If (AddFileList.Items.Count <= AddFileList.SelectedIndex + 1) Then
			Return
		End If

		Dim downItemIndex As Integer = AddFileList.SelectedIndex
		Dim upItemIndex As Integer = AddFileList.SelectedIndex + 1

		Dim saveDownItem As String = AddFileList.Items(downItemIndex)
		Dim saveUpItem As String = AddFileList.Items(upItemIndex)

		AddFileList.Items(upItemIndex) = saveDownItem
		AddFileList.Items(downItemIndex) = saveUpItem

		AddFileList.SelectedIndex = upItemIndex
		FileIndexDown.Focus()
	End Sub

	Private Sub FileMergeBtn_Click(sender As Object, e As EventArgs) Handles FileMergeBtn.Click
		PDFFunction.Merge_PDF(Files)
	End Sub
End Class

 

 

[Function]

Imports org.apache.pdfbox.util

Public Class PDFFunction
	Public Sub App_All_Btn_Enabled()
		PdfMerge.FileCleanBtn.Enabled = False
		PdfMerge.FileDeleteBtn.Enabled = False
		PdfMerge.FileMergeBtn.Enabled = False
		PdfMerge.FileIndexUp.Enabled = False
		PdfMerge.FileIndexDown.Enabled = False
	End Sub

	Public Sub List_Add_Item_Btn_Enabled()
		PdfMerge.FileCleanBtn.Enabled = True
	End Sub

	Public Sub List_Add_Two_Item_Btn_Enabled()
		If PdfMerge.AddFileList.Items.Count >= 2 Then
			PdfMerge.FileMergeBtn.Enabled = True
			PdfMerge.FileIndexUp.Enabled = True
			PdfMerge.FileIndexDown.Enabled = True
		End If
	End Sub

	Public Sub List_Select_Btn_Enabled()
		If PdfMerge.AddFileList.SelectedIndex = -1 Then
			PdfMerge.FileDeleteBtn.Enabled = False
			PdfMerge.FileIndexUp.Enabled = False
			PdfMerge.FileIndexDown.Enabled = False
		Else
			PdfMerge.FileDeleteBtn.Enabled = True
			PdfMerge.FileIndexUp.Enabled = True
			PdfMerge.FileIndexDown.Enabled = True
		End If
	End Sub

	Public Sub Delete_Other_Btn_Enabled()
		If PdfMerge.AddFileList.Items.Count = 1 Then
			PdfMerge.FileMergeBtn.Enabled = False
			PdfMerge.FileIndexUp.Enabled = False
			PdfMerge.FileIndexDown.Enabled = False
		End If

		If PdfMerge.AddFileList.Items.Count = 0 Then
			PdfMerge.FileCleanBtn.Enabled = False
			PdfMerge.FileMergeBtn.Enabled = False
		End If
	End Sub

	Public Sub Merge_PDF(ByVal Files As Dictionary(Of String, String))
		Dim util As New PDFMergerUtility
		Dim saveFilePath As New DialogResult

		Dim savePath As SaveFileDialog = Save_File_Setup()
		saveFilePath = savePath.ShowDialog()

		If Not (saveFilePath = DialogResult.OK) Then
			MsgBox("취소",, "CANCEL")
			Return
		End If

		Dim File_Validate As Boolean = PDF_Merge_Source(util, Files)

		If Not (File_Validate) Then
			Return
		End If

		util.setDestinationFileName(savePath.FileName.ToString)
		util.mergeDocuments()
		MsgBox("PDF 합치기가 완료되었습니다.",, "COMP")
	End Sub

	' SaveFileDialog Default Setup
	Private Function Save_File_Setup() As SaveFileDialog
		Dim savePath As SaveFileDialog = New SaveFileDialog()
		savePath.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
		savePath.DefaultExt = "pdf"
		savePath.Filter = "PDF (.pdf)|*.pdf"
		Return savePath
	End Function

	Private Function PDF_Merge_Source(ByRef util As PDFMergerUtility, ByVal Files As Dictionary(Of String, String)) As Boolean
		Dim findName As String = ""
		Try
			For index = 0 To PdfMerge.AddFileList.Items.Count - 1
				findName = PdfMerge.AddFileList.Items(index)
				util.addSource(Files(findName))
			Next
			Return True
		Catch e_runtime As java.lang.RuntimeException
			MsgBox("'" + findName + "' 파일을 확인해 주세요.", , "ERROR")
			Return False
		Catch e_all As Exception
			MsgBox("[ERROR] 예상치 못한 에러가 발생하였습니다.
관리자에게 문의해 주세요.
" + e_all.ToString,, "ERROR")
			Return False
		End Try
	End Function
End Class

 

 

Comments