메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

비주얼 베이직의 효과적인 코딩 기법

한빛미디어

|

2004-02-19

|

by HANBIT

21,909

저자: 박승민, 오피스데브 개발팀장/데브피아 비주얼 베이직 부분 시삽/2003 Microsoft MVP

1. 조건문

비주얼 베이직에서 조건문은 대부분의 프로젝트에서 많이 활용된다. 보통 조건문으로 If 문이나 Select…Case 문을 많이 쓰며, 조건문의 예는 다음과 같다.

1) If 문

Private Sub Command1_Click( )
   If Text1 = "사장" Then
      MsgBox 1
   ElseIf Text1 = "차장" Then
      MsgBox 2
   ElseIf Text1 = "사원" Then
      MsgBox 3
   End If
End Sub

Text1이라는 TextBox에 "사장"이면 1을 보여주고, "차장"이면 2를, 사원이면 "3"을 보여준다. 이 코드를 Select~Case 문으로 고쳐보자.

2) Select ~ Case 문

Private Sub Command1_Click( )
   Select Case Text1
      Case "사장"
         MsgBox 1
      Case "차장"
         MsgBox 2
      Case "사원"
         MsgBox 3
   End Select
End Sub

이 코드처럼 Select…Case 문을 사용하면 나중에 "전무", "이사" 등의 항목이 추가되어도 If 문보다 처리하기 쉬워지고 가독성도 쉬워진다. 이 코드를 다시 한 번 다른 방법으로 바꿔보자.

3) 논리값 연산

Private Sub Command1_Click( )
   MsgBox -(Text1 = "사장") - (Text1 = "차장") * 2 - (Text1 = "사원") * 3
End Sub

이렇게 하면 일단 코딩양은 줄어드나 가독성이 떨어진다. 잠시 살펴보면, Text1에 "사장"이란 글자가 들어가면 다음과 같이 연산 처리가 된다.

MsgBox -(-1) - (0) * 2 - (0) * 3

그래서 1을 리턴하고, Text1에 "사원"이란 글자가 들어가면 다음과 같이 연산처리가 되어 3을 얻게 된다.

MsgBox -(0) - (0) * 2 - (-1) * 3

그럼 이보다 약간 가독성이 좋은 Switch 문을 사용해보겠다.

4) Switch 문

MsgBox Switch(Text1 = "사장", 1, Text1 = "차장", 2, Text1 = "사원", 3, True, 0)

Switch 문은 생각보다 간단하며, 다음과 같은 형식이다.

Switch (조건1, 조건1이 참일 때의 값, 조건2, 조건2가 참일 때의 값, 조건3, 조건3이 참일 때의 값...)

Text1이 "사장"인지 비교해서, 참이면 그 다음인자 1을 취득하고 끝난다. 거짓이면 두번째 조건을 보고 Text1이 "차장"인지 비교해서 참이면 2 거짓이면 그 다음 값과 비교한다. 마지막의 True, 0 은 해당조건이 없으면 Null 값을 리턴하는데, 이를 막으려고 사용하는 것이다. 즉, 조건이 없으면 무조건 0을 리턴하라는 의미다.

위 예제와 반대로 1일 때 사장, 2일 때 차장, 3일 때 사원이라는 것을 얻고 싶을 때 위에서 예를 든 것처럼 If 문이나 Select… Case 문을 쓸 수도 있지만 간단한 방법이 있다. Choose문을 사용하면 된다. 개인적으로 멋진 함수라고 생각한다.

5) Choose 문

MsgBox Choose(Val(Text1), "사장", "차장", "사원") & ""

Val은 문자를 숫자형(더블형)으로 바꿔주는 함수이고 Text1에 1이 들어가면 다음 인자인 "사장"을 취하고 2가 되면 그 다음 인자인 "차장"을, 3이 되면 그 다음 인자인 "사원"을 취하는 함수다. Switch 문과 마찬가지로 아무것도 취하지 못하면 Null을 리턴한다. 비주얼 베이직에서 Null 값은 에러로 취급되므로 Null & "" 연산을 거치면 빈문자열이 되어 마지막에 & "" 를 붙인 것이다.

