CComboBox 컨트롤에 대하여 알아보자.

리소스 편집창에서 툴바에 있는 빨간색 아이콘.. 이 콤보 박스를 나타내는 아이콘이다.
마우스로 드래그 하거나, 클릭하여 놓으면 된다.

콤보박스는 2가지로 구성이 되어있는데..
첫번째는 먼저 현재 선택된 아이템을 보여주는 에디트부분과
데이터 항목을 쭈욱 열거해주는 리스트 부분으로 나누어진다.
이러한 속성을 먼저 꾸며 놓은 것이 위의 3가지 대표적인 타입니다.

Simple - 에디트와 리스트가 한번에 모두 펼쳐진 상태로 보여진다. 에디트는 수정 가능.
Dropdown - 리스트는 감추어진 상태이고, 에디트 우측의 버튼을 누르면 아래로 리스트가 쭈욱 늘어나는 스타일이다. 에디트는 수정 가능
Drop List - 기본 속성은 Dropdown과 같으나 에디트 영역을 사용자가 수정할 수 없다.

처음에 콤보를 접하여 열심히 데이터를 넣은 후 다이알로그에 올려놓고..
콤보를 딱 선택하였는데.. 아래 그림처럼 나왔었다.

도대체 내가 넣어논 데이터는 어디로 사라진걸까? -_-;;;
비주얼 스튜디오 2003부터는 달라졌지만, 6.0에서는 지금도 저렇다.. 패치도 않하나 --^;

저런 현상을 막아주기 위해서, 리소스 편집창에서 편집할 경우, 콤보 박스의 우측 버튼을 마우스로 눌러주면 위 그림처럼 주변의 트랙커가 활성화 된다. 마우스로 찍을 때마다 토글 되므로 그림처럼 활성화된 트랙커를 이용하여 크기를 변경할 수 있다.

콤보박스는 주로, 여러가지 선택 아이템중에 하나를 고를 경우에 주로 사용된다. 물론
내부 구조를 좀 손봐서 동시에 여러개를 선택할 수 있도록 첵크박스를 넣거나 하기도 하지만
기본적인 속성은 그렇다. (다나와사이트의 목록 선택창)

다음으로 콤보박스의 기본 속성에 대하여 살펴보자.
속성창에는 4개이 페이지가 존재하는데, General 및 Extended Styles는 거의 모든 컨트롤의
공통속성이기도 하고, 이전 강좌(에디트 기초01)에서 설명하였으므로 생략하도록 한다.

콤보박스 속성창중 Data 페이지.

데이터 페이지의 하얀색 에디트 영역은 콤보박스에 미리 필요한 데이터를 넣어 둘 수 있도록
제공되는 입력창이다. 간단하고 미리 픽스된 크기의 데이터들은 넣어두면 좋다.

주의1. 데이터 한줄을 넣고, ENTER를 치면 창이 닫겨 버리거나 포커스를 잃을 수 있다.
다음줄로 넘어가려면 Ctrl + ENTER로 넘겨주어야 한다.

주의2. 한글을 입력하고 있던 경우는 마우스커서나 키보드 에로우키를 이용하여 현재 줄의
마지막으로 이동해 주어야 Ctrl + ENTER 가 먹는다.

콤보박스의 속성중 Styles 페이지.

1. Type
기본 설명은 위에서 달았으므로, 참고할 MSDN 영문 자료를 넣었다.
CBS_DROPDOWN   Similar to CBS_SIMPLE, except that the list box is not displayed unless the user selects an icon next to the edit control.
CBS_DROPDOWNLIST   Similar to CBS_DROPDOWN, except that the edit control is replaced by a static-text item that displays the current selection in the list box.
CBS_SIMPLE   The list box is displayed at all times. The current selection in the list box is displayed in the edit control.

2. Owner draw
이 속성은 콤보박스를 다루는 기초과정에서는 사실상 사용하지 않는다.
상당한 노가다 작업이 필요하고 내부 로직을 알고 있어야하므로.. 응용편정도에서 다룰 것이다.

No - 현재의 기본 속성을 유지한다.
Fixed - 사용자가 콤보박스의 리스트 박스 영역을 임의로 정의하여 그릴 수있다. 모든
           아이템의 높이가 동일하게 적용된다.
Variable - 사용자가 콤보박스의 리스트 박스 영역을 임의로 정의하여 그릴 수있다. 모든
           아이템의 높이를 개별적으로 다르게 설정할 수 있다.

3. Has strings
Owner draw 속성을 No가 아닌 것으로 선택하여 데이터를 사용자가 정의하여 그릴때 적용되는
속성으로 문자열 데이터를 다룬다고 알려준다.

주의. Owner draw 로 데이터를 처리할 경우 위 속성을 정의하여 주지 않으면 GetText 멤버를
이용하여 문자열을 읽어올 수 없게된다.

4. Sort
내부에 존재하는 문자열 데이터를 자동으로 소팅해준다.

5. Vertical Scroll
데이터가 화면 출력범위를 넘을 경우 스크롤바를 생성해준다.

6. No Integal height
콤보박스가 생성될 때 아래로 늘어지는 리스트 박스의 크기를 보통 사용자가 정해놓는다.
이 경우, 아이템의 하나의 폭의 배수가 이 리스트 전체 크기와 맞지 않으면 시스템에서 크기를
조절하여 아이템에 맞도록 자동으로 조절한다.
만약 이 옵션을 켜 놓으면 크기가 맞지 않는 경우에.. 나머지 만큼의 부분을 보여주게 된다.

7. OEM Convert
ANSI Character Set 을 OEM Character Set 으로 변경한다. 기본적으로 Windows NT/2000/XP는
ANSI 캐릭터 셑의 Escape Character를 지원하지 않는다. 이러한 Windows 캐릭터 셑을 DOS
캐릭터 셑으로 변환하는 것을 ANSI to OEM conversion 이라고 부른다.

