PROGRAMMING WORKSHOP

화일관리도구 |

항목목록상자|화일목록상자|테이블구성

항목관리부분을 잠시 놓아두고
화일관리부분을 추가하도록 하자
화일부분을 하면서 또 다른 경우의 수가 발생하게 되므로
화일 부분을 추가하면서 항목부분을 수정혹은 추가될 것이다
항목목록상자에서 어떤 항목을 선택하면 이 항목이 최하위 항목인지를
확인하고 , 최하위항목이면 화일작업을 할수 있게 한다



UserForm의 콘트롤을 정보를 보여주고, 삭제,저장,수정등의
Interface역할을 하고 관련 정보는
아래와 같이 간략하게 만든 두개의 테이블에 보관된다



작은 소루션이지만
참 오밀조밀 흥미로운 작은 데이타베이스라고 보시고
참고 하시면 응용할 곳이 많은 좋은 학습자료가 될 것이다
항목입력, 항목을 선택하면 해당 항목에 포함할 화일입력
화일목록상자에서 화일을 선택하면 삭제, 혹은...다음화일에서 추가


***[LOG-IN]***

해당 분류에 화일을 저장하겠습니까??를 하여 확인을 하면
화일관리테이블에 해당 정보를 저장하면 될 것이다
몇가지 옵션이 있을수 있다
첫째
화일의 위치는 그냥 현재 있던 곳에 그냥두고 화일의 경로와 화일명만
관리하는 것이 효율적이고 의미가 있을 것인지...
아니면
아예 화일을 옮겨서 분류명대로 폴더를 만들고 화일을 해당 폴더에
저장을 하던지...
아니면
화일을 복사하여 옮겨서 분류명대로 폴더를 만들고 화일을 해당폴더에
저장을 하던지...화일이 두개가 되는 셈이 되니 자원의 낭비이고...

가장 바람직한 방법은 첫번째 방법대로 화일의 물리적 위치는 그대로 두고
화일이 어디에있는지에 대한 정보만 테이블에 보관하는 것이 바람직할 것이다
그래야 화일관리도구의 의미가 있을 것이다
그렇게 화일정보만 테이블에 저장하는 관련작업을 해보도록 한 것이다
이제 화일목록에서 화일이 선택되면 삭제를 하던가..열던가..
어떤 작업을 해보도록 하자

화일정보를 테이블에서 삭제를 하기 위하여..
목록에 화일명만 나타난다
이것을 목록상자에서 선택한다
삭제 버튼을 크릭한다
이것을 사용자에게 어떤 경로의 어떤 화일을 삭제하시겠습니까??
라는 확인 메시지를 띄우기 위하여 경로정보를 갖여와야한다
그래서 함수를 사용하여

Function getPathByFileName(sFile As String)
On Error Resume Next
Dim rTbl As Range
Set rTbl = modMain.getTable(modMain.FILE_TABLE_START)
getPathByFileName = rTbl.Columns(4).Find(sFile, , , xlWhole).Offset(, -1)
End Function

getPathByFileName 이라는 함수에 목록상자에 나타난 화일명을 선택하면
이 화일명으로 해당화일의 경로명을 얻어올수 있다
그런데 ...삭제를 하기로 확인 버튼을 크릭하면..
이것을 수행하기 위하여 화일관리테이블에서 또 정보를 찾아야 한다
위의 함수에서 이미 찾았던 것을 또 찾아야 한다..
이런....한번 함수를 사용할때, 몇번째 행이였는지 알아 왔더라면
두번 가지 않아도 될 일을 한셈이다
그런데 함수는 하나의 결과값만 돌려주니까..경로명밖에 얻을수 없잖아??!!
아니다, 몇개의 정보도 받아 올수 있다

Function getPathByFileName(sFile As String, ByRef iRowNum As Integer)
On Error Resume Next
Dim rTbl As Range
Set rTbl = modMain.getTable(modMain.FILE_TABLE_START)
With rTbl.Columns(4).Find(sFile, , , xlWhole).Offset(, -1)
    getPathByFileName = .Value
    iRowNum = .Row
End With
End Function

매개변수전달 방법에는 ByRef와 ByVal 두가지가 있다
하나는 변수자체를 주고 받는 것이고..ByRef
하나는 단순히 변수에 들어있는 값(Value)만 전달하는 것이다..ByVal
이 두개를 명시적으로 지정하지 않을때는 ByRef로 처리하게 된다
위에서 일부러 표기한 것은 여러분들이 보기 쉽게 하기 위하여 표현한것이고
지정하지 않으면 ByRef 이다
이것을 하는 것을 아래의 화일에서 테스트 해보시고, 이해하시기 바란다
물론 이것 보다 더 효율적인 방법으로 가기 전에 알아야 할 부분들이니까..
분류명을 몇개 만드시고 화일을 몇개 설정한후 삭제 버튼을 크릭해 보시면서
관찰하시기 바란다

