PROGRAMMING WORKSHOP

프로젝트기성관리 |

지난화일에서 기성을 몇회추가한후 손으로 해당기성범위로 삭제를
해 버려도 목록상자에는 몇회기성이라는 목록은 그대로 유지된다
삭제후 이것을 다시 선택하면 도급물량외의 것이 모두 닫혀 버린다
이런것은 원하지 않는 결과이다
손으로 삭제를 할때 목록상자의 해당기성제목목록도 삭제가 되어 버리면
좋은데, 이것을 하려면 시트의 이벤트등에서 처리하여야 하는데
상황이 복잡해진다
그냥 목록이 남아있지만 이것을 크릭하면
제어하는 구문을 하나 더 넣어주는 것이 편리할 것이다
아래와 같이

If sLabel <> "" Then
    If Not isThisItemExist(sLabel) Then
        MsgBox modMain.MSG_NO_PARTIAL_COM_RANGE, , modMain.PROJ_NAME
        GoTo X
    End If
    modMain.showOnlyCurrentTableAndSummaryOfMain sLabel
End If

1회기성이라는 라벨의 내용이 시트상에 있는지 확인하는 함수를
하나 만들어서 처리한다
이렇게 토막을 내어서 만들어 놓는 것이 좋은 방법이다
함수는 modMain 모듈에 아래와 같이 작성하여 모아 놓는다
이렇게 하면 나중에 다른 프로젝트에 적절히 활용할수 있는 함수들이
될수도 있는 것들이 된다

Function isThisItemExist(sLabel)
On Error Resume Next
Dim rFldRow As Range
Set rFldRow = modMain.getSheet(modMain.BASIC_DATA_SHT).Rows(modMain.BASIC_DATA_FLD_ROW - 1)
isThisItemExist = Not rFldRow.Find(sLabel, , , Excel.XlLookAt.xlWhole) Is Nothing
End Function

상수를 많이 선언해두면 한참있다가 코드를 만져도 낯섫지 않다
위와 같이 데이타시트의 제목행이 몇번째였더라????라는 것을 찾을 필요없이
이미 선언해 두었던 상수들이 고마운 것이다
휠드행에서 한칸 위의 것에 [1회기성],[2회기성]이라는 제목을 작성했었으니
전달받은 목록상의 아이템을 Find메소드로 찾아서 있으면 True
없으면 False를 돌려주는 함수
또하나 중요한 것은 아래의 구문에서 GoTo X라고 했다

Application.ScreenUpdating = False
...
...
...
...

If Not isThisItemExist(sLabel) Then
        MsgBox modMain.MSG_NO_PARTIAL_COM_RANGE, , modMain.PROJ_NAME
        GoTo X
End If
...
...
...
...
...
X:
Application.ScreenUpdating = True
End Sub

그냥 Exit Sub을 해주어도 되는데 왜 구태여 GoTo X라고 하여
X라벨을 들렸다가 나가라고 하는가???
이것은 처음시작할때 ScreenUpdating=False로 해주었으니
나올때는 반드시 ScreenUpdating=True로 해주어야 하고 이것이
마지막에 작성되어 있으니, 다른 구문 제치고 이리로 가서 마지막
구문을 실행하고 나가라는 의도의 것인 것이다
이것은 엑셀자체에서는 별로 큰 문제가 되지 않지만
VB.Net의 VSTO에서 엑셀 추가기능화일등을 작성할때는 치명적오류가 되어
컴퓨터가 얼어 붙어 버리는 경우가 있다
그러니 이런 것을 엑셀이 알아서 처리하겠거니 하면 곤란하다

기성을 추가할때 , 전회기성이 작성완료되고 상황이 종료되어야
다음기성을 추가하는 것이 상식일 것이다
전회기성이 작성되고, 완료되었는지를 확인한후 다음 기성을 추가하도록
하는 제어를 해주어야 할 필요성이 있을 것이다
전회기성이 완료되었는지 어떤 것으로 확일을 할 것인가?
사용자에게 추가할것인가??를 묻고 할 것인가?
아니면
전회기성자료를 읽고서 알아서 추가할지 결정할 것인가?
이런 것들이 프로그래밍과정에 정해주고가야 할 시나리오인 것이다
각본이 잘 짜여지면 프로그래밍은 그냥 술술, 넘어가지만
이런 각본이 엉성하면 한없이 늘어지고, 끝이 보이지 않게된다
기계가 하는 일은 사람이 정해진 각본대로 움직이기 위한 것이다
기계가 알아서 하지 않는다
새로운 기성회수를 추가하려면 전회기성의 완료여부를 확인한후
만들어야 할 것이다



함수를 하나 추가하여야 할 것이다
한 곳에 몰아서 모두 작성하면 정신 사나워진다

Function isPreviousReportFinished(iPreviousReport As Integer)
...
End Function

