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

+ Recent posts