유저폼(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