728x90

MFC에서 MDI로 처음 생성하면 CCommandLineInfo  클래스 변수를 ParseCommandLine() 하기 전에 


m_nShellCommand 변수가 FileNew로 초기화 되어 있다.



이것은 CCommandLineInfo 클래스를 보면 왜 그런지 알 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class CCommandLineInfo : public CObject { public: // Sets default values CCommandLineInfo(); // plain char* version on UNICODE for source-code backwards compatibility virtual void ParseParam(const TCHAR* pszParam, BOOL bFlag, BOOL bLast); #ifdef _UNICODE virtual void ParseParam(const char* pszParam, BOOL bFlag, BOOL bLast); #endif BOOL m_bShowSplash; BOOL m_bRunEmbedded; BOOL m_bRunAutomated; BOOL m_bRegisterPerUser; enum { FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE, FileDDENoShow, AppRegister, AppUnregister, RestartByRestartManager, FileNothing = -1 } m_nShellCommand; // not valid for FileNew CString m_strFileName; // valid only for FilePrintTo CString m_strPrinterName; CString m_strDriverName; CString m_strPortName; // valid only for RestartByRestartManager CString m_strRestartIdentifier; ~CCommandLineInfo(); // Implementation protected: void ParseParamFlag(const char* pszParam); void ParseParamNotFlag(const TCHAR* pszParam); #ifdef _UNICODE void ParseParamNotFlag(const char* pszParam); #endif void ParseLast(BOOL bLast); };


위에서 m_nShellCommand의 enum을 보면, m_nShellCommand 변수의 0 값이 FileNew이다.

그 때문에 별도의 값을 처리하지 않으면 FileNew로 기본 초기화 되는 것이다.


때문에 App::InitInstance() 에서 다음과 같이 m_nShellCommand 변수의 값을 FileNotthing;으로 변경해주면 된다.


1
2
3
4
5
6
7
8
9
// 표준 셸 명령, DDE, 파일 열기에 대한 명령줄을 구문 분석합니다.
CCommandLineInfo cmdInfo;

// 프로그램 시작 시 빈창을 띄우지 않는다.
cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing;


ParseCommandLine(cmdInfo);
 



728x90
728x90

MFC에서 Thread를 그냥 막 사용하다보니 잘못 사용한 예가 있어서 정리한다.


예를 들어 


void CMyClass::CreateSystemStatusThread()

{

static auto Func = [&]()

{

while(m_bSystemStatusThreadOnOff)

{

if (m_bSystemOnOff)

((CStatic*)GetDlgItem(IDC_STATIC))->SetBitmap(m_pngOn);

else

((CStatic*)GetDlgItem(IDC_STATIC))->SetBitmap(m_pngOff);


Sleep(1000);

}

}


m_bSystemStatusThreadOnOff = true;

m_thrSystemStatus = std::thread(Func);

}


void CMyClass::DestroySystemStatusThread()

{

m_bSystemStatusThreadOnOff = false;


if (m_thrSystemStatus.joinable())

    m_thrSystemStatus.join();

}



위와 같이 thread를 만들어놨다고 치자.

그렇다면 DestroySystemStatusThread()를 어디에서 호출해야 하는지 생각을 해보아야 한다.


나의 경우 당연히 CMyClass의 소멸자에 넣으면 잘 종료될 것이라고 생각을 하였지만, 당연히 아니었다.

왜냐하면 CMyClass의 소멸자가 호출되기전에 GetDlgItem(IDC_STATIC)가 먼저 없어지기 때문이다.

즉, 프로그램 종료 중에 GetDlgItem(IDC_STATIC)를 호출하면서 프로그램이 잘못된 것을 참조했다는 에러가 발생하게 된다.


그래서 MFC의 소멸 순서를 확인해 본 결과, OnDestroy()에서 DestroySystemStatusThread() 호출해서 Thread를 먼저 파괴해주어야 한다.

728x90
728x90

CImage를 이용하여 많은 이미지를 사용하고 있었는데, 이미지를 변경할 때마다 Memory Leak이 발생하는 현상을 발견하고,


구글링을 해도 딱히 방법이 나오지 않아서 직접 테스트하다가 해결하였다.


기존 코드


위의 경우 분명 DeleteObject() 를 이용해서 hOldBmp 를 지우기 때문에 메모리 릭이 없을 거라 판단을 하고 작업을 진행하고 있었다. 


하지만 여러가지 테스트 결과 DeleteObject() 한번 더 해줘야 하는 것 같다.


수정한 코드


우선 img를 static으로 선언하여 계속 가지고 있으면서 이미지를 변경할 때마다 img를 Destroy()하고 또 한번 DeleteObject()를 해야만 Memory Leak이 발생하지 않았다.



MFC를 이용해서 UI 작업 하는 게 생각만큼 쉽지는 않나보다... ㅎㅎ

728x90
728x90

       

               [기본 다이얼로그] 


 

        [타이틀바와 테두리에 이미지를 넣은 후]



나는 MFC에서 위와 같이 변경하기 위해인터넷에서 정말 많은 자료를 검색하다가 잘 정리해놓은 블로그가 없어서 예전에 찾았던 코드를 분석하였다.


http://grandstayner.tistory.com/entry/MFC-%EB%8B%A4%EC%9D%B4%EC%96%BC%EB%A1%9C%EA%B7%B8Dialog-%EC%97%90-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%9E%85%ED%9E%88%EA%B8%B0


다이얼로그 커스터마이징 코드를 분석하며 그냥 필요한 부분만 추출하였다.


bitmap 대신 png를 이용하여 작업을 하였다.



1. 클래스 헤더 파일에 CRgn 과 CImage 변수들을 추가한다.



2. 이미지들을 로드한다. 

   (CUtil::GetExeDirectoryW() 함수는 그냥 현재 폴더의 경로를 알아내기 위해 내가 만들어서 사용하고 있는 Util 클래스이다.)



3. Draw 할 함수들을 만든다.



5. OnPaint()에서 배경을 그려준다.



6. OnActivate(), OnNcPaint(), OnNCActive() 에 Border를 그려준다.



7. OnSize() 에 다음 코드를 추가해준다.



이렇게하면 잘 입혀질 것이다.

728x90
728x90


다음과 같이 PreTranslateMessage()에서 마우스 버튼을 눌렀을때 이미지를 변경하려고 했는데, 버튼을 연속으로 누르다보면 인식이 안되는 현상이 있었다. 그래서 이리저리 테스트를 해보다보니 연속으로 누르다가 WM_LBUTTONDOWN이 아니라, WM_LBUTTONDBLCLK 이 발생한다는 것을 확인하였다.


그래서 다음과 같이 수정하여 해결을 하였다.




728x90
728x90


EditBox, ListBox, Static Text, Radio Button 등 컨트롤의 배경을 투명하게 만들고 싶을 때에는 다음과 같은 작업을 해줘야 한다.



1. 컨트롤의 속성에서 Transparent를 True로 변경해준다.



2. stdafx.h 에 다음과 같이 라이브러리를 추가해준다.



3. 투명하게 할 Item이 있는 Dialog의 OnInitDialog()에 다음과 같이 추가해준다.



4. 투명하게 할 Item이 있는 Dialog의 OnCtlColor()에 다음과 같이 추가해준다.


nCtlColor의 종류는 아래와 같다.
#define CTLCOLOR_MSGBOX         0
#define CTLCOLOR_EDIT               1
#define CTLCOLOR_LISTBOX          2
#define CTLCOLOR_BTN               3
#define CTLCOLOR_DLG               4
#define CTLCOLOR_SCROLLBAR     5
#define CTLCOLOR_STATIC           6


ps. 왜인지 모르겠지만, CTLCOLOR_BTN(버튼) 의 경우에는 적용이 되지 않는다.

728x90
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

+ Recent posts