I'm on your side, when times get rough.

2009-03-28

(C) Local Computer Name & IP Address

Filed under: Programming — Peter_KIM @ 11:37

#pragma hdrstop

 

#include <stdio.h>

#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")

 

#define SAFE_FREE(x) { if((x) != NULL) { free((void *)(x)); (x) = NULL; } }

 

int GetLocalAddress(void);

//—————————————————————————

 

#pragma argsused

int main(int argc, char* argv[])

{

        GetLocalAddress();

        return 0;

}

//—————————————————————————

 

int GetLocalAddress()

{

        CHAR*          pszSystemName  = NULL;

        ULONG          iLen           = _MAX_PATH;

 

        HOSTENT*       pHostEnt       = NULL;

        WSADATA        wsaData         = {0};

        struct in_addr*        pAddress       = NULL;

 

        INT            idx            = 0; // Loop Index

        BOOL           bRetVal        = FALSE;

 

        pszSystemName = (CHAR*)malloc((iLen+1)*sizeof(CHAR));

        *pszSystemName = 0;

        if (GetComputerName(pszSystemName, &iLen) == FALSE) {

               goto CLEAR_RESOURCE;

        }

        printf("%sn", pszSystemName);

 

        // Initialize the winsock

        if (WSAStartup(0x202,&wsaData) != 0) {

               goto CLEAR_RESOURCE;

        }

 

        //get host information from the host name

        pHostEnt = gethostbyname(pszSystemName);

        if (pHostEnt == NULL) {

               goto CLEAR_RESOURCE;

        }

       

        //check the length of the IP adress

        if (pHostEnt->h_length != 4) {

               goto CLEAR_RESOURCE;

        }

 

        while (pHostEnt->h_addr_list[idx] != NULL){

               pAddress =  malloc(sizeof(struct in_addr));

 

               CopyMemory(&pAddress->S_un.S_addr, pHostEnt->h_addr_list[idx],

                        pHostEnt->h_length);

               printf("%d.%d.%d.%dn",

                        pAddress->S_un.S_un_b.s_b1, pAddress->S_un.S_un_b.s_b2,

                        pAddress->S_un.S_un_b.s_b3, pAddress->S_un.S_un_b.s_b4);

 

               SAFE_FREE(pAddress);

                ++idx;

        }

        bRetVal = TRUE;

 

CLEAR_RESOURCE:

        SAFE_FREE(pszSystemName);

        WSACleanup();

        return bRetVal;

}

 

Win32 Control: Extended Hover Button (MFC)

Filed under: Programming — Peter_KIM @ 08:52

다음 소스 코드는 MFC 라이브러리를 이용하여, 작성한 확장된 Button 컨트롤입니다. 우리가 GUI 프로그램을 작성하면서, 가장 많이 사용하는 컨트롤은 Button 입니다. 대부분의 언어에서 누구나 한번쯤은 어떤 버튼을 사용했을 것입니다.

 

MFC 버전 9.0 라이브러리 이전에는 버튼에 그래픽 이미지를 적용하여, 좀더 화려하게 표시하기 위하여, 주로 CBitmapButton 클래스를 사용했습니다. MFC 9.0 버전에서는 CMFCButton 클래스가 존재하여, 좀더 화려하게 버튼을 표시할 수 있습니다. 그러나, 이전에도 언급한 바와 같이, CMFCButton 클래스는 3rd Party 제작사의 라이브러리를 가져다가 이름만 바꾼 클래스에 불과하므로, 좀더 새로운 코드나, 깊이 있는 원리를 알고자 하는 개발자들에게는 마치, 마약과 같은 것이 될 수도 있습니다.

 

우선 클래스를 사용하여, 표현되는 버튼의 모양을 보겠습니다. 각각은 일반 상태(BTN_NORMAL), 마우스가 버튼 위에 있을 때(BTN_HOVER), 버튼이 눌려졌을 때(BTN_SELECT)를 표시합니다.

그럼, 지금부터 클래스의 주요 아키텍처에 대하여 살펴 보겠습니다. (클래스의 소스 코드는 부분적으로만 설명합니다. 전체의 코드는 문서의 아래에 링크에서 내려 받아 보시길 바랍니다.)

기본적으로 제공되는 Win32 버튼 컨트롤을 새로운 모양으로 표시하기 위하여, BS_OWNERDRAW 속성을 지정하여 버튼을 생성해야 합니다. 이 속성을 지정하면, 윈도우는 WM_DRAWITEM 메시지를 발생시킵니다. CBitmapButton 클래스를 살펴보겠습니다. 여러 함수 DrawItem 함수에서 버튼에 지정된 Bitmap 이미지를 표시하는 것을 볼 수 있습니다.

// afxext.h

class CBitmapButton : public CButton

{

        DECLARE_DYNAMIC(CBitmapButton)

……

protected:

        virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);

};

// winbtn.cpp

void CBitmapButton::DrawItem(LPDRAWITEMSTRUCT lpDIS)

{

……

        // use the main bitmap for up, the selected bitmap for down

        CBitmap* pBitmap = &m_bitmap;

        UINT state = lpDIS->itemState;

……

        // draw the whole button

        CDC* pDC = CDC::FromHandle(lpDIS->hDC);

        CDC memDC;

        memDC.CreateCompatibleDC(pDC);

        CBitmap* pOld = memDC.SelectObject(pBitmap);

……

        pDC->BitBlt(rect.left, rect.top, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);

        memDC.SelectObject(pOld);

}

이 클래스의 구조를 이용하여, CButton 클래스를 상속받는 클래스를 생성합니다.

 

#pragma once

class CAdvHoverButton : public CButton

{

        DECLARE_DYNAMIC(CAdvHoverButton)

……

protected:

        virtual void PreSubclassWindow();

        virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);

};

DrawItem 함수에 위의 그림과 같은 이미지와 글씨를 GDI 함수를 이용하여, 표시하도록 하겠습니다.

void CAdvHoverButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)

{

……

// 이미지 리스트에서 해당 비트맵을 그린다.

::ImageList_Draw(m_himl, nIdx, lpDrawItemStruct->hDC, m_ptImage.x, m_ptImage.y, LD_TRANSPARENT);

// 버튼의 Caption 문자열을 그린다.

::DrawState(lpDrawItemStruct->hDC, (HBRUSH)NULL, NULL, (LPARAM)m_szCaption, (WPARAM)0, m_ptCaption.x,

        m_ptCaption.y, 0, 0, DST_TEXT);

// 버튼의 Border 선을 그린다.

::DrawEdge(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, EDGE_BUMP, BF_ADJUST | BF_RECT | BF_FLAT);

}

함수에서 표현하는 부분은 이미지, 문자열, 외곽선 입니다. DrawItem 함수가 호출되기 전에 각각의 위치와 크기를 결정해야 합니다. 아래의 함수는 전체 소스코드의 일부를 표현한 것입니다.

void CAdvHoverButton::UpdateButton()

