[.net] ADO (6.0) + ODBC드라이버를 이용한 DB Connection (MS-SQL, Excel, Access)

 

 

Microsoft ActiveX Data Objects 6.0 Library (adodb)와 ODBC 를 이용해서 각 DB에 Connection하는 클래스

 

 

Public Class DBConn
    Public myDB As New ADODB.Connection
    Public myRS As New ADODB.Recordset
    Public qq As StringBuilder

    Public Function DBOpen() As Boolean
        Dim Opened As Boolean
        Try
            myDB.Open("Provider=SQLOLEDB.1;Persist Security Info=False;Data Source=111.111.111.111,1111;Initial Catalog=DBNAME;User ID=DBNAME;Password=DBPASS;")

            If myDB.State Then
                Opened = True
            Else
                Opened = False
            End If
        Catch ex As Exception
            Opened = False
        End Try
        myRS.CursorType = ADODB.CursorTypeEnum.adOpenStatic
        myRS.CursorLocation = ADODB.CursorLocationEnum.adUseClient
        myRS.LockType = ADODB.LockTypeEnum.adLockOptimistic

        DBOpen = Opened
    End Function


    Public Sub DBClose()
        Try
            myDB.Close()
            loFunctions.releaseObject(myDB)
        Catch ex As Exception
        End Try
    End Sub

End Class




Public Class xlDBConn
    Public xlDB As New ADODB.Connection
    Public xlRS As New ADODB.Recordset
    Public qq As StringBuilder

    Public Function xlDBOpen(xlFileNM As String) As Boolean
        Dim Opened As Boolean

        Try
            With xlDB
                .ConnectionString = "Dsn=Excel Files;dbq=" + xlFileNM + ";driverid=1046;fil=excel 12.0;maxbuffersize=2048;pagetimeout=5"
                .Open()
            End With

            If xlDB.State Then
                Opened = True
            Else
                Opened = False
            End If
        Catch ex As Exception
            Opened = False
        End Try

        xlDBOpen = Opened
    End Function


    Public Sub xlDBClose()
        Try
            xlDB.Close()
            loFunctions.releaseObject(xlDB)
        Catch ex As Exception
        End Try
    End Sub

End Class



