728x90


리사이징이 되어야하는 프레임 윈도우나 다이얼로그 윈도우를 위한 스킨 기능입니다.

프로젝트를 진행하면서 구현했던 기능들을 정리하면서 간단한 예제 소스도 함께 만들어보았습니다.

초기에는 프레임 윈도우에만 구현하였다가 지금은 다이얼로그 윈도우에도 동일하게 적용했습니다.

 

프레임 윈도우에 적용하고 싶으신 분은 이 소스를 참조해서 동일하게 구현하셔도 되겠습니다.

 

개발 환경

- Window XP : Intel X86 32bit

- Visual Studio 2005 : MFC 8.0 Unmanaged C++

 

캡션과 보더 부분에 들어가는 비트맵은 총 8개 입니다.

- 캡션 왼쪽

- 캡션 가운데(width:1pixel)

- 캡션 오른쪽

- 보더 왼쪽(height:1pixel)

- 보더 왼쪽아래

- 보더 아래(width:1pixel)

- 보더 오른쪽(height:1pixel)

- 보더 오른쪽아래

 

시스템 박스에 들어가는 비트맵은 총 12개입니다.

- 최소화 박스 : out, over, on

- 최대화 박스 : out, over, on

- 종료 박스 : out, over, on

- 복귀 박스 : out, over, on

 

캡션에 들어가는 아이콘은 총 2개 입니다.

- active, inactive

 

모든 이미지, 아이콘 파일은 현재 파일로 노출되어 있고 실행시 로드해서 각 핸들을 글로벌 객체의 멤버로 가지고

있게 됩니다. 추후에는 XML로 인덱스를 관리한다거나 이미지 DB파일로 확장해서 사용해도 좋을 것 같습니다.

 

스킨 버튼은 공통 배경이 되는 Base 비트맵과 기능별 아이콘으로 구성됩니다. 스킨 버튼 설명은 다음 강좌에서 다른 스킨 컨트롤들과 함께 설명하겠습니다.(CListCtrl, CTabCtrl, CToolBar, CMenuBar,...)

 

참고로 스킨 리사이징이 완전한 다각형 윈도우까지 되는건 아닙니다. 현재는 라운드 형태의 스킨 윈도우만 가능합니다. 그 부분은 추후 기능을 보완하도록 하겠습니다.

 

다음은 스킨 관련 클래스입니다.

CGlobal : 비트맵, 아이콘, RGB, FONT 들의 정보를 저장하기 위한 글로벌 클래스입니다.

CSkinDialog : 실제 상속 받아서 사용하게 될 스킨(캡션/프레임) 다이얼로그입니다.

CMemDC : 각종 컨트롤에서 사용하는 더블 버퍼링 기능을 위한 DC 클래스 입니다.

CSkinButton : 버튼 스킨 클래스입니다.

 

다음은 CGlobal 클래스의 주요 함수별 기능 설명입니다.

1. void LoadBitmaps()

 : Bitmap 파일을 로드하여 핸들을 배열로 저장, 최초 호출 함

 

2. HBITMAP GetBitmap(UINT nBitmapID)

 : 키 인덱스로 해당 비트맵의 핸들을 얻음

2. SIZE GetBitmapSize(UINT nBitmapID)

 : 키 인덱스로 해당 비트맵의 사이즈를 얻음

 

3. void LoadIcons()

 : Icon 파일을 로드하여 핸들을 배열로 저장, 최초 호출함

 

4. HICON GetIcon(UINT nIconID, UINT nType)

 : 키 인덱스로 해당 아이콘의 핸들을 얻음

 

5. void SetRGB(UINT nRGBID, COLORREF cf)

: 키 인덱스에 해당하는 RGB를 설정함, 최초 설정함

 

6. COLORREF GetRGB(UINT nRGBID)

: 키 인덱스에 해당하는 RGB를 얻음

 

7. CFont * SetFont(UINT nFontID, CString sFaceName, UINT nFontSize, BOOL bBold=FALSE)

: 키 인덱스에 해당하는 폰트를 생성하여 배열로 저장함

 

8. CFont * GetFont(UINT nFontID)

: 키 인덱스에 해당하는 폰트를 얻음

 