잠깐 IIf 문을 살펴보면 IIf 문은 두 가지의 경우의 수에 대한 값을 가져올 때 편하다. 예를 들, Text1이 사장일 때 1의 값을 그렇지 않으면 2의 값을 취하고 싶을 때 다음과 같이 하면 된다. 간단히 많이 쓸 수 있는 구문이다.

6) IIf 문

MsgBox IIf(Text1 = "사장", 1, 2)

이 코드를 If 문에서는 다음처럼 표현한다.

If Text1 = "사장" Then
   MsgBox 1
Else
   MsgBox 2
End If

2. 반복문

프로젝트에서 조건문과 같이 반복문은 항상 필요하다. 변수 또는 텍스트박스에 일일이 같은 데이터 또는 특정 규칙에 대한 데이터를 일일이 코딩하는 것보다 배열 또는 컨트롤 배열을 만들어서 반복문을 통해 입력하면 코딩이 간결해진다. 물론 반복문을 사용해서 코딩이 간결해질 수도 있지만 그렇지 않은 경우도 많다. 지금은 컴퓨터 성능이 좋아져서 그다지 차이를 못 느낄 수도 있지만, 결과가 같아도 필요하지 않은 반복문은 해당 애플리케이션의 성능에 타격을 줄 수 있다. 좋은 코딩은 필요할 때만 반복문을 사용하는 것이다. 그렇다고 반복문을 사용해서 몇 분이면 끝날 코딩을 반복문을 쓰지 않으려고 궁리하다가 몇시간씩 걸리면 곤란하다. 적절히 판단해서 사용해야 한다. 때로는 수학적 지식이 필요하고 경험적인 요소도 좌우를 하지만, 가끔은 발상자체를 바꿔보면 반복문을 안 쓰고도 해결되는 경우가 있다.

1) 반복문을 써야 코딩이 간결해지는 예제

(예 1)
폼에 TextBox가 10개 이상 있다고 가정할 때 데이터를 지운다면 컨트롤 배열인 경우에는 다음과 같이 코딩을 할 것이다(TextBox의 이름이 Text1라 할 때) 물론 동적 컨트롤이 아닌 경우에는 개수를 알기 때문에 Text1.Count - 1 대신 숫자를 넣는 방법이 더 일반적이다.

Private Sub Command1_Click( )
   Dim i As Integer
   For i = 0 To Text1.Count - 1
      Text1(i) = ""
   Next i
End Sub
   
그렇다면 텍스트박스 배열이 아니고 일반 텍스트박스가 여러 개인 경우이면 일일이 데이터를 지울지도 모르겠다. 이럴 때는 For Each ~ Next 문을 쓰면 간단히 해결된다.

(예 2)
이번에는 폼 안의 모든 컨트롤, 즉 Me.Controls(실제로 Me는 생략 가능)가 아니라 어떤 컨트롤, 프레임(이름이 Frame1로 가정)이라고 해보자. 프레임 안에 있는 텍스트박스만 지울 때는 다음과 같이 Container 속성을 쓰면 된다. 컨테이너는 포함하는 컨트롤을 의미하며, 다음 예는 텍스트박스를 포함하는 컨트롤의 이름이 "Frame1"일 때만 데이터를 지우라고 한 것이다.

Private Sub Command1_Click( )
   Dim txtTmp As Control
   For Each txtTmp In Me.Controls
      If TypeName(txtTmp) = "TextBox" And txtTmp.Container.Name = "Frame1" Then
         txtTmp = ""
      End If
   Next
End Sub

2) 반복문을 쓰지 말아야 코딩이 간결해지는 예제

(예 1)
1부터 n까지의 합을 구하는 예제다.

Private Sub Command1_Click( )
   Dim i As Integer
   Dim lngSum As Long
   Const n = 100
   
   For i = 1 To n
      lngSum = lngSum + i
   Next i
   MsgBox lngSum
End Sub