Public Class aceDBConn
    Public aceDB As New ADODB.Connection
    Public aceRS As New ADODB.Recordset
    Public qq As StringBuilder

    Public Function aceDBOpen(aceFileNM As String) As Boolean
        Dim Opened As Boolean

        Try
            With aceDB
                .ConnectionString = "Dsn=MS Access Database;dbq=" + aceFileNM.Replace("\\", "\") + ";driverid=25;fil=MS Access;maxbuffersize=2048;pagetimeout=5;uid=admin"
                .Open()
            End With

            If aceDB.State Then
                Opened = True
            Else
                Opened = False
            End If
        Catch ex As Exception
            Opened = False
        End Try

        aceDBOpen = Opened
    End Function


    Public Sub aceDBClose()
        Try
            aceDB.Close()
            loFunctions.releaseObject(aceDB)
        Catch ex As Exception
        End Try
    End Sub


End Class

ADODB나 OLEDB로 ACCESS DB(mdb, accdb)를 사용할 때.

 

일련번호 형식의 SEQ라던지 Identity 필드값을 초기화 하는 방법.

 

참고로 MS-SQL 에서는 이렇게 처리한다.

 

DBCC CHECKIDENT('테이블명',RESEED,0)

이렇게 하면 다음번 insert시에  시드가 1부터 시작하게 되고..

 

DBCC CHECKIDENT('테이블명',RESEED,9999)

이렇게 하면 10000부터 시작된다.

 

 

 

이걸 엑세스(access 2007 에서만 해봤음)  에서 구현하려면~

 

ALTER TABLE [테이블명] ALTER COLUMN [컬럼명] COUNTER(1,1)

 

무슨.... 엑세스에 가서 데이터베이스 압축/복구를 하라느니 복잡하고 귀찮은 설명이 있는데

 

외부프로그램에서나 엑세스 내부에서나  위 쿼리를 한번 돌려주면 끝남. 단, 그전에 테이블의 모든 데이터를 삭제하고 해야함.

 

또한 PK,FK 등  관계가 걸려있는 경우에는 안 될수도 있음..

 

 

[.NET] 디렉토리의 파일 목록 조사 + 필터링. Linq와 람다식을 이용한 Directory.GetFiles 확장자 지정하기.

 

 

사용자에게 폴더를 선택하게끔 하고, 해당 폴더 안에 들어있는 파일을 리스트업 해줘야 할 때가 있다.

.NET에서는  System.IO.Directory.GetFiles 를 이용해서 디렉토리 내 파일 목록을 얻어올 수 있다.

사용방법은 잘들 아시겠지만 다음과 같다.

 

Dim DataFileNM As String() = Directory.GetFiles(TargetFolderNM, "*.*", SearchOption.TopDirectoryOnly)

 

여기서 보듯이 GetFiles메서드에는 패턴검색 옵션을 지정할 수가 있게 되어있는데(searchpattern)

이 패턴 옵션이 뭔가 어정쩡하다.

 

예를들어, 폴더 내의 이미지파일을 모두 보여주고 싶을 때에는 파일확장명에 *.gif, *.tif, *.jpg, *.png, *.bmp 가 지정되어야 할 것이다.

하지만 익숙한 확장자 나열 기법을 적용해서

Directory.GetFiles(TargetFolderNM, "*.gif|*.jpg|*.png", SearchOption.TopDirectoryOnly) 처럼 사용하면 에러가 발생한다.

searchpattern에는 와일드카드를 사용할 수 있지만, 조건식을 나열할 수는 없는 반쪽짜리인 것이다..-_-

꼭 단일 확장자로만 지정을 해줘야 한단다.

 

그래서 그럼 이렇게 해보았다. Concat메서드로  리턴된 배열을 합쳐버리는 방법이다.

 

DataFileNM = Directory.GetFiles(TargetFolderNM, "*.jpg", SearchOption.TopDirectoryOnly)
DataFileNM = DataFileNM.Concat(Directory.GetFiles(TargetFolderNM, "*.tif", SearchOption.TopDirectoryOnly).ToArray)

 

하지만 이 방법 역시 경우에 따라서 오류가 발생한다.

Concat 메서드는 연결할 첫 번째 시퀀스가 nothing일 경우 ArgumentNullException 에러를 발생시킨다.

즉, 첫번째줄에서 *.jpg파일이 하나도 발견되지 않았다면  *.tif 에서 반환된 파일목록 배열은 붙을곳을 찾지 못해 오류가 나는 것이다..

모든 폴더에 jpg파일이 최소한 한개씩은 있어준다는 보장이 없으니 이 역시 사용불가이다. 물론 이것도 조건분기를 처리해서 DataFileNM이 Nothing일 경우엔 그냥 할당하고  아닐경우에만 Concat을 하는 식으로 코딩할 수는 있겠으나 바람직한 코딩이 아니다.

 

 

사설이 길었는데, 이러한 연유로 아래의 방식을 찾았다.  별도의 함수를 만들지 않아도 되고 코드 길이도 짧다.

실로 베스트한 방식이 아닐수 없다. 후후후 -_ㅡv

여기엔 Linq와 람다식이 사용되었다.

 

[VB.net]

'***** 지정된 폴더에서 특정 이미지 파일만 찾아온다.
Dim DataFileNM As String()
Dim exts As String() = {".tif", ".jpg", ".gif"}

 

DataFileNM = Directory.GetFiles(TargetFolderNM, "*.*", SearchOption.TopDirectoryOnly).Where(Function(s) exts.Contains(Path.GetExtension(s), StringComparer.OrdinalIgnoreCase)).ToArray 

 

[C#.net]

string[] exts = new [] {".tif", ".jpg", ".gif"};

string[] DataFileNM =  Directory.GetFiles(TargetFolderNM, "*.*", SearchOption.TopDirectoryOnly).Where(s=>exts.Contains(Path.GetExtension(s), StringComparer.OrdinalIgnoreCase)).ToArray;

 

 

일단  exts에 필터링 할 확장자를 배열로 지정해 놓고

Directory.GetFiles(TargetFolderNM, "*.*", SearchOption.TopDirectoryOnly) 로 폴더내 모든 파일을 가져온다(String배열)

여기서 .Where(Function(s) exts.Contains(Path.GetExtension(s), StringComparer.OrdinalIgnoreCase)) 으로  확장자를 필터링해서 .ToArray로 다시 변환해서 리턴하는 것이다.

 

그러면 DataFileNM 스트링배열 개체에  해당 파일명이 아름답게 담겨지게 된다!

 

 

오늘부터 SKT에서 T끼리 음성통화 및 문자  무제한 무료 요금제를 출시했단다.

그래서 우리집.. 나와 집사람 두명의 SKT 요금제를 가지고 어느정도 혜택이 되는가 비교해봤다.

 

와이프 17만원 갤럭시S3 LTE52요금제 사용.

 난  81.4만원 아이폰4s 올인원34 요금제 사용. 지난달부터는 와이프의 데이터중 500MB를 뻇어오기 해서  사용중이다.

 

쓰잘데기없는 문자무제한같은건 배제하고  데이터와 음성통화 시간과 요금만 비교.

LTE52를 LTE T끼리55로 대체했을 때  2,475원이 더 나온다. 통화시간은 공통250분에서   망외180분+망내무제한 으로 변경.

LTE52를 LTE T끼리45로 대체했을 때 비로소 5,225원이 줄어든다. 통화시간은 망외130분+망내무제한으로  SKT통화량이 많은 특성을 감안할때 무방할것으로 보인다. 다만 데이터가 1.1기가로 줄어듦으로  데이터 뺏아오기는 불가능할것 같다.

 

문제는 내 요금제 올인원34를 T끼리35로 변경하는 경우인데...

3G요금제는 고사시키려는것인가? ㅋ  스페셜할인액이 갑자기 확줄어들고, 작년11월부로 가입이 중단된 더블할인도  혜택을 못받게 되는걸로 가정했다.(아마도 못받게 될것 같다) *더블할인 그대로 승계되어 할인받을수 있는 것으로 확인됨(포스팅 하단 참조)

 

해서 총 요금이 6.66만원으로 10,880원이나 증가하게 된다. 물론 데이터는 550메가로, 와이프껄 뺏어오지 않아도 쓸만큼 되긴 하는데.. 통화시간도 별 상관 없고..

LTE T끼리45로 한단계 낮은 요금으로 변경해서 5225원 세이브 해봐야  내가 T끼리35로 바꾸면   세이브되는거 다 상쇄되고 총 5천원정도 더 인상된다는 소리가 된다.. 총 데이터량도 2000+100메가에서 1100+550메가로 500메가가까이 줄어버리는게 되고..

현재 가장 나은 방법은 LTE T끼리55로 바꿔서 2475원을 더 부담하고  나는 현재요금제를 유지하는 방법인 것 같다.

이렇게되면 SKT는 우리가정에서 2475원을 더 받아가게 되는거네 -_-ㅋㅋ 역시나.. 공짜는 없지.

 

결과적으로는  망내무제한이라는거 외에 메리트가 전혀없다..ㅋㅋㅋㅋ

뭐,,, 홀로쓰는사람들 LTE요금제에  할부원금을  저렇게 후려쳐서 잘 산분들이라면 몇천원정도 요금절약이 되는지 모르겠으나

우리가족 기준으로 계산해 본 결과  조삼모사 라는 사실을 확인하게 되었다.

 

이 포스팅 작성 당시 고객센터에 문의를 넣어놓은 상태였는데, 이제 명확히 답변이 왔다.

------------------------------------------------------------------------------------------------

문의주신 번호를 살펴보니 고객님께서는 올인원34요금제 사용하면서
스페셜할인혜택으로 월 -11,000원(부가세별도) 할인 및 T 할부지원 월 -5,600원을
할인 받고 계신 것으로 확인됩니다.

이에 3G T끼리35요금제로 변경시 스페셜할인혜택 및 T할부지원 혜택은 그대로 승계되고요.

다만, 스페셜할인혜택은 아래와 같이 변경되는점 참고해주시길 바랍니다.

[올인원34요금제 => 3G T끼리35요금제로 변경시]
- 스페셜할인 혜택(부가세별도) : 월 -11,000원 => 월 -7,200원으로 변경

------------------------------------------------------------------------------------------------

 

따라서, 현재  스페셜할인 -11,000 + 약정할인 -5,600 + 스페셜할인부가세 -1,100 으로 월 -17,700 할인받던 부분이

스페셜할인 -7,200 + 약정할인 -5,600 + 스페셜할인부가세 -720으로 월 -13,520원 할인받게 되는것으로 계산된다.

따라서 3G T끼리35요금제로 갈 경우 기존 55,740원보다 +5,280원 증가한  61,020원이 청구되게 된다.

 

와이프 요금제를 LTE T끼리45로 변경해서 -5,225원이 적게 나오게 되었으나 내가 +5,280원이 되므로 결국 +55원이 된다.

다만 데이터 선물하기를 매달 할 필요가 없어지며, SKT 사용자에게 전화통화를 무제한으로 할수 있게 되므로,,

통화량에 신경쓰지 않고 마음껏 전화를 해도 된다는 메리트가 생긴다. 가계통신비 절약 효과는 없음..

 

.NET Framework 4.0 을 기반으로 VSTO 애플리케이션을 작성하는데,

참조로 다른 DLL을 가져다 쓰고 빌드를 하려니 이런 오류가 발생한다.

"혼합 모드 어셈블리는 런타임의 버전 'v2.0.50727'에 대해 빌드되며 추가 구성 정보 없이 '4.0' 런타임에 로드할 수 없습니다."

 

그럼 하위버전의 DLL을 같이 쓸수 없다는말인가?!  하위호환성을 중요시하는 MS의 닷넷인데?!

찾아보니 간단한 해결방법이 있었다.

App.config 파일 (응용프로그램 설정을 사용하게 되면 자동으로 생김) 에 몇줄 추가해 주는 방법이 그것이다.

일단 App.config파일이 없다면  [프로젝트]-[추가]-[구성 요소] 에서 "응용 프로그램 구성 파일"을 추가해주면 된다.

여기에 아래의 코드를 적어넣고 저장후 다시 빌드를 하면 오류가 발생하지 않는단다.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0"/>
  </startup>
</configuration>

 

그런데,,,,,,,,,,

app.config를 만들어 놓고  아무리 빌드를 다시 해보아도 동일한 에러가 발생한다. 해결이 안된다. OTL

이유인즉슨, 일반 윈폼프로그램이 아닌 VSTO( Visual Studio Tools for Office ) 애플리케이션이기 때문이다.

이 app.config는 실행파일을 기준으로 동작을 하는 모양이다.

VSTO 로 개발된 프로그램은 (나의 경우는 Excel 문서수준 사용자지정 애플리케이션) 당연히 OFFICE를 기반으로 실행된다.

그래서 또 열심히 구글링을 한 결과 Office의 실제 실행파일 옆에 app.config를 만들어야 한다는 사실을 알게 되었다.

오피스2010버전 엑셀의 경우

C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.EXE 

이게 실행파일이다.

그래서 동일한 폴더에 동일한 파일명으로 .config를 생성했다.

C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.EXE.config

물론 파일의 내용은 위의 코드와 동일하게 해서..

 

이제 빌드를 해보니 혼합 어셈블리 빌드가 성공적으로 잘 된다.

 

 

 

 

[.NET] 시스템 환경변수 Path에 특정 경로 추가하기.

 

프로그램을 만들어 배포를 하게되면 경우에 따라 System 환경 변수의 Path를 건드려 주어야 할 때가 있다.

 

 

헌데 없어보이게  사용자에게 직접 환경변수를 추가하라고 할 수는 없는 노릇이고

제일 무난하고 편한 Install Factory로 해도, Install Shield 로 해도 방법이 없는것은 아니지만 해본결과 별루이다.

레지스트리를 직접 변경해주는 방식으로 하게 되는데, 이때 기존의 Path내용을 유지하면서 새로운것만 추가하기가 간단치 않다.

 

일단 '사용자 환경변수' 레지스트리의 위치는

 HKEY_CURRENT_USER\Environment\  이다.

사용자변수는 간단하다.

문제는 시스템 환경변수이다. 시스템환경변수는 최소 두군데 이상 동시에 존재한다.

일단 기본위치는

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment

이곳이지만, 사용자계정에따라 ControlSet001, ControlSet002 ... 등등으로 나뉘어진다.

HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Session Manager\Environment

어드메 있는것을 바꿔야 하는지까지는 잘 모르겠다. 이미 이렇게 널부러져 있는것을 확인한순간 다른방법을 고민했으니..

 

 

.NET에는 System.Environment 라는 훌륭한 클래스가 기본제공되질 않는가!

그래서 방법은 프로그램 실행시 시스템 환경변수값을 불러다가  내가 추가하고자 하는 경로가 이미 존재하면 제껴버리고,

없으면  맨앞에 경로를 하나 추가해서 다시 셋팅해주는 것이다.

그게 ControlSet001이 수정되야 하는지 002가 수정되야 하는지, 아니면 CurrentControlSet 에서 수정되야 하는지 알필요 따윈 없다.

어차피 이 프로그램이 System.Environment 가  찾아내준 위치의 시스템환경변수값만 조작해주면 DLL을 불러들이는데는 지장이 없으니까.

 

그래서 작성한 코드.

 

    Public Shared Sub SystemPathControl(AppPath As String)

        Dim _sysPath As String = System.Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine)
        Dim oldPath() As String = _sysPath.Split(";")
        Dim newPath As New StringBuilder

        If Not oldPath.Contains(AppPath) Then
            newPath.Append(AppPath + ";")
            For Each ePath As String In oldPath
                newPath.Append(ePath + ";")
            Next
            System.Environment.SetEnvironmentVariable("Path", newPath.ToString, EnvironmentVariableTarget.Machine)
        End If

    End Sub

 

 

사용된 클래스는 보시다시피..

System.Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine)