[캐릭터 셑에 대하여 자세히 알지 못하는 관계로 써본적이 없는 속성입니다. -_-...]
[위 내용을 이해하는데 도움이 되는 페이지.
http://www.bribes.org/perl/wANSIConsole.html ]

8. Auto HScroll
선택된 문자열이 에디트 영역에 출력될 경우 너비가 넘어가면 자동으로 스크롤 될 수 있도록
지원해준다. 이를 설정하지 않으면 전체 문자열을 볼 수 없다.

9. Disable no scroll
데이터들이 들어있는 리스트 박스에 데이터가 충분치 않아 스크롤바가 보여질 필요가 없더라도
이 옵션이 켜져 있으면 Disable 된 스크롤바가 보여진다.

10. Upper/Lower Case
입력된 문자열 중에 영문을 대문자/소문자로 자동 전환해 준다.

첵크와 라디오는 상당히 많이 사용되지만 그 직관적인 인터페이스 만큼 사용도 단순하다.
지금까지 사용해오면서도 특별하게 다뤄본 기억이 없을만큼... 흠..

먼가 응용할만한 자료가 없을까 검색 하다보니, 코드프로젝트에 첵크그룹박스라는 자료가 있었다.
이를 응용하여 간단한 참고자료를 만들어 보았다.

위 화면은 별로 볼건 없지만 다음과 같은 기능을 제공한다.
1. 첵크박스를 선택하면 해당 그루박스 내부의 컨트롤들이 자동으로 Enable/Disable토글된다.
2. 첵크박스 그룹은 해당 첵크박스의 동작에 영향을 받는다.
3. 라디오버튼은 여러 그룹중에 선택되어진 하나만 활성화 되고, 나머지는 자동으로 Disable된다.
    즉, 라디오의 특성처럼 하나의 그룹만 선택가능하다.

그림 처럼 첵크는 여러그룹이 선택가능하지만 라디오는 오직 한 그룹만 선택가능하다.

그럼 핵심 함수부를 살펴보자.
이 함수는 해당 버튼(첵크 or 라디오)과 그를 둘러싼 그룹박스 아이디, 그리고 부모 윈도우를
인자로 가지는 함수이다.
기능은 해당 버튼의 상태에 따라 그룹박스 내부 컨트롤을 Enable 혹은 Disable 시킨다.

void CheckGroup(INT CheckID, INT GroupID, CWnd* pWndParent)
{
    //
일단 버튼의 첵크 상태를 읽어온다.
    //
이게 첵크던 라디오던 상관없다. CButton을 상속한 컨트롤이기 때문이다.

    BOOL bCheck = pWndParent->IsDlgButtonChecked(CheckID);
   
    //
영역의 사각정보를 저장할 임시 변수들
    CRect rcGroup, rcChild;
   
    //
그룹박스의 영역 정보를 읽어온다.
    pWndParent->GetDlgItem(GroupID)->GetWindowRect(rcGroup);
   
    //
현재 다이알로그의 차일드 윈도우중 첫번째 것을 가져온다.
    CWnd* pWnd = pWndParent->GetWindow(GW_CHILD);
    while (pWnd)
    {
        //
해당 그룹과, 버튼을 제외한 차일드 윈도우.
        if(pWnd->GetDlgCtrlID() != CheckID && pWnd->GetDlgCtrlID() != GroupID)
        {
            //
차일드 윈도우의 영역 정보를 읽어온다.
            pWnd->GetWindowRect(rcChild);
   
            //
만약 차일드 윈도우가 그룹박스 내부에 존재하면, bCheck 상태에 따라
            //
인에이블 정보를 토글한다.

            if (rcChild.IntersectRect(rcGroup, rcChild))
                pWnd->EnableWindow(bCheck);
        }
        pWnd = pWnd->GetWindow(GW_HWNDNEXT);
    }
}

그리고, 아래 4개의 멤버 함수는 각각의 첵크 박스와 라디오 버튼을 눌렀을 때
인자를 어떻게 전달했는지를 보여준다.
void CSssDlg::OnCheckUser()
{
    CheckGroup(IDC_CHECK1, IDC_STATIC1, this);
}

void CSssDlg::OnCheckSub()
{
    CheckGroup(IDC_CHECK2, IDC_STATIC5, this);
}

void CSssDlg::OnRadioUser()
{
    CheckGroup(IDC_RADIO1, IDC_STATIC9, this);
    CheckGroup(IDC_RADIO2, IDC_STATIC13, this);
}

void CSssDlg::OnRadioSub()
{
    CheckGroup(IDC_RADIO1, IDC_STATIC9, this);
    CheckGroup(IDC_RADIO2, IDC_STATIC13, this);
}

화면 구성에 비하여 상당히 깔끔하고, 단촐한 코드로 구성되어 사용하기도 쉬울것이다.

처음에는 위에서 처럼 처리하는것이 아니라, Tab Order Group 속성을 이용하면
해당 그룹을 묶어서 처리할 수 있지 않을까 하는 생각을 가져보기도 했었는데..
아쉽게도 Group 속성에 대한 이해가 모자라 일반적인 꽁수로 해결을 보았다.

첵크와 라디오를 어떻게 응용해 볼 수 있을까?

59.zip
0.15MB



첵크 박스의 Tri State는 라디오에는 없는 단독 속성이라 따로 설명을 하였다.
저 속성은 옵션 다이알로그를 만들거나 복합 옵션들을 구현할 때 사용되는, 상당히 직관적인 속성이다.

다음은, 첵크와 라디오가 가지는 공통속성 중에서 특별하게 다루어야할 만한 것들 을 골라보았다.
머 그래봤자 남는거라고는 멀티라인, 아이콘, 이미지 정도이다.

그림의 아래쪽에 보이는 색상 변경 부분은 그동안 다루었던 에디트와 스태틱 컨트로에서 사용되었던 배경 투명화와 색상 변경하기 기법을 적용하였다.
매 컨트롤마다 써먹으면 식상(?)하니 한군데 몰아부처 이렇게도 사용할 수 있음을 예시한다.

1.
아이콘 로딩.
첵크 박스나 라디오 버튼에서 Styles 페이지에서 Icon 속성을 첵크한다.

//
첫번째 첵크박스의 포인터를 얻은 후 CButton으로 형변환한다.
CButton* pButton = (CButton*)GetDlgItem(IDC_CHECK1);
//
아이콘을 로딩한 후 SetIcon 메서드를 이용하여 아이콘을 넣어준다.
pButton->SetIcon(::LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME)));