함수이름을 만들때 , 설명적으로 작성하면 많은 도움이 될 것이다
Is Previous report finished???
이전 보고서는 완료되었나???와 같이 그대로 함수의 기능을 표현하면
도움이 될 것이다
이제 또 고민이 생기게 된다
기성의 항목을 모두 입력하지는 않을 것이다
해당 기성기간에 사용자가 상황을 판단하여 어떤 것은 발생시키고
어떤 것은 빈공란으로 남겨지게 될 것이다
혹은 한회기성이 현장의 사고로 하나도 발생하고 넘어갈수도 있을 것이다
아무튼 이런 상황을
프로그래밍개발자는 모른다..
그렇다면 어떻게 판단할 것인가??
이런것이 실무자와 개발자의 논쟁거리가 되고, 실무자는 바뻐죽겠는데
뭘 자꾸 물어 오고, 그렇다고 개발자가 제멋대로 편리한대로 만든다면
실무자는 사용을 하지 않게 되는 다람쥐챗바퀴도는 개발하고, 버리고
개발하고, 버리고 하는 것이 될 것이다
사무자동화가 발전하려면 실무자가 이해를 하고 사용하려는 욕구가
없이는 말짱 헛일이다
여기에서는 사용되지 않은 항목을 선택하여 실무자가 확인할수 있는
절차를 사용하도록 하자

그런데 기성추가를 하면 양식이 만들어질때 엉뚱한 위치에 만들어지는 것을
보실수 있었을 것이다
아래의 그림과 같이



이것은 Worksheet개체의 UsedRange가 자주지우고, 다시 쓰고하는 워크시트상에서
이미 지워진 부분의 범위를 사용한것으로 기억을 하고 있는 것이다
그러니 우리눈으로는 빈공간인데 엑셀은 이것이 정보가 있는 것으로 기억하고 있는 것이다
저장되기전의 공간을 유지하고 있는 것이다
저장을 한후 UsedRange를 사용하면 지워진 공간을 지워진것으로 기억이 되고
그래서 이부분을 Range개체의 CurrentRegion이나 다른 방법으로
찾아주는 것이 헷갈리지 않는다
대개가 그냥 편리한대로 UsedRange를 사용하지만,
이 코딩의 편리함이 오류를 갖여오게 된다
그래서 이번화일에서는 이부분을 다른 방법으로 바꿔주도록 하자

Sub buttonClick(oBtn As MSForms.CommandButton)
...
...
...
Dim rStart As Range
'Set rStart = Worksheets(modMain.BASIC_DATA_SHT).UsedRange.Rows(modMain.BASIC_DATA_FLD_ROW)
'Set rStart = rStart.Cells(rStart.Cells.Count).Offset(-1, 1)
Set rStart = modMain.getReportStartCell
...
...
...
...
End Sub
 아래와 같이 별도의 함수를 만든다
Function getReportStartCell()
Dim shtX As Worksheet
Dim rStart As Range
Dim iCol As Integer
Set shtX = Worksheets(modMain.BASIC_DATA_SHT)
Do
    Set rStart = shtX.Rows(modMain.BASIC_DATA_FLD_ROW).Cells(1).Offset(, iCol)
    iCol = iCol + 1
Loop While rStart <> ""
Set getReportStartCell = rStart.Offset(-(modMain.BASIC_DATA_FLD_ROW - 1))
End Function

하나의 소루션을 만들다 보면 함수가 아주 많이 만들어진다
그래서 소루션을 하나 만들다 보면 함수라이브러리가 자동으로 만들어지는 셈이다
이것이 여러분의 자산이 되는 것일 것이다
의식적으로 이것은 이럴때 사용하면 좋겠다!! 라고 메모를 해두면
더욱 좋을 것이다
그런데 위와 같이 함수를 작성하면 다른 곳에서 사용하기 힘들게 된다
함수에 매개변수를 전달하는 습관이 좋은 것이다
아래와 같이 변경하여 작성해 보자

Function getReportStartCell(rX As Range, sDirection As String)
Dim iOffset As Integer
Dim rStart As Range
Do
    Select Case sDirection
        Case "Right"
            Set rStart = rX.Offset(, iOffset)
        Case "Down"
            Set rStart = rX.Offset(iOffset)
    End Select
    iOffset = iOffset + 1
Loop While rStart <> ""
Set getReportStartCell = rStart
End Function

위와 같이 매개변수를 전달하는 함수를 작성하면 오른쪽으로 첫째셀을 찾거나
아래방향으로 첫째셀을 찾거나 하는 다양한 기능으로 활용을 할수 있는
함수가 되는 것이다
질문이 있어서 중간에 샛길로 빠졌었다
다시 다음회기성을 추가하려고 할때 이전 기성에 대한 확인절차를
이야기 하도록 하자

