PROGRAMMING WORKSHOP

화일관리도구 |단축메뉴이용하기

단축메뉴라는 것을 하나 만들어서 달아 보자
화일목록상자를 오른쪽 마우스를 크릭하면 그림과 같이 메뉴가 나타나게 해보자
UserForm에는 이런 기능이 없지만
엑셀의 기능을 최대한 활용하여 응용해보도록 하자
실용적이지는 않겠지만, 프로그래밍학습차원에서 해보도록 한다
Class모듈을 다시 한번 활용학습하는 기회가 되고
명령줄의 활용기회가 되는 셈이다



명령줄(Commandbar)을 만들고 이것을 UserForm의 목록상자를
오른쪽 마우스버튼으로 크릭하면 명령줄이 나타나게 하는 것
그러려면 우선 크래스모듈을 하나 삽입하고 아래와 같이 작성한다

Option Explicit
명령버튼을 만들기 위한 개체변수
Private WithEvents btnMove As Office.CommandBarButton
Private WithEvents btnSelectAll As Office.CommandBarButton
Private WithEvents btnOpenFile As Office.CommandBarButton
Private WithEvents btnDeleteFile As Office.CommandBarButton
Private WithEvents btnAddFile As Office.CommandBarButton

UserForm에서 메뉴명령을 사용할 개체
Public WithEvents lst As MSForms.ListBox

 
선언부에 WithEvents 키워드로 유효한 개체변수를 선언하면 
아래와 같이 해당개체의 이벤트프로시져를 사용할수 있게 된다
Private Sub btnAddFile_Click(ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean)
 ...
End Sub

Private Sub btnDeleteFile_Click(ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean)
 ...
End Sub

Private Sub btnMove_Click(ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean)
 ...
End Sub

Private Sub btnOpenFile_Click(ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean)
 ...
End Sub

Private Sub btnSelectAll_Click(ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean)
 ...
End Sub

UserForm의 사용할 목록상자의 MouseUp이벤트프로시져에서 메뉴를 생성하게 된다
Button=1은 왼쪽버튼이고 2는 오른쪽 버튼이다


Private Sub lst_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
   If Button = 2 Then buildShortCutMenu X, Y
End Sub
위의 목록상자에 메뉴줄을 만드는 것을 호출하는 프로시져, 이곳에서 메뉴를 만든다

Sub buildShortCutMenu(X As Single, Y As Single)
On Error Resume Next
CommandBars("ShortCutMenu").Delete
On Error GoTo 0
With CommandBars.Add(Name:="ShortCutMenu", Position:=msoBarPopup)
    Set btnMove = .Controls.Add(Type:=msoControlButton)
    btnMove.Caption = "이동"
    Set btnSelectAll = .Controls.Add(Type:=msoControlButton)
    btnSelectAll.Caption = "전체선택"
    Set btnOpenFile = .Controls.Add(Type:=msoControlButton)
    btnOpenFile.Caption = "화일열기"
    Set btnDeleteFile = .Controls.Add(Type:=msoControlButton)
    btnDeleteFile.Caption = "화일삭제"
    Set btnAddFile = .Controls.Add(Type:=msoControlButton)
    btnAddFile.Caption = "화일추가"
    .ShowPopup
End With
CommandBars("ShortCutMenu").Delete
End Sub

Worksheet도 하나의 개체이고
위에서 크래스모듈로 만든 것도 개체다, 엄밀히 따지면 아직 개체가 생성되지 않은
그냥 개체를 위한 설계도면이다
여러분이 워크시트를 하나 프로그래밍적으로 새로 만들던,명령버튼을
크릭하여 만들던..위와 같은 개체를 정의한 설계도를 실행시키면 비로서
새로운 워크시트개체가 만들어지듯이 위의 크래스모듈의 것도 어디에선가
실행을 시켜서 개체를 만들어야 한다
그렇지 않으면 위의 내용은 아무짝에도 쓸모없는 물건이 되는 것이다
위의 것을 아래와 같이 생성한다

UserForm모듈시트의 전역변수로 위의 크래스모듈에서 생성될 개체를 담을 변수를 하나 선언하고
Public oShortCutMenu As clsShortCutMenu