2.
비트맵 로딩.
첵크 박스나 라디오 버튼에서 Styles 페이지에서 Bitmap 속성을 첵크한다.
//
첫번째 첵크박스의 포인터를 얻은 후 CButton으로 형변환한다.
CButton* pButton = (CButton*)GetDlgItem(IDC_CHECK1);
//
비트맵을 로딩한 후 SetBitmap 메서드를 이용하여 비트맵을 넣어준다.
pButton->SetBitmap(::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP3)));

3. Pushlike
첵크 박스나 라디오 버튼에서 Styles 페이지에서 Pushlike속성을 첵크한다.
이 속성은 별거 없어보이지만, 이걸 이용하면 버튼을 2가지 상태로 (눌린상태, 아닌상태)로 아주 쉽게 구분하여 하나의 버튼을 다양하게 이용할 수 있다.

만약 파워버튼이라고 가정하면..
1.
튀어 나와있을 때 - 캡션 파워오프
2.
눌려져 있을 때 - 캡션 파워온
.. 상태를 표현할 경우는 그냥 CButton보다 쉽게 접근하여 사용이 가능하다.

4. Multiline
첵크 박스나 라디오 버튼에서 Styles 페이지에서 Multiline 속성을 첵크한다.
표현 해주고자 하는 캡션이 길 때 사용한다.

5.
배경의 투명화와 색상 변경.
//
아래 코드는 이제 설명하지 쉽게 이해할 수 있을 것이다.
HBRUSH CSssDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
 
    if(pWnd->GetDlgCtrlID() == IDC_CHECK8 || pWnd->GetDlgCtrlID() == IDC_RADIO9)
    {
        pDC->SetTextColor(RGB(255, 0, 0));
        pDC->SetBkColor(RGB(0, 255, 0));
    }
    else if(pWnd->GetDlgCtrlID() == IDC_CHECK9 || pWnd->GetDlgCtrlID() == IDC_RADIO10)
    {
        pDC->SetTextColor(RGB(255, 0, 0));
        pDC->SetBkColor(RGB(0, 255, 0));
        return m_brh;
    }
    else if(pWnd->GetDlgCtrlID() == IDC_CHECK10 || pWnd->GetDlgCtrlID() == IDC_RADIO11)
    {
        pDC->SetTextColor(RGB(255, 0, 0));
        pDC->SetBkMode(TRANSPARENT);
        return (HBRUSH)GetStockObject(NULL_BRUSH);
    }
 
    // TODO: Return a different brush if the default is not desired
    return hbr;
}

76.zip
0.09MB

이전에 이어 첵크박스가 가지고 있던 속성중에 주요한 속성들을 살펴보겠다.
속성 페이지의 Styles 에서 기존에 못보던 Tri State라는  속성을 보았을 것이다.

샘플을 수행한 화면이다.
아래 상위의 두개 첵크 박스는 모두 Tri State 속성을 부여하였고, 나머지는 아니다.

 

일반적으로는 첵크상태(BST_CHECKED)와 막연한 상태???(BST_INDETERMINATE)
언첵크 상태(BST_UNCHECKED)로 구분되며...
이번 장에서는 BST_INDETERMINATE에 대하여 자세히 살펴본다.

우선 장을 진행하기에 앞서 살펴보아야할 것들에 대하여 나열해 보겠다.
ON_COMMAND_RANGE
IsDlgButtonChecked
EnableWindow
CheckDlgButton

중요한 내용들이며 각각 다음과 같은 기능을 제공한다. 특히나 ON_COMMAND_RANGE UI를 다룰 경우 상당히 유용한 매크로이므로 알아두면 써먹을 곳이 많다.

1. ON_COMMAND_RANGE
제시 : 만약에 버튼이 100개 있다면? OnButton1 ~ OnButton100 까지 100개를 다 만들것인가?
MFC
에서 제공해주는 아주 유용한 매크로로 여러개의 컨트롤 아이디를 하나의 함수로 매핑해주는
 
기능을 제공한다. 이 기능을 이용하면 연속적인 아이디를 가진 컨트롤을 몽땅 한 함수에서 처리할
 
수 있으므로 불필요한 함수의 증가를 막고, 코드 관리에 아주 유용하다.
, 기능에서 보아 짐작할 수 있겠지만.. 저 매크로는 자동화로 제공되지 않으므로 개발자가 직접
손수 타이핑해주어야 한다.

2. IsDlgButtonChecked
첵크 박스의 첵크 상태를 읽어내는 함수로 CWnd의 멤버 함수이다.

3. EnableWindow
CWnd
를 상속한 컨트롤의 활성화/비활성화를 처리해주는 멤버 함수이다.

4. CheckDlgButton
첵크 박스의 여러가지 첵크 상태를 임의로 변경해 줄 수 있는 CWnd의 멤버함수이다.

---------------------------------------------------------------------------------
다이알로그 헤더에 추가될 코드

 //
프로토콜 상위 첵크박스 상태.
 BOOL m_protocol;
 //
프로토콜 하위 첵크박스들의 상태.
 UINT m_protocol_sub;
 //
서포트 상위 첵크박스 상태
 BOOL m_support;
 //
서포트 하위 첵크박스들의 상태
 UINT m_support_sub;

 //
두개 그룹의 첵크 박스의 이벤트 핸들러를 묶어 놓은 Range 함수 2.
 // 리턴값은 LRESULT 이고, 인자는 UINT 타입의 인자 1개이다. 잘 기억해두자.
 afx_msg LRESULT OnCheckProtocolRange(UINT nID);
 afx_msg LRESULT OnCheckSupportRange(UINT nID);

---------------------------------------------------------------------------------
다이알로그 소스에 추가될 코드

// 변수 초기화
CSssDlg::CSssDlg(CWnd* pParent /*=NULL*/)
: 
CDialog(CSssDlg::IDD, pParent)
{
    
//{{AFX_DATA_INIT(CSssDlg)
    
//}}AFX_DATA_INIT
    
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
    
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

    
m_protocol = FALSE;
    
m_support = FALSE;
    
m_protocol_sub = 0;
    
m_support_sub = 0;
}