이처럼 해도 되지만, 반복문으로 하는 것보다 다음 방법으로 하는 것이 더 효율적이다. 코딩은 수학적인 요소에 따라 많이 좌우가 될 때가 있다. 1~n의 합계는 다음과 같은 공식으로 구해진다. 이런 경우는 꼭 주석을 다는 습관을 들여야 한다. 수학 공식을 모르면 어쩔 수 없이 반복문을 사용해야 하겠지만 말이다.

Private Sub Command1_Click( )
   Const n = 100
   
   MsgBox n * (n + 1) / 2 "1부터 n까지의 합계
End Sub

(예 2)
요즘 유행하는 로또복권의 예를 들어보자. 로또 복권은 1 ~ 45의 난수를 발생시키되 중복되지 않게 6자리로 당첨을 결정하는 복권이다. 예제는 당첨 번호 생성기로, 다음은 일반적으로 처리하는 경우다. 당첨번호가 6자리이므로 일단 0~5회 돌아야 하니까 반복문이 한 번 들어갈 것이고 기존의 값과 동일한 값이 있다면 다시 난수를 발생시켜야 하는데, 다음은 반복문 2번으로 표현했는데 반복문, 조건문(If 문) 및 Goto 문으로 표현할 수도 있다. 요즘은 Goto 문을 사용하지 않으므로 반복문 2번으로 표현했다. 정확히 반복문은 3번 사용했다.

ReDim Preserve 문은 배열을 재할당할 때 쓰는 문이다. ReDim만 쓰면 기존의 데이터가 사라지기 때문에 Preserve를 붙여야 한다. Join은 Split과 반대 개념으로 Join(배열변수, 데이터 구분자) 형식으로 쓰며 모든 배열을 데이터 구분자를 기준으로 합친다.

Private Sub Command1_Click( )
   Dim i As Integer
   Dim j As Integer
   Dim strOut( ) As String
   Dim intNum As Integer
   Dim flgchk As Boolean
   
   Randomize "프로젝트 시작 시 같은 난수를 발생시키지 않기 위함
   For i = 0 To 5 "6개의 번호 생성
      ReDim Preserve strOut(i) "동적 배열로 strOut을 하나 더 빈 공간 할당
      Do
         flgchk = False "플랙
         intNum = Int(Rnd * 45) + 1 "1 ~ 45의 난수 발생
         strOut(i) = intNum "난수를 배열에 할당
         For j = 0 To i - 1 "기존 값을 체크하기 위한 루프
            If strOut(i) = strOut(j) Then
               flgchk = True "같은 값을 뽑아 온 경우에 플래그 정보에 True를 할당
            End If
         Next j
      Loop While flgchk "플래그 정보가 True이면 즉, 같은 값을 뽑아 왔으면 다시 난수 발생 위치로 이동
   Next i
   MsgBox "당첨번호 : " & Join(strOut, ", ") "당첨번호를 하나로 합침
End Sub

이 코드를 다음처럼 바꿨더니, 많이 간결해졌다. InStr 함수를 사용해 반복문을 2번만 사용했다. 참고로 InStr(문자열, 찾을 문자열) 형태로도 많이 사용한다. 찾을 문자열에서 intNum 이 되지 않고 " " & intNum & ","이 되는 이유는, intNum이 10을 뽑은 후 후에 다시 1을 뽑아도 중복으로 인식되기 때문에 이를 막기 위해서 " 13," 에서 " 1," 이나 " 3," 을 찾으면 없다는 것을 인식하게 하려고 공백 및 컴마(,)를 붙인다. 문자열에서 InStr로 검색할 때는 이 점에 주의해야 한다.

Private Sub Command1_Click( )
   Dim i As Integer
   Dim strOut As String
   Dim intNum As Integer
   
   Randomize "프로젝트 시작 시 같은 난수를 발생시키지 않기 위함
   For i = 0 To 5 "6개의 번호 생성
      Do
         intNum = Int(Rnd * 45) + 1 "1 ~ 45의 난수 발생
      Loop While InStr(" " & strOut, " " & intNum & ",") "기존에 해당 되는 난수가 있으면 다시 난수 생성
      strOut = strOut & intNum & ", " "해당 난수를 취함
   Next i
   strOut = Left(strOut, Len(strOut) - 2) "맨 뒤에 컴마(,) 와 공백을 제거
   MsgBox "당첨번호 : " & strOut "당첨번호 표시