UserForm이 로딩될때의 Initialize이벤트에 빨강색 프로시져를 하나 추가한다
Private Sub UserForm_Initialize()

fillCategories
setControls
setShortCutMenu
End Sub

전역변수에 아래와 같이 생성한 개체를 담아준다
Sub setShortCutMenu() 

Set oShortCutMenu = New clsShortCutMenu 
그리고 해당목록상자를 크래스모듈내의 목록상자개체변수에 담는다
Set oShortCutMenu.lst = Me.lstFile 
End Sub

아래의 화일에서
각자가 크래스모듈내의 빈탕으로 남겨진 각각의 명령버튼의 이벤트프로시져에
하고 싶은 일들을 작성해 보시기 바란다
그리고 다음 화일에서 비교해 보시면서 ..

***[LOG-IN]***

크래스모듈을 넣고 만드는 개체는 [사용자정의 개체]라고 말할수 있을 것이다
흔히 함수를 VBA로 만들어서 사용할때 [사용자정의 함수]라고 말하듯이
엑셀에 내장된 개체나 함수가 아닌 사용자가 만들어서 사용하는 것
그러니 필요할때마다 만들어 보려고 하는 것이 중요하다
실은 VB.Net이라는 것으로 만약 발전을 하게 된다면
이곳에서는 사용자정의개체를 만들어 사용하는 것이 VBA에서 프로시져를
만들어서 사용하는 것 만큼이나 흔하게 만들어서 사용하게 되니까..
뜻이 있다면 자꾸 도전을 해보시는 것이 다음 단계공략에서도 피가 되고 살이 된다
그리고 UserForm자체가 낯섫다고 한다면 UNO_Weekly에서 연재 되는 UserForm시리즈에
재 도전해보시면서 기초를 다지시기 바란다

그림과 같이 이동메뉴는 화일의 경로를 다른 경로로 옮기고 싶다면
해당경로를 선택하면 그쪽으로 이동하게 하고 싶다고 할때
메뉴시스템을 Tree형식으로 만들어 갈수 있을 것이다



그런데 아쉬운 것은 DropDown타입의 버튼에는 이벤트가 없다
Tree타입으로 만들기 위하여 변수를 아래와 같이 작성하고 실행하면(이벤트를 활용하겠다고..)



와 같이 이벤트가 공급이 되지 않는 개체 인것이다..
WithEvents 없이 사용하면 그냥 껍데기 메뉴만 만들어질수 있을뿐..
이벤트를 사용할수 없으니, 원하는 작업을 할수 없는 것이다
애초부터 이벤트가 있을 필요가 없는 개체를 한번 사용해 볼까?!
하는 것이 잘못..
그렇다면 항상 돌아가는 길은 있는 법..
그냥 [이동] 메뉴버튼만 두고 UserForm을 하나 더 띄워보자



콘트롤이던 UserForm이던 필요하면 계속 만들어서 사용하면된다
Worksheet개체가 필요하면 계속 만들어서 사용하듯이

새롭게 만든 UserForm은 ListBox콘트롤 하나만 넣고
이곳에 분류명 목록을 메인폼에 나타났던 내용을 그대로 사용하면 되니까..
코드를 그대로 사용하면 된다
하다보면, 그대로 사용하게 되는 경우가 많이 생기고..
이럴때 코드의 효율적 관리라는 관점으로 보게 되는 것이다

새로만든 폼을 이름을 frmMoveTo를 띄우는 메뉴버튼의 크릭이벤트에

Private Sub btnMove_Click(ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean)
frmMoveTo.Show
End Sub

frmMoveTo폼이 로딩되면서 해당UserForm모듈시트의 내용대로 초기화된다