***[LOG-IN]***

프로그래밍은 정보를 어디에 보관하고 어떻게 적절히 활용하느냐의 문제이다
항목분류목록상자를 크릭하면 해당 분류항목에 화일이 있는지
찾아서 화일목록상자에 채워주도록 한 것이 아래의 항목분류명목록상자의
Change 이벤트에서 호출하는 프로시져였다
이 프로시져는 해당분류명에 해당하는 정보를 화일관리테이블에서 찾아서
화일명을 올렸었다..
이때.. 한번 찾을때 한꺼번에 찾아서 목록상자에 올려 놓으면
또 다시 관련정보를 찾아야 하는 위의 함수를 번번히 호출하는 일은
없을 것이다
반복하여 갖여 오지말고, 한번에 갖여다가 놓으면 좋은 것이다
물론 한번에 갖여 오는 량이 엄청많다면 다르게 생각하여야 겠지만
이 정도의 것은 한번에 갖여와도 효율적임에 별로 영향을 주지 않는다

Private Sub fillFilesList(sCategory As String)
On Error Resume Next
Dim rFileTable As Range
Dim rRow As Range
Dim iCategoryID As Integer
iCategoryID = modMain.getParentIDByCategoryName(modMain.stripBad(sCategory))
Set rFileTable = modMain.getTable(modMain.FILE_TABLE_START).Offset(1)
Set rFileTable = rFileTable.Resize(rFileTable.Rows.Count - 1)
For Each rRow In rFileTable.Rows
    If rRow.Cells(2) = iCategoryID Then
    Me.lstFile.AddItem rRow.Cells(4)
    End If
Next
End Sub

위와 같이 화일명만 달랑 화일목록상자에 올린 것이다
이곳에 여러개의 정보를 올려 놓으면 좋지 않겠느냐는 것이다
목록상자는 여러개의 열을 갖을수 있는 배열형식의 정보를 갖고 있는 것이다
아래와 같이 수정하면 많은 일의 량을 줄일 수 있다
아래와 같이 목록상자에 관련화일에 관련된 정보와 같은 행의 정보를
추가적으로 담아주고, 화일목록상자의 목록을 선택하면 해당 화일명이
갖고 있는 관련추가 정보를 그냥 읽어 낼수 있는 것
그러니 쓸데 없이 또 테이블로 가서 정보를 찾아오는 일을 하는
교통비와 시간이 절약 되는 셈이 되는 것이다



Private Sub fillFilesList(sCategory As String)
On Error Resume Next
Dim rFileTable As Range
Dim rRow As Range
Dim iCategoryID As Integer
iCategoryID = modMain.getParentIDByCategoryName(modMain.stripBad(sCategory))
Set rFileTable = modMain.getTable(modMain.FILE_TABLE_START).Offset(1)

Me.lstFile.ColumnCount = 4
Me.lstFile.ColumnWidths = ";0;0;0"
Set rFileTable = rFileTable.Resize(rFileTable.Rows.Count - 1)
For Each rRow In rFileTable.Rows
    If rRow.Cells(2) = iCategoryID Then
    Me.lstFile.AddItem rRow.Cells(4)
    Me.lstFile.List(Me.lstFile.ListCount - 1, 1) = rRow.Cells(3)
    Me.lstFile.List(Me.lstFile.ListCount - 1, 2) = rRow.Cells(2)
    Me.lstFile.List(Me.lstFile.ListCount - 1, 3) = rRow.Row
    End If
Next
End Sub

위의 파랑색으로 화일목록상자에 추가적 정보를 실어 놓는 것이다
목록상자이던 콤보상자이던 정보를 배열로 갖고 있다
ListCount속성은 해당 목록상자에 목록이 모두 몇개인지 알수 있다
-1을 하여 주는 이유는 배열은 0에서 부터 시작하니까..
-1을 해 준 것이다
그리고 열위치를 한열씩 이동하면서 정보를 추가적으로 올리면 된다
눈에 보이지는 않지만, 배열즉 엑셀의 테이블 같은 것이 목록상자가
갖고 있다고 보시면 된다
엑셀의 범위테이블과 다른 점은 첫째열이나 첫째행은 항상 0으로
시작된다는 것을 염두에 두시면 된다

이제 이것을 역으로 읽어 내 보자..
연습용으로 메시지박스에 띄워보자