System.Environment.SetEnvironmentVariable("Path", newPath.ToString, EnvironmentVariableTarget.Machine)

이 되겠다.

Get으로 Machine(시스템) 환경변수 중 Path를 가져와서 매개변수로 들어온 AppPath값이 있는지 찾아보구

없으면 젤앞에 AppPath를 붙이고 나머지껄 줄줄이 이어붙여서 newPath를 만들어준 다음

Set으로 다시 Machine의 Path값을 바꿔주는 초간단 함수가 되겠다.

Get/Set EnvironmentVariable 메서드의 마지막 매개변수는 사용자/시스템 변수값에 대한 상수이다.

시스템변수는 EnvironmentVariableTarget.Machine

사용자변수는 EnvironmentVariableTarget.User

 

 

이제 이것을 프로그램 시작할때 도는 프로시져 한귀퉁이에서 호출해주면 되시겠다.

사실 이 행위를 플그램 실행때마다 매번 하는게 솔직히 비효율적일 수가 있다.

어디서 지나가다 주워본것 같은데, 닷넷은 배포후 최초 실행되었는지를 리턴해주는 함수가 있다고 봤다(다시 찾아보려니 찾을수가 없지만 ㅜㅜ)

이걸 조합한다면  프로그램 인스톨 후 최초 실행시에만 한번 돌려주도록 만들 수도 있을것이다.

 

최초실행여부를 확인해 주는 함수를 아시는분은 댓글로 제보좀 부탁드립니다 ㅋㅋ 

 

[MSSQL] 써보면 편리한 Split 함수 세 개

 

프로그래밍 언어에는 문자열 관련 함수가 많이 존재하지요. 그만큼 많이 쓰이기도 합니다.

언어마다 약간의 차이는 있지만 대표적인 함수들은 함수명과 매개변수까지 똑같지요.
MS-SQL에도 마찬가지로 기본적으로 문자열 함수가 지원됩니다.


Right, Left, SubString(Mid), Trim, Replace, CharIndex(InStr) ...


그런데 자주 쓰게 되는 문자열함수인 Split 만은 찾아볼 수가 없군요. 저는 Split 을 좋아라하는데..
언어에서 Split 함수는 어떤 문자열을 특정 구분자로 분리해서 배열변수에 담아주는 역할을 합니다.
그런데 MS-SQL에서는 '배열변수' 개념이 없기 때문에 배열변수 대신 테이블변수로 대체해 사용하게 되죠.


사정이 이렇다보니 SQL에서 문자열 Split을 한다면 리턴되는 결과값 변수로 나올수가 없고 테이블이 되어 나올 수밖에 없겠지요.
다른 문자열 함수들처럼 SELECT문에 그냥 갖다쓸수가 없게 됩니다. 그래서 없는가 봅니다.
 
그렇지만 Split은 없으면 아쉬운 편리한 함수 중 하나입니다.
그래서 사용자정의 함수를 만들어 보았습니다. 우리가 일반적으로 흔히 사용하는(알고있는) Split함수의 용법과 다르지 않습니다.
 


 

1. 문자열을 특정 구분자로 단순히 쪼갠다 : FN_SPLIT

    SELECT * FROM FN_SPLIT(@문자열,@구분자)

 