다음은 CSkinDialog 클래스의 주요 함수별 기능 설명입니다.

1. void DrawFrame()

: 실제 GDI를 이용하여 캡션, 보더를 그림

 

2. virtual BOOL PreTranslateMessage(MSG* pMsg)

: 마우스 이벤트 발생 시 시스템 박스의 이미지 변경과 기능 처리를 위한 함수

 

3. int OnCreate(LPCREATESTRUCT lpCreateStruct)

: 비트맵, 아이콘 핸들 초기 설정

 

 4. void OnDestroy()

: GDI 오브젝트 소멸

 

5. void OnSize(UINT nType, int cx, int cy)

: 라운드 형태로 윈도우 영역 설정

 

6. void OnNcPaint()

: None Client 영역을 위한 Paint 함수로 DrawFrame 함수를 호출함

 

7. void OnPaint()

: 클라이언트 영역의 배경을 칠할 함수(OnEraseBackground에서 해도 되지만 특정 때 호출되지 않는 문제가 발생)

 

8. BOOL OnNcActivate(BOOL bActive)

: 윈도우가 액티브/인액티브 될 때 DrawFrame 함수를 호출함

 

9. void OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)

: 스킨 캡션, 보더의 사이즈에 따른 클라이언트 영역을 재조정함

 

11. void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)

: 윈도우 리사이징 시 최소/최대값을 설정함

 

12. LRESULT OnEnterSizeMove(WPARAM wParam, LPARAM lParam)

: 윈도우 이동 시 투명 처리함


 13. LRESULT OnExitSizeMove(WPARAM wParam, LPARAM lParam)

: 윈도우 이동 종료시 불투명 처리함

 

강좌라고 하기에는 내용이 너무 부실한거 같아 죄송합니다. 간단히 샘플 소스 공개 입니다. ㅎ ^^;;

다음에는 여러 컨트롤의 스킨 예제 소스도 올려 보겠습니다. 

기타 의문점이나 개선사항 있으면 리플 부탁드립니다. 많은 도움이 되겠습니다.

 

Update history 

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

- 2008.04.11 - 16:09 : SetCapture/ReleaseCapture를 사용하여 시스템 박스 기능을 새로 구현하였습니다.

- 2008.04.11 - 18:00 : 최대화 상태에서 윈도우가 이동되는 문제점이 수정되었습니다. Restore 이미지가 변경되었습니다. 더블클릭 메시지처리가 추가되었습니다.

- 2008.04.12 - 05:35 : 최대화 관련 윈도우 표시 문제점 수정



SkinDialog5.zip




출처 : http://heart4u.co.kr/tblog/489

728x90
728x90

탭 컨트롤을 좀 더 이쁘게 만들고 싶어서 검색하다보니 이런 자료를 찾았다.


꽤 예전에 만들어진 것 같은데... 


역시 능력자는 많은가보다. 







화면에서 보는 것과 같이 자유롭게 커스터마이징 가능하도록 Tab Control을 상속/구현하였습니다.

아직 탭/보더가 스킨 이미지로 구현된 컨트롤은 아닙니다.

현재는 GDI/GDI+ 를 이용한 직접 그리는 형태가 되겠습니다.

 

개발 환경

- Window XP : Intel X86 32bit

- Visual Studio 2005 : MFC 8.0 Unmanaged C++

 

현재 구현된 탭 스타일은 아래와 같습니다.

1. 버튼 스타일

- 가로/세로 스타일이 가능함

- 단, 세로 스타일일때는 멀티라인이어야함(이건 기본 컨트롤도 마찬가지임)

- GDI+ 이용한 그라데이션 효과를 줌

- 탭 위치 스타일이 현재 기본(Top)만 가능함. Bottom은 현재 미구현

 

2. 일반 스타일

- 가로/세로 스타일이 가능함

- 단, 세로 스타일일때는 멀티라인이어야함(이건 기본 컨트롤도 마찬가지임)

- 3D 스타일과 플랫 스타일로 변경하는 옵션

- 보더의 색을 지정하는 옵션

- 탭 위치 스타일이 현재 기본(Top)만 가능함. Bottom은 현재 미구현

 