// 메시지 등록
BEGIN_MESSAGE_MAP(CSssDlg, CDialog)
    
//{{AFX_MSG_MAP(CSssDlg)
    
//}}AFX_MSG_MAP

    
// 메시지 맵에는 아래와 같은 용법으로 등록한다.
    
// 첫번째는 시작되는 컨트롤 아이디,
    
// 두번째는 마지막 컨트롤 아이디.
    
// 세번째는 멤버함수 이름
    
ON_COMMAND_RANGE(IDC_CHECK1, IDC_CHECK3, OnCheckProtocolRange)
    
ON_COMMAND_RANGE(IDC_CHECK4, IDC_CHECK7, OnCheckSupportRange)
END_MESSAGE_MAP()


// 프로토콜 그룹의 첵크박스 핸들러
//  메시지 맵에 처럼 등록해 놓으면 IDC_CHECK1, IDC_CHDEK2, IDC_CHECK3 첵크시
// 모든 이벤트가 아래 함수로 온다물론 어떤 컨트롤을 건드렸는지는 nID  넘어온다.
LRESULT CSssDlg::OnCheckProtocolRange(UINT nID)
{
    
// 어떤 놈을 건드렸나?
    
switch(nID)
    {
    
// 나는 상위 프로토콜 그룹 첵크 박스
    
case IDC_CHECK1:
        {
            
// IDC_CHECK1  첵크되어있나?
            
//  코드는 Tri State 가진 첵크 박스는 첵크를 하면 3가지 상태를 모두 토글하므로
            
// IDC_CHECK1 사용자가 직접 클릭했늘 경우는 CHECK/UNCHECK상태만을
            
// 가지도록 처리해주는 루틴이다.
            
BOOL bChecked = IsDlgButtonChecked(IDC_CHECK1);
            
if(m_protocol == FALSE && bChecked)
                
CheckDlgButton(IDC_CHECK1, BST_CHECKED),
                
m_protocol = TRUE;
            
else if(m_protocol == TRUE && bChecked)
                
CheckDlgButton(IDC_CHECK1, BST_UNCHECKED),
                
m_protocol = FALSE;

            
// IDC_CHECK1  첵크되어 있다면 하부 첵크박스를 모두 활성화 시킨다.
            
// 아니라면 모두 비활성화 시키지...
            
GetDlgItem(IDC_CHECK2)->EnableWindow(m_protocol);
            
GetDlgItem(IDC_CHECK3)->EnableWindow(m_protocol);
            
return 0;
        }
    
// IDC_CHECK1 제외한 모든 첵크박스
    
default:
        {
            
// 현재 첵크박스가 몇번째 것인지 구한다.
            
int n = nID - IDC_CHECK2;
            
// 현재 첵크박스의 상태를 구한다.
            
BOOL bChecked = IsDlgButtonChecked(nID);

            
// 첵크 상태에 따라 m_protocol_sub  설정한다.
            
if(bChecked)
                
m_protocol_sub |= 0x01 << n;
            
else
                
m_protocol_sub &= ~(0x01 << n);

            
// 상태에 따라 IDC_CHECK1 첵크 상태를 변경시킨다.
            
if(m_protocol_sub)
                
CheckDlgButton(IDC_CHECK1, BST_INDETERMINATE);
            
else
                
CheckDlgButton(IDC_CHECK1, BST_CHECKED);
        }
    }

    
return 0;
}

// 서포트 그룹의 첵크박스 핸들러
// 코드의 내부 기능  동작은 OnCheckProtocolRange  동일하다.
// 단지 구분하기 위하여 따로 놓았을 ...
LRESULT CSssDlg::OnCheckSupportRange(UINT nID)
{
    
switch(nID)
    {
    
case IDC_CHECK4:
        {
            
BOOL bChecked = IsDlgButtonChecked(IDC_CHECK4);
            
if(m_support == FALSE && bChecked)
                
CheckDlgButton(IDC_CHECK4, BST_CHECKED),
                
m_support = TRUE;
            
else if(m_support == TRUE && bChecked)
                
CheckDlgButton(IDC_CHECK4, BST_UNCHECKED),
                
m_support = FALSE;

            
GetDlgItem(IDC_CHECK5)->EnableWindow(m_support);
            
GetDlgItem(IDC_CHECK6)->EnableWindow(m_support);
            
GetDlgItem(IDC_CHECK7)->EnableWindow(m_support);
            
return 0;
        }
    
default:
        {
            
int n = nID - IDC_CHECK5;
            
BOOL bChecked = IsDlgButtonChecked(nID);
            
if(bChecked)
                
m_support_sub |= 0x01 << n;
            
else
                
m_support_sub &= ~(0x01 << n);

            
if(m_support_sub)
                
CheckDlgButton(IDC_CHECK4, BST_INDETERMINATE);
            
else
                
CheckDlgButton(IDC_CHECK4, BST_CHECKED);
        }
    }

    
return 0;
}

 ------------------------------------------------------------------------------------
모두 7개의 첵크 박스를 하나씩 다 제어하려면 함수도 많아지고, 이만 저만 귀찬은 것이 아닐겁니다.
복잡한 UI는 수집개의 버튼과 수십개의 첵크박스, 에디트, 라디오, 에디트.. 를 동반하는데
일일이 하나씩 모두 할 필요는 없겠죠? ^^;

구지 함수를 2개가 아닌 1개로 해도 되지만, 이를 나눈 이유는 코드 수를 줄이는 것도 중요하지만
서로 다른 그룹임을 구분해 주는 것이 나중에 코드를 수정하거나 보완할때 수월하기 때문입니다.

57.zip
0.09MB



첵크 박스와 라디오 버튼은 CButton 에서 기능이 특화된 두 부류이다.
이전 강좌에서 CStatic를 Text와 Picture로 나누어 특화시킨것과 마찬가지이고...
물론 CButton 컨트롤을 동적생성해서 위 두 속성을 주어 변경하는 것도 당연히 가능하다..