다음회의 기성양식을 추가할 것인가 말것인가를 말로 해서는 안될것이고
아래와 같이 폼을 하나 더 만들어서 해당폼에 전회기성물량발생과
전체물량을 보여주는 것을 만들어서 사용자로 부터 확인하게 하는 것이
좋을 것 같다



아마도 이 부분이 새로운 도전꺼리가 될 것이다
폼을 두개를 사용하는 예제는 없었는데 , 이번기회에 만들어 올리는 기회가 되었다
그래서 프로그래밍워크샵은 다양한 경험을 하는 기회과 된다
업무의 내용도 그렇지만, 프로그래밍의 테크닉을 좀더 다양하게 할수 있는 것이다
현재 열려 있는 폼에서 크릭하면 새로운 폼이 열린다
그런데 새로운 폼과 열려있는 폼과의 통신이 원할하게 이루어져야 할 것이다
또한 새로운폼이 열리면 열려있던 폼은 눈에 보이지 않게 하는 것이 좋고
새로운 폼을 닫으면 당초 열려있는 폼이 다시 나타나게 하는 것이
세련된 작업이 될 것이다
두개의 폼에서 공통으로 사용할 영역이 필요하게 될 것이다
modMain같은 공동사용모듈이 그래서 항상 필요한 것이다
두개의 폼간의 통신이란..
새폼이 열리면서 어떤 범위의 내용을 폼에 담을 것인가를
전달하여야 할 것이고
새로운 폼이 닫힐때는 새기성양식을 추가할 것인지, 안할것인지에 대한
정보를 이미 열려있던 폼에 전달을 하여야 하는 것이 두개의 폼간의
전달내용이 될 것이다
아래와 같이 간단한 두개의 변수가 통신의 역할이 된다

Public bNextReportOK As Boolean ' 다음기성을 만들것인지 확인용변수
Public iPreviousReportNum As Integer  ' 새폼에서 목록상자에 채울 기성회수

이미 열린폼에서 iPreviousReportNum 에 작성된 값을
새로운 폼에서 로딩하면서 읽어서 이부분의 내용을 목록상자에 채우는 것이
아래와 같다
이것도 modMain모듈에 두고 새로운 폼이 열리면서 매개변수를 전달하여
외부모듈에서 새로운 폼을 초기화하도록 한다

'
'아래의 매개변수는 UserForm자체개체를 전달하여 이 폼에 작성하게 하는 것
'이전 총도급 물량열을 찾고, 작업이름열을 찾고 , 이전기성물량열을 찾아서
'행방향으로 순환하면서 3개의 값을 목록에 채워주어
'사용자가 이전 기성물량발생내용을 확인하고, 다음 기성작업을
'하는데 도움이 되게 한다
'물론 시트상의 내용을 눈으로 보아도 좋겠지만
'여기에서 프로그래밍워크샵의 학습목적을 위한 것이니
'이런 저런 개발가능성을 많이 알고 ,각자가 현장에서 적절하게 응용하여 나가는데
'필요한 도구들이 될 것이다

Sub ReadLastReport(oFrm As MSForms.UserForm)
Dim rWBS As Range
Dim rWriteCol As Range
Dim rCurrentReport As Range
Dim iRow As Integer
Dim rX As Range
Dim rRow As Range
Set rWBS = getWBSColumn
Set rCurrentReport = modMain.getCurrentReportRange(modMain.iPreviousReportNum & modMain.BASIC_DATA_COMPLETION_LABEL)
Set rWriteCol = Intersect(rWBS.EntireRow, rCurrentReport).Columns(modMain.CURRENT_WRITE_COL)
With oFrm
       With .ListBox1
            .ColumnCount = 3
            .ColumnWidths = "300;50;50"
            For Each rX In rWBS.Cells
                If getWBSLevel(rX.Value) = modMain.WBS_MAX Then
                    Set rRow = rX.EntireRow
                    .AddItem rRow.Cells(modMain.ITEM_NAME_COL)
                    .List(iRow, 1) = rRow.Cells(modMain.ITEM_QTY_COL)
                    .List(iRow, 2) = IIf(Intersect(rRow, rWriteCol) = "", 0, Intersect(rRow, rWriteCol))
                     iRow = iRow + 1
                End If
               
            Next
    End With
    .Label1.Caption = modMain.iPreviousReportNum & " 회기성기록내용입니다, 확인하시고 진행하세요"
    .cmdOK.Caption = modMain.iPreviousReportNum + 1 & " 회기성양식만들기진행!!"
End With
End Sub

위의 내용까지 아래화일에 구현되었고
모듈시트에 필요한 주석들이 달려 있으니 학습하시도록..

***[LOG-IN]***

다음 화일에서는 메인폼을 자동화하면서 콘트롤들을 삽입하였었는데
아직 능숙하지 않은 분들이 힘들어 하시니..
이것을 풀어서 디자인타임에 콘트롤을 직접 그려 넣는 방식으로
바꾸도록 하여야 할 것 같다