다음은 CTabCtrlEx 클래스의 주요함수 설명입니다.

 

1. void SetColor(COLORREF crText, COLORREF crBorder, COLORREF crBackIn, COLORREF crBackOut, BOOL bRedraw=TRUE)

 : 텍스트, 배경안쪽, 배경바깥쪽, 보더 색 설정

 

2. void Set3dBorder(BOOL b3dBorder)

 : 보더 스타일 설정(TRUE=3D, FALSE=플랫)

 

3. void SetItemImage(int nItem, int nImage)

 : 해당 탭 아이템의 이미지 설정

 

4. virtual void TextOutVertical(CDC* pDC, CRect rect, CStringW sText)

 : 세로 스타일일 경우 텍스트를 한문자씩 세로로 출력

 

5. virtual void DrawMainBorder(LPDRAWITEMSTRUCT lpDrawItemStruct)

 : 메인 보더를 그림

    

6. virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)

 : 해당 탭 아이템의 텍스트를 출력

 

7. virtual void DrawItemBorder(LPDRAWITEMSTRUCT lpDrawItemStruct)

 : 해당 탭 아이템의 보더를 그림




TabCtrlEx.zip




출처 : http://yhs1981.tistory.com/entry/MFC-%EC%BB%A4%EC%8A%A4%ED%84%B0%EB%A7%88%EC%9D%B4%EC%A7%95%EC%9D%84-%EC%9C%84%ED%95%9C-Tab-Control

728x90
728x90

// 멤버 변수 추가.

HBITMAP m_bmpBackground;



// OnInitDialog() 에 추가.

m_bmpBackground = (HBITMAP)LoadImage(AfxGetApp()->m_hInstance, "이미지 경로", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);



// OnPaint() 에 추가.

CPaintDC dc(this); // device context for painting

CBitmap bmp;

BITMAP bmpInfo;

CDC memDC;


bmp.Attach(m_bmpBackground);

bmp.GetBitmap(&bmpInfo);


memDC.CreateCompatibleDC(&dc);

memDC.SelectObject(bmp);


dc.BitBlt(0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, &memDC, 0, 0, SRCCOPY);


memDC.DeleteDC();

bmp.Detach();

bmp.DeleteObject();



위와 같이 추가를 해주면 다이얼로그에 이미지가 잘 입혀질 것이다.




728x90
728x90

아래의 코드를 그냥 인쇄하고 싶은 버튼에 넣으면 된다...


    // 인쇄 코드.

    int nWidth, nHeight;

    CClientDC dc(this);  //this->pImgWnd

    CDC MemDC;

    MemDC.CreateCompatibleDC(&dc);


    CRect rect;

    GetClientRect(rect);

    nWidth = rect.Width();

    nHeight = rect.Height();


    CBitmap BMP;

    BMP.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height());

    CBitmap* pOldBitmap = MemDC.SelectObject(&BMP);

    MemDC.BitBlt(0, 0, nWidth, nHeight, &dc, 0, 0, SRCCOPY);


    /*

    SECJpeg* jpg = new SECJpeg();

    jpg->CreateFromBitmap(&MemDC,&BMP);

    jpg->SaveImage("Test.jpg");

    */


    HANDLE hDib;

    LPSTR pDib;

    LPBITMAPINFO lpBitInfo;

    HANDLE hlpBitInfo;

    //CBitmap BMP;


    //BMP.LoadBitmap(IDB_BITMAP1);


    hDib = GlobalAlloc(GHND, nWidth*nHeight * 3);

    pDib = (LPSTR)GlobalLock(hDib);

    hlpBitInfo = GlobalAlloc(GHND, sizeof(BITMAPINFOHEADER) + sizeof(BITMAPINFO));

    lpBitInfo = (LPBITMAPINFO)GlobalLock(hlpBitInfo);


    //BITMAPINFO

    lpBitInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

    lpBitInfo->bmiHeader.biWidth = nWidth;

    lpBitInfo->bmiHeader.biHeight = nHeight;

    lpBitInfo->bmiHeader.biPlanes = 1;

    lpBitInfo->bmiHeader.biBitCount = 24;

    lpBitInfo->bmiHeader.biCompression = BI_RGB;

    lpBitInfo->bmiHeader.biSizeImage = nWidth * nHeight * 3;

    lpBitInfo->bmiHeader.biXPelsPerMeter = 0;

    lpBitInfo->bmiHeader.biYPelsPerMeter = 0;

    lpBitInfo->bmiHeader.biClrUsed = 0;

    lpBitInfo->bmiHeader.biClrImportant = 0;

    ////BITMAPINFO  


    HDC hdc = ::GetDC(this->m_hWnd);

    GetDIBits(hdc, (HBITMAP)BMP, 0, nHeight, pDib, lpBitInfo, DIB_RGB_COLORS);

    ::ReleaseDC(this->m_hWnd, hdc);


    static DOCINFO docinfo = { sizeof(DOCINFO), _T("IMAGE"), NULL };


    CPrintDialog dlg(FALSE);

    if (dlg.DoModal() == IDCANCEL)

        return;


    HDC hpdc = dlg.GetPrinterDC();

    int cx, cy;


    cy = GetDeviceCaps(hpdc, VERTRES);

    cx = GetDeviceCaps(hpdc, HORZRES);


    if (StartDoc(hpdc, &docinfo))

    {

        if (StartPage(hpdc))

        {

            StretchDIBits(hpdc,

                0, 0, cx, cy, 0, 0, nWidth, nHeight, pDib, lpBitInfo, DIB_RGB_COLORS, SRCCOPY);

            EndPage(hpdc);

        }

        EndDoc(hpdc);

    }

    ::RestoreDC(hpdc, -1);