위 그림은 첵크 박스의 사용 목적을 일목 요연하게 보여줄 수 있는 것이라 생각하고 캡처해 보았다.
그림에서 처럼 다양한 어떤 기능이나 옵션을 제공할 때 원하는 것을 하나 혹은 여러개를 선택할 수 있도록 사용자 인터페이스를 제공해 주는 목적을 가진다.
( 물론 위 그림처럼 트리와 첵크박스를 저렇게 이뿌게 조합할려면 노가다가 필수다 -_-;;;)
( 필자 생각으로는 UI는 노가다의 산물이 아닐까 라고 가끔 생각해 본다.. ㅎㅎ)

위의 그림은 곰플레이어의 옵션 다이알로그의 한 페이지를 캡처한 화면이다.
[자동으로 계속하기 설정]이라고 써있는 부분을 보면 3가지 기능이 나열되어 있고, 그 중에 하나를
선택할 수 있도록 되어있다.
위 처럼 하나의 주요한 기능를 여러가지 관점에서 바라 볼 수 있고, 사용자가 그 중에 하나를 선택하여 동작하도록 제공해주는 인터페이스가 라디오 버튼이다.

비슷하면서도 큰 차이점이..

첵크박스는 여러가지 옵션을 다양하게 선택할 수 있도록 해주는 반면
라디오 버튼은 하나의 관점에서 특화된 옵션을 다양한 각도로 선택할 수 있도록 제공해준다.

이제부터 리소스 편집창을 살펴보자.

왼쪽의 빨강 똥그라미는 첵크박스, 오른쪽의 파랑 똥그라미는 라디오 버튼이다.

이제 부터 각 컨트롤의 속성 페이지를 살펴보자.

우선 첵크박스의 프로퍼티중 스타일을 살펴본다. 나머지 탭들은 기존의 설명과 중복되어 생략한다.

1. Auto - 사용자가 마우스로 첵크박스의 사각형 부분을 선택하면 상태가 자동으로 토글된다.
  이 옵션을 꺼놓으면 사용자가 직접 상태를 모두 변경시켜주어야한다.
2. Left Text - 글자가 왼쪽으로 가고 첵크박스가 오른쪽으로 간다.
3. Tri State - 첵크/언첵크 외에 첵크된 상태에서 배경이 회색으로 바뀐다. 즉 3가지 상태를 가진다.

4. Push Like - 일반적인 푸쉬버튼과 같은 형태를 가진다.

5. Multiline - 글자를 다중 라인으로 작성할 수 있다.

6. Notify - 상태 변경등의 정보를 부모윈도우로 전달해준다.
7. Flat - 플랫한 모양으로 바뀐다.

8. Icon - 글자대신 아이콘을 넣을 수 있다.

9. Bitmap - 글자대신 비트맵을 넣을 수 있다.

10. Horizontal alignment - 수평 글자 정렬
11. Vertical alignment - 수직 글자 정렬

라디오 버튼의 속성을 살펴보자.

첵크박스의 속성과 대부분 같고, Tri State만 없는 것을 볼 수 있을 것이다.
모든 속성의 설명은 첵크박스와 같다..
당연한가? 모두 버튼이니까..
그렇다면? ...
...
그렇다 리소스상의 버튼의 속성중에 위와 중복되는 속성은 모두 동일하게 사용할 수 있다.

여기까지는 그냥 기본 속성의 설명이었을 뿐이고, 라디오 버튼은 General 페이지에 있는
Group라는 옵션에 지대한 영향을 받는다...

지금까지 다룬 다른 컨트롤들은 저 속성을 거의 그냥 무시하고 지나갔다.

[사실 필자도 다른 컨트롤에서 저 그룹 속성이 어떤 동작 특성을 가지는지는 잘 모른다 -_-]

지금까지 이해해온 대로라면 라디오 버튼은 비슷한 기능을 가진 옵션들을 모아놓고 그중에 하나를 선택하는 기능을 제공해준다고 하였다.

또한 Ctrl+D를 눌러 라디오 버튼을 순서대로 정렬해 놓으면 실행시킨 후에 선택할 수 있는 라이오 버튼 항목은 단 1개이다.

그렇다면 하나의 다이알로그 폼에 그런 선택속성을 가진 그룹이 여러개 존재한다면???

위의 빨간 글중에 파랑색으로 표기한 그룹이라는 단어가 눈에 띄일것이다....

그렇다, 저걸 첵크 해놓으면 저걸 첵크해놓은 라디오 버튼 중에 탭 오더가 낮은 것들은 자동으로 하나의 선택그룹으로 묶이게 된다.

여러개의 라디오 버튼을 여러개의 그룹으로 묶으려면? 중간에 특성이 갈라지는 놈의 속성 페이지를 열어서 저걸 첵크해놓으면 된다..

이렇게 첵크박스와 라디오 버튼의 속성을 간단하게 살펴보았다.
다음에는 우선 저 속성을 설명하면서 나타난 화면을 직접 구현해 보는 시간을 가져보고자 한다..

Picture 컨트롤에 이미지를 넣는 것은 쉬운일이다.
컨트롤 자체가 그러하도록 설계되었기 때문이지만, 이 또한 글자를 제대로 표현할 수 없다.
그렇다면 이미지에 글자를 그려 넣을 것인가?

기존에 Text 의 배경을 투명하게 하고, 색상을 넣을 수 있다면 이미지도 넣을 수 있지 않을까?
답은 그렇다이다.

MFC에서 제공해주는 많은 객체중에 CBrush는 상당히 잘 꾸며진 객체이다.
그중에 관심을 가져볼만한것이 Hatch와 Partten 기능이다.
해치는 제공해준 패턴만을 그려주지만, 패턴기능은 사용자가 비트맵을 제공함으로써
배경을 원하는 형태로 그릴 수 있도록 해준다.

아래는 비주얼 스튜디오에서 리소스 편집창의 일부를 캡쳐한 화면이다.

저 배경에 아래의 그림을 깔아보자 ^^;

어디서 많이본 그림인데??
윈도우즈에서 기본으로 제공해주는 커피잔.bmp이다 저걸 컨트롤의 배경으로 사용할 것이다.

아래의 그림은 그걸 구현해 놓은 실행화면이다.

위의 그림은 Text 컨트롤(스태틱에서 문자열만을 제공하도록 특화됨)의 배경에 비트맵을 패턴으로
깔은 것이다.
하나의 그림을 전체 배경으로 넣으려면 그림 크기를 컨트롤 크기와 동일하게 맞추면 된다.