End Sub

(예 3)
문자열에 특정 문자열이 몇 개 있는지 알아내는 코드다. For ~ Next 문으로 돌려서 알아내도 되지만 이보다 간단히 다음처럼 Split 문을 사용할 수도 있다.

Private Sub Command1_Click( )
   Dim strWhole As String
   Dim strFind As String
   Dim tmpArray( ) As String
   
   strWhole = "MiniBook Mini MiniBook MiniBook 2003년 MiniBook"
   strFind = "MiniBook"
   tmpArray = Split(strWhole, strFind)
   MsgBox UBound(tmpArray) - LBound(tmpArray)
End Sub

문자열의 개수를 알아내는 코드를 줄이려고 임시 배열을 썼는데, 배열을 쓰지 않고 다음과 같이 하는 것이 더 효율적이다. 간단히 strWhole의 길이에서 strWhole에서 strFind의 길이를 제외한 길이를 뺀 결과에서 strFind의 길이를 나누어주면 된다.

Private Sub Command1_Click( )
   Dim strWhole As String
   Dim strFind As String
   
   strWhole = "MiniBook Mini MiniBook MiniBook 2003년 MiniBook"
   strFind = "MiniBook"
   MsgBox (Len(strWhole) - Len(Replace(strWhole, strFind, ""))) / Len(strFind)
End Sub

(예 4)
(예 3)과 비슷한 예제로 배열에 데이터가 있는지 알 수 있는 방법이다. 이것도 반복문 대신 Split 문의 반대 개념인 Join 문을 통해 배열에 데이터가 있는지 체크한다. 물론 For 문으로 다음과 같이 돌려서 체크해도 된다.

Dim i As Integer
Dim strTmp(9) As String
Dim flg As Boolean

"... (중략 : 배열 처리)

For i = 0 to 9
If Len(strTmp(i)) > 0 Then
flg = True
End If
Next i

If flg Then
Msgbox "배열에 값이 존재합니다"
Else
Msgbox "배열에 값이 존재하지 않습니다."
End If

앞의 코드를 Join 문으로 하면 간단해진다.

Dim strTmp(9) As String

"...(중략 : 배열 처리)

If Len(Join(strTmp,"")) > 0 Then
MsgBox "배열에 값이 존재합니다."
Else
MsgBox "배열에 값이 존재하지 않습니다."
End If

설명을 드리자면 Join 문(Join이란 구분자가 있다면 구분자로 데이터를 연결해준다)으로 구분자 없이 돌리니까 Join(strTmp,"") 과 strTmp(0) & strTmp(1) & .... & strTmp(9)는 같아진다. 거기서 Len으로 길이 체크를 해주면 배열에 값이 있는지 한 번에 알 수가 있다.

(예 5)
전체 경로 이름에서 파일 이름만 분리해내는 코드다. 다음에 3가지 방법을 제시했다. 어느 것이 가장 좋은지 확인해보기 바란다.

다음은 루프문을 사용해서 뒤에서부터 "\"를 찾아서 우측에서 "\" 다음 위치의 문자열까지 취득하는 코드다.

Private Sub Command1_Click( )
   Dim i As Integer
   Dim strChr As String
   Dim strPath As String
   
   strPath = "C:\Temp\MiniBook\test.txt"
   
   For i = Len(strPath) To 1 Step -1
      strChr = Mid(strPath, i, 1)
      If strChr = "\" Then
         MsgBox Right(strPath, Len(strPath) - i)
         Exit Sub
      End If
   Next
End Sub

이 코드는 Split 문으로 "\"로 데이터를 구분하고 맨 마지막 첨자의 배열을 취하면 파일 이름을 얻어올 수 있다.