{

……

        m_hFont = (HFONT)::SendMessage(::GetParent(m_hWnd), WM_GETFONT, 0, 0);

        ::SelectObject(hDC, (HGDIOBJ)m_hFont);

        ::GetTextExtentPoint32(hDC, m_szCaption, nLenCaption, &sizeCaption);

……        

        switch (m_nCapPos) {

        case CAdvHoverButton::CAP_LEFT:

        case CAdvHoverButton::CAP_RIGHT:

               nBtnWidth = m_sizeBmp.cx + sizeCaption.cx;

               nBtnHeight= (m_sizeBmp.cy > sizeCaption.cy) ? m_sizeBmp.cy : sizeCaption.cy;

               nBtnWidth += 5; nBtnHeight += 4;

 

               m_ptCaption.y = (nBtnHeight – sizeCaption.cy) / 2;

               m_ptImage.y   = (nBtnHeight – m_sizeBmp.cy )  / 2;

 

               if (m_nCapPos == CAdvHoverButton::CAP_LEFT) {

                       m_ptCaption.x = 2; m_ptImage.x = m_ptCaption.x + sizeCaption.cx + 1;

                       } else {

                       m_ptImage.x   = 2; m_ptCaption.x = m_ptImage.x + m_sizeBmp.cx + 1;

                       }

        break;

……

 

디자인 타임에 다이얼로그 위에 그려진 버튼(리소스에 포함)을 클래스에 연결하려면, Sub-Class 처리가 필요합니다. , WM_DRAWITEM 메시지를 발생시키기 위하여, Sub-Class 처리 전에, 버튼 컨트롤에 BS_OWNERDRAW 속성을 지정하여야 합니다. MFC 라이브러리에서 제공하는 PreSubclassWindow 함수를 이용하여, 리소스에 포함된 컨트롤의 속성을 변경할 수 있습니다. 아래와 같은 코드를 작성합니다.

void CAdvHoverButton::PreSubclassWindow()

{

        unsigned int  dwStyle   = 0;

        unsigned long dwExStyle = 0;

        dwStyle  = ::GetWindowLong(m_hWnd, GWL_STYLE) | BS_OWNERDRAW;

        dwExStyle = ::GetWindowLong(m_hWnd, GWL_EXSTYLE) & ~WS_EX_DLGMODALFRAME & ~WS_EX_CLIENTEDGE;

 

        ::SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);

        ::SetWindowLong(m_hWnd, GWL_EXSTYLE, dwExStyle);

        ::SetWindowPos(m_hWnd, NULL, 0, 0, 0, 0,

                   SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);

        CButton::PreSubclassWindow();

}

대략 이런 코드를 작성하게 되면, 그림에서 표현한 Hover 버튼을 만들 수 있습니다.

 

그럼, 이제 이 버튼 클래스를 사용하는 방법에 대하여, 알아보겠습니다. 우선 다이얼로그 위에 버튼 컨트롤을 올려 놓습니다. HIMAGELIST 개체를 이용하여 NORMAL, HOVER, SELECT 상태의 순서로 표시할 이미지를 만듭니다.

 

// h 파일

CAdvHoverButton m_wndHoverBtn1;

 

// CPP 파일

m_wndHoverBtn1.SubclassDlgItem(IDC_BUTTON1, this);

m_wndHoverBtn1.SetImageList(IDB_CLOSE);

m_wndHoverBtn1.SetShowCaption(1, CAdvHoverButton::CAP_LEFT);

m_wndHoverBtn1.SetSizeToContent(0);

m_wndHoverBtn1.SetDrawBorder(0);

m_wndHoverBtn1.SetFont(“FixedSys”, 10);

m_wndHoverBtn1.UpdateButton();

함수 각각의 인자의 값에 따라서 표현되는 버튼의 모양이 각각 상이합니다. , 함수의 일부를 수정하면 보다 더 효과적으로 시각 효과를 나타낼 수 있습니다.

 

전체 코드는 아래의 주소에서 내려 받을 수 있습니다.

https://skydrive.live.com/embedicon.aspx/.Public/HoverButtonTest.7z?cid=1bbcdfedee1c617e

 

2009-03-22

C/C++ 전각(EM) / 반각(EN) 변환

Filed under: Programming — Peter_KIM @ 17:23

#include <windows.h>

#include <stdlib.h>

#include <stdio.h>

 

int PASCAL WinMain(HINSTANCE hCurInstance, HINSTANCE hPrevInstance,

                   LPSTR lpCmdLine, int nCmdShow) {

        LPCTSTR lpszHalf = "ABCDABCD가";

        wchar_t* pszWide = NULL;

        char* pszEM = NULL;

        char* pszEN = NULL;

 

        int nWideLen = 0;

        int nHalfLen = 0;

        int idx = 0;

 

        nHalfLen = lstrlen(lpszHalf);

        nWideLen = MultiByteToWideChar(CP_ACP, MB_COMPOSITE, lpszHalf, nHalfLen, NULL, 0);

 

        pszWide = (wchar_t*)malloc((nWideLen+1)*2);

        MultiByteToWideChar(CP_ACP, MB_COMPOSITE, lpszHalf, nHalfLen, pszWide, nWideLen);

        *(pszWide + nWideLen) = 0x00;

 

        // To EM

        for (idx = 0; idx < nWideLen; ++idx) {

               if (*(pszWide + idx) > 0x0021 && *(pszWide + idx) <= 0x007e)

                       *(pszWide + idx) += 0xfee0;

               else if (*(pszWide + idx) == 0x0020)

                       *(pszWide + idx) = 0x3000;

        }

        pszEM = (char*)malloc(nWideLen*2 + 1);

        WideCharToMultiByte(CP_ACP, 0, pszWide, -1, pszEM, nWideLen*2 + 1, NULL, 0);

        free(pszEM);

 

        // To EN

        for (idx = 0; idx < nWideLen; ++idx) {

               if (*(pszWide + idx) > 0xff00 && *(pszWide + idx) <= 0xff5e)

                       *(pszWide + idx) -= 0xfee0;

               else if (*(pszWide + idx) == 0x3000)

                       *(pszWide + idx) = 0x0020;

        }

        nHalfLen = WideCharToMultiByte(CP_ACP, 0, pszWide, -1, pszEN, 0, NULL, 0);

        pszEN = (char*)malloc(nHalfLen);

        WideCharToMultiByte(CP_ACP, 0, pszWide, -1, pszEN, nHalfLen, NULL, 0);

        free(pszEN);

 

        // Release

        free(pszWide);

        return 0;

}

2009-03-19

C# 전각(EM) / 반각(EN) 변환

Filed under: Programming — Peter_KIM @ 15:45

private string Full2Half(string sFull)

{

    char[] ch = sFull.ToCharArray(0, sFull.Length);

    for (int i = 0; i < sFull.Length; ++i)

    {

        if (ch[i] > 0xff00 && ch[i] <= 0xff5e)

            ch[i] -= (char)0xfee0;

        else if (ch[i] == 0x3000)

            ch[i] = (char)0x20;

    }

       return (new string(ch));

}

 

private string Half2Full(string sHalf)

{

    char[] ch = sHalf.ToCharArray(0, sHalf.Length);

    for (int i = 0; i < sHalf.Length; ++i)

    {

      if (ch[i] > 0x21 && ch[i] <= 0x7e)

          ch[i] += (char)0xfee0;

      else if (ch[i] == 0x20)

          ch[i] = (char)0x3000;

    }

    return (new string(ch));

}

2009-03-18

Win32 Control: ListView 【Hide Horizontal Scroll】

Filed under: Programming — Peter_KIM @ 04:07

 

Resource File (RC)

……

IDD_DIALOG1 DIALOGEX 0, 0, 220, 79

STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU

CAPTION “Dialog”

FONT 8, “MS Shell Dlg”, 400, 0, 0x1

BEGIN

CONTROL      “”,IDC_LIST1,“SysListView32”,LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,143,64

PUSHBUTTON      “OK”,IDOK,165,57,50,14

PUSHBUTTON      “Button1”,IDC_BUTTON1,165,33,50,14

END……

 

CPP File

Int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,  char* lpszCmdLine, int nCmdShow)