지금까지 스태틱 컨트롤 강좌를 살펴보면 저 기능을 구현하는 것은 아주 쉽다.
1. CBitmap 객체를 선언한 후 커피잔.bmp를 로딩한다.
2. CBrush 객체를 선언한 후 패턴브러쉬로 생성하고 저 비트맵을 설정해준다.
3. 기존의 강좌에서 나온 배경에 색칠하기 기능을 이용하여 그 브러쉬를 리턴해준다.
끝~

BOOL CSssDlg::OnInitDialog() 
{
    CDialog::OnInitDialog();
    
    bm.LoadBitmap(IDB_BITMAP3);
    brh.CreatePatternBrush(&bm);
    return TRUE;  // return TRUE unless you set the focus to a control
    // EXCEPTION: OCX Property Pages should return FALSE
}

HBRUSH CSssDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    
    switch(nCtlColor)
    {
    case CTLCOLOR_STATIC:
        {
            pDC->SetTextColor(RGB(255, 255, 0));
            pDC->SetBkMode(TRANSPARENT);
            return brh;
        }
    }
    return hbr;
}

저 형태로 모든 코드가 마무리되었다.
지금까지 스태틱 컨트롤에 대한 기본적인 내용을 다루었으며, 저러한 기능을 모아
클래스로 생성해 둔다면 나중에 편하게 사용할 수 있을 것이다.
이것이 바로 C로는 가져보기 힘든, C++만의 장점이다.

리소스 편집창에는
그룹박스,  버튼이 다음 순서로 나오지만..
MFC를 다루는 대부분의 서적에서 다루는 내용만으로 사용에 전혀 무리가 없을 것이다.

그룹박스 투명화는 지금까지의 방법을 사용하면 테두리 사각형에 어색하게 찍힌 글자가 나오고
버튼 자체는 상속받아 이러저러한 기능을 구현하지 않으면 부모윈도우에서 처리해줄 일이 거의 없다.

다음으로는 버튼의 기능중에 특화된 첵크박스와 라디오 버튼을 함께 다루어 보겠습니다.
첵크 박스와 라디오 버튼도 CButton의 일부이지만 특화된 만큼 따로 다룰 것입니다.
즐거운 주말 되세요. ^^;

65.zip
0.09MB

에디트에 대한 기초강좌를 마무리한 시점에서 질문이 하나 들어왔다..
지금까지의 내용을 기초로 에디트의 전체 배경을 바꾸었는데.. 깜빡이거나 먼가 이상하다...??

지난 강좌를 찾아보니, 에디트에 대한 배경 처리가 제대로 마무리 되지 못한 상태에서
강좌를 마쳐 혼란을 가져온것일 수 있을꺼 같아..
질문을 토대로 배경처리 기능을 마무리 하려고 한다.

위 그림은 두가지로 그 기능이 분류된다.

1. 왼쪽 에디트
-> 글자 바탕과 배경색상을 모두 녹색으로 처리한 것
2. 오른쪽 에디트
-> 글자 바탕은 TRANSPARENT 시키고, 바탕에는 HATCH 브러쉬를 뿌린것이다.

위 두가지 구현에는 다음과 같은 차이가 있다.
1번은 글자와 배경을 모두 그려주므로, 글씨를 쓸때 잔상이 남거나 깜빡임이 전혀없다.
2번은 글자는 쓰되 배경을 그려주지 않으므로 글씨를 쓸때 깜빡임은 없으나 잔상이 남는다.
이러한 이유로 두가지의 구현 부분에 조금 차이를 가지게 된다.

기본적인 코드는 브러쉬를 2개 만든다는 점만 빼고는 헤더에 추가할 내용은 없다.
BOOL CSssDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // 솔리드, 해치 브러쉬를 각각만든다.
    m_brush1.CreateSolidBrush(RGB(0, 255, 0));
    m_brush2.CreateHatchBrush(HS_DIAGCROSS, RGB(0, 255, 0));

    return TRUE;
}

// 클래스 위저드에서 WM_CTLCOLOR 이벤트 핸들러를 추가한다.
HBRUSH CSssDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

    switch (nCtlColor)
    {
    case CTLCOLOR_EDIT:
        pDC->SetTextColor(RGB(255, 0, 0));
        if (pWnd->GetDlgCtrlID() == IDC_EDIT1)
        {
            // 글자 바탕을 녹색으로 하고, 녹색 솔리드 브러쉬를 리턴한다.
            // 이게 전부다.
            pDC->SetBkColor(RGB(0, 255, 0));
            return m_brush1;
        }
        else if (pWnd->GetDlgCtrlID() == IDC_EDIT2)
        {
            // 글자의 배경을 그려주게 되면 바탕의 해치브러쉬 영역에 글자뒷 부분은
            // 녹색 사각형이 그려지게 된다.
            // 이를 막아주기 위하여, 배경그리는 기능을 제거한다. TRANSPARENT
            pDC->SetBkMode(TRANSPARENT);

            // 에디트 컨트롤 영역을 구한 후에
            // 브러쉬로 색칠해준다.
            CRect rc;
            GetDlgItem(IDC_EDIT2)->GetWindowRect(rc);
            ScreenToClient(rc);
            pDC->FillRect(rc, &m_brush2);

            return m_brush2;
        }
    }
    return hbr;
}

클래스위저드에서 IDC_EDIT2의 EN_CHANGE 이벤트 핸들러를 추가한다.
이 핸들러를 추가하는 이유는 두번째 에디트는 글자의 배경이 뒤에 그려지는 해치 브러쉬의
패턴을 덥어쓰는 걸 막아주기 위하여 TRANSPARENT 속성을 주었으므로, 글씨의 뒷부분을
그려주지 않게된다. 그러므로 잔상이 남게되어 글자에 대한 변경이 발생하면
배경을 다시 그려주도록 하기 위해서이다.
void CSssDlg::OnChangeEdit2()
{
    CRect rc;
    GetDlgItem(IDC_EDIT2)->GetWindowRect(rc);
    ScreenToClient(rc);
    InvalidateRect(rc);
}

49.zip
0.06MB