출처 : http://frostguy.tistory.com/42

728x90
728x90

MFC는 기본적으로 글자 크기를 Dialog 전체적으로 바꾸게 되어 있다.


하지만 아래와 같이 작업을 해서 Item의 글자를 개별적으로 변경이 가능하다.


static CFont font;

LOGFONT logFont;


// Edit 컨트롤의 Font 정보를  LogFont에 가져온다.

GetDlgItem(IDC_EDIT_TOTAL)->GetFont()->GetLogFont(&logFont);


// Font 글자 설정.

logFont.lfWeight = 1000;

logFont.lfHeight = 30;


// logFont의 정보로 설정.

font.CreateFontIndirect(&logFont);


// Edit 박스의 Font를 설정.

GetDlgItem(IDC_EDIT_TOTAL)->SetFont(&font);




출처 : http://worhkd.tistory.com/entry/MFC-Dialog-Item%EB%93%A4%EC%9D%98-%EA%B8%80%EC%9E%90%ED%81%AC%EA%B8%B0-%EB%B3%80%EA%B2%BD

728x90
728x90

특히 신입 개발자분들은... 이글을 꼭 읽어보도록 하자.


영어로 되어 있지만 구글 번역기가 있으므로 부담없이 읽을 수 있다. ㅎㅎ




원문 : https://software.intel.com/en-us/articles/the-ultimate-question-of-programming-refactoring-and-everything?utm_campaign=IHI-HPC-Q4_16&utm_medium=Syndication&utm_source=Taboola&utm_content=&utm_term=&utm_source=taboola&utm_medium=referral




728x90
728x90

어댑터라는 것은 우리 주변에서도 흔히 볼 수 있다. 대표적으로 110V 전용 가전 제품에 220V 어댑터를 끼워 사용하는 것이다.


어댑터를 보면 어댑터 패턴이 무엇인지 대충 감이 올 수도 있겠다.



이 패턴을 사용할만 한 경우를 만들어보면,



"포토뷰어 엔진을 판매하는 회사 A가 있다. 이 A 회사의 엔진이 우리 회사의 포토뷰어 엔진보다 성능이 좋다는 결과를 통보 받았다. 그래서 나는 우리 회사의 포토 뷰어 엔진을 교체 하기 위해 A라는 회사에 엔진을 요청했고 엔진 명세서를 받아봤다."



즉 '나'는 성능의 향상을 위해 포토 뷰어 엔진을 기존의 엔진에서 A 업체가 제공하는 엔진으로 바꿔야만 하는 상황이다.



이제 명세표를 보면 A엔진과 기존 엔진의 구조를 살표보자.