{

DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), GetDesktopWindow(), (DLGPROC)DialogProc);

return 0;

}

 

long __stdcall DialogProc(HWND hDlg, unsigned int message, unsigned int wParam, unsigned int lParam)

{

HWND hWndLV = GetDlgItem(hDlg, IDC_LIST1);

switch (message) {

case WM_INITDIALOG:

OnInitializeListView(hWndLV);

return TRUE;

        case WM_DESTROY:

PostQuitMessage(0);

break

        case WM_COMMAND:

{

switch(LOWORD(wParam)) {

case IDOK:

case IDCANCEL:

EndDialog(hDlg, LOWORD(wParam));

return TRUE;

break;

case IDC_BUTTON1:

ShowScrollBar(hWndLV, SB_HORZ, FALSE);

break;

}

}

break;

……

}

……

return FALSE;

}

 

void OnInitializeListView(HWND hWndLV)

{

char message[] = “LVM_APPROXIMATEVIEWRECT,LVM_ARRANGE,LVM_CREATEDRAGIMAGE,LVM_DELETEALLITEMS,”

               “LVM_DELETECOLUMN,LVM_DELETEITEM, LVM_EDITLABEL,LVM_ENABLEGROUPVIEW,”;

……

const char seps[] = “,”;

char *token = NULL;

LVCOLUMN lvc = {0};  LVITEM lvi = {0};

int i = 0; RECT rect  = {0};

 

GetClientRect(hWndLV, &rect);

 

lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;

lvc.fmt = LVCFMT_LEFT;

lvc.cx = rect.right – rect.left + 50;

 

lvi.mask = LVIF_TEXT;

lvi.iSubItem = 0;

 

ListView_InsertColumn(hWndLV, 0, &lvc);

 

token = strtok(message, seps);

while(token != NULL) {

lvi.iItem = i;

lvi.pszText = token;

 

ListView_InsertItem(hWndLV, &lvi);

token = strtok(NULL, seps);

++i;

}

}

모두 알고 있는 것처럼, 단지 ListView 컨트롤을 하나 올려 놓고, 헤더 설정, 아이템 추가의 상당히 기초적인 작업을 했습니다. 이렇게 작성된 코드를 빌드하여, 실행하면 다음과 같은 결과 화면을 볼 수 있습니다.

“Button1” 버튼을 누르면 가로 스크롤 막대가 사라집니다. 그러나 세로 방향으로 스크롤을 하면, 사라졌던 가로 스크롤 막대가 다시 표시됩니다. ListView 컨트롤의 속성 중에 “No Scroll” 값을 “True”으로 변경해 보겠습니다(LVS_NOSCROLL).

ShowScrollBar() 함수를 이용하여, 가로 스크롤 막대를 보이지 않게 하더라도, ListView Control 내부의 이벤트 처리에서 사라진 가로 스크롤 막대를 다시 보이게 만드는 것을 알 수 있습니다.

그렇다면, ListView 컨트롤에 이벤트 메시지가 전달되기 전의 시점에서 메시지를 가로채어, ShowScrollBar() 함수를 호출하면, 스크롤 막대를 영구적으로 숨길 수 있으리라 생각되지 않습니까?

 

위의 예제 코드에서 처리할 수 있는 방법은 두 가지가 있습니다.

하나는, 아예 다이얼로그 프로시저(DialogProc)에서 ShowScrollBar() 함수를 계속 호출해주는 것입니다. 이런 방식은 너무 쉽습니다.

long __stdcall DialogProc(HWND hDlg, unsigned int message, unsigned int wParam, unsigned int lParam)

{

HWND hWndLV = GetDlgItem(hDlg, IDC_LIST1);

ShowScrollBar(hWndLV, SB_HORZ, FALSE);

……

}

또 다른 하나는 ListView 컨트롤의 메시지를 가로채는 것입니다. 이런 것이 해볼 만 한 것입니다.

HHOOK hhookSysMsg = NULL

 

long __stdcall DialogProc(HWND hDlg, unsigned int message, unsigned int wParam, unsigned int lParam)

{

HWND hWndLV = GetDlgItem(hDlg, IDC_LIST1);

 

switch (message)

{

case WM_INITDIALOG:

OnInitializeListView(hWndLV);

hhookSysMsg = SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc, NULL, GetCurrentThreadId());

return TRUE;

        case WM_DESTROY:

UnhookWindowsHookEx(hhookSysMsg);

PostQuitMessage(0);

break

……

}

SetWindowsHookEx 함수에 대한 이야기는 전에 했으니, 그냥 통과합니다.

LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)

{

        CHAR szClassName[_MAX_PATH] = {0};

        PCWPSTRUCT pcwstruct = (PCWPSTRUCT)lParam;

 

        GetClassName(pcwstruct->hwnd, szClassName, _countof(szClassName));

        if (!lstrcmpi(szClassName, “SysListView32”))

               ShowScrollBar(pcwstruct->hwnd, SB_HORZ, FALSE);

 

        return CallNextHookEx(hhookSysMsg, nCode, wParam, lParam);

}

 

설명에서 사용한 ScrollBar 관련 소스 파일은 다음의 주소에서 다운로드 받을 수 있습니다.

https://skydrive.live.com/embedicon.aspx/.Public/ScrollBarTest.7z?cid=1bbcdfedee1c617e&sc=documents

 

2009-03-16

.NET Control: ListBox 【Horizontal Scroll】

Filed under: Programming — Peter_KIM @ 04:07

.NET Control: ListBox Horizontal Scroll읽고 지나가기.

 

앞선 글의 내용은 Win32 기반의 ListBox 컨트롤의 기초적인 내용이었습니다. 이번에는 .NET Framework 환경에서 제공되는 ListBox 컨트롤을 살펴보겠습니다. 역시 기초적인 것입니다. 하지만, 서로 비교해보면 재미있는 점이 있습니다. 앞서서도 이야기했지만, .NET Framework 환경에서 제공되는 컨트롤은 Win32 컨트롤을 그저 사용하기 편리하게 한번 감싸놓은 것에 불과하다는 것입니다.

 

Windows Forms 응용 프로그램을 생성하고, Form 위에 ListBox 컨트롤을 하나 올려봅니다. 다음과 같이 코드를 추가하여 결과를 살펴보겠습니다.

private void Form1_Load(object sender, EventArgs e)

{

InitializeListBox();

}

 

private void InitializeListBox()

{

this.listBox1.HorizontalScrollbar = true;

 

this.listBox1.Items.Add("Adds the specified filename to a list box that contains a directory listing.");

this.listBox1.Items.Add("Deletes a string in a list box.");

this.listBox1.Items.Add("Finds the first string in a list box that begins with the specified string.");

……

}

코드를 빌드하여 실행하면, 아래와 같은 결과를 볼 수 있습니다.

 