CREATE FUNCTION [dbo].[FN_SPLIT]
(
@리스트 VARCHAR(MAX)
,@분리자 VARCHAR(10)
)
RETURNS @TB TABLE
( 
POS int IDENTITY PRIMARY KEY,
VAL1 varchar(200)
)
AS
/*
- '' 값도 반환한다
- 마지막은 분리자로 끝낸다
SELECT * FROM  dbo.[FN_SPLIT]('^1^^333^2^222^3^333^4^444^5^555^6^666^7^777^8^888^9^999','^') A
SELECT * FROM  dbo.[FN_SPLIT]('1^^22^^^^^^444444','^^') A
*/
BEGIN
 DECLARE
  @시작위치 SMALLINT
 ,@마지막위치 SMALLINT
 ,@카운터 SMALLINT
 ,@분리자크기 SMALLINT
 SELECT @분리자크기 = LEN(@분리자)

 IF RIGHT(@리스트,@분리자크기)!=@분리자
 BEGIN
  SET @리스트=@리스트+@분리자
 END
 SET @리스트=@분리자+@리스트
 
 SET @시작위치 = 1
 SELECT @마지막위치 = CHARINDEX (@분리자,@리스트 ,@시작위치+@분리자크기)
 SET @카운터 = 0
 WHILE (1=1)
 BEGIN
  SET @시작위치 = CHARINDEX (@분리자,@리스트 )
  SELECT @마지막위치 = CHARINDEX (@분리자,@리스트 ,@시작위치+@분리자크기)
        IF @마지막위치 <= 0 BREAK
  INSERT INTO @TB(VAL1) VALUES (SUBSTRING(@리스트,@시작위치+@분리자크기,@마지막위치-@시작위치-@분리자크기))
  SELECT @리스트 = STUFF(@리스트,@시작위치,@분리자크기,'')
  SET @카운터 = @카운터 + 1
 END
 RETURN
END

 

사용 예시

 SELECT * FROM FN_SPLIT('1^2^3^^5^444444','^')

 결과

   

 

테이블형 반환 함수이므로 SELECT * FROM 절에  하나의 테이블처럼 사용하면 됩니다.

클라이언트단에서 특정 순서를 가지고 데이터를 문자열로 이어붙여서 파라메터로 넘기고,
이걸 서버단에서 Split으로 쪼개서 사용할 수 있습니다.


물론 2단 이상 트리구조를 가진 데이터라면 XML로 주고받고 하는게 더 좋겠죠.
하지만 간단한 부분을 처리함에 있어서는 상당히 유용함이 분명합니다.

다만, 성능이 그리 뛰어난 함수는 아니므로 수천개 이상 되는 문자열이라면 이 FN_SPLIT함수 외에 다른방법을 사용하는 것을 추천드립니다.
사실 요즘 서버 성능이 워낙 좋아져서,, 이런 함수 좀 돌린다고 해서 무리가 가거나 부하가 많이 걸릴 일은 별로 없습니다만.. ㅎㅎ

 

 

 

 

2. 문자열을 SPLIT한 후 그룹핑하자 #1:  FN_SPLIT_GRP

   SELECT * FROM FN_SPLIT_GRP(@문자열,@구분자,@그룹수)


다음은 자주사용되지는 않지만 Split많으로 뭔가 살짝 아쉬울 때 사용할 수 있는 함수입니다.
예제를 보시는게 가장 이해가 쉬울 것 같습니다.

 

CREATE  FUNCTION [dbo].[FN_SPLIT_GRP]
(
@리스트 VARCHAR(MAX)
,@분리자 VARCHAR(1)
,@그룹수 SMALLINT
)

RETURNS @TB TABLE
( 
POS int IDENTITY PRIMARY KEY,
VAL1 varchar(500) default '',
VAL2 varchar(500) default '',
VAL3 varchar(500) default '',
VAL4 varchar(500) default '',
VAL5 varchar(500) default '',
VAL6 varchar(500) default '',
VAL7 varchar(500) default '',
VAL8 varchar(500) default '',
VAL9 varchar(500) default ''
)
AS
/*
1^2^3^4^5^6^7^8^9^111^222^333^444^555^666^777^888^999^:한 요소의 집합들로 이루어짐( GROUP형)
====>
1^111^
2^222^
3^333^
4^444^

마지막은 항상 분리자
SELECT * FROM  dbo.[FN_SPLIT_GRP]('1^2^3^4^5^6^7^8^9^111^222^333^444^555^666^777^888^999^','^',1) A
SELECT * FROM  dbo.[FN_SPLIT_GRP]('1^2^3^4^5^6^7^8^9^A^AA^AAA^AAAA^AAAAA^AAAAAA^AAAAAAA^AAAAAAAA^AAAAAAAAA^','^',2) A
SELECT * FROM  dbo.[FN_SPLIT_GRP]('1^2^3^4^5^6^7^8^9^111^222^333^444^555^666^777^888^999^1111^2222^3333^4444^5555^6666^7777^8888^9999^','^',3) A
*/
BEGIN


	DECLARE @RETURNSTR VARCHAR(MAX)
	

	DECLARE @TEM_STR1 varchar(500)
	DECLARE @TEM_STR2 varchar(500)
	DECLARE @TEM_STR3 varchar(500)
	DECLARE @TEM_STR4 varchar(500)
	DECLARE @TEM_STR5 varchar(500)
	DECLARE @TEM_STR6 varchar(500)
	DECLARE @TEM_STR7 varchar(500)
	DECLARE @TEM_STR8 varchar(500)
	DECLARE @TEM_STR9 varchar(500)

	SET @TEM_STR1=''
	SET @TEM_STR2=''
	SET @TEM_STR3=''
	SET @TEM_STR4=''
	SET @TEM_STR5=''
	SET @TEM_STR6=''
	SET @TEM_STR7=''
	SET @TEM_STR8=''
	SET @TEM_STR9=''

	---- 마지막 분리자가 없으면 분리자를 추가한다
	IF RIGHT(@리스트,1)!=@분리자
	BEGIN
		SET @리스트=@리스트+@분리자
	END


	DECLARE
	 @시작위치 SMALLINT
	,@마지막위치 SMALLINT
	,@카운터 SMALLINT

	SET @시작위치 = 0
	SET @마지막위치 = -1
	SET @카운터 = 0
	
	IF (@그룹수=1)
	BEGIN
	    WHILE  CHARINDEX (@분리자,@리스트 ,@시작위치+1) > 0
	    BEGIN

			    SET @마지막위치 = CHARINDEX (@분리자,@리스트 ,@시작위치+1)
    	
			    SELECT @TEM_STR1=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)
		        INSERT INTO @TB(VAL1) VALUES (@TEM_STR1)

			    SET @시작위치 = CHARINDEX (@분리자,@리스트 ,@마지막위치)

			    SET @카운터 = @카운터+1

	    END
	END




	IF (@그룹수>1)
	BEGIN