/* 내가 쓰는 엔진 명세 (MyLib) */ class MyLib { public: void printPhoto(String fileName)          { ... } void printPhotoList(String[] listName)    { ... } void deletePhoto(String fileName)        { ... } void addPhoto(String fileName)           { ... } }



/* A 엔진 명세 (ALib) */ class ALib { public:

void printPhoto(String fileName)          { ... } void deletePhoto(String fileName)        { ... } void addPhoto(String fileName)          { ... } }




엔진이 완전히 같다면 좋을텐데 미묘하게 다른 구석이 있다. 차이가 있는 것은 제공 받은 엔진에서 printPhotoList() 가 없다는 것이다. 어쨋든 엔진 교체를 위해 코드 분석에 들어가보자. 아래 코드는 기존 엔진을 사용한 코드이다. 기존 엔진의 이름은 MyLib 이다.


/* 기존 코드 */ int main() { MyLib lib = new MyLib(); String photoList = { "abc.jpg" , "def.jpg" }; lib.printPhotoList(photoList); lib.printPhoto("abc.jpg"); lib.deletePhoto("abc.jpg"); lib.addPhoto("aaa.jpg"); }




자, 이제 함수명이 동일하니 한번 엔진을 바꿔보자.


/* 바뀐 코드 Ver.1 */ int main() { ALib lib = new ALib(); // change String photoList = { "abc.jpg" , "def.jpg" }; for(int i = 0 ; i < photoList.length; i++)    // for문 추가됨

{ lib.printPhoto(photoList[i]); } lib.printPhoto("abc.jpg"); lib.deletePhoto("abc.jpg"); lib.addPhoto("aaa.jpg"); }




이제 프로그램은 잘 돌아갈거라고 생각이 되지만, 기본 로직에 변경이 생겼다. 바로 전에 없었던 for()이 들어가게 된 것이다. 물론 이 코드를 더 이상의 유지 보수를 하지 않을 예정이라면 이대로 놔둬도 상관없겠지만, 프로그램을 만들면 무조건 유지 보수를 해야하기 때문에 이렇게 두는 것은 좋지 않은 방법이다.




따라서 기본 로직을 그대로 이용할 수 있게끔 어댑터 패턴을 사용해보자.


class IPhotoEngine { public:

virtual void printPhoto(String fileName) = 0;     virtual void printPhotoList(String[] listName) = 0;

virtual void deletePhoto(String fileName) = 0; virtual void addPhoto(String fileName) = 0; }




인터페이스 클래스를 만들었다. 이제 어댑터 클래스를 만들어보자.



class ALibAdapter : public IPhotoEngine { private: ALib lib; public: ALibAdapter( ALib lib ) { this.lib = lib; } void printPhoto(String fileName) { lib.printPhoto( fileName ); } void printPhotoList(String[] listName) { for(int i = 0 ; i < listName.length; i++) { lib.printPhoto( listName[i] ); } } void deletePhoto(String fileName) { lib.deletePhoto( fileName ); } void addPhoto(String fileName) { lib.addPhoto( fileName ); } }





이제 이 클래스를 사용해 보자.



/* 바뀐 코드 Ver.2 */
int main()
{
	ALib alib = new ALib();

	PhotoEngine lib = new ALibAdapter( alib );
	String photoList = { "abc.jpg" , "def.jpg" };
	lib.printPhotoList(photoList);
	lib.printPhoto("abc.jpg");
	lib.deletePhoto("abc.jpg");
	lib.addPhoto("aaa.jpg");
}



물론 ALib가 아닌 다른 엔진을 사용할 때에도 마찬가지로 IPhotoEngine 인터페이스를 포함한 어댑터 클래스를 만들기만 하면 언제든 로직을 재사용할 수 있다.


이것이 바로 어댑터 패턴을 사용하는 목적이기도 하다.


만약 기존엔진을 다시 써야하는 상황이 올때를 대비해서 기존 엔진의 어댑터 클래스를 만들어보자.