앞선 C 프로그램에서는 가로 스크롤 막대를 표시하기 위하여, 복잡한 코드를 많이 사용했습니다. 그러나, C# 코드에서는 HorizontalScrollbar 속성에 true 값을 설정한 것 외에는 추가적으로 작업한 것이 없습니다.

ListBox 컨트롤이 진보한 것일까요? 이 의문을 풀기 위해서, .NET Framework 라이브러리 소스를 디버그 해 보겠습니다. this.listBox1.Items.Add 줄에 중단점을 설정하고, F11 키를 이용하여, 소스 안으로 들어가다 보면, ListBox.CS 파일을 만나게 됩니다.

public int Add(object item) {

owner.CheckNoDataSource();

int index = AddInternal(item);

owner.UpdateHorizontalExtent();

return index;

}

코드를 살펴보니, 앞선 C 코드에서 사용했던 함수와 이름이 비슷한 함수(UpdateHorizontalExtent)가 발견됩니다.

UpdateHorizontalExtent 함수를 살펴보겠습니다.

private void UpdateHorizontalExtent() {

if (!multiColumn && horizontalScrollbar && IsHandleCreated) {

int width = horizontalExtent;

if (width == 0) {

width = MaxItemWidth;

}

SendMessage(NativeMethods.LB_SETHORIZONTALEXTENT, width, 0);

}

}

C 코드에서 사용했던 것과 동일한 코드가 발견됩니다.

이번에는 소스코드에서 사용한 width, MaxItemWidth 값을 어떻게 설정했는지 알아보겠습니다.

private void UpdateMaxItemWidth(object item, bool removing) {

……

// Only update if we are currently caching maxWidth

if (maxWidth > -1) {

// Compute item width

int width;

using (Graphics graphics = CreateGraphicsInternal()) {

width = (int)(Math.Ceiling(graphics.MeasureString(GetItemText(item), this.Font).Width));

}

……

}

소스 코드를 보니, GDI+ 라이브러리에 정의된 함수와 같은 이름의 함수(MeasureString)가 사용되었습니다. C 코드에서 사용한 GetTextExtentPoint32 함수(GDI32)와 동일한 역할을 하는 함수입니다.

결국 Win32 메시지를 컨트롤에 전달하여, 수평 스크롤 막대를 표시하는 것을 알 수 있습니다.

 

만일 특정 환경에서 코드를 작성하고자 할 때, 다른 프레임워크에서 개발된 정형화된 코드를 살펴본다면, 상당히 훌륭한 기술 개념을 습득하는데, 매우 도움이 될 것임에 틀림이 없습니다.

 

2009-03-15

Win32 Control: ListBox 【Horizontal Scroll】

Filed under: Programming — Peter_KIM @ 15:05

GUI 프로그램에 “ListBox” 컨트롤을 사용하면서 부딪히는 문제중의 하나가 이 컨트롤에서 스크롤 막대를 표시하는 것입니다. 간단하게 다이얼로그 프로그램을 생성해 보겠습니다. ListBox 컨트롤을 여러 번 사용했던 사람들에게는 매우 기초적인 내용일 수 있습니다.

 

Win32 프로젝트를 생성하고, 프로젝트에 다이얼로그 리소스를 하나 추가합니다. 추가된 다이얼로그 위에 ListBox 컨트롤을 올립니다. 소스 코드를 살펴보겠습니다.

 

프로그램의 리소스 파일 (RC)

……

IDD_DIALOG1 DIALOGEX 0, 0, 250, 250

STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU

CAPTION "Dialog"

FONT 8, "MS Shell Dlg", 400, 0, 0x1

BEGIN

LISTBOX  IDC_LISTBOX,10,10,150,60, WS_VSCROLL | WS_HSCROLL | WS_TABSTOP

END

……

올려진 리스트박스 컨트롤의 스타일에 스크롤을 표시하기 위하여 WS_VSCROLL | WS_HSCROLL 값을 지정합니다.

 

프로그램의 C/CPP 파일

Int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,  char* lpszCmdLine, int nCmdShow)

{

DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), GetDesktopWindow(), (DLGPROC)DialogProc);

return 0;

}

 

long __stdcall DialogProc(HWND hDlg, unsigned int message, unsigned int wParam, unsigned int lParam)

{

switch (message) {

case WM_INITDIALOG:

OnInitializeListBox(hDlg);

return TRUE;

……

}

……

return FALSE;

}

 

void OnInitializeListBox(HWND hWndLB)

{

char message[] =

"Adds the specified filename to a list box that contains a directory listing.;"

"Gets the application-defined value associated with the specified list box item.";

const char seps[] = ";";

char *token = NULL;

token = strtok(message, seps);

while(token != NULL) {

SendMessage(hWndLB, (unsigned int)LB_ADDSTRING, 0, (LPARAM)token);

token = strtok(NULL, seps);

}

}

이렇게 작성된 코드를 빌드하여, 실행하면 다음과 같은 결과 화면을 볼 수 있습니다.

가로 스크롤 막대를 포함했음에도 불구하고, 스크롤 막대가 표시되지 않았습니다. ListBox 컨트롤을 자주 사용했던 분들은 대부분 이런 현상과 그 해결 방법을 알 수 있으리라 생각합니다.

 

한가지 분명한 것은 이런 현상도 잘 살펴보면, 해결 방법이 있습니다. 이미 완벽하게, 그러한 문제를 해결할 수 있도록, 여러 API 함수들이 존재합니다. 문제는, 이 함수를 어떻게 사용하는 것이냐는 것입니다. 어떤 API 함수들은 간단하게 사용할 수 있는 반면에, 어떤 함수들은 그 함수를 사용하기에 앞서서 복잡한 다른 함수들을 사용하여, 함수의 기능이 수행될 수 있도록 설정을 해두어야 합니다.

 

다음의 코드를 프로그램에 추가합니다.

void OnAdjustListBoxHScroll(HWND hWndLB)

{

int nTextLen = 0, nWidth = 0;

int nCount = 0, idx = 0;

HDC hDC = NULL;

HFONT hFont = NULL;

SIZE sz = {0};

char *pszText = NULL;

 

nCount = SendMessage(hWndLB, LB_GETCOUNT, 0, 0);

hFont = (HFONT)SendMessage(hWndLB, WM_GETFONT, 0, 0);

hDC = GetDC(hWndLB);

SelectObject(hDC, (HGDIOBJ)hFont);

 

for (idx = 0; idx < nCount; ++idx) {

nTextLen = SendMessage(hWndLB, LB_GETTEXTLEN, idx, 0);

pszText = (char*)malloc(nTextLen + 1);

*pszText = 0;

SendMessage(hWndLB, LB_GETTEXT, idx, (LPARAM)pszText);

GetTextExtentPoint32(hDC, pszText, nTextLen, &sz);

nWidth = max(sz.cx, nWidth);

free(pszText);

}

ReleaseDC(hWndLB, hDC);

SendMessage(hWndLB, LB_SETHORIZONTALEXTENT, nWidth, 0);

}

