사용자 도구

사이트 도구


kb:wtl

Windows Template Library

http://sourceforge.net/projects/wtl/

윈도우즈 애플리케이션 및 UI 컴포넌트 개발을 위한 C++ 라이브러리. ATL을 기반으로 개발되었으며, 각종 컨트롤, 대화창, 윈도우 프레임, GDI 오브젝트 등을 제공한다.

API로 GUI 프로그래밍하려니 귀찮고, MFC를 쓰자니 의존성이 짜증나는 경우, 대안으로 사용할 수 있다. 템플릿 기반으로 작성되었고, 특별한 DLL 같은 것을 필요로 하지 않는다. 문제는 관련 문서가 너무 부족하다는 것이다. 공식적인 헬프 파일도 존재하지 않고, 인터넷에 있는 문서들도 MFC 같은 것에 비하면 거의 없는 것과 마찬가지다.

장단점을 요약해 보자면 다음과 같다.

from http://discuss.fogcreek.com/joelonsoftware/default.asp?cmd=show&ixPost=18197

I built a couple of medium sized GUI apps with WTL.

My experience was generally positive, but I had been using ATL for at least four years by that point, and considered something of an expert in it. With that background, WTL was a breath of fresh air to me compared to the MFC prison.

The plus side of WTL:

  • Designed like ATL rather than MFC. Functionality is composed via multiple inheritance in a shallow hierarchy rather than using a deep single-inheritance hierarchy where the functionality you want isn't in the branch you need it (What do you mean I need a view to have scrolling support?)
  • All source code is available, and it's quite readable if you understand ATL. This also alleviates the “end of life” arguments - who cares if it doesn't get maintained? You've got the source code, and it's easy to tweak.
  • Since WTL is built around composition, it's dirt simple to extend to do whatever you need to.
  • Support from WTL's author is quite good - there's a WTL yahoo group that he hangs out on.

Down sides of WTL:

  • If you don't know ATL, or are afraid of templates, stay away. It won't make any sense at all.
  • Documentation is essentially non-existant. There's a good intro article (2 parts) on http://www.develop.com somewhere that explains the basics of what's in the library and how to use it. http://www.codeproject.com has a WTL section with some good stuff. But there's no books and no printed articles and no likelyhood of any appearing any time soon.
  • Very slow compile times. Lots of templates mean lots of work for the compiler.

In general, WTL is the expert's tool. If you know Win32 programming well, and understand how ATL is put together, WTL will make you VERY productive, even without the handholding of wizards. If you aren't, well, you're in for a bit of a tough road. But the destination is well worth it.

On the other hand, if you just want to churn out a couple of dialogs, use WinForms instead.

시작하기

  • 라이브러리 이름이 말해주듯이 “template” 라이브러리이기 때문에 헤더 파일만 있으면 만사 OK다. 배포본을 다운로드받아 적당한 곳에다 압축을 푼 다음, Visual C++ > Tools → Option 으로 가서 include 디렉토리를 설정해 주면 끝이다.
  • 위자드를 추가하기 위해서는 배포본 압축 푼 곳에 가보면 appwiz 라는 디렉토리가 있을 것이다. 그 안에 있는 스크립트 파일(setupXX.js)을 해당하는 Visual C++ 버전에 맞춰서 실행하면 끝.

1. 리스트뷰 컨트롤 Full Row Select 켜기

myListCtrl.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT);

API를 모르니 원…

2. 모달 다이얼로그 컨트롤 리사이징

class cMainDialog : public CDialogImpl<cMainDialog>,
                    public CDialogResize<cMainDialog> // !!!
{
    ...
    BEGIN_MSG_MAP_EX(cMainDialog)
        MSG_WM_INITDIALOG(OnInitDialog)
        ...
        CHAIN_MSG_MAP(CDialogResize<cMainDialog>) // !!!
    END_MSG_MAP()
 
    // !!!
    BEGIN_DLGRESIZE_MAP(cMainDialog)
        DLGRESIZE_CONTROL(IDC_GEOMETRY_GROUP, DLSZ_SIZE_X)
        DLGRESIZE_CONTROL(IDC_LEVEL_EDIT, DLSZ_SIZE_X)
        DLGRESIZE_CONTROL(IDC_LEVEL_BUTTON, DLSZ_MOVE_X)
        DLGRESIZE_CONTROL(IDC_GEOMETRY_EDIT, DLSZ_SIZE_X)
        DLGRESIZE_CONTROL(IDC_GEOMETRY_BUTTON, DLSZ_MOVE_X)
    END_DLGRESIZE_MAP()
 
    LRESULT OnInitDialog(HWND hwndFocus, LPARAM lParam)
    {
        ...
        DlgResize_Init(true, true, WS_THICKFRAME);  // !!!
        ...
    }
};