Private Sub lstFile_Change()
If lstFile.ListIndex < 0 Then Exit Sub

If Me.lstFile.Text <> "" Then
    Me.cmdFileDelete.Enabled = True
Else
    Me.cmdFileDelete.Enabled = False
End If

Dim sX As String
sX = "화일명: " & Me.lstFile.List(Me.lstFile.ListIndex) & vbNewLine & _
    "경로명:" & Me.lstFile.List(Me.lstFile.ListIndex, 1) & vbNewLine & _
    "분류ID:" & Me.lstFile.List(Me.lstFile.ListIndex, 2) & vbNewLine & _
    "테이블행번호:" & Me.lstFile.List(Me.lstFile.ListIndex, 3)
MsgBox sX
End Sub

위의 내용을 지난화일에 직접한번 해보신후 아래화일과 비교해
보시면 좀더 빨리 발전 하실수 있을 것이다

***[LOG-IN]***

그러면 화일 삭제버튼을 크릭프로시져에서 경로명과 테이블의 몇번째행인지에 대한 정보를 아래와 같이 수정하면 될 것이다

Private Sub cmdFileDelete_Click()
Dim sPath As String
Dim sFile As String
Dim iRowNum As Integer

'별도의 함수를 호출할 필요없다
'sPath = getPathByFileName(sFile, iRowNum)
'자체목록상자에 보관된 정보를 읽어 보면 된다
'목록상자의 열이 여러열이지만 열폭을 0로 하여 화일명만
'나타나고 나머지는 숨어 있는 것이다
With Me.lstFile
    sFile = .List(.ListIndex, 0)
    sPath = .List(.ListIndex, 1)
    iRowNum = .List(.ListIndex, 3)
End With
If MsgBox(sPath & sFile & " 를 삭제하시겠습니까?", vbYesNo, modMain.PROJ_NAME) = vbYes Then
    If MsgBox(sPath & sFile & "의 실제화일도 삭제하시겠습니까?", vbYesNo, modMain.PROJ_NAME) = vbYes Then
        VBA.FileSystem.Kill sPath & "\" & sFile
        MsgBox sPath & "\" & sFile & " 을 영구삭제되었습니다"
    End If
    
    Dim rTbl As Range
    Set rTbl = modMain.getTable(modMain.FILE_TABLE_START)
    Application.Goto rTbl.Rows(iRowNum)이것은 필요없다, 눈으로 확인하기 위한 것..
    rTbl.Rows(iRowNum).Delete Excel.XlDeleteShiftDirection.xlShiftUp
 항상 어떤 작업을 한후 초기화작업이 중요하다
	삭제를 하였으면 목록상자가 갱신되어야 할 것이고
	삭제 버튼은 Enabled=False로 하여 유효하지 않은 크릭을 방지하는 것이 좋다
	항상 어떤 작업후에 상황정리를 하여야 하여야 한다는 것을 잊지 말자
	
    fillFilesList modMain.stripBad(Me.lstCategoryView.Text)
    cmdFileDelete.Enabled = False
End If

아래화일로 삭제를 하는 과정을 하나, 하나 따져 보시기 바란다
귀찮더라도 하나의 기능에 하나의 화일로 하는 것이 보시는 분들이
이해하기 쉬울 것같아서..스텝바이스텝으로 가기로 하자
고참님들은 그냥 한꺼번에 해서 올리지..왜 작은 기능별로 토막을 치나
하시겠지만...천천히 쫓아오시는 분들을 위하여 그렇게 하자!!
화일목록상자에서 열을 여러개 만들고 눈에 보이는 열 외에는 전부 폭을 0로 하여
감추었었다
이와 같은 요령으로 분류명 목록상자에도 관련정보를 표현하는 것을 해보시기 바란다
이것도 결국은 분류명을 삭제하거나 수정할때 필요한 사용하여야할
필요한 정보가 될 수 있으니까..
물론 나중에 수정할 것이지만, 미리 해보시는 것도 연습이 되고 좋을 것이니까..!!

***[LOG-IN]***

진행 중간에 이런 요청이 있었다..
현재까지 진행중, 분류명의 맨 마지막 분류에만 화일을 배정하게 되어 있는데
모든 분류명에서 상하위 막론하고 배정하게 하는 것이 좋지 않겠냐고 하신다
화일탐색기에서 폴더내에 폴더도 있지만 화일도 있는 것과같은 형식이면
좋겠다는 이야기!!...당연... 이번 추가에서 이부분도 같이 다루도록 하고