/*
@TT에 일단 모든 데이터를 입력한 후
===> 그룹별로 한 컬럼씩 순서대로 배치한다
*/

        --테이블 변수 선언
        Declare @TT table
        (
        POS int IDENTITY (1,1) PRIMARY KEY,
        VAL1 varchar(500)
        )

        Declare @TTL_ARRAYNUM INT
        Declare @GROUP_ARRAYNUM INT

	    WHILE  CHARINDEX (@분리자,@리스트 ,@시작위치+1) > 0
	    BEGIN

			    SET @마지막위치 = CHARINDEX (@분리자,@리스트 ,@시작위치+1)

			    SELECT @TEM_STR1=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)
		        INSERT INTO @TT(VAL1) VALUES (@TEM_STR1)

			    SET @시작위치 = CHARINDEX (@분리자,@리스트 ,@마지막위치)

			    SET @카운터 = @카운터+1
	    END

        SELECT @TTL_ARRAYNUM=COUNT(*) FROM @TT
        SET @GROUP_ARRAYNUM=@TTL_ARRAYNUM/@그룹수


        Declare @CURR_NUM INT

        IF (@그룹수>=2)
	    BEGIN
	    /*
	    일단 그룹갯수 만큼 VAL1 컬럼 데이터 입력
	    */
	    
			INSERT INTO @TB(VAL1) 
			SELECT VAL1 
			FROM @TT A
			WHERE
            A.POS <= (@GROUP_ARRAYNUM)*1



			SET @CURR_NUM=2
			UPDATE AA SET VAL2=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END







        IF (@그룹수>=3)
	    BEGIN
	    /*
	    VAL3 컬럼 데이터 업뎃
	    */

			SET @CURR_NUM=3
			UPDATE AA SET VAL3=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


        IF (@그룹수>=4)
	    BEGIN
	    /*
	    VAL4 컬럼 데이터 업뎃
	    */

			SET @CURR_NUM=4
			UPDATE AA SET VAL4=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


        IF (@그룹수>=5)
	    BEGIN

			SET @CURR_NUM=5
			UPDATE AA SET VAL5=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


        IF (@그룹수>=6)
	    BEGIN

			SET @CURR_NUM=6
			UPDATE AA SET VAL6=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


        IF (@그룹수>=7)
	    BEGIN

			SET @CURR_NUM=7
			UPDATE AA SET VAL7=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


        IF (@그룹수>=8)
	    BEGIN

			SET @CURR_NUM=8
			UPDATE AA SET VAL8=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


        IF (@그룹수>=9)
	    BEGIN

			SET @CURR_NUM=9
			UPDATE AA SET VAL9=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


	END



	RETURN

END

 

사용 예시

SELECT * FROM FN_SPLIT_GRP
('1^2^3^4^5^6^7^8^9^111^222^333^444^555^666^777^888^999
^1111^2222^3333^4444^5555^6666 ^7777^8888^9999^','^',3)

결과

 

 

FN_SPLIT_GRP 에는 매개변수가 하나 더 추가됩니다.

 

‘그룹수’  라는 매개변수로, 1~9까지 쓸 수 있습니다.(그 이상이 필요하다면 다른 방법을 강구하세요.. ㅎㅎ)


 

‘그룹수’가 들어감으로 인해서 이 함수는 최대 9 x n 형태의 테이블을 반환할 수 있게 됩니다.
위 예시에서 보시다시피 문자열은 다음과 같은 3 그룹으로 나눌 수 있습니다.
1^2^3^4^5^6^7^8^9^
111^222^333^444^555^666^777^888^999^
1111^2222^3333^4444^5555^6666^7777^8888^9999^

 

그리고 이것을 각각 SPLIT 해서 하나의 열(Column)로 추가해 주는 것이지요.


물론 성능을 고려하고 만들어진 함수는 아닙니다. 어디에 응용하는가는 각자에게 달려 있음을…ㅋㅋ

 

 

 

3. 문자열을 SPLIT한 후 그룹핑하자 #2: FN_SPLIT_SET

SELECT * FROM FN_SPLIT_SET(@문자열,@구분자,@그룹수)

 

이 함수 역시 문자열 단순 쪼개기에서 한단계 더 나아갑니다.
개인적으로는 FN_SPLIT FN_SPLIT_SET을 가장 많이 사용하게 되더군요.
이것도 역시 사용예시를 보시면 이해가 되실 것 같네요.

 

CREATE  FUNCTION [dbo].[FN_SPLIT_SET]
(
@리스트 VARCHAR(MAX)
,@분리자 VARCHAR(1)
,@그룹수 SMALLINT
)

RETURNS @TB TABLE
( 
POS int IDENTITY PRIMARY KEY,
VAL1 varchar(400),
VAL2 varchar(400),
VAL3 varchar(400),
VAL4 varchar(400),
VAL5 varchar(400),
VAL6 varchar(400),
VAL7 varchar(400),
VAL8 varchar(400),
VAL9 varchar(400)
)
AS
/*
1^111^2^222^3^333^4^444^:앞에서 부터 완결된 셋( SET형)
====> 
1^111^
2^222^
3^333^
4^444^

마지막은 항상 분리자

SELECT * FROM  dbo.[FN_SPLIT_SET]('1^111^2^222^3^333^4^444^5^555^6^666^7^777^8^888^9^999^','^',1) A

SELECT * FROM  dbo.[FN_SPLIT_SET]('1^AAA^2^BBB^3^CCC^4^DDD^5^EEE^6^FFF^7^GGG^8^HHH^9^III^','^',2) A

SELECT * FROM  dbo.[FN_SPLIT_SET]('1^111^2^222^3^333^4^444^5^555^6^666^7^777^8^888^9^999^','^',3) A

SELECT * FROM  dbo.[FN_SPLIT_SET]('1^111^2^222^3^333^4^444^5^555^6^666^7^777^8^888^9^999^','^',4) A

SELECT * FROM  dbo.[FN_SPLIT_SET]('1^111^2^222^3^333^4^444^5^555^6^666^7^777^8^888^9^999^','^',4) A

SELECT * FROM  dbo.[FN_SPLIT_SET]('1^111^2^222^3^333^4^444^5^555^6^666^7^777^8^888^9^999^','^',5) A

*/
BEGIN


	DECLARE @RETURNSTR VARCHAR(400)
	

	DECLARE @TEM_STR1 varchar(400)
	DECLARE @TEM_STR2 varchar(400)
	DECLARE @TEM_STR3 varchar(400)
	DECLARE @TEM_STR4 varchar(400)
	DECLARE @TEM_STR5 varchar(400)
	DECLARE @TEM_STR6 varchar(400)
	DECLARE @TEM_STR7 varchar(400)
	DECLARE @TEM_STR8 varchar(400)
	DECLARE @TEM_STR9 varchar(400)

	SET @TEM_STR1=''
	SET @TEM_STR2=''
	SET @TEM_STR3=''
	SET @TEM_STR4=''
	SET @TEM_STR5=''
	SET @TEM_STR6=''
	SET @TEM_STR7=''
	SET @TEM_STR8=''
	SET @TEM_STR9=''

	---- 마지막 분리자가 없으면 분리자를 추가한다
	IF RIGHT(@리스트,1)!=@분리자
	BEGIN
		SET @리스트=@리스트+@분리자
	END


	DECLARE
	 @시작위치 INT
	,@마지막위치 INT
	,@카운터 INT

	SET @시작위치 = 0
	SET @마지막위치 = -1
	SET @카운터 = 0
	

	WHILE  CHARINDEX (@분리자,@리스트 ,@시작위치+1) > 0
	BEGIN

			SET @마지막위치 = CHARINDEX (@분리자,@리스트 ,@시작위치+1)
	
			IF (@카운터 % @그룹수=0)
			BEGIN
				SET @TEM_STR1=''
				SET @TEM_STR2=''
				SET @TEM_STR3=''
				SET @TEM_STR4=''
				SET @TEM_STR5=''
				SET @TEM_STR6=''
				SET @TEM_STR7=''
				SET @TEM_STR8=''
				SET @TEM_STR9=''

			END

			IF (@카운터 % @그룹수=0)
			BEGIN
				SELECT @TEM_STR1=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF (@카운터 % @그룹수=1)
			BEGIN
				SELECT @TEM_STR2=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)
			END

			IF (@카운터 % @그룹수=2)
			BEGIN
				SELECT @TEM_STR3=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)
			END

			IF (@카운터 % @그룹수=3)
			BEGIN
				SELECT @TEM_STR4=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF (@카운터 % @그룹수=4)
			BEGIN
				SELECT @TEM_STR5=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF (@카운터 % @그룹수=5)
			BEGIN
				SELECT @TEM_STR6=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF (@카운터 % @그룹수=6)
			BEGIN
				SELECT @TEM_STR7=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF (@카운터 % @그룹수=7)
			BEGIN
				SELECT @TEM_STR8=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF (@카운터 % @그룹수=8)
			BEGIN
				SELECT @TEM_STR9=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF ((@카운터 % @그룹수)=@그룹수-1)
			BEGIN
			    INSERT INTO @TB(VAL1,VAL2,VAL3,VAL4,VAL5,VAL6,VAL7,VAL8,VAL9) VALUES (@TEM_STR1,@TEM_STR2,@TEM_STR3,@TEM_STR4,@TEM_STR5,@TEM_STR6,@TEM_STR7,@TEM_STR8,@TEM_STR9)

			END

			SET @시작위치 = CHARINDEX (@분리자,@리스트 ,@마지막위치)

			SET @카운터 = @카운터+1

	END

	RETURN