Private Sub Command2_Click( )
   Dim strPath As String
   Dim strOut( ) As String
   
   strPath = "C:\Temp\MiniBook\test.txt"
   
   strOut = Split(strPath, "\")
   MsgBox strOut(UBound(strOut))
End Sub

이 방법은 Split 문과 마찬가지로 VB 6.0부터 제공되는 InstrRev 함수(Instr의 역순으로 찾는 함수)를 이용해서 루프문 없이 뒤에서부터 "\"를 찾아서 그 다음 문자부터 끝까지 취하는 코드다.

Private Sub Command3_Click( )
   Dim strPath As String
   
   strPath = "C:\Temp\MiniBook\test.txt"
   MsgBox Mid(strPath, InStrRev(strPath, "\") + 1)
End Sub

3. 컨트롤

1) 디폴트 프로퍼티(Default Property)

디폴트 프로퍼티라는 말은 컨트롤에서 속성을 생략해도 컨트롤 이름만으로도 대신할 수 있는 대표 속성이다. 텍스트박스를 하나 만들고 Text1.Text = "123" 대신에 Text1 = "123"으로도 표현 가능하다. TextBox, ListBox, ComboBox의 디폴트 프로퍼티는 Text이고 Label은 Caption이고 OptionButton, CheckBox, CommandButton은 Value 값이다. 디폴트 프로퍼티를 컨트롤마다 외울 필요는 없고 경험이 쌓이다 보면 많이 쓰는 속성이므로 저절로 감이 온다. 물론 많이 생략할수록 코딩량이 줄어든다. 코드 명명법만 지킨다면 생략해도 무슨 컨트롤인지 알 수 있기 때문에 가독성에 그다지 지장은 없다. 단 모든 경우에 디폴트 프로퍼티를 생략할 수 있는 것은 아니며, 특히 With 문에서는 보통 생략하지 않는다.

With Text1
   .Text = "abc"
   .SetFocus
   .SelStart = 0
   .SelLength = Len(.Text)
End With

TextBox에 "abc" 글자를 넣고 전체 선택(반전)되는 예제다. 앞에서 보면 TextBox의 디폴트 프로퍼티인 .Text는 사실 생략할 수도 없는 형태다. 그렇다고 위에 With Text1이 있는데 또다시 Text1 = "abc"하는 것도 우습다. 그래서 보통은 이 같은 경우에 .Text = "abc"라고 표현한다.

디폴트 프로퍼티에 관해서 또 하나 주의할 것은 속성을 생략했다고 모두 디폴트 프로퍼티는 아니다. 문제를 하나 내자면, 폼에 텍스트박스, 커멘드버튼을 하나 만든 후 다음을 보고 디폴트 파라미터가 숨겨져 있는 곳은 어디인지 찾아보자.

Private Sub Command1_Click( )
   Dim txtTmp As TextBox
   
   Set txtTmp = Text1 "① txtTmp 뒤에, ② Text1 뒤에
   txtTmp = "abc" "③ txtTmp 뒤에
End Sub

.
;
;
;

Set 문 뒤에는 String 값이 올 수 없고 오브젝트 변수 또는 컨트롤이 온다. 또한 참조하는 형, 참조대상 형도 같은 형이 되어야 한다. 즉, Set 문 뒤에 Text1.Text와 같은 String 형이 올 수 없다. 따라서 Set 문에서 참조하든 참조대상이든 둘 다 디폴트 프로퍼티를 갖을 수는 없다. Set 문에서 txtTmp는 Text1의 같은 메모리 주소를 참조하고 txtTmp.Text = "abc"라고 바뀌면 Text1.Text도 값이 바뀥다. 따라서 정답은 ③번이다. 따라서 txtTmp 뒤에 .Text를 붙여도 에러가 발생하지 않는다.

2) Controls Collection

(예 1)
폼에 3개의 텍스트박스(Text1, Text2, Text3)가 있다고 가정하자. 버튼을 눌렀을 때 모든 텍스트박스가 지워지게 하려면 어떻게 해야 할까? 보통은 다음과 같이 코딩한다.

Private Sub Command3_Click( )
   Text1 = ""
   Text2 = ""
   Text3 = ""
End Sub

이번에는 텍스트박스가 50개라면(실제로 그럴 일은 없겠지만) 어떻게 해야 할까? 정말 초보시라면 다음과 같이 하는 사람도 있을 것이다. 속도면에서는 나쁜 방법은 아니지만 본인의 손가락을 학대하지는 말자.

Private Sub Command3_Click( )
   Text1 = ""
   Text2 = ""
   "...
   "(중략)
   "...
   Text50 = ""
End Sub

(예 2)
어떤 사람은 다음과 같이 컨트롤 배열을 이용해야 한다고 생각할 것이다. 보통은 디자인할 때 텍스트박스의 개수가 몇 개인지 알고 있으므로 Text1.Count - 1보다는 실제의 숫자(위의 예에서는 50)를 사용한다.

Private Sub Command4_Click( )
   Dim i As Integer
   
   For i = 0 To Text1.Count - 1
      Text1(i) = ""
   Next i
End Sub

이야기의 초점을 여기에 맞추면 안 될 것 같아 다시 본론으로 들어가서 데이터베이스 연동 프로그램을 만들 때 텍스트박스에 컨트롤 배열을 쓰고 숫자를 다 일일이 외울 수 없으니 Enum 문을 써서 상수와 Index를 연결하는 것을 본적이 있다. 필자가 처음 이 구문을 봤을 때 상당히 좋은 아이디어 느꼈다. 자세히 말하자면, 폼의 선언부 또는 모듈에 다음과 같이 코딩한다.

Enum Person
   cName = 0 "이름, 데이터베이스 필드 이름 비슷하게 이름을 부여함
   cTel = 1 "전화번호
   cAddress = 2 "주소
End Enum

값을 읽어올 때 또는 대입할 때 다음과 같이 코딩하면 배열 텍스트박스도 일반 텍스트박스처럼 인덱스(숫자)로 접근하지 않고 문자(실제로는 상수)처럼 접근할 수 있다. 이름을 입력받는 일반 텍스트박스에 이름을 붙인다면 취향마다 다르겠지만 보통 txtName이라 이름붙인다. txtName = "홍길동"을 다음 코딩에서 Text1(cName) = "홍길동"으로 표현할 수 있다. 물론 이것만 보면 코딩이 길지만 실제로 데이터베이스 관련 처리를 할 경우 레코드셋(rs로 가정)을 다룰 때는 변수 i를 선언하고 For~Next 문 안에 Text1(i) = rs(i)로 코딩하면 상당히 코딩량도 줄고 실제 속도도 rs! 필드 이름 형태보다 rs(Index) 형태가 빠르기 때문에 권장할 만한 방법이다.

Private Sub Command1_Click( )
   Text1(cName) = "홍길동"
   Text1(cTel) = "02-123-4567"
End Sub

(예 3)
굳이 텍스트박스 배열을 쓰지 않고 다음과 같이 Controls 컬렉션을 쓸 수도 있다.

Private Sub Command1_Click( )
   Dim i As Integer
   
   For i = 0 To Me.Controls.Count - 1 "0부터 현재 폼의 모든 컨트롤의 개수만큼 루프
      If TypeName(Controls(i)) = "TextBox" Then "그 컨트롤의 타입이 TextBox이면
         Controls(i).Text = "" "해당 컨트롤을 지움
      End If
   Next
End Sub

필자는 위의 방법보다 다음 방법을 선호한다.

Private Sub Command1_Click( )
   Dim ctl As Control

   For Each ctl In Me.Controls "폼 안의 모든 컨트롤을 하나씩 불러들임
      If TypeName(ctl) = "TextBox" Then "그 컨트롤의 타입이 TextBox이면
         ctl = "" "해당 컨트롤을 지움
      End If
   Next
End Sub

TypeName은 정말 꼭 알아둬야 할 함수다. TypeName은 RunTime 모드든 디버깅 모드든 그 힘을 발휘한다. 무슨 말인지 위 소스를 F8로 한단계씩 실행해보면서 직접 실행창(Ctrl + G)에서 ? TypeName(ctl) 해보면 무슨 컨트롤인지 보여준다. 컨트롤뿐만 아니라 변수도 Integer인지 String형인지 알게 해주는 유용한 함수다. 흔히 에러가 발생했을 때 필자는 TypeName을 이용해 해결한 적이 많다. TypeName으로 컨트롤 형식을 알아내는 것처럼 TypeOf 명령도 다음과 비슷한 수행을 한다.

Private Sub Command1_Click( )
   Dim ctl As Control
   For Each ctl In Controls "위 코드랑 비교에서 Me는 생략 가능
      If TypeOf ctl Is TextBox Then "위의 예는 "TextBox"이지만 TypeOf에서는 따옴표가 붙지 않음에 주의
         ctl = ""
      End If
   Next
End Sub

3) ActiveControl