현재화일이 위치한 경로 Index를 전역변수에 보관하도록 하고
Dim iCurrentSelectedCategoryID As Integer
폼을 초기화시키는 두개의 프로시져를 실행한다
Private Sub UserForm_Initialize()
setControl
fillCategories
End Sub
이것은 각 폼마다 똑같은 것이 있다
나중에 이렇게 중복되는 것을 하나로 만들고 호출하도록 하는 것이 좋을 것이다
Sub setControl() Me.Caption = "선택화일 다른 경로로 옮기기" Me.lstMoveto.Font.Name = "맑은 고딕" Me.lstMoveto.Font.Size = 10 End Sub 이것도 메인폼의 분류명채우는 프로시져와 똑같지만 우선 이곳에 같은 내용을 또 하나 만들자 Tree형식의 부모,자식관계의 분류명을 목록상자에 넣는 것이라서 재귀형식으로 처리한점 다시 한번 상기 하시고... Private Sub fillCategories() On Error Resume Next Dim rTable As Range Dim iX As Integer, rRow As Range Me.lstMoveto.Clear Set rTable = modMain.getTable(modMain.CATEGORY_TABLE_START) If rTable.Rows.Count = 0 Then Exit Sub Set rTable = rTable.Offset(1).Resize(rTable.Rows.Count - 1) For Each rRow In rTable.Rows If rRow.Cells(modMain.TBL_CATE_P_CATEGORY_ID_COL).Value = 0 Then Me.lstMoveto.AddItem rRow.Cells(modMain.TBL_CATE_CATEGORY_COL) collectChild rRow.Cells(modMain.TBL_CATE_CATEGORY_ID_COL), rTable, 1 End If Next iCurrentSelectedCategoryID = frmFileManager.lstCategoryView.ListIndex Me.lstMoveto.ListIndex = iCurrentSelectedCategoryID End Sub 재귀프로시져 Private Sub collectChild(iID As Integer, rTable As Range, iLevel As Integer) Dim rRow As Range For Each rRow In rTable.Rows If rRow.Cells(modMain.TBL_CATE_P_CATEGORY_ID_COL) = iID Then Me.lstMoveto.AddItem String(iLevel, " ") & modMain.CATEGORY_TXT_INDENT & rRow.Cells(modMain.TBL_CATE_CATEGORY_COL) collectChild rRow.Cells(modMain.TBL_CATE_CATEGORY_ID_COL), rTable, iLevel + 1 End If Next End Sub

위와 같이 멍청한 짓을 한 경우...
나중에 정리하지..뭐...하고 중복되는 프로시져를
그대로 갖여다가 사용하고 하다 보면 나중에 처리할수 있는 엄두가 나지 않게 된다
이것이 좀 껄끄러운데..지금 수정을 할까..그냥 빨리 실행시키는 방법을 갈까??
망설이게 된다..
이때 방법은 즉각 고치는 것이 총체적으로 볼때 시간이 절약된다
그래서 아래의 화일에서 위의 내용을 수정했다
메인폼인 frmFileManager의 fillCategories 프로시져를 조금 수정하여
다른 폼에서도 (위의 frmMoveto 폼)에서 같이 사용하게 하는 것이 좋은 방법
위와 같이 frmMoveTo에 재작성한 내용을 모두 없애고 아래와 같이
frmFileManager의 프로시져를 같이 사용하게 하자


어떤 목록상자에 채울지,매개변수로 목록상자를 전달받게 하면
다른 폼에서도 이것을 호출하여 사용하게 하면 좋은 것
'Private Sub fillCategories()
Public Sub fillCategories(oList As MSForms.ListBox)
On Error Resume Next
Dim rTable As Range
Dim iX As Integer, rRow As Range

'Me.lstCategoryView.Clear
oList.Clear
Set rTable = modMain.getTable(modMain.CATEGORY_TABLE_START)
If rTable.Rows.Count = 0 Then Exit Sub
Set rTable = rTable.Offset(1).Resize(rTable.Rows.Count - 1)
For Each rRow In rTable.Rows
    If rRow.Cells(modMain.TBL_CATE_P_CATEGORY_ID_COL).Value = 0 Then
        'Me.lstCategoryView.AddItem rRow.Cells(modMain.TBL_CATE_CATEGORY_COL)
		oList.AddItem rRow.Cells(modMain.TBL_CATE_CATEGORY_COL)
        collectChild rRow.Cells(modMain.TBL_CATE_CATEGORY_ID_COL), rTable, 1, oList
    End If