END

 

 

사용 예시

SELECT * FROM FN_SPLIT_SET
('1^AAA^2^BBB^3^CCC^4^DDD^5^EEE^6^FFF^7^GGG^8^HHH^9^III^','^',2)

결과

 


FN_SPLIT_GRP와 마찬가지로 ‘그룹수’ 매개변수가 추가되고, 역시 1~9까지만 사용 가능합니다.
문자열과 결과를 보시면 쉽게 이해가 가시리라 사료됩니다.


웹이던 어플리케이션이던,, 9컬럼 이내의 테이블 형태를 DB에 고스란히 집어 넣으려 할때에 사용하면 매우 편리하죠.

 

 

예를 하나 들어볼께요.

 

 

이런 모양의 데이터를 DB에 고대~~로 집어넣을 일이 있을 때, SPLIT_SETSPLIT_GRP 를 각각 이용한다면

 

FN_SPLIT_GRP

 

SELECT * FROM FN_SPLIT_GRP('언어,수리,외국어,사회탐구,사회탐구,사회탐구,언어,수리나,외국어,한국지리,경제지리,세계사,95,90,85,45,40,35,123,120,100,60,55,50,95,91,80,80,70,50,1,2,3,2,4,6',',',6)

 


 

FN_SPLIT_SET


SELECT * FROM FN_SPLIT_SET('언어,언어,95,123,95,1,수리,수리나,90,120,91,2,외국어,외국어,85,100,80,3,사회탐구,한국지리,45,60,80,2,사회탐구,경제지리,40,55,70,4,사회탐구,세계사,35,50,50,6',',',6)

 

둘 다 결과값은 동일하지만 문자열을 이어 붙이는 순서가 다릅니다.

GRPSET 둘 중 어느걸 써도 상관이 없다는 말이죠.


테이블에 바로 인서트 하려면 이렇게 하면 되겠죠.

 

INSERT INTO TBL
SELECT
VAL1 AS 교과
,VAL2 AS 과목
,VAL3 AS 원점수
,VAL4 AS 표준점수
,VAL5 AS 백분위
,VAL6 AS 등급
FROM FN_SPLIT_SET('언어,언어,95,123,95,1,수리,수리나,90,120,91,2,외국어,외국어,85,100,80,3,사회탐구,한국지리,45,60,80,2,사회탐구,경제지리,40,55,70,4,사회탐구,세계사,35,50,50,6',',',6)
ORDER BY POS

 

실제로는 점수테이블을 저런식으로 넣진 않겠지만 예를들기 위해서 써봤습니다.
언제나 응용은 하기 나름이지요..ㅎ

잘 사용하시길...

 

 

VBE 편집기에서 코드를 쉽게 주석 처리하기 + 우클릭 메뉴 편집

 

 

VBE에서 VBA를 작성 할 때, 범위 주석문이 제공되지 않아서 불편함을 느낀 적이 있으실 겁니다.

다른 언어들, 다른 편집기에서는, 심지어 Html에서도  여러 행(Line)을 한꺼번에 주석 처리하는 블록주석이 존재하는데..

VBE에서는 한줄씩 정성껏 " ' " 를 입력해 주어야 합니다. 번거롭고 불편하지요.

 

 

사실 VBE는 선택한 여러 줄을 한꺼번에 주석으로 처리해 주는 기능이 준비되어 있습니다.

방법을 알아보도록 하겠습니다.

 

 

 

 

여러 줄 한꺼번에 주석으로 처리하기

 

1. [보기] - [도구모음] 에서 [편집] 을 선택합니다.

 

 

 

2. 편집 도구모음이 뜨면 중간에 [주석 블록 설정], [주석 블록 해제]  버튼이 있습니다.

 

 

 

3. 주석처리할 부분을 선택하고 [주석 블록 설정] 버튼을 누르면 자동으로 맨 앞에 ' 가 붙어서 주석처리 됩니다.

 

 

 

 

이 정도만 해도 일단은 한줄씩 ' 를 붙이던 것과 비교해 굉장히 편리하게 주석 블럭을 설정할 수 있게 됩니다.

 

 

여기서 2% 정도 효율성을 더 높여 보도록 할까요? ㅎㅎ

 

마우스 우클릭 메뉴(Context 메뉴)에 사용자 지정 기능 추가하기

 

VBE 편집기에서 마우스 우클릭을 하면 그자리에서 팝업메뉴가 뜬다는 건 다 알고 계실 겁니다.

실제로 코드를 편집하는 코드 창에서는 물론이고, 유저폼, 속성창, 도구모음 등등

각각의 화면 구성 요소에서 우클릭을 할 때마다 보여지는 팝업메뉴는 제각기 다르지요.

이 우클릭 메뉴에 자신이 원하는 기능을 집어넣을 수 있습니다. 매우 편리합니다^^

여기서는 이 포스팅의 주제인 '주석 블록 설정' 기능을 넣어보도록 하겠습니다.

 

 

1. [보기] - [도구모음] 에서 [사용자 지정] 을 선택합니다.

 

 

 

 

2. 사용자지정 창에서 '바로 가기 메뉴'를 체크합니다. 그럼 우측 이미지처럼 작은 옵션창 하나가 또 뜹니다.

 

 

 

3. '코드 창' 을 눌러보면 아래로 풀다운 메뉴가 펼쳐지는걸 확인할 수 있습니다.

 

VBE편집기에서 마우스 우클릭을 했을 때 어떤 메뉴가 보여지게 될 지에 대한 사전 정의와, 해당 메뉴 모양을 확인 할 수 있구요.

여기서는 코드 창 -> 코드 창(VBE 코드 편집 화면 일반) 에서 우클릭 했을 때의 메뉴를 편집해 보겠습니다.

 

 

4. 사용자 지정 창에서 [명령] 탭으로 이동해서 [편집]범주를 눌러 [주석 블록 설정], [주석 블록 해제] 명령을 찾습니다.

그리고 [주석 블록 설정] 명령을 드래그 해서 아래 그림처럼  제일 위에 끌어다 놓습니다.

마찬가지로 [주석 블록 해제] 명령도 끌어다 놓습니다.

 

 

 

5. 이제 VBE 편집기에서 주석처리할 명령어를 선택하고 마우스 우클릭 해보면, 방금 추가했던 두 개의 메뉴가  보이는 걸 확인할 수 있습니다.

 

 

 

 