class MyLibAdapter : public PhotoEngine { private: MyLib lib; public: MyLibAdapter( MyLib lib ) { this.lib = lib; } void printPhoto(String fileName) { lib.printPhoto( fileName ); } void printPhotoList(String[] listName) { lib.printPhotoList( listName ); } void deletePhoto(String fileName) { lib.deletePhoto( fileName ); } void addPhoto(String fileName) { lib.addPhoto( fileName ); } }



그리고 간단하게 기존 엔진을 사용하도록 바꾸면 된다.



/* 바뀐 코드 Ver.3 */
int main()
{
	MyLib myLib = new myLib();
	PhotoEngine lib = new MyLibAdapter( myLib );
	String photoList = { "abc.jpg" , "def.jpg" };
	lib.printPhotoList(photoList);
	lib.printPhoto("abc.jpg");
	lib.deletePhoto("abc.jpg");
	lib.addPhoto("aaa.jpg");
}




출처 : http://jdm.kr/blog/11




728x90
728x90

컴포지트 패턴(Composite Pattern)은 간단하게 말해서 단일 객체(Single Instance)든 객체들의 집합(Group of Instance) 이든 같은 방법으로 취급하는 것이다. 다시 말해, 개별적인 객체들과 집합간의 처리 방법의 차이가 없을 경우 사용하면 된다.


여기서 컴포지트의 의미는 일부 또는 그룹을 표현하는 객체들을 트리 구조(Tree Structures)로 구성한다는 뜻이다.




컴포지트 패턴을 활용은 다음과 같은 곳에서 사용할 수 있다.


파일 시스템을 간단하게 구현한다고 생각했을 때, 먼저 필요한 것은 파일이다. 그래서 파일 클래스를 만들었다.


class File { private: String name; // ... }



그리고 파일들을 가질 수 있는 디렉토리 클래스가 필요할 것이다. 


class Directory { private: String name; List<File> children; // ... public: void add(File file) { // ... } }




디렉토리 클래스는 자신의 이름과 파일들을 가질 수 있다. add() 를 이용해 파일을 추가할 수도 있을 것이다.

근데 이 구조에서 디렉토리 안에 디렉토리가 있는 것을 어떻게 표현해야 할까?




/** INode 클래스는 기본적인 파일 및 디렉토리의 근간이라고 가정합니다. 모든 파일과 디렉토리는 이름을 가지고 있을테니 이름을 반환할 getName() 메소드를 가집니다. */ class INode { public: virtual String getName() = 0; } /** File 클래스는 INode 인터페이스를 구현하면 끝입니다.

자신은 자식 요소를 가질 필요가 없기 때문이죠.

*/ class File : public INode { private: String name; // ... public: virtual String getName()    { return name; } } /** Directory 클래스는 INode 인터페이스를 구현하는 것 외에도

자식 요소를 담아둘 List가 필요합니다. */ class Directory : public INode { private:     String name;     std::list<INode> children; // ... public: virtual String getName()    { return name; } public void add(Node node) { children.add(node); } }



이제 사용을 한다면 다음과 같이 사용하게 될 것이다.



Directory dir = new Directory();
dir.add(new File()); // 디렉토리에 파일 하나를 삽입!
dir.add(new Directory()); // 디렉토리에 디렉토리를 삽입!
Directory secondDir = new Directory();
secondDir.add(dir); // 기존 루트 디렉토리를 새로 만든 디렉토리에 삽입!



간단하지만 트리 구조를 표현할 수 있는 파일 시스템을 만들었다. 이것이 바로 컴포지트 패턴을 이용한 것이다.



실제로 컴포지트 패턴의 UML은 다음과 같다.



Component : 모든 표현할 요소들의 추상적인 인터페이스를 말한다. 위의 예제 코드에서 INode 인터페이스가 이 역할을 

                  담당하고 있었다.


Leaf : Leaf 요소는 Component로 지정된 인터페이스를 구현한 것이다. 위의 예제 코드에서 File 클래스가 이 역할을 담당

        하고 있었다.


Composite : Composite 요소는 Component 요소를 자식으로 가진다. 따라서 Component 요소를 관리하기 위한 메소드들

                을 추가적으로 구현해줘야한다. 위의 예제 코드에서는 Directory 클래스가 이 역할을 담당하고 있었다.




출처 : http://jdm.kr/blog/228

728x90

+ Recent posts