3. ClientEdge 그리기

딱히 ATL/WTL 과 관련된 것도 아니고, 윈도우 생성할 때 WS_EX_CLIENTEDGE 플래그 주면 그만인 내용이지만…

CRect client, tmp;
GetClientRect(&client);
 
CBrush light, face, shadow, black;
light.CreateSolidBrush(::GetSysColor(COLOR_BTNHIGHLIGHT));
face.CreateSolidBrush(::GetSysColor(COLOR_BTNFACE));
shadow.CreateSolidBrush(::GetSysColor(COLOR_BTNSHADOW));
black.CreateSolidBrush(::GetSysColor(COLOR_3DDKSHADOW));
 
// top
tmp.SetRect(client.left, client.top, client.right - 1, client.top + 1);
dc.FillRect(tmp, shadow);
tmp.SetRect(client.left, client.top + 1, client.right - 1, client.top + 2); 
dc.FillRect(tmp, black);
 
// bottom
tmp.SetRect(client.left, client.bottom - 2, client.right - 1, client.bottom - 1);
dc.FillRect(tmp, face);
tmp.SetRect(client.left, client.bottom - 1, client.right - 1, client.bottom - 0);
dc.FillRect(tmp, light);
 
// left
tmp.SetRect(client.left, client.top + 1, client.left + 1, client.bottom - 1);
dc.FillRect(tmp, shadow);
tmp.SetRect(client.left + 1, client.top + 1, client.left + 2, client.bottom - 2);
dc.FillRect(tmp, black);
 
// right
tmp.SetRect(client.right - 2, client.top + 1, client.right - 1, client.bottom - 2);
dc.FillRect(tmp, face);
//tmp.SetRect(client.right - 2, client.top + 1, client.right - 1, client.bottom - 2);
//dc.FillRect(tmp, shadow);
 
// center
tmp.SetRect(client.left + 2, client.top + 2, client.right - 2, client.bottom - 2);
dc.FillSolidRect(tmp, ::GetSysColor(COLOR_APPWORKSPACE));

4. Contained Window

class cMainWindow : public cWindowImpl<...>
{
private:
    CContainedWindowT<CEdit> m_Edit;
 
public:
    BEGIN_MSG_MAP(cMainWindow)
        ...
    ALT_MSG_MAP(1)
        MESSAGE_HANDLER(WM_CHAR, OnEditChar)
    END_MSG_MAP()
 
    LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        ...
        if (!m_Edit.Create(this, 1, m_hWnd, rcDefault))
            return -1;
        ...
    }
 
    LRESULT OnEditChar(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        ...
    }
};

메시지맵 ID에 주의

5. 더블 버퍼링

CDoubleBufferImpl 클래스를 상속받으면 된다. 그런데 약간 이상한게 CDoubleBufferImpl 클래스 내부에 OnPaint 함수와 메시지맵이 정의가 되어있는데, OnPaint 함수가 제대로 호출되지 않는다. 그러므로 실제 상속받은 클래스에서 한번 선언해준다.

class cMainDialog : public CDialogImpl<cMainDialog>,
                    public CDoubleBufferImpl<cMainDialog>
{
public:
    enum { IDD = IDD_MAIN };
 
    BEGIN_MSG_MAP_EX(cMainDialog)
        ...
        MESSAGE_HANDLER(WM_PAINT, OnPaint) // 메시지맵은 정의하되, 실제 함수는 선언하지 않아야한다.
        ...
    END_MSG_MAP()
 
    void DoPaint(CDCHandle dc)
    {
        // 여기서 뭔가를 실제로 그려준다.
        ...
    }
};

6. IDLE 처리