이 방법으로 마우스 우클릭에  자주 쓰는 나만의 메뉴를 추가해서 편리하게 사용할 수 있습니다.

 

 

헌데...

이래도 불편함이 완전히 가시지 않는군요.

 

키보드 위주의 작업을 할땐 키보드만을 사용해서,  마우스 위주의 작업을 할땐 마우스만을 사용해서 작업하는게 속도나 능률 면에서 훨씬 우세하지요.

 

군대에서 한글97 사용법을 처음 교육받을 때, 제 선임병들은 아예 PC에서 마우스를 빼버렸었습니다.

전세규(전투세부시행규칙) 같이 표, 사진, 글상자 들이 수두룩하게 포함된 수백페이지짜리 복잡한 문서를 작성할 때에도, 마우스 없이 오로지 키보드만으로.. 모든 단축키를 다 외워서 쓰게끔 훈련받았죠.

처음에는 너무도 불편했지만 숙달되니까 마우스 있어도 안쓰게 되고 키보드만으로 작업하는게 훨씬 빠르고 편했던 기억이 납니다.

 

 

아무튼, VBE에서 코드를 편집하는데 주석은 꽤나 빈번히 쓰이는 기능입니다.

보통 우리는 프로그래밍 코드를 입력 할 때, 양 손 모두 사용해 키보드를 두드리게 됩니다.

주석블럭을 설정하려 할 때마다 손을 마우스로 가져가서 마우스조작을 해야 하는게 은근 귀찮은 일이 되기도 하지요.

키보드 만으로도 주석블록설정을 해 봅시다..

우선 위에서 설명한 Context메뉴 설정을 해 주시구요,

1. Shift + 방향키(↑, ↓) 로 주석 처리 할 블록을 선택합니다.

2. Context( - 우클릭키) 를 눌러서 메뉴를 띄운 다음, 

3. 방향키(↑, ↓) 로 주석 블록 설정 메뉴를 선택한 후

4. [Enter] 로 기능을 실행할 수 있습니다.

 

※ Context키는 보통 스페이스바 오른쪽의 Alt와 Ctrl키 사이에 위치하고 있습니다.

혹시 자신의 키보드에 Context키가 없다면 Shift + F10 로 편집기 바로가기메뉴를 띄울 수 있습니다.

 

http://officetanaka.net/excel/vba/tips/tips04.htm

 

화면에 메시지를 표시하려면 MsgBox 함수를 사용합니다.

 

예를 들어 다음 코드는 화면에 "LOTIONY" 라는 문자열을 표시합니다.

Sub Sample ()
    MsgBox "LOTIONY"
End Sub

 

화면에는 [OK] 버튼이 있는 메시지 상자가 표시됩니다.

이 메시지 상자는 사용자가 OK 버튼을 누를 때 까지 계속해서 모달창으로 표시가 되지요.

 

지정된 시간이 지나면 자동으로 닫히는 메시지 상자를 만들수는 없을까 해서 찾아보았습니다.

 

우선 MsgBox가 가지고 있는 기본 인자값들만으로는 구현이 불가능했습니다.

그리고 그것을 대체할만한 함수나 기능이 VB/VBA 에는 별도로 마련되어 있지 않습니다.

이 기능을 구현하려면 Windows Scripting Host (WSH) 를 사용합니다.

 

 

 

지정된 시간이 지나면 자동으로 닫히는 메시지박스(MsgBox) 만들기

 

Sub test()
    Dim WSH As Object
    Set WSH = CreateObject("WScript.Shell")
    WSH.Popup "5 초 후 자동으로 닫습니다", 5, "Title", vbInformation
    Set WSH = Nothing
End Sub

 

 

 

WSH.Popup (strText, [nSecondsToWait], [strTitle], [nType])

 

 매개변수

 의미

 strText

 메시지 상자에 표시 할 문자열. 필수 요소

 nSecondsToWait

 메시지 상자를 닫을 때까지의 시간. 선택적

 strTitle

 메시지 상자의 제목. 선택적

 nType

 아이콘이나 버튼의 종류. 선택적

 

 

 

매개변수 nSecondsToWait에서 지정한 시간이 되기 전에 사용자가 버튼을 조작하면 언제든지 메시지박스를 닫을 수 있습니다.

매개변수 nType은 다음 값을 지정할 수 있고 MsgBox 에서 사용되는 상수와 다르지 않습니다.

 

 값

VBA 상수 

의미 

 0

 vbOKOnly  [OK] 버튼을 표시합니다

 1

 vbOKCancel  [OK] 버튼과 취소 버튼을 표시합니다

 2

 vbAbortRetryIgnore  중지 버튼, 다시 시도 버튼 및 무시 단추를 표시합니다

 3

 vbYesNoCancel  [예] 버튼 아니요 단추 및 취소 단추를 표시합니다

 4

 vbYesNo  [예] 버튼과 [아니오] 버튼을 표시합니다

 5

 vbRetryCancel  다시 시도 버튼과 취소 버튼을 표시합니다

 16

 vbCritical   아이콘을 표시합니다

 32

 vbQuestion    아이콘을 표시합니다

 48

 vbExclamation   아이콘을 표시합니다

 64

 vbInformation   아이콘을 표시합니다

 

 

 

Popup메서드로 띄운 메시지 상자에서 버튼을 클릭했을 때 반횐되는 값도 MsgBox와 동일합니다.

 

 값

VBA 상수

클릭된 버튼 

 1

 vbOK  [OK] 버튼

 2

 vbCancel  취소 버튼

 3

 vbAbort  중지 버튼

 4

 vbRetry  다시 시도 버튼

 5

 vbIgnore  무시 버튼

 6

 vbYes  [예] 버튼

 7

 vbNo  아니오 버튼

 

시간이 경과해서 자동으로 메시지상자가 닫힌 경우에는 -1 이 리턴됩니다.
 


유저폼(Userform)은 엑셀 솔루션을 만들 때 많이 사용되는 요소입니다.


대부분의 경우, 평범하게 컨트롤들 올려서 사용하겠지요. 유저폼의 용도가 원래 그런것이니까 ^^;

하지만 모달리스(Modaless) 유저폼에 윈도우 API를 사용하면 단순한 유저폼이 아니게 변신시킬 수도 있습니다.

참고1
모달리스 유저폼을 만들면 유저폼이 떠 있는 상태에서도 그 밑에 있는 엑셀 시트를 제어하고, 여러 개의 유저폼을 동시에 띄워두고도 각각 별개로 컨트롤이 가능하게 됩니다.
프로그램의 순서나 흐름 제어를 지킬 수 없기 때문에 (사용자가 어떤 창에서 어떤 액션을 할지 예측할 수도 없고 통제할 수도 없으므로)  개발자가 의도하는 순서대로 진행되어야 할 경우엔 모달리스를 사용하면 안 됩니다.

모달리스(Modaless) 설정을 변경하는 방법은 두 가지가 있습니다.
  1. Userform 속성에서  [ShowModal] 프로퍼티를 False로 변경.
  2. 유저폼을 띄울 때  옵션값을 0으로 지정.  Userform1.Show 0 


API로 할 수 있는 건 여러가지가 있지만, 실제로 써먹을 수 있을만한 3가지만 소개하겠습니다.