스태틱 컨트롤을 이미지 형태로 사용할 경우 보통은 리소스에 있는 이미지를 연결시킨다.


왼쪽은 리소스 편집기에서 정적으로 이미지를 연결시킨 픽처 컨트롤이고,
오른쪽은 속성을 비트맵으로 설정해 놓고, 아무런 작업도 진행하지 않은 경우이다.

그렇다면 파일에서 비트맵을 로딩하여 필요할 때마다 바꾸려면 어떤 과정을 거쳐야할까?

아래는 디스크에 있는 파일을 읽어 들여서 오른족에 보이는 컨트롤에 동적 로딩한 화면이다.

복잡한 과정은 전혀 필요없다.

BOOL CSssDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
   
    //
파일에서 이미지를 비트맵 타입으로 읽어온다.
    //
현재는 커렌트 경로에 존재하므로 전체 경로를 넣지 않았지만.
    //
실행파일과 다른 경로에 존재한다면 절대 경로를 넣어야한다.
    //
전달되는 인자값은 MSDN에 자세히 설명이 나와있다.
    HANDLE h = ::LoadImage(AfxGetInstanceHandle(), "image_file.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

    //
화면의 픽처 컨트롤을 임시로 CStatic 컨트롤로 받아들인다.
    CStatic* pStatic = (CStatic*)GetDlgItem(IDC_STATIC_FILE);

    //
이미지 핸들과 스태틱 컨트롤이 제대로 값을 가지고 있다면
    if(h && pStatic) {
        //
컨트롤에 비트맵을 연결시킨다.
       pStatic->SetBitmap((HBITMAP)h);
    }
  
    return TRUE; // return TRUE unless you set the focus to a control
    // EXCEPTION: OCX Property Pages should return FALSE
}


아이콘일 경우도 위의 과정에서 필요한 형태만 변경한다면 아주 쉬울것이다.

참고. 만약 이미 비트맵이 로딩되어 있다면.. GetBitmap()으로 비트맵 핸들을 받은 후에
그 핸들을 CloseHandle()을 이용하여 해제해주고, 새로이 SetBitmap()을 해주어야 한다.

이렇게 생성하거나 할당한 리소스를 필요할 때 해제해 주지 않으면 나중에 [리소스가 부족합니다.]
라는 엉뚱한 메시지를 볼 수 있을것이다.

58.zip
0.05MB




아래 그림을 보면 배경을 다양한 형태로 처리할 수 있음을 알수 있다.
사실상 저렇게 쓰는 경우는 별반 없지만... UI 작업을 하다보면
어떤 정신 세계가 독특한 커스터머나 디자이너를 만날지 모른다..

화면을 보면 무엇을 설명하고자 하는지 쉽게 이해할 수 있을 것이다.

스태틱 컨트롤의 배경 전체 색상을 바꾸어 주기위해서는 저 영역을 먼가로 칠해주어야한다.
MFC
에서 먼가 영역을 칠할 때 필요한 객체는 CGdiObject를 상속받은 놈 중에 CBrush 라고 하는
일종의 빗자루? ? 비끄무리한 객체가 있다.

우선 헤더에 CBrush m_brush; 라고 객체를 하나 선언한다.
BOOL CSssDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
   
    //
브러쉬 객체를 녹색으로 생성한다.
    m_brush.CreateSolidBrush(RGB(0, 255, 0));

 

    return TRUE; // return TRUE unless you set the focus to a control
    // EXCEPTION: OCX Property Pages should return FALSE
}

그리고, 지금까지와 마찬가지로.. WM_CTLCOLOR 이벤트 핸들러를 추가한후
다음과 같이 코드를 수정한다.

HBRUSH CSssDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
   
    switch(nCtlColor)
    {
    case CTLCOLOR_STATIC:
        {
            pDC->SetTextColor(RGB(255, 0, 0));
           
            if(pWnd->GetDlgCtrlID() == IDC_STATIC_1)
                pDC->SetBkColor(RGB(0, 255, 0));
            else if(pWnd->GetDlgCtrlID() == IDC_STATIC_3)
                //
? 그냥 브러쉬를 리턴하면 끝이냐?
                //
그렇다 그냥은 디폴트 브러쉬가 날라가게 되는데..
                //
여기에 먼가 특별한 브러쉬를 만들어 리턴하면 그게 그 컨트롤의
                //
배경에 영향을 미치게 된다.
                //
다양한 브러쉬를 만들어 리턴해보자 ^^;
                return m_brush;
            else if(pWnd->GetDlgCtrlID() == IDC_STATIC_2)
            {
                pDC->SetBkColor(RGB(0, 255, 0));
                return m_brush;
            }
        }
    }
    // TODO: Return a different brush if the default is not desired
    return hbr;
   
}

56.zip
0.04MB



---------------------------------------------------------------------------------
이 부분을 진행하다 보니, 만약 패턴 브러쉬를 넣었을 경우는 어떻게 될까? 라는
생각이 들어서 별도로 진행해 보았다.

이 그림은 위의 소스 일부를 수정하여, 변경해 본 것으로.. Hatch 브러쉬를 이용하여
배경을 칠해본것이다. 자? 두번째를 어떻게 처리할 수 있을까?

BOOL CSssDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
    m_brush.CreateHatchBrush(HS_DIAGCROSS, RGB(0, 255, 0));
  
    return TRUE;  // return TRUE unless you set the focus to a control
    // EXCEPTION: OCX Property Pages should return FALSE
}


HBRUSH CSssDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
   
    switch(nCtlColor)
    {
    case CTLCOLOR_STATIC:
        {
            pDC->SetTextColor(RGB(255, 0, 0));
           
            if(pWnd->GetDlgCtrlID() == IDC_STATIC_1)
                pDC->SetBkColor(RGB(0, 255, 0));
            else if(pWnd->GetDlgCtrlID() == IDC_STATIC_3)
                return m_brush;
            else if(pWnd->GetDlgCtrlID() == IDC_STATIC_2)
            {

                // ???
                //
한번 직접 구현해 보자.
                //
결과물은 아래의 첨부파일에 포함되어 있다.           
            }
        }
    }

    // TODO: Return a different brush if the default is not desired
    return hbr;
}

48.zip
0.04MB