Next
End Sub

재귀프로시져도 매개변수하나 추가하고
'Private Sub collectChild(iID As Integer, rTable As Range, iLevel As Integer)
Private Sub collectChild(iID As Integer, rTable As Range, iLevel As Integer, oList As MSForms.ListBox)
Dim rRow As Range
For Each rRow In rTable.Rows
    If rRow.Cells(modMain.TBL_CATE_P_CATEGORY_ID_COL) = iID Then
        'Me.lstCategoryView.AddItem String(iLevel, " ") & modMain.CATEGORY_TXT_INDENT & rRow.Cells(modMain.TBL_CATE_CATEGORY_COL)
        oList.AddItem String(iLevel, " ") & modMain.CATEGORY_TXT_INDENT & rRow.Cells(modMain.TBL_CATE_CATEGORY_COL)
        collectChild rRow.Cells(modMain.TBL_CATE_CATEGORY_ID_COL), rTable, iLevel + 1, oList
    End If
Next
End Sub

아래 화일에 위의 내용을 주석처리하였으니 잘 관찰하시고
귀찮은듯하지만 ,효율적인 매개변수의 활용에 대한 생각을 굳히시면 좋을 것이다

***[LOG-IN]***


아래와 같이 어떤 분류명에 있는 내용을 다른 분류명으로 옮기겠냐는
메시지 박스까지 띄워본다



목록상자에 나타난 분류명중에 옮기고 싶은 분류명을 선택하면
실행되는 프로시져는 아래와 같다

Private Sub lstMoveto_Change()
If bStopEvent Then Exit Sub
If lstMoveto.ListIndex = iCurrentSelectedCategoryID Then Exit Sub
Dim sFileToMove As String
Dim sMoveToCategory As String
Dim iIndex As Integer
Dim lstFile As MSForms.ListBox
Dim sCate As String
Dim iMoveToCategory As Integer
Dim iMoveToCategory_ As Integer
Dim oCategories As New Collection
Set lstFile = frmFileManager.lstFile
iMoveToCategory = Me.lstMoveto.ListIndex
iMoveToCategory_ = iMoveToCategory
부모경로 찾아 올라가기, 찾아서 집합체에 담는다
Do
    sCate = Me.lstMoveto.List(iMoveToCategory_)
    oCategories.Add sCate
    iMoveToCategory_ = iMoveToCategory_ - 1
Loop Until InStr(sCate, "|") = 0
집합체를 꺼꾸로 순환하여 문자열로 만든다
For iIndex = oCategories.Count To 1 Step -1
    sMoveToCategory = sMoveToCategory & oCategories(iIndex) & vbNewLine
Next
옮길 화일명 문자열로 모으기
For iIndex = 0 To lstFile.ListCount - 1
    If lstFile.Selected(iIndex) = True Then
        sFileToMove = sFileToMove & lstFile.List(iIndex) & vbNewLine
    End If
Next
If MsgBox(sFileToMove & "를 " & String(2, vbNewLine) & sMoveToCategory & vbNewLine & " 로 옮기겠습니까?", vbYesNo) = vbYes Then
    MsgBox "다음 화일에서.."  
End If
End Sub

코드란 항상 복잡해 보이지만, 잘 딜다 보면 별 것아닌 생각을 정리하는 것
생각이 정리가 잘되면 코딩은 자동으로 작성될수 있을 것이다

***[LOG-IN]***

이제 화일의 위치를 다른 분류명소속으로 바꾸는 것은 이미 잘 준비놓았던 테이블을 가져오기 프로시져라던가, 각 테이블의 열의 위치를 표현하는 상수들이 있기때문에 하나도 힘들지 않게 처리되는 것이다 처음 준비할때 준비를 철저히 해두면 기능이 추가 되면서 이미 준비하였던 자원을 잘 활용하게 되는 것이다


For iIndex = 0 To lstFile.ListCount - 1
    If lstFile.Selected(iIndex) = True Then
        sFileToMove = sFileToMove & lstFile.List(iIndex) & vbNewLine
        지난화일의 구문중에 아래 한줄을 추가한다
		옮길화일의 화일 ID값을 같이 묶어서 집합체에 담아주기
		oFiles.Add lstFile.List(iIndex) & "|" & lstFile.List(iIndex, 3)
    End If