ListBox 컨트롤에 LB_SETHORIZONTALEXTENT 메시지를 전달하여, 가로 스크롤이 가능한 너비를 Pixel 단위로 지정해야 합니다. ListBox 컨트롤에 삽입되는 문자열의 너비를 Pixel 단위로 알아내야 합니다. 문자의 가로, 세로의 크기는 글꼴과 글꼴의 크기에 따라서 다르다는 것을 생각해야 합니다. 그러므로, GDI 함수를 이용하여, 문자열의 Pixel 단위 크기를 얻어야 합니다. GetTextExtentPoint32 함수를 이용하여, 문자열의 너비와 높이를 계산할 수 있습니다. 함수의 첫 번째 인자로 Device Context 핸들 값을 설정하는데, 이 인자에 설정된 글꼴에 따라서, 계산된 값은 달라질 수 있습니다. 그러므로, WM_GETFONT 메시지를 이용하여, 컨트롤의 글꼴을 획득하고, 구해진 글꼴을 SelectObject 함수를 이용하여, Device Context 핸들에 설정해야 합니다.

 

코드를 빌드하고, 실행하면, 가로 스크롤 막대가 표시되는 것을 볼 수 있습니다.

 

2009-03-10

.NET Control: SetWindowsHookEx

Filed under: Programming — Peter_KIM @ 04:33

.NET Control: SetWindowsHookEx

 

Control.CS 파일의 WndProc 함수 코드를 살펴보면, 이 클래스의 구현 코드가 Win32 API 수준의 구현 코드와 유사하게 함수들이 구현되어 있는 것을 알 수 있습니다. .NET Framework 기본 컨트롤역시 Native Win32 환경에서 제공되는 것과 같은 것이므로, 앞서서 살펴보았던 SetWindowsHookEx 함수 역시 .NET 프로그램에서 적용이 가능합니다. 이 함수를 이용하여, .NET 응용 프로그램에서 메시지를 가로채는 기법에 대한 자세한 설명과 예제는 다음의 문서에 잘 나와 있습니다.

http://support.microsoft.com/kb/318804/

한가지 주의할 점은 .NET 프레임워크에서는 Global-Hook 구현이 불가능하다는 것입니다.

 

다음의 코드에서, 지난 글(Win32 Control: “SetWindowsHookEx”)에서 C 코드로 처리했던 것과 마찬가지로 “#32770” 클래스의 인스턴트에 대하여, 형태를 변경하겠습니다. C# 코드에서 동일한 작업을 하기 위하여, 관련 클래스를 찾아보겠습니다. 유감스럽게도 .NET Framework 클래스 중에서, SetWindowsHookEx 함수와 관련되어 처리될 클래스가 제공되지 않습니다. 그러므로, Interop 서비스를 사용하여 관리되지 않는 코드를 호출할 것입니다.(P-Invoke)

그러므로, 대부분의 코드는 지난 C 코드와 동일하며, 프로그램의 처리 방식도 같습니다. .NET Framework 클래스에서 보다 향상된 코드를 제공하지 않는 한, 이런 방법은 매우 유용한 코드를 작성할 수 있는 유일한 방법입니다. 그러므로, 무엇인가 컴퓨터 코드를 이용하여, 하고자 한다면 가장 기본이 되는 코드를 먼저 파악하는 것이 매우 중요합니다.

 

코드를 살펴보겠습니다.

먼저, C 코드에서 사용되었던 전역 변수를 C# 클래스의 멤버 변수로 선언합니다.

C 

HHOOK hhookSysMsg;

const CHAR _afxOldWndProc[]= “AfxOldWndProc423”;

C#

public static IntPtr hhookSysMsg = IntPtr.Zero;

public static String sOldWndProc = “OldWndProcedure”;

이 코드에서 중요한 것은 사로 다른 언어 사이에서 발생하는 데이터 형식의 변환이 필요하다는 것입니다.

MSDN 문서를 찾아보면, 마샬링(Mashaling)에 대하여 많은 설명이 나옵니다. 프로그램을 조금이나마 해본 사람들이라면 알고 계시겠지만, 사실 이런 개념은 .NET Framework 등장 이전에도 항상 이슈화 되었던 것입니다. 가령 Native C/C++ 데이터형식과 데이터베이스 서버의 데이터 형식 사이에서의 형식의 변환, Script 언어와 COM(OLE) 코드 사이의 형식 변환들은 지금까지도 많은 주의를 요구합니다.(요즘 한가지 언어로만 개발하는 사람은 거의 없을 듯합니다.)

.NET 프레임워크에서 관리되지 않는 코드를 호출하기 위해서 반드시 알아야 하는 것이 C/C++ 데이터 형식을 .NET Framework 데이터 형식으로 변환하는 규칙입니다. 역시 자세한 설명은 MSDN 문서를 보고 숙지하시길 바랍니다.

 

SetWindowsHookEx 함수와, 연관되어 사용된 Windows API 함수를 호출하기 위하여, DllImport 특성을 이용하여, 함수를 선언합니다.