본 예제에서도 이전처럼 새로운 클래스를 추가하거나 하는 작업들은 하지 않고, 모든 작업을 다이알로그에서 처리 가능하도록 하고자 한다.


위의 그림이 샘플 예제이며,
내부에 사용된 기능은 다음과 같다.

1. 폰트를 만들어 언더라인을 긋는다.
2. 하이퍼 링크 컨트롤 위에 마우스가 가면 손가락 모양으로 바뀐다.
3. 클릭하면 링크된 웹사이트가 뜬다.
4. 클릭이 끝나면 한번 클릭된 것으로 인식하고, 글자 색상을 바꾼다.

구현되는 기능은 일반적인 하이퍼 링크 컨트롤의 기능을 100% 지원한다.
단, 툴팁도 추가할 수 있지만.. 현재 강좌 범위를 넘어가므로 나중을 기약하며 생략한다.

준비해야할 것과 추가해야할 코딩..
1. 먼저 폼에 스태틱 컨트롤 중에 Text 컨트롤을 하나 올리고, IDC_STATIC_1 로 설정한다.
2. 컨트롤의 속성중에 Styles 탭에서 Notify를 첵크한다. (중요하다.)
3. 다이알로그의 헤더 파일에 CFont m_font; 멤버를 하나 추가한다.
4. 다이알로그의 헤처 파일에 BOOL m_m_clicked; 멤버를 추가한다.

BOOL CSssDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
   
    //
밑줄이 쫘악 그어진 폰트를 하나 만든다.
    LOGFONT log;
    GetFont()->GetLogFont(&log);
    log.lfUnderline = TRUE;
    m_font.CreateFontIndirect(&log);

    //
만들어진 폰트를 스태틱 컨트롤에 적용한다.
    GetDlgItem(IDC_STATIC_1)->SetFont(&m_font);

 

    return TRUE;  // return TRUE unless you set the focus to a control
    // EXCEPTION: OCX Property Pages should return FALSE
}

HBRUSH CSssDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
   
    switch(nCtlColor)
    {
    case CTLCOLOR_STATIC:
        {
            if(pWnd->GetDlgCtrlID() == IDC_STATIC_1)
            {
                //
클릭 한적이 없으면 파랑색으로...
                if(m_clicked == FALSE)
                    pDC->SetTextColor(RGB(0, 0, 255));
                //
한번 클릭하고 나면 보라색 비끄무리하게 바꾼다.
                else
                    pDC->SetTextColor(RGB(255, 100, 100));
               
                //
기왕 하는거 배경은 투명한 형태로 계속 유지하자.. --;
                pDC->SetBkMode(TRANSPARENT);
                return (HBRUSH)GetStockObject(NULL_BRUSH);;
            }
        }
    }
    // TODO: Return a different brush if the default is not desired
    return hbr; 
}


여기 까지는 기존에 강좌에 나온것과 별반 다른작업이 없다. 다음 2가지 과정을 거치고 나면
하이퍼링크 컨트롤로 변신한다. ^^;

클래스 위저드를 열어 WM_SETCURSOR 이벤트를 추가한다.

그러고 나면 다음과 같은 코드가 추가된다.

BOOL CSssDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
    return CDialog::OnSetCursor(pWnd, nHitTest, message);
}


간단하게 WM_SETCURSOR 이벤트에 대하여 설명해 보면, 다이알로그 위에서 마우스가 움직이는 동안 필요한 커서를 제어할 수 있도록 기능을 제공하는 것이다.
일단 코드를 다음과 같이 변경한다.
BOOL CSssDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
    CPoint pt;   //
마우스 커서 위치를 저장할 객체
    CRect rc;   // 스태틱 컨트롤의 위치를 저장할 객체.

     // 마우스 커서의 위치를 찾아온다.
    GetCursorPos(&pt);
    //
스태틱 컨트롤의 위치를 찾아온다.
    GetDlgItem(IDC_STATIC_1)->GetWindowRect(rc);

     // 만약 마우스가 스태틱 컨트롤 위에 와있으면..
    if(rc.PtInRect(pt))
    {
        // IDC_HAND
라는 스탠다드 커서를 읽어와서 커서를 변경시킨다.
        SetCursor(AfxGetApp()->LoadStandardCursor(MAKEINTRESOURCE(IDC_HAND)));

        //
꼭 리턴을 해주어야하는데, 이로써 화면에 바뀐커서가 적용된다.
        //
리턴 해주지 않으면 아무리 커서를 바꾸어도 전혀 변경이 없다.
        //
아래 return CDialog::OnSetCursor(pWnd, nHitTest, message); 에서 커서를
        //
원상 복구 시켜버리기 때문이다.
        return TRUE;
    }
 
 return CDialog::OnSetCursor(pWnd, nHitTest, message);
}


이제 마지막 하나의 기능이 남아있다.
스태틱 컨트롤을 마우스로 클릭하고 나면, 그걸 인식하여 웹페이지를 열어 주어야한다.
아까전에 속성창에서 Notify를 첵크하고 하였던 것이 기억날것이다. 만약 이 속성을 주지 않는다면
아무리 마우스를 컨트롤에 놓고 꼭, 꼭 찍어도 아래의 함수는 동작하지 않는다.

클래스 위저드를 열어서 IDC_STATIC_1의 BN_CLICKED 이벤트를 추가한다.


그러면 해당 이벤트의 핸들러가 추가된다.

그리고 코드를 다음과 같이 입력하면 된다.
void CSssDlg::OnStatic1()
{
    m_clicked = TRUE;
    GetDlgItem(IDC_STATIC_1)->Invalidate();
    ShellExecute(m_hWnd, "open", "http://crowback.tistory.com", NULL, NULL, SW_SHOW);
}

여기서 유용한 함수중에 하나가 ShellExecute라는 함수인데.. 외부 프로그램을 실행시킬때 주로
사용하는 기능이다.

설명은 길지만 전체 추가한 라인수는 대략 30라인 정도밖에는 되지않는 아주 간단한 코드이다.
이렇게 하여 하이퍼링크 하나를 넣기 위하여 새로운 클래스를 추가하는 번거로움을 막을 수 있다.

29.zip
0.04MB

 

+ Recent posts