타이틀바(캡션바) 없는 유저폼 만들기(Caption hidden Userform)


 모양

API 적용


    Dim hwnd As Long
    hwnd = FindWindow(IIf(Application.Version >= 11, "ThunderDFrame", "ThunderXFrame"), Caption)

    '***** 캡션창을 없앤다.
    SetWindowLong hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) And Not WS_CAPTION





모서리가 둥근 유저폼 만들기(Round Rect Userform)


 모양

 

API 적용


    Dim hwnd As Long
    hwnd = FindWindow(IIf(Application.Version >= 11, "ThunderDFrame", "ThunderXFrame"), Caption)

    '***** 설정된 값으로 모서리가 둥근 유저폼 생성한다.
    SetWindowRgn hwnd, CreateRoundRectRgn(0, 0, Me.Width + 100, Me.Height, 20, 20), True




유저폼 반투명하게 만들기(Transparent Userform)


 모양

API 적용


    Dim hwnd As Long
    hwnd = FindWindow(IIf(Application.Version >= 11, "ThunderDFrame", "ThunderXFrame"), Caption)

    '***** 반투명 레이어로 만들기 위해 유저폼 셋팅
    SetWindowLong hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) Or WS_EX_LAYERED
    
    '***** 반투명 폼으로 적용(0~255)
    SetLayeredWindowAttributes hwnd, 0, 195, LWA_ALPHA


위의 방법을 다 적용하면 다음과 같이 타이틀바 없고, 모서리가 둥글고, 반투명한  유저폼도 생성할 수 있습니다. ^^


<타이틀바 없고, 모서리가 둥글고, 반투명한 유저폼>


하지만 너무 과도하게 효과를 넣으면 오히려 보기 싫어질 수가 있겠죠?


위에 간단하게 API 코드를 적었습니다만, 당연히 그냥은 실행이 안됩니다.

API 함수를 선언해 줘야 하고, 사용된 상수값들도 셋팅해 줘야겠죠.


그런데 위와 같은 유저폼을 옮기고 싶으면 어떻게 할까요? 

MouseMove이벤트와 API를 이용해서 Userform 아무곳을 클릭하고 드래그 하면 이동되게 할 수 있습니다.


또, 예기치않게 사용자가 창을 닫아버리는 걸 방지하고 싶다면 어떻게 할까요?

캡션바가 없어졌으므로 우측상단의 X 버튼은 자연히 사용할 수 없게 되었습니다.

Alt+F4로 폼을 닫는 것도 막아버릴 수 있습니다. 

사용자는 오직 내가 만들어놓은 Close버튼을 눌러야만 창을 닫을 수 있습니다.

이렇게 사용자의 행동을 내가 원하는 대로 제한시킬 수 있습니다. 종종 생기죠, 이런 경우. ㅎㅎ



위의 모든 내용을 아우르는 코드입니다..


1. Userform을 하나 생성하고, 버튼을 하나 만듭니다.(CommandButton1)

2. 아래 코드를 붙여넣습니다.

'//********** API용 상수값 설정
Private Const WS_CAPTION = &HC00000

Private Const LWA_COLORKEY = &H1        '## 색상지정값 확정
Private Const LWA_ALPHA = &H2           '## 투명도 확정
Private Const GWL_STYLE = (-16)         '## 윈도우 스타일
Private Const GWL_EXSTYLE = (-20)       '## 확장형윈도우 스타일
Private Const WS_EX_LAYERED = &H80000   '## 계층형 윈도우 생성

Private Const WM_NCMOUSEMOVE = &HA0
Private Const WM_NCLBUTTONDOWN = &HA1
Private Const WM_NCLBUTTONUP = &HA2
Private Const WM_NCLBUTTONDBLCLK = &HA3
Private Const WM_NCRBUTTONDOWN = &HA4
Private Const WM_NCRBUTTONUP = &HA5
Private Const WM_NCRBUTTONDBLCLK = &HA6
Private Const WM_NCMBUTTONDOWN = &HA7
Private Const WM_NCMBUTTONUP = &HA8
Private Const WM_NCMBUTTONDBLCLK = &HA9
Private Const HTCAPTION = 2


Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function CreateRoundRectRgn Lib "gdi32" (ByVal x1 As Long, ByVal y1 As Long, ByVal x2 As Long, ByVal y2 As Long, ByVal X3 As Long, ByVal Y3 As Long) As Long
Private Declare Function SetWindowRgn Lib "user32" (ByVal hwnd As Long, ByVal hRgn As Long, ByVal bRedraw As Boolean) As Long
Private Declare Function SetLayeredWindowAttributes Lib "user32" (ByVal hwnd As Long, ByVal crKey As Long, ByVal bAlpha As Byte, ByVal dwFlags As Long) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Sub ReleaseCapture Lib "user32" ()

                                                       




Private Sub CommandButton1_Click()
    Unload Me
End Sub




Private Sub UserForm_Initialize()
    
    Dim hwnd As Long
    hwnd = FindWindow(IIf(Application.Version >= 11, "ThunderDFrame", "ThunderXFrame"), Me.Caption)
    
    '***** 캡션창을 없앤다.
    SetWindowLong hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) And Not WS_CAPTION
    
    '***** 설정된 값으로 모서리가 둥근 유저폼 생성한다.
    SetWindowRgn hwnd, CreateRoundRectRgn(0, 0, Me.Width + 100, Me.Height, 20, 20), True
    
    '***** 반투명 레이어로 만들기 위해 유저폼 셋팅
    Call SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) Or WS_EX_LAYERED)
    
    '***** 반투명 폼으로 적용(0~255)
    Call SetLayeredWindowAttributes(hwnd, 0, 195, LWA_ALPHA)
    
    Me.Height = Me.Height + 1
    
End Sub




Private Sub UserForm_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)

    Dim hwnd As Long

    '***** 마우스 왼쪽 버튼을 누은 상태에서 Drag한다면
    If Button = 1 And Shift = 0 Then

        '***** 유저폼의 핸들을 취득한다.
        hwnd = FindWindow(IIf(Application.Version >= 11, "ThunderDFrame", "ThunderXFrame"), Me.Caption)

        '***** 마우스 이벤트이외의 이벤트 발생을 허용
        Call ReleaseCapture
        
        '***** 해당 지점을 Caption Bar로 인식시킨다.
        Call SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0&)

    End If
    
End Sub




Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    '***** Alt+F4로도 종료하지 못하도록 조치함
    Cancel = CloseMode = 0
End Sub




참고2

FindWindow 함수로는 현재 유저폼의 핸들을 취득합니다. 

엑셀 자체 프로그램의 핸들은 Application.hwnd 메서드로 바로 구할 수가 있지만, 유저폼에서는 지원되지 않습니다.

유저폼의 핸들은 FindWindow API함수를 이용해 찾을 수 있습니다.

hwnd = FindWindow(IIf(Application.Version >= 11, "ThunderDFrame", "ThunderXFrame"), Me.Caption)


이 포스팅에 소개한 예제 파일입니다.


이외에도 유저폼에 애니메이션 효과를 줄 수 있는 AnimateWindow 함수, 

포지션 및 크기를 Windows기준으로 설정할 수 있는 MoveWindow 함수, 

각 유저폼(또는 특정 핸들)을 다른 폼에 종속시킬 수 있는 SetParent 함수 등

필요에 따라 많은 API함수를 적용해 볼 수 있습니다.


+ Recent posts