[DllImport(“user32.dll”, EntryPoint = “SetWindowsHookEx”, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

public static extern IntPtr SetWindowsHookEx(Int32 idHook, HookProc lpfn, IntPtr hMod, Int32 dwThreadId);

 

[DllImport(“user32.dll”, EntryPoint = “UnhookWindowsHookEx”, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

public static extern bool UnhookWindowsHookEx(IntPtr hhk);

 

[DllImport(“user32.dll”, EntryPoint = “CallNextHookEx”, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

public static extern IntPtr CallNextHookEx(IntPtr hhk, Int32 nCode, IntPtr wParam, IntPtr lParam);

올바른 선언을 위하여, 가급적 EntryPoint 요소에 Windows API 함수 이름을 기입하는 습관을 갖는 것이 좋습니다. 또, 언어에 관계없이 정수형 변수를 사용할 때에는 int 형식 보다는 Int32, Int16 형식으로 사용하는 것이 좋습니다. (언어가 이런 형식을 지원한다면……) int 형식은 메인 프로세서(CPU)의 레지스터와 동일한 크기를 표현하는 형식이며, 사용하는 운영 체제의 종류(32비트, 64비트)와 언어(컴파일러)에 따라서 다르게 표현될 수 있습니다.


 

C 

……

hhookSysMsg = SetWindowsHookEx(WH_CBT, CBTProc, NULL, GetCurrentThreadId());

……

LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)

{ …… }

C#

public delegate IntPtr HookProc(Int32 nCode, IntPtr wParam, IntPtr lParam);

……

CBTProcedure = new HookProc(Form1.CBTHookProc);

hhookSysMsg = SetWindowsHookEx(WH_CBT, CBTProcedure, IntPtr.Zero, GetCurrentThreadId());

……

public static IntPtr CBTHookProc(Int32 nCode, IntPtr wParam, IntPtr lParam)

{ …… }



C 코드를 C# 코드로 고쳐 작성하는 것이 정말 쉽지는 않습니다. C/C++ 코드에서 LRESULT CALLBACK 키워드로 처리한 부분을 C# 코드로 표현하는 것과, SetWindowsHookEx 함수의 두 번째 인자를 설정하는 것은 약간의 기술이 필요할 수도 있습니다. 한 함수의 주소를 다른 함수의 인자로 사용하는 것이 C# 언어에서 매우 어려울 수 있습니다.

이것을 해결하기 위하여, 두 가지 관점에서 접근해 보겠습니다. 첫 번째 방법은 컴파일러의 오류 메시지를 적극 활용해 보는 것입니다.

SetWindowsHookEx 함수와 HOOKPROC 형식의 Win32 선언은 다음과 같습니다.

HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId);

typedef LRESULT (CALLBACK* HOOKPROC)(int code, WPARAM wParam, LPARAM lParam);

C# 코드에서 SetWindowsHookEx 함수를 다음과 같이 선언하고, 호출 코드를 작성합니다.

[DllImport(“user32.dll”, EntryPoint = “SetWindowsHookEx”, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

public static extern IntPtr SetWindowsHookEx(Int32 idHook, IntPtr lpfn, IntPtr hMod, Int32 dwThreadId);

…… hhookSysMsg=SetWindowsHookEx(WH_CBT,(IntPtr)CBTHookProc,IntPtr.Zero,GetCurrentThreadId());

컴파일러는 다음과 같은 오류를 내보낼 것입니다.

오류 CS0428: ‘CBTHookProc’ 메서드 그룹을 비대리자 형식’System.IntPtr'(으)로 변환할 수 없습니다. 메서드를 호출하시겠습니까?

인자를 대리자로 전달하고, 처리되는 함수를 대리자가 호출하는 프로시저 형태로 선언하면 될듯합니다.

두 번째 방법은 윈도우의 전형적인 Event Handler 프로시저를 고려하는 것입니다. C 코드에서 사용한 CBTProc 함수는 WH_CBT 메시지(이벤트)에 대한 처리 프로시저라는 것을 기억해야 합니다. 그럼, .NET Framework 라이브러리에서 제공되는 이벤트 처리 함수의 선언과 동일하게 하면 될 것 같습니다. 이벤트 처리기(Event Handler)는 대리자를 통해 호출되는 프로시저(Method)입니다.

Windows Forms 응용프로그램에서, 각종 컨트롤의 이벤트와 관련되어 다음과 같은 코드를 볼 수 있습니다.

this.button1.Click += new System.EventHandler(this.button1_Click);

EventHandler 키워드를 이용하면 될듯합니다. 이 키워드에 대해 조금 더 자세히 살펴보기 위하여, 정의 부분을 탐색해보겠습니다.

public delegate void EventHandler(object sender, EventArgs e);

반환 값이 설정되어 있지 않은 delegate 함수였습니다. C 코드의 LRESULT 형식을 IntPtr 형식으로 변환하여 다음과 같이 이벤트 처리기 함수를 선언합니다.

public delegate IntPtr HookProc(Int32 nCode, IntPtr wParam, IntPtr lParam);

또, SetWindowsHookEx 함수의 두 번째 인자는 다음과 같이 수정합니다.

public static extern IntPtr SetWindowsHookEx(Int32 idHook, HookProc lpfn, ……);

*주의: SetWindowsHookEx 함수의 마지막 인자에 .NET 프레임워크에서 제공하는 AppDomain.GetCurrentThreadId() 함수를 사용하여 컴파일하면, 다음과 같은 경고 메시지를 표시합니다.

경고 CS0618: ‘System.AppDomain.GetCurrentThreadId()’은(는) 사용되지않습니다. ‘AppDomain.GetCurrentThreadId has been deprecated because it does not provide a stable Id when managed threads are running on fibers (aka lightweight threads). To get a stable identifier for a managed thread, use the ManagedThreadId property on Thread.  http://go.microsoft.com/fwlink/?linkid=14202&#8217;

이 메시지대로, ManagedThreadId 속성을 사용하면, 프로그램은 정상적인 동작을 하지 않게 됩니다. 함수가 반환하는 ID 값과 속성이 가진 ID 값이 다르기 때문입니다. 이것에 대하여 많은 사람들의 논란이 있는데, 정작 중요한 것은 경고 메시지의 지시대로 했다가는 프로그램은 동작하지 않는다는 것입니다. 그럼, 위의 경고를 회피하고, 처리하는 방법은 없을까요? 컴파일 지시어로 CS0618 경고를 무시하도록 할 수는 있습니다. 그러나, 근본적인 해결은 될 수 없습니다.

Win32 API 함수를 사용해 보겠습니다. 아래의 함수를 이용하여, 획득한 값은 정확히 ‘System.AppDomain.GetCurrentThreadId()’ 함수를 사용하여 얻은 값과 일치합니다.

[DllImport(“Kernel32.dll”, EntryPoint= “GetCurrentThreadId”, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

public static extern Int32 GetCurrentThreadId();

아래와 같이, 코드에서 사용할 데이터 형식과 기타 Win32 API 함수를 선언합니다.

private const Int32 HCBT_CREATEWND = 3;

private const Int32 WH_CBT = 5;

 

[StructLayout(LayoutKind.Sequential)]

public struct CBT_CREATEWND

{

public IntPtr lpcs; // CREATESTRUCT

public IntPtr hwndInsertAfter;

}

C# 언어로 구현된 WH_CBT 메시지 처리 프로시저의 구현 부분을 C 코드와 비교하여 보겠습니다.

LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)

{

LPCREATESTRUCT lpcs;

LPCBT_CREATEWND lpcbtcw;

HWND hNewhWnd;

LPCTSTR pszClassName;

CHAR szClassName[_MAX_PATH];

WNDPROC oldWndProc;

switch (nCode) {

case HCBT_CREATEWND:

lpcbtcw = (LPCBT_CREATEWND)lParam;

hNewhWnd = (HWND)wParam;

lpcs = lpcbtcw->lpcs;

if ((unsigned long)(lpcs->lpszClass) > 0xffff) {

pszClassName = lpcs->lpszClass;

} else {

*szClassName = 0x00;

//GlobalGetAtomName((ATOM)lpcs->lpszClass, szClassName, _countof(szClassName));

GetClassName(hNewhWnd, szClassName, _countof(szClassName));

pszClassName = szClassName;

}

if (!lstrcmpi(pszClassName, “#32770”) && !(lpcs->style & WS_CHILD)) {

lpcs->cx = 200;

lpcs->cy = 200;

oldWndProc = (WNDPROC)GetWindowLongPtr(hNewhWnd, GWLP_WNDPROC);

if (oldWndProc != NULL && GetProp(hNewhWnd, _afxOldWndProc) == NULL) {

SetProp(hNewhWnd, (LPCTSTR)_afxOldWndProc, (HANDLE)oldWndProc);

if ((WNDPROC)GetProp(hNewhWnd, _afxOldWndProc) == oldWndProc) {

GlobalAddAtom(_afxOldWndProc);

SetWindowLongPtr(hNewhWnd, GWLP_WNDPROC, (LONG)WndProc32770);

}

}

}

break;

}

return CallNextHookEx(hhookSysMsg, nCode, wParam, lParam);

}

public static IntPtr CBTHookProc(Int32 nCode, IntPtr wParam, IntPtr lParam)

{

CBT_CREATEWND cbtcw;

IntPtr hNewhWnd = IntPtr.Zero;

StringBuilder sbClassName = null;

IntPtr oldWndProc = IntPtr.Zero;

 

switch (nCode) {

case HCBT_CREATEWND:

cbtcw = (CBT_CREATEWND)Marshal.PtrToStructure(lParam, typeof(CBT_CREATEWND));

hNewhWnd = wParam;

sbClassName = new StringBuilder(MAX_PATH);

GetClassName(hNewhWnd, sbClassName, MAX_PATH);

if (String.Equals(sbClassName.ToString(), “#32770”)) {

oldWndProc = (IntPtr)GetWindowLong(hNewhWnd, GWLP_WNDPROC);

if((oldWndProc!=IntPtr.Zero)&&(GetProp(hNewhWnd, sOldWndProc)==IntPtr.Zero)) {

SetProp(hNewhWnd, sOldWndProc, oldWndProc);

if (GetProp(hNewhWnd, sOldWndProc) == oldWndProc) {

GlobalAddAtom(sOldWndProc);

SetWindowLongProc(hNewhWnd, GWLP_WNDPROC, WndProc32770);

}

}

}

break;

}

return CallNextHookEx(hhookSysMsg, nCode, wParam, lParam);

}

C 코드에서는 LPCBT_CREATEWND 포인터에서 멤버인 LPCREATESTRUCT 포인터에 접근하는 것이 가능했습니다.

C# 코드에서 이런 작업을 수행하기 위하여, CBTHookProc 함수의 lParam 인자를, Marshal.PtrToStructure 함수를 이용하여 CBT_CREATEWND 구조체로 변환합니다. C 코드처럼 접근하기 위하여, 구조체의 멤버인 lpcs 개체에 대하여 다음과 같은 코드를 작성합니다.

CREATESTRUCT cs = (CREATESTRUCT)Marshal.PtrToStructure(cbtcw.lpcs, typeof(CREATESTRUCT));

불행스럽게도 이 작업은 허용이 되지 않습니다. C 코드에서는 구조체(CREATESTRUCT)의 멤버인 클래스 이름(lpszClass)에 대한 접근이 불가능합니다. 아래의 문서에 이유에 대한 설명과 해결 방법이 있습니다. (제가 테스트한 결과 C# 코드에서는 아예 구조체로 변환되지 않습니다.)

http://support.microsoft.com/?id=106079

문서에서 사용 제시한 방법대로 GetClassName 함수를 사용합니다.

코드의 아래 부분에 보면, 윈도우에 프로시저를 할당하는 코드가 있습니다.

SetWindowLongProc, 이 함수의 Win32 원형은 SetWindowLong 입니다. 마지막 인자에 대리자를 설정하여, CS0428 오류를 극복합니다.

코드의 나머지 부분은 아래의 소스 코드를 받아보시어, 살펴 보시길 바랍니다.

https://skydrive.live.com/embedicon.aspx/.Public/TestHook.7z?cid=1bbcdfedee1c617e

코드를 빌드하여, 실행하면, 다음과 같은 화면을 볼 수 있습니다. (주의: 플랫폼 대상 – x86)

C 프로그램에서 수행했던 것과 동일한 결과를 얻을 수 있습니다.

 

 

2009-03-09

.NET Control: IMessageFilter

Filed under: Programming — Peter_KIM @ 16:04

.NET Control: IMessageFilter 읽고 지나가기

.NET 응용 프로그램에서 이벤트(메시지)를 가로채기 위하여, 우선적으로 고려해 볼만한 것은IMessageFilter 인터페이스입니다. 이 인터페이스를 잘 이용한다면, 응용 프로그램에서의 특정 상태에 대한 이벤트를 가로채어 별도의 프로시저에 할당(처리)하는 것이 가능합니다. 앞서 이야기 했지만, 이 인터페이스는 응용 프로그램 수준에서 대기 중인(게시된) Windows 메시지만 처리가 가능합니다. Windows 프로시저(Call-Back 함수)를 직접 호출하는 이벤트(메시지)는 처리하지 못하는 제한이 있습니다. 다음의 문서를 참고하십시오.

http://support.microsoft.com/kb/166212

 

그럼, 위의 문서를 참고하여, 코드를 작성해 보겠습니다. (C#)

C# Windows Forms 응용 프로그램을 생성하고, 생성된 폼(Form) 위에 버튼과 리스트박스 컨트롤을 올려 놓습니다.

public partial class Form1 : Form, IMessageFilter

{

…….

        private const Int32 WM_MOUSEFIRST = 0x0200;

        private const Int32 WM_MOUSELAST = 0x0209;

        private const Int32 WM_RBUTTONDOWN = 0x0204;

        private const Int32 WM_KEYFIRST = 0x0100;

        private const Int32 WM_KEYLAST = 0x0109;

 

        private void Form1_Load(object sender, EventArgs e) {

            button1.MouseDown += new MouseEventHandler(button1_MouseDown);

            button1.KeyPress += new KeyPressEventHandler(button1_KeyPress);

            Application.AddMessageFilter(this);

        }

 

        private void Form1_FormClosed(object sender, FormClosedEventArgs e) {

            Application.RemoveMessageFilter(this);

        }

 

        private void button1_MouseDown(object sender, MouseEventArgs e) {

            MessageBox.Show("MouseDown");

        }

 

        private void button1_KeyPress(object sender, KeyPressEventArgs e) {

            MessageBox.Show(String.Format("KeyPress {0}", e.KeyChar.ToString()));

        }

 

        public bool PreFilterMessage(ref Message m)

        {

            bool ret = false;

            String text = String.Empty;

            if (m.HWnd.Equals(this.button1.Handle)) {

                if (m.Msg > WM_MOUSEFIRST && m.Msg <= WM_MOUSELAST) {

                   text = String.Format("Blocked Mouse Messages:0x{0}", m.Msg.ToString("X4"));

                   this.listBox1.SelectedIndex = this.listBox1.Items.Add(text);

                   ret = (m.Msg == WM_RBUTTONDOWN) ? false : true;

                }

                else if (m.Msg >= WM_KEYFIRST && m.Msg <= WM_KEYLAST) {

                   text = String.Format("Passed Keyboard Messages:0x{0}",m.Msg.ToString("X4"));

                   this.listBox1.SelectedIndex = this.listBox1.Items.Add(text);

                   ret = false;

                }

            }

            return ret;

        }

…….

프로그램의 폼(Form1) 클래스가 IMessageFilter 인터페이스를 상속받게 합니다.

“WinUser.H” 파일을 참고하여, 프로그램에서 가로챌 마우스 이벤트와 키보드 이벤트의 상수 값을 정의합니다.

폼이 로드 이벤트에, 메시지 필터를 추가하기 위하여, Application.AddMessageFilter(this);  코드를 추가합니다. 

 

IMessageFilter 인터페이스를 상속받은 클래스에는, PreFilterMessage 함수를 명시적으로 구현해야 합니다. PreFilterMessage 함수에 마우스 이벤트와 키보드 이벤트를 가로채도록 코드를 추가합니다.

 

위의 코드를 컴파일, 링크하여 실행하여 보겠습니다.

위의 코드에서 마우스 오른쪽 버튼이 눌리는 경우 함수의 반환 값이 거짓(false)으로, 계속 이벤트를 사용하도록 설정되므로, 연속적으로 button1_MouseDown 이벤트 처리 함수가 실행되지만, 마우스 왼쪽 버튼이 눌리는 경우, 함수의 반환 값은 참(true)으로, 이벤트는 처리된 것으로 설정됩니다.

 

.NET Framework Control 클래스 상속받는 확장 클래스를 작성하는 경우, 위의 코드를 약간 응용한다면, 조금은 유용하게 사용자의 이벤트를 처리할 수 있습니다.

2009-03-04

.NET Control: WndProc, WmCreate (C#)

Filed under: Programming — Peter_KIM @ 08:37

 

.NET Control: WndProc, WmCreate (C#)

.NET Framework 상태에서 윈도우 메시지를 가로채는 것은 Win32 API 함수를 이용하는 것보다 더 쉬울 수도 있습니다.

하나의 방법은 System.Windows.Forms 네임스페이스에 있는 메시지 필터 인터페이스(IMessageFilter)를 사용하면 메시지가 컨트롤 또는 폼으로 처리(Dispatch)되기 전에 응용 프로그램에서 메시지를 가로챌 수 있습니다. 이 인터페이스를 상속받고, PreFilterMessage 함수를 구현하면 가능한데, 대부분의 마우스, 키보드 이벤트처럼 메시지 큐(Message Queue)에 전달되어 대기중인 이벤트에 대한 처리만 가능합니다. WM_CREATE, WM_ACTIVATE 이벤트처럼, Window API 함수로 직접 전달되는 함수에 대한 처리는 불가능합니다. (이 기법은 나중에 살펴보겠습니다.)

이에 대한 설명은 다음의 문서를 참고하시길 바랍니다. (MFC 라이브러리에 적용되는 내용이긴 하지만, .NET 프레임워크에서도 문서에서 설명하는 것과 마찬가지로 적용됩니다.)

http://support.microsoft.com/kb/166212

또 다른 방법은 WndProc 함수를 다시 정의(Override)하는 것입니다.

Visual Studio 환경 구성에서 .NET Framework Source 디버깅을 지원하도록 설정한 뒤에, Windows Forms 응용 프로그램을 만들고, 폼에 버튼(또는 다른 컨트롤)을 하나 올려보겠습니다. 그리고, 컨트롤을 생성하는 코드에 중단점을 설정하고 디버그로 프로젝트를 실행합니다. 중단점에 커서가 도달하면, “F11”키를 이용하여, 소스 코드의 내부로 무작정 들어갑니다. 그렇게 얼마 동안 하다 보면, “Control.CS” 파일이 나타나게 됩니다.

디버깅을 중지하고, “Control.CS” 파일을 살펴보겠습니다. 소스를 살펴보면, WndProc 수를 볼 수 있습니다. 이 함수에서 메시지를 처리하는 것을 보니, 전형적인 Win32 (C/C++) 코드와 비슷합니다. 단지, 언어적인 차이가 있을 뿐, 골격은 같다는 것을 알 수 있습니다. 메시지의 정의나 함수의 이름도 winuser.h 파일에 정의된 이름과 비슷합니다.

.NET Framework 기본 컨트롤 클래스는 Win32 기본 컨트롤의 속성과 이벤트 처리 함수를 감싸서(Wrapping), 만들어진 것임을 알 수 있습니다. 그러므로 .NET Framework 프로그램에서 사용된 컨트롤에 대하여, 대부분의 Win32 API 함수들의 적용이 가능합니다. 결국 모든 것은 Win32 체제로 돌아가게 되어 있는 것입니다.

 

다음의 코드에서는 버튼을 이용하여 기본 WndProc 함수를 다시 정의(Override)하여, Window 메시지를 처리해보겠습니다.

 

Windows Forms 응용 프로그램을 만들고, 클래스를 추가합니다.

추가한 클래스를 컨트롤 클래스로 만들기 위하여, 기본 컨트롤 클래스를 상속 받습니다. 추상 클래스가 아닌 어떤 클래스라도 좋습니다. (예를 들어 Control, Button, CheckBox, RadioButton……)

Win32 API 함수에서 사용되는 구조체와 함수를 정의합니다.

class MyControl : Button

    {

        private const Int32 WM_NCPAINT = 0x0085;

        private const Int32 WM_CREATE = 0x0001;

        private const Int32 WM_DRAWITEM = 0x002B;

        private const Int32 WM_REFLECT = 0x2000;

        private const Int32 WM_PAINT = 0x000F;

        private const Int32 WM_ERASEBKGND = 0x0014;

 

        [DllImport(“user32.dll”)]

        public extern static IntPtr BeginPaint(IntPtr hWnd, ref PAINTSTRUCT ps);

        [DllImport(“user32.dll”)]

        public extern static bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT ps);

        [DllImport(“user32.dll”)]

        public extern static IntPtr GetDC(IntPtr hWnd);

        [DllImport(“user32.dll”)]

        public extern static bool ReleaseDC(IntPtr hWnd, IntPtr hDC);

 

        public struct PAINTSTRUCT

        {

            private IntPtr hdc;

            public bool fErase;

            public Rectangle rcPaint;

            public bool fRestore;

            public bool fIncUpdate;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]

            public byte[] rgbReserved;

        }

        [StructLayout(LayoutKind.Sequential)]

        public struct RECT

        {

            public Int32 left;

            public Int32 top;

            public Int32 right;

            public Int32 bottom;

        }

        public struct DRAWITEMSTRUCT

        {

            public Int32 ctlType;

            public Int32 ctlID;

            public Int32 itemID;

            public Int32 itemAction;

            public Int32 itemState;

            public IntPtr hWndItem;

            public IntPtr hDC;

            public RECT rcItem;

            public IntPtr itemData;

        }

추가한 클래스에 다음과 같이 WndProc 함수를 다시 정의(Override)합니다.

protected override void WndProc(ref Message m)

{

switch (m.Msg)

    {

    case WM_CREATE:

……

    break;

    case (WM_DRAWITEM | WM_REFLECT):

        WmDrawItem(ref m);

break;

    default:

        base.WndProc(ref m);

        break;

}

}

코드가 완성되면, case WM_CREATE: 줄에 중단점을 설정하고, 디버깅을 시도해봅니다. WndProc 함수가 호출되고, WM_CREATE 메시지가 전달된 것을 확인할 수 있습니다. 정말, Win32 (C/C++) 프로그램과 다른 것이 없습니다.

 

이런 식으로, Window(Control) 개체에 전달되는 이벤트(메시지)에 대한 처리 함수를 만들어서 기본 메시지 처리 프로시저를 대체하거나, 확장한다면, 앞서서 보았던 MFC, C 프로그램과 마찬가지로 컨트롤의 섬세한 제어가 가능합니다.

 

여기에 사용된 코드는 다음의 주소에서 다운로드 할 수 있습니다.

https://skydrive.live.com/embedicon.aspx/.Public/SubclassBtn.7z?cid=1bbcdfedee1c617e

프로그램의 소스에서는 위에서 선언한 Window 메시지의 처리 프로시저를 작성했습니다. 각각의 메시지에 대한 기능은 역시 MSDN 문서를 참고하시길 바랍니다. 어렵지 않은 코드이므로 모든 주석은 생략했습니다.

 

어떤 프레임워크에서 응용 프로그램을 작성하더라도, 컨트롤에 대한 기본은 각각의 운영 체제에서 제공하는 각종 메시지 처리 프로시저와 그 컨트롤을 화면에 표현하기 위한 그래픽 처리 프로시저라고 생각합니다. 그 기본적인 코드는 역시 C/C++ 언어일 수 밖에 없습니다. 기본적으로 Windows 운영 체제를 작성한 언어 자체가 그런 언어이며, .NET Framework 역시 그 근본은 Win32이므로, 기본적인 것을 이해하는 것은 보다 향상된 프로그램을 작성하는데, 매우 도움이 됩니다.

 

Blog at WordPress.com.