Next
옮기겠다는 사용자의 결정에 따라서
옮기는 작업을 아래와 같이 하면 될 것이다
If MsgBox(sFileToMove & "를 " & String(2, vbNewLine) & sMoveToCategory & vbNewLine & " 로 옮기겠습니까?", vbYesNo) = vbYes Then
    Dim iIDOld As Integer
    Dim iIDNew As Integer
    Dim rCategoryCol As Range
    Dim rCategoryID As Range
    Dim varX As Variant
    Dim iFileRow As Integer
	화일테이블의 분류코드를 바꾸면 되니까..이미 준비한 상수와 함수를 호출하면 된다
    Set rCategoryCol = modMain.getTable(modMain.FILE_TABLE_START).Columns(modMain.TBL_FILE_CATEGORY_ID_COL)
    iIDNew = modMain.getIDByCategory(modMain.stripBad(oCategories(1)))
	이것은 필요없지만 그냥 만들어 본것이고..
    iIDOld = modMain.getIDByCategory(modMain.stripBad(frmFileManager.lstCategoryView.List(iCurrentSelectedCategoryID)))
	옮겨야 할 화일을 순환하면서,해당화일위치의 화일 ID값을 알아내어
	해당 분류코드를 바꿔주면 되는 것
    For Each varX In oFiles
        iFileRow = Split(varX, "|")(1)
        For Each rCategoryID In rCategoryCol.Cells
            If rCategoryID.Row = iFileRow Then
                rCategoryID = iIDNew
            End If
        Next
    Next
	분류코드를 바꿔준후 화일목록을 다시 초기화, 이것도 이미 만들어사용하던 것을
	호출하면 된다..
    frmFileManager.fillFilesList (frmFileManager.lstCategoryView.Text)
	작업이 끝났으니 폼을 내리고..
    Unload Me
End If

***[LOG-IN]***

화일 관리도구는 특별한 질문이 없는 한
이 정도에서 마치고, 다음 코너로 넘어가 보자
다음 코너는 DB를 활용하는 방법을 해보도록 하자
ADO와 Access를 활용하고 엑셀의 UserForm을 활용하는 다른 코너를
하나 추가하도록 하자


항목추가 버튼을 크릭하니까..에러가 난다고 하는 메일을 주셨다
어라...위에서 주욱 설명했듯이 fillCategories 프로시져를
매개변수를 받는 것으로 수정하고
이것을 항목분류추가 프로시져에서 호출할 때 매개변수를 빼먹어서 그런 것이다
아마도 이 코너를 보시는 분들은..이 정도는 알아서 수정하실 것으로 믿고..
fillCategories Me.lstCategoryView VBA와 VB.Net의 다른 점은 위와 같은 실수를 하면
폼을 로딩 시킬때 댐박에 지적을 한다..
수정하라고!!!!
그러나 VBA편집기는 그런 것까지 해주지 않아서 실행후 에러가 난다
VB.Net과 같이 하다 보니..사람이 게을러진다..하하..
그래서 VBA는 정말 좋은 개발연습도구이고 실무도구인셈이다
그리고 UNO_Weekly에서 진행되는 VB.Net시리즈를 잘 소화시켜 주신
분께서 VB.Net으로 이 코너에서 진행한 화일관리도구를 만들어서 보내주셨다
김수권님..땡큐!!

***[LOG-IN]***

쏘스는 없고 , 실행화일이니, 관심있는 분들은 실행해 보시고
학습하는 방법중, 직접 만들어 보는 것에 대한 관심을 갖는데 도움이 되었으면 좋겠다
실행하고 분류명을 최초 추가시킬때 에러메시지가 나타날 것이다
이것은 도움말이 잘되어 있으니..같이 첨부된 Access화일의 속성을 설정해주도록
설명이 잘 되어 있다
많은 분들이 VBA로 프로그래밍의 내공을 쌓은후 VB.Net같은 개발도구에도
관심을 갖도록 더욱 열심히 올려야할 것 같다