CIdleHandler 클래스는 기본적으로 idle 처리를 한번 한 후에는 WM_MOUSEMOVE, WM_PAINT 이외의 메시지가 도착해야만 다시 idle 처리를 한다. 항상 idle 처리를 하게 만들기 위해서는 클래스를 상속받는 것이 편하다.

class cMainFrame : public CFrameWindowImpl<cMainFrame>, 
                   public CUpdateUI<cMainFrame>,
                   public CMessageFilter,
                   public CIdleHandler
{
    ...
    virtual BOOL OnIdle()
    {
        // 여기서 할 일을 정의한다.
 
        return TRUE; // 별 의미 없다.
    }
    ...
};
 
class cCustomMessageLoop : public CMessageLoop
{
public:
    virtual BOOL OnIdle(int nIdleCount)
    {
        CMessageLoop::OnIdle(nIdleCount);
 
        // 리턴값이 중요하다! CMessageLoop::OnIdle 함수는 기본적으로 FALSE를 반환한다.
        return TRUE; 
    }
};
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                   LPTSTR lpCmdLine, int nCmdShow)
{
    cCustomMessageLoop theLoop;
    cMainFrame theWindow;
 
    theModule.Init(NULL, hInstance);
    theModule.AddMessageLoop(&theLoop);
 
    ....
 
    return 0;
}

7. Custom Modal Dialog

CDialogImpl 클래스를 상속받은 모달 Dialog를 만든 경우, Dialog 속성 창에서 Style을 Popup으로 해줘야 정상적으로 동작한다.

8. DDX

DDX_FLOAT 매크로를 사용하기 위해서는 atlddx.h 파일을 include하기 전에 _ATL_USE_DDX_FLOAT 매크로를 정의해줘야한다.

9. 메뉴 업데이트

메뉴 업데이트(활성화/비활성화, 체크 등)를 사용하기 위해서는 CUpdateUI를 상속받은 다음, BEGIN_UPDATE_UI_MAP, END_UPDATE_UI_MAP 맵을 정의해줘야한다. 실제 업데이트는 UIEnable, UISetCheck 등의 함수를 이용한다.

class cMainFrame : public CFrameWindowImpl<cMainFrame>, 
                   public CUpdateUI<cMainFrame>,
                   public CMessageFilter,
                   public CIdleHandler
{
    BEGIN_UPDATE_UI_MAP(cMainFrame)
        UPDATE_ELEMENT(ID_VIEW_ORIGINALMESH, UPDUI_MENUPOPUP)
        UPDATE_ELEMENT(ID_VIEW_NAVIGATIONMESH, UPDUI_MENUPOPUP)
        UPDATE_ELEMENT(ID_VIEW_WIREFRAME, UPDUI_MENUPOPUP)
        UPDATE_ELEMENT(ID_VIEW_BODY, UPDUI_MENUPOPUP)
    END_UPDATE_UI_MAP()
};

10. 단축키

  • 단축키를 지원하기 위해서는 CMessageFilter를 상속받고, PreTranslateMessage> 함수를 오버라이드해준 다음, 윈도우 생성 시에 AddMessageFilter 함수를 이용해 메시지 루프에다 필터를 추가해야한다.

11. RichEdit 컨트롤 사용하기

RichEdit 컨트롤을 사용하기 위해서는 다음과 같은 매크로를 atlctrls.h 파일을 include 하기 전에 정의하고, 어딘가에서 라이브러리를 직접 로드해줘야한다.

// RichEdit 1.0을 사용하기 위한 정의
//#define WINVER		0x0400
//#define _WIN32_IE	0x0400
//#define _RICHEDIT_VER	0x0100
 
// RichEdit 2.0을 사용하기 위한 정의
#define WINVER          0x0500
#define _WIN32_WINNT    0x0500
#define _WIN32_IE       0x0501
#define _RICHEDIT_VER   0x0200
 
#include <atlctrls.h>
 
...
 
HINSTANCE hInstRich = ::LoadLibrary(CRichEditCtrl::GetLibraryName());
ATLASSERT(hInstRich != NULL);
 
...
 
::FreeLibrary(hInstRich);

링크

kb/wtl.txt · 마지막으로 수정됨: 2014/11/07 14:19 (바깥 편집)