당연히 이렇게 하는 것이 의미가 있을 것이다
그리고 보니까,앗차 실수, 화일테이블에서 두번째 CategoryID가 분류테이블의
CategoryID열을 참조하지 않고 P_CategoryID를 참조하게 되어 있었다
이것은 잘못한 것이다...마지막 분류명에만 화일을 배정하는 것에서는 상관이 없지만
각 분류마다 화일을 배정하기 위하여서는 정상적으로 CategoryID를 참조하여야 한다

이렇게 하다 보면 수정을 해야 한다
그런데 처음 작성할때는 열의 위치를 그냥 상수로 입력한다
아래와 같이 숫자가 상수이다

If rFileTable > 1 Then
    For Each rRow In rFileTable.Rows
        If rRow.Cells(2) = iCategoryID Then
        Me.lstFile.AddItem rRow.Cells(4)
        Me.lstFile.List(Me.lstFile.ListCount - 1, 1) = rRow.Cells(3)
        Me.lstFile.List(Me.lstFile.ListCount - 1, 2) = rRow.Cells(2)
        Me.lstFile.List(Me.lstFile.ListCount - 1, 3) = rRow.Row
        End If
    Next
End If

위와 같이 몇번째열이였더라...숫자만 갖고는 감이 잘 안잡혀서
테이블을 눈으로 보고 다시 체크하여야 하는 번거로움이 생기는 것이다
코딩속에는 이런 값을 숫자 그대로 넣는 것을 배제 하는 것이 좋은 습관이다
이런 것을 단순한 코드에서는 설명하고 이해시켜드리기 힘든 것이지만
지금 만들고 있는 것을 보면서 이와 같은 상황을 설명드리면..
아하!!! 그래서 열심히 상수선언을 하는 구나 !! 라는 것이 접수가 된다
그래서 선언부에 아래와 같이 테이블의 열위치에 대한 정의를
해두는 것이 나중에 어떤 변경이 발생하더라도 쉽게 수정을 할수 있게 되는 것이다
이것은 각 크래스모듈의 선언부에 하여도 좋겠고
여러개의 모듈에서 사용한다고 하면 일반모듈시트에 하는 것도 좋고
아무튼 선언부에 작성하는 습관이
자신의 소루션에 대한 자원관리차원에서도 반드시 습관을 갖는 것이 좋다

Public Const TBL_CATE_CATEGORY_ID_COL As Integer = 1
Public Const TBL_CATE_P_CATEGORY_ID_COL As Integer = 2
Public Const TBL_CATE_CATEGORY_COL As Integer = 3
Public Const TBL_CATE_DESE_COL As Integer = 4
Public Const TBL_FILE_ID_COL As Integer = 1
Public Const TBL_FILE_CATEGORY_ID_COL As Integer = 2
Public Const TBL_FILE_FILEPATH_COL As Integer = 3
Public Const TBL_FILE_FILENAME_COL As Integer = 4
Public Const TBL_FILE_DESC_COL As Integer = 5

처음에는 작성하는 것이 번거롭겠다고 생각하겠지만
나중에는 참으로 고마운 정보들이 되는 것이다
다음 화일에서 아래와 같이 수정된 내용들을 모듈시트에서 살펴보시기 바란다
보기에는 복잡해 보이지만, 야무진 코딩이 되는 것이다

Function getIDByCategory(sCate As String)
On Error Resume Next
Dim rTbl As Range
Set rTbl = modMain.getTable(modMain.CATEGORY_TABLE_START)
숫자를 사용한 이전 구문
'getIDByCategory = rTbl.Columns(3).Find(sCate, , , xlWhole).Offset(, -2)
'아래는 상수를 사용하여 수정한 구문
getIDByCategory = Intersect(rTbl.Columns(modMain.TBL_CATE_CATEGORY_COL).Find(sCate, , , xlWhole).EntireRow, rTbl).Cells(modMain.TBL_CATE_CATEGORY_ID_COL)
End Function

그리고 함수나 프로시져를 만들었을때는 항상 직접실행창에서
테스트를 해보는 습관도 붙이는 것이 좋다



***[LOG-IN]***

만들다 보면 또 변한다..
삭제를 화일 하나만 하지 말고 여러개를 한꺼번에 삭제하고 싶은데..
라는 생각이 나게 되어있다
그래서 항상 정보를 생각할때 , 이런 종류의 정보가 하나만 있는 것이 아니고
여러개가 항상 있을 것이다!!! 라는 생각을 하고 있는 것이 좋다
목록상자에서 여러개를 선택하게 하는 속성은 무엇일까?
목록상자에서 선택된 여러개를 읽어 오는 방법은 무엇일까?
여러개를 읽어다가 어떤 변수에 담아야 할까? 배열에 담을까?집합체에 담을까??
하나, 하나가 실력이다