프로그램 개발하다 보면 뻔하겠지만 사용자들은 좀더 사용하기 쉽게 만들어 달라고 개발자에게 요구한다. 다음은 필자가 ERP를 개발할 때의 요구 조건 중 하나인데 텍스트박스에서 엔터 키로 그 다음 텍스트박스로 이동하는 것은 인터넷 사이트에 보면 많이 있는데 엔터 키뿐만 아니라 화살표로도 텍스트박스의 포커스를 이동할 수 있게끔 사용자가 요청한 적이 있다. 이미 이와 비슷한 소스가 있는지는 모르지만 아무튼 다음 소스는 필자가 만든 것이다. 폼의 KeyPrview를 True로 하고 몇 개의 텍스트박스를 만들어놓고, 다음과 같이 하면 화살표로도 텍스트박스의 포커스를 옮길 수도 있다.

Option Explicit

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
   If TypeName(ActiveControl) <> "TextBox" Then "현재 활성 컨트롤이 텍스트박스가 아니라면
      Exit Sub "프로시저 탈출
   End If
   With ActiveControl
      Select Case KeyCode
         Case vbKeyLeft, vbKeyUp "현재 컨트롤에서 좌측화살표나 위쪽화살표를 눌렀다면
            If .SelStart = 0 Then "텍스트 박스의 커서 위치가 맨 처음인 경우에
               Call MoveFocus(False) "탭 인덱스의 역순으로 포커스 이동
            End If
         Case vbKeyRight, vbKeyDown "현재 컨트롤에서 우측화살표나 아래화살표를 눌렀다면
            If .SelStart = Len(.Text) Then "텍스트 박스의 커서 위치가 맨 마지막인 경우에
               Call MoveFocus(True) "탭 인덱스 순서대로 포커스 이동
            End If
         Case vbKeyReturn "현재 컨트롤에서 엔터 키를 눌렀다면
            Call MoveFocus(True) "탭 인덱스 순서대로 포커스 이동
      End Select
   End With
End Sub

Private Sub MoveFocus(bln As Boolean)
   SendKeys IIf(bln, "", "+") & "{TAB}", True
      "파라미터가 False면 Shift + Tab, True이면 Tab키를 누르게 함
   If TypeName(ActiveControl) = "TextBox" Then "현재 활성화된 컨트롤이 텍스트박스이면
      With ActiveControl
         .SelStart = 0
         .SelLength = Len(.Text) "현재 컨트롤의 텍스트 값을 반전
      End With
   End If
End Sub

여기서 ActiveControl을 쓰지 않는다면, 혹은 ActiveControl 자체를 모르시는 사람은 TextBox의 KeyDown 이벤트에서 일일이 코딩하거나 모듈을 만들어서 처리할 함수를 만들고 TextBox가 20개든 30개든 계속 모듈에 있는 함수를 부른다. 무작정 텍스트박스를 배열로 만드는 것도 그렇고 텍스트박스가 많으면 많을수록 코딩이 상당히 길어진다. 코딩이 길어진다기보다 폼에 TextBox의 KeyDown 이벤트로 지저분하게 도배가 될 것이다.
TAG :
댓글 입력
자료실