Tuesday, January 26, 2021

Visual C++ 유용한 팁 정리

<Visual C++의 유용한 단축키>

* Ctrl+F3 : 현재 커서가 있는 위치의 단어가 나오는 다음 위치를 찾아줍니다. Ctrl+Shift+F3을 누르면 이전 걸 찾아주죠.

* Ctrl+H : replace입니다. 특히 일정부분안의 변수 이름을 변경하거나 할 때 범위를 선택한 다음에 Ctrl+H를 누르면 편하죠.

* Ctrl+], Ctrl+E : 괄호 짝을 맞춰줍니다. {위에서 누르면 짝이 맞는 }를 찾아주지요.

* Ctrl+L : 한줄을 잘라냅니다. -> 클립보드로

* Ctrl+Shift+L : 한줄을 지웁니다.

* Ctrl+J, K : #ifdef ... #endif 쌍을 찾는데 사용합니다.

* 그외 Ctrl을 누르고 커서나 Delete, Backspace를 누르면 단어 단위로 동작을 하게 됩니다.

* Alt+F8 : 인덴트 정리. 일정 범위를 선택하고 누르면 라인 앞부분의 공백을 자동으로 조정해줍니다.

* CTRL+SHIFT+T : 커서 위치의 단어와 앞 단어가 서로 교체 됩니다.

* ALT+SHIFT+T : 커서 위치의 한줄과 윗줄이 서로 교환됩니다.

* Shift + F9 : 디버그 모드에서 추가하고픈 변수나 등등 앞에 커서를 위치 시킨후 Shift+F9를

누르면 Watch Window에 자동으로 추가.

* Ctrl + Alt + T , Ctrl + Spacebar : 멤버목록 팝업창이 나타납니다 (참고로 대소문에 관계없습니다.)

* Ctrl + Shift + U : 소문자를 대문자로 바꾸고 싶을때 블럭 지정을 한다음 이 키를 누르면 소문자가 대문자로 둔갑.

* Ctrl + U : 대문자를 소문자로 변경

* Ctrl + Shift + 8 : Tab은 ^모양으로 공백문자는 .로 변경, 다시 누르면 설정이 해제



<ALT+F4로 종료안되게 하려면>

BOOL MyApp::PreTranslateMessage(MSG* pMsg)
{
        if(pMsg->message == WM_SYSKEYDOWN &&
                pMsg->wParam == VK_F4) {
                      return TRUE;
        }
}

이 함수를 오버라이드 하여서 집어넣으면 됩니다.  



<제일 짧은, 파일 크기 알아내는 함수>

일단 제가 본 방법들을 열거해 보겠습니다.

      1] 파일을 열고, 파일 끝으로 위치를 옮겨서 그 위치값을 읽는다.

      2] 파일을 fopen()이 아닌 open()으로 열어서 핸들을 얻은 다음 그것을 이용해서 filelength()함수로 파일 크기를 구한다.



간단하니 결론만 말씀드리죠. 저는 다음과 같이 파일 크기를 구합니다.

      fileSize = filelength( fileno(fp) );

fp는 FILE *fp; 로 정의된 파일 포인터입니다.

물론 fopen()으로 파일을 연 상태에만 가능합니다.

그러나 파일 크기를 구한다는 것이 DIR프로그램이 아닌 이상 파일을 열고나서 구하는 거 아니겠습니까?



<클래스 이름 등록방법>

PreCreateWindow(CREATESTRUCT& cs)에서 cs.lpszClass를 변경해주면 될 것 같은데 잘되지 않는 경우가 있습니다. 이부분에서 사용자들이 헤매는 경우가 많이 있죠.

다음의 코드를 활용하시면 잘되실 겁니다.



BOOL CTestDlg::PreCreateWindow(CREATESTRUCT& cs)
{
     if(!CWnd::PreCreateWindow(cs))
          return FALSE;

     HINSTANCE hInst=AfxGetInstanceHandle();
     WNDCLASS wc;

     ::GetClassInfo(hInst, cs.lpszClass, &wc);
     wc.lpszClassName=cs.lpszClass="MyClass";

     return AfxRegisterClass(&wc);
}



<외국 리소스를 한글 리소스로 수정하기>

1. 간단하게 고치기

영국인가 에서 만든 소스를 분석하다가 리소스를 볼려고 누르니 언어가 다르다고 안나오더군요 계속 고민하다가 이럴 때는 rc화일에서

원래 코드

//*****************************************************
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
//*****************************************************



한국말로 수정한뒤 코드

//******************************************************
#if !defined(AFX_RESOURCE_DLL)||defined(AFX_TARG_KOR)
#ifdef _WIN32
LANGUAGE LANG_KOREAN, SUBLANG_DEFAULT
#pragma code_page(949)
#endif//_WIN32
//******************************************************



이렇게 수정해주니 코드가 나오더군요.

물론 다아시겠지만 열기에서 rc화일을 text로 열어서 수정해주셔야겠지요



2. 근본적 해결방법

다 아시겠지만 VC++은 다국어 리소스를 지원합니다.

외국에서 만든 샘플 예제를 Down받아서 리소스를 오픈할때 현재 시스템의 코드 페이지와 다르면 오픈되지를 않습니다.

지원하지 않는 코드 페이지 때문에 리소스가 제대로 오픈되지 않는다면 해당하는 코드 페이지의 .nls(National Language Support)파일을 시스템에 복사해 넣어주면 됩니다.

해당 nls파일은 Win95/98 CD에 있고 이파일(예:cp_1252.nls)을 windows system 폴더안에 복사한 후 레지스트리(\HKEY_LOCAL_MACHINE\System\CurrentControlSet\control\Nls\Codepage)에 수동으로 등록시켜 주면 됩니다.



<F10 키를 처리하기>

도스시절 프로그램에서 F10 키는 주로 풀다운 메뉴를 띄우는데 사용됩니다.

그래서 우리는 F10 으로 메뉴를 뛰우는데 익숙하죠. 윈도에서도 마찬가지로 F10 키가 메뉴를 활성화시키는데 사용되죠. F10 키가 메뉴를 활성화 하는 것을 막고 나의 고유 기능을 실행하게 할려면 어떻게 해야 하죠?

몇가지 방법이 있는데...

1.- SetWindowsHookEx() 의 사용

키보드의 행동을 바꾸기 위해서 SetWindowsHookEx 함수를 사용하는 것입니다.우선 키보드 훅(Hook)을 작성하고 쓰레드 전반에 이 훅을 설치하는 것이죠.코딩이 좀 까다롭지만..오버헤드가 별루 없기 때문에 ..가장 좋은 제안이 될것입니다.

2.- PreProcessMessage() 와 WM_SYSKEYDOWN 테스트

메시지 선처리기인 PreProcessMessage 에서 WM_SYSKEYDOWN 메시지가 발생했을때 F10 키인지 확인하는 것입니다.그러나 매번 조사해야 하기 때문에 (모든 키에 대해서..) 많은 오버헤드를 발생시켜 속도저하를 가져옵니다.

3.- F10 키에 대한 억셀레이터 테이블 작성

추천할만한 다른 좋은 방법입니다. 그러나 잘 안먹힐때가 있어서.

4.- OnSysKeyDown() 핸들러에서 F10 테스트

문서화되어 있는 방법이죠...그런데 ...이해할수 없는건..

MFC 에서는 F10 키에 대한 SysKeyDown 메시지를 SDI View 에 보내지 않습니다. 문제의 소지가 되죠.



<ESC키로부터 Dialog 사라짐을 방지>

ESC키로부터 Dialog 사라짐을 방지하려면



// 멤버 변수
        BOOL m_bClose

// 생성자
        m_bClose = FALSE;

// OnClose
void CKeyDlg::OnClose()
{
        m_bClose = TRUE;
        CDialog::OnClose();
}

// WindowProc
LRESULT CKeyDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
        if( message == WM_COMMAND && wParam == WM_DESTROY )
        {
                if( m_bClose == FALSE )
                        return NULL;
                // esc키로 부터 다이얼로그 사라짐을 방지
                // 요길 FALSE(NULL)로 막아주면
                // (정상적으로는 아래 return 값이 1로 됨)
                // 다음에 message WM_DESTROY, WM_NCDESTROY가
                // 날아오지 않아 종료 되지 않음
        }
        return CDialog::WindowProc(message, wParam, lParam);
}


/// 참고
// dialog에서 esc 또는 close 버튼은  message - 111, wParam - 2 가 날아옴
// return 값이 TRUE 이면
// message - 2에 이어 message -82가 날아옴


//#define WM_COMMAND                      0x0111
//#define WM_DESTROY                      0x0002
//#define WM_NCDESTROY                    0x0082
//#define WM_CLOSE                        0x0010

/* Enter 키 */

버튼 중에 Property에 Default라고 체크가 된 것이 있을 것입니다.

버튼중 하나만이 이 속성을 가질 수 있는데 Enter키를 치면 이 버튼이 실행됩니다.

Default로 IDOK버튼이 먹게 되지요. 해서 다른 버튼에 이속성을 주거나 IDOK버튼의  함수를 고치세요.



<fscanf()에서 쓸데 없는 값 읽지않고 버리기>

화일명: aaa.dat
자료: 1 2.0 3

이럴경우 자료를 fscanf()로 읽어 들일 때 불필요한 자료가 있을 수 있습니다. 만약 위의 데이터를 읽어 들인다면 -> 실제로 2.0은 필요 없는 자료라 가정하고

int a, b;
float dummy;
FILE* fp;

if((fp = fopen("aaa.txt", "r"))== NULL)
   return;

fscanf("%d %f %d", &a, &dummy, &b);
fclose(fp);

일반적으로 필요없는 2.0도 dummy라고 읽어 들인다음 버립니다.

이런 쓸데가 없는 데이터를 읽어 들이면 시간도 낭비고... 에구 코드도 복잡하고....

이럴 경우 *를 사용하시면 아주 좋죠... ^^;

fscanf("%d %*f %d", &a, &b);

요렇게 바꾸면 실수형 자료가 나오면 그냥 넘어 가요...

각각의 데이터 자료형만 맞추어 주면 그냥 안읽고 넘어간 다음에 그 다음 데이터를 읽지요

자신이 만든 데이터라면 이런 경우가 없겠지만 남이 만든 데이터를 읽어 올 경우에

자기에게 불필요한 데이터가 생길 경우 일일이 다 읽어서 버린다면 넘 짜증 나겠지요 ^^;



<Toggle 기능 구현하기>

버튼을 누를 경우 서로 토글되는 기능을 구현 하겠습니다.

static int rwflg = 1;
rwflg = !rwflg;//가장 중요
if(rwflg) {
      // 버튼을 누를 경우 두번째 실행.
} else {
      //버튼을 누를 경우 처음 실행
}



<한글 윈도우에서 일본어 프로그램 빌드하기>

일단. 리소스는 당연히 일본어 코드로 변환되어 있어야 합니다. 한글 윈도우에서 보면 코드가 깨져 보이겠지요? 코드 변환 방법은 Microsoft Word 같은 걸 이용하셔도 되고, 아니면 프로그램을 만드셔도 되구요.. 가능하면 리소스 작업은 일본어 윈도우에서 하시기를 권합니다. Copy&Paste 로 했는데도, 일부코드가 깨어지는 문제가 있더라구요.. 정확히 이유는 모르겠지만, 리소스의 Text 를 붙여넣는 작업은 가능한한 일본어 윈도우에서 하시기를 권합니다. 그리고 빌드하실 프로젝트의 리소스 파일(*.rc 파일) 에는 한국어가 있으면 절대로 안됩니다!! 테스트를 위해서 몇개의 다이얼로그만 일본어로 바꿨는데 빌드가 안되더군요.. 반드시 전체 리소스 파일에는 영어 아니면 일본어 코드만 존재 해야 합니다.

그리고 Resource file (rc 파일들)의 내용을 다음과 같이 바꾸시면 됩니다. 모든 부분을 바꾸어야 하는지는 확실치 않지만, 일단 이렇게 바꾸면 빌드랑 실행에 문제는 없습니다..

1. #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_KOR) 를

    #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_JPN) 으로...

2. LANGUAGE 18, 1 를

    LANGUAGE 17, 1 로...

3. #pragma code_page(949) 를

    #pragma code_page(932) 로...

4. l.kor\\afxres.rc 를

    l.jpn\\afxres.rc 로..

이상입니다.. ^^.. 도움이 됐을까요?? 그리고 주의하실 점은 Font 입니다.. 일본의 폰트에 맞추어 크기랑 이름을 정해줘야만 하지요..



<#과 ##>

'##'에 대안 질문이 나온 김에 '#'과 '##' 대해 이야기를 한번 해볼까 합니다.

먼저 #부터 시작하겠습니다.

#은 C/C++에서 많이 나오는 기호죠. #include 랄지, #define 이랄지. 그런데, #이 다른 용도로 쓰이는 경우가 있습니다.

다음과 같은 코드가 있습니다.

#include <stdio.h>
#define STRING(a) printf(a)

void main(){
    STRING(trowind);
}

매크로를 아시는 분이라면 위에 코드가 어떻게 변환되는지 아실 겁니다. STRING(trowind) 부분이 printf(trowind)로 바뀌겠죠. (이 변환은 preprocessor에서 담당합니다. 자세한 내용은 'Preprocessor' 또는 '전처리기'에대한 자료를 참고하세요.)

이건 명백히 컴파일 에러입니다. trowind라는 변수가 없으니까요.

그럼 이제 STRING() 매크로 함수를 다음과 같이 바꿔보겠습니다.

#define STRING(a) printf(#a)

바꾼거라고는 'a' 앞에 #를 붙인 것 밖에 없습니다. 그러면 위의 STRING(trowind) 는 다음과 같이 바뀝니다.

printf("trowind")

즉 #의 용도는 매크로에서 치환되는 문자열에 큰따옴표를 붙여주는 것입니다. 이걸 잘 이용하면 다음과 같은 기능을 구현할 수 있죠.

#include <stdio.h>
#include <stdlib.h>

// MFC에도 ASSERT란 매크로가 있기 때문에 MY_ASSERT로 합니다.
#defien MY_ASSERT(a) if ( !a ) { printf("Error : "); printf(#a); }

void main() {
    int a = 0;
    int b = 1;
    MY_ASSERT(a > b)
}

(결과는 직접 확인해 보시기 바랍니다.)



##은 #과는 다르게 작동합니다. ##은 주어진 문자열을 그대로 바꿔줍니다. 예를 들어보죠.

다음과 갈은 매크로가 있습니다.

#define RUNTIME_CLASS(class_name) class##class_name;

그럼 다음과 갈이 됩니다.

RUNTIME_CLASS(CMyDocument) -> classCMyDocument

##은 그 사용 예를 찾기가 쉽진 않죠. 하지만, MFC에서는 이 ##을 기가막히게 잘 사용합니다.  MFC로 애플리케이션을 만들 때 기본적으로 만들어지는 클래스 중에 View나 Document 클래스에 보면 DECLARE_DYNCREATE, IMPLEMENT_CYNCREATE 매크로가 있습니다. 이 매크로를 한번 분석해 보세요.



<드라이브 포맷하기>

MSDN에는 설명이 하나도 없네요..



DWORD WINAPI SHFormatDrive(HWND hwnd, UINT drive, UINT fmtID, UINT option)



이런 형식입니다..드라이브는 ID가

0=A, 1=B,2=C....

fmtID는 -1이구여..

option은

0 - 빠른포맷

1 - 전체포맷

2 - 시스템 디스크 작성



<에디트박스에서 커서를 임의의 위치에>

음.. 에디트 박스에서 커서를 뒤쪽이나.. 임의의 위치에 놓을 수 있는 방법을 물어 보시는 분께 도움될 자료입니다.

1. 커서를 맨 끝에

SetSel(-1, -1);

2. 커서를 임의의 위치에

SetSel(n, n);

======================================
1. 응용할 멤버 함수
======================================

에디트 박스의 멤버 함수중에서 커서를 임의의 위치에 놓을 처리를 하는 함수는 SetSel( ... ) 과 ReplaceSel( ... ) 입니다.

두 함수는 많이 사용되기 때문에 아시고 계시리라 믿~~습니다.

  ex) 에디트 박스의 멤버 변수를 다음과 같이 정의된 상태에서 예를 들어 가면서 설명을 드립니다.

      CEdit   변수 m_editMyBox;
      CString 변수 m_strMyBox;

======================================
2. 커서를 맨 뒤쪽에 놓기
======================================

  커서를 맨 뒤쪽에 놓는 방법은 의외로 간단 합니다.

   UpdateData( TRUE );

  //--> 전체를 선택
  m_editMyBox.SetSel( 0, -1 );        

  //--> 현재의 데이터로 다시 치환
  m_editMyBox.ReplaceSel( m_strMyBox );

  //--> 포커스를 에디트 박스로 설정
  m_editMyBox.SetFocus();

이렇게 처리하시면 커서가 데이터의 제일 끝에 놓일 겁니다.

======================================
3. 커서를 임의의 자리에 놓기
======================================

방법은 마찬가지로..SetSel(..) 과 ReplaceSel(...)을 이용하는 것인데요..

어떤 형태로 응용할 수 있냐 하면..

  SetSel( 시작 위치, 끝 위치, ..?? 안살펴 봤음..세번째 인자)에서 보면, 시작 위치를 처음(0)으로 하고 끝 위치를 커서가 놓이고자 하는 위치로 지정한 후에, 현재의 데이터를 처음부터 커서가 놓이고자 하는 위치까지 다른 버퍼에 복사한 후에, ReplaceSel로 복사 합니다.

소스 코드를 보시면 더 쉬우실 겁니다.

UpdateData( TRUE );

//--> 커서가 놓일 위치까지 복사할 임시 버퍼(크기는??)
char cDataBuf[100];

//--> 쓰레기 값을 방지하기 위해.. 초기화
for( int i=0; i<100; i++ )    cDataBuf[i] = NULL;

//--- 6번째에 커서를 위치하고 싶을 경우...
for( i=0; i<6; i++ )
      cDataBuf[i] = m_strMyBox[i];

//****보충 설명.. CString 형의 데이터 같은 경우에서****

데이터를 원할때는 버퍼 형태로 사용을 하셔도 됩니다.

음.. 무슨 말이냐 하면.. 제가 CString 형으로 선언된 멤버 변수 m_strMyBox의 데이터를 임시 버퍼에 복사하는 과정에서 m_strMyBox[i]라고 사용을 했는데..

m_strMyBox는 에디트 박스의 멤버 변수이거든요.

위와 같은 방법을 복사를 하셔도 되고.. 다른 방법도 있겠죠..

//--> 원하는 위치만큼 지정
     m_editTest.SetSel( 0, 6 );

//--> 바꾸면 되겠죠.. 복사된 내용으로..
     m_editTest.ReplaceSel( cDataBuf );

//--> 포커스를 놓으면.. 끝..
     m_editTest.SetFocus();



이렇게 처리하면.. 에디트 박스에서 제일 끝이나 임의의 자리에 커서를 놓을 수 있군요..



<릴리즈 버젼 실행시 런타임 에러 찾아내기>

릴리즈 버젼 실행시 에러가 났을 경우..참 곤란했죠.

물론 고수님들께서야 이미 알고계시겠지만..

나름대로 엄청 가슴이 아팠습니다. 디버그모드는 괜찮은데..릴리즈는 에러를 때리구..어디서 나는지조차 모르겠구...그때 쓸만한 잼있는 방법입니다.(물론 모르는 분에 한해서..^^)



Step 1.

프로젝트 세팅 들어가면..

Link라는 탭 선택해서..보면

Generate Mapfile이라구 있을꺼랍니다.

고걸 선택해서 샥 돌려보면..map파일이 release디렉토리에 생기죠.



Step 2.

그리구 나선..에러를 만들어 돌려보고, 어느 주소에서 에러가 났는지 캡춰

그리구서 map파일에 있는 주소랑 매치해 보는겁니다.

그럼 어느 함수에서 에러가 났는지 알 수 있죠~



<모든 Top Level Windows 에게 메세지 보내기>

SendMessage 아시져? 거기에서 HWND 인자에 HWND_BROADCAST을 넣으면,모든 윈도우에게 메세지가 간답니다..



<일반적인 윈도 소멸 순서>

MFC framework에서, 사용자가 frame 윈도를 닫게되면, 윈도는 기본적으로 OnClose 핸들을 call 한다. OnClose 내부에서는 DestroyWindow를 호출한다. 가장 마지막으로 호출되는 함수는 OnNcDestroy이다. 이 함수는 약간의 정리를 하는 함수로 윈도를 정리하기 위하여 기본적으로 불려지는 함수 이다. OnNcDestroy함수 내부에서는 PostNcDestroy함수를 호출한다.

afx_msg OnClose( )

Framework은 CWnd나 application을 종료 시키기 위해서 이 함수를 마치 시그날처럼 호출한다. 내부의 기본적인 구현은 DestroyWindow를 호출한다.

virtual BOOL DestroyWindow( )

Destroy Window 함수는

  1)  CWnd내부에 있는 윈도를 destroy 한다.

  2)  또한 윈도를 해제하고 입력 포커스를 제거하기 위한 적절한 message를 윈도에게 보낸다.

  3)  윈도의 메뉴 제거  

  4)  application queue를 비운다.

  5)  timer를 제거

  6)  clipboard의 소유주 제거

  7)  Clipboard-viewer chain을 끊는다. ( CWnd가 viewer chain의 맨 위에 있을 경우 )

  8)  WM_DESTROY( OnDestroy )와 WM_NCDESTROY( OnNcDestroy ) 메시지를 윈도에게 보낸다.

하지만 아직 CWnd 객체는 destroy하지 않는다.



이 함수를 오버라이드 하더라도 이 함수를 호출하지 않아도 된다. ( 왜냐면 시스템이 알아서 호출을 해주니까..) 뭐 굳이 하고 싶다면 해도 된다. 만약 내부에 자식 윈도가 있더라도 걱정하지 말자 자식들을 먼저 destroy한 다음에 자신이 죽으니까....



afx_msg void OnDestroy()

Framework이 CWnd에게 현재 CWnd가 소멸되고 있는 중이라고 알려 주기위하여 호출 한다. OnDestroy함수는 CWnd 함수가 화면에서 사라진 다음에 호출된다.

OnDestroy is called first for the CWnd being destroyed, then for the child windows of CWnd as they are destroyed. It can be assumed that all child windows still exist while OnDestroy runs.



afx_msg void OnNcDestroy()

Client가 아닌  영역이 destroy될 때 Frame work에 의하여 불려지는 함수이다. 윈도가 소멸될때 마지막으로 불려지는 함수이다. 기본적으로 약간의 정리를 한후, PostNcDestroy 함수를 호출한다. 대게는 이 함수를 오버라이드 하지 않지만 만약 하게되면 내부에서 상위 클래스의 OnNcDestroy를 호출해야만 한다. 그렇지 않으며녀 내부적으로 윈도를 위해 할당된 메모리가 free되지 못한다.



virtual void PostNcDestroy()

윈도가 소멸된 후 OnNcDestroy함수에 의하여 불려지는 기본함수이다. 사용자들이 상속받아 만든 클래스의 정리를 위한 코드를 삽입하면 된다. ( ex. delete this; )

위의 함수를 그냥 대충 보지 않고 세심히 본 사람이라면 이제 자신이 삽입하기를 원하는 코드를 어느부분에 삽입을 해야되는지 알 수 있을 것이다. 그럼 아래의 예를 통하여 알아보자.

먼저 View class에서는 3가지 소멸 핸들러를 제공한다. 다음을 보자.

BOOL CAniView::DestroyWindow()
{
         // TODO: Add your specialized code here and/or call the base class

         return CView::DestroyWindow();
}

void CAniView::OnDestroy()
{
        CView::OnDestroy();

        // TODO: Add your message handler code here
}

void CAniView::PostNcDestroy()
{
        // TODO: Add your specialized code here and/or call the base class

        CView::PostNcDestroy();
}

위에 나열 된 순서가 바로 view가 닫힐 때 불려지는 순서이다.   우리가 유심히 살펴 보아야할 부분은 TODO의 위치이다. 과연 어떤 이유에서 각각의 TODO의 위치가 결정 되는가?

만약  윈도가 없어지기 전에 작업을 해야할 일이 있다면   4번 중에 코드를 삽입하자. 또한 윈도가 사라진 다음에 작업을 해야 한다면 22번 줄에 삽입을 한다. delete this 함수를 사용한다면 25번째 줄에 삽입하자.

view에서는 제공되지 않지만  OnClose 함수도 TODO의 위치를 잠시 보자.

void CAboutDlg::OnClose()
{
      // TODO: Add your message handler code here and/or call default

      CDialog::OnClose();
}



<프로파일링[profiling]>

끝날때까지 어느 부분에서 어느 정도의 시간이 걸렸는지를 알 수가 있습니다.. 기타.. 다른 기능도 많이 있구요..  

여러기능중에 function timing만 써봤거든요..

output 창에 나온 결과를 간단히 써보면..



program Statistics
------------------
    Command line at 2001 Jan 15
08:51: "D:\myProg\TestApp\PropertyTest\Debug\PropertyTest"
    Total time: 2848.924 millisecond
    Time outside of functions: 11.820 millisecond
    Call depth: 15
    Total functions: 141
    Total hits: 1868
    Function coverage: 51.8%
    Overhead Calculated 6
    Overhead Average 6

Module Statistics for propertytest.exe
--------------------------------------
    Time in module: 2837.104 millisecond
    Percent of time in module: 100.0%
    Functions in module: 141
    Hits in module: 1868
    Module function coverage: 51.8%

        Func          Func+Child           Hit
        Time   %         Time      %      Count  Function
---------------------------------------------------------
    2599.647  91.6     2716.741  95.8       44 CWinThread::PumpMessage(void) (mfc42d.dll)
     130.907   4.6      131.192   4.6      207 CWnd::DefWindowProcA(unsigned int,unsigned int,long) (mfc42d.dll)
      58.981   2.1     2835.973 100.0        1 CDialog::DoModal(void) (mfc42d.dll)



어느 함수가 시간이 젤 많이 걸린지 나오죠.. ^^;;

프로그램짜다가 나중에 한번 돌려보면..

어느 부분에서 퍼포먼스가 떨어지는지 확인할 수 있겠죠..

잡설은 고만하고..



Project 메뉴 Setting에서 Link 탭을 선택하면..

Enable Profiling 에 체크하시고..

Rebuild All 하신 후에..

Build메뉴 젤 아래에 Profile해주시면..

선택창이 뜨는데.. 선택하시고

프로그램 실행한후에.. 종료하면 output창에 결과가 나옵니다..



Windows 2000에서 profile 메뉴가 활성화되지 안된다는 분이 계시는데 해결 방법은 다음과 같습니다.

Visual Studio가 설치될 때 Profile과 관련된 레지스트리 정보를 HKEY_CURRENT_USER 아래에 써넣습니다. 그래서 Visual Studio를 설치할 때 사용했던 계정으로 로그인하지 않으면 profile이 활성화되지 않습니다.

이를 해결하려면 VC를 설치할 때 접속했던 계정으로 로그인을 하던가, 아니면 레지스트리에 값을 써넣어주면 됩니다. text editor를 이용하여 a.reg라는 파일을 만들고 다음의 내용을 넣어줍니다.

a.reg의 내용

REGEDIT4
[HKEY_LOCAL_MACHINE\Software\Microsoft\DevStudio\6.0\General]
"ProfilerInstalled"=dword:1


이 파일을 더블클릭하면 레지스트리에 값이 써넣어지고 VC에 profile 메뉴가 활성화됩니다.

이 정보는 MSDN에서 Q224382로 찾아보시면 됩니다.



<해상도 변경하기>

말 그대로 프로그래밍 상에서 현재의 해상도를 원하는데로 변경할 수 있습니다.

전역함수로 ChangeDisplaySettings 라는 함수가 있더군요.

암데서나 불러 쓸수 있죠.  

이전 해상도를 보관했다가 다시 복귀할 수 있도록 해보겠습니다.



현재의 해상도를 얻어오는 함수는 GetSystemMetrics 입니다.

먼저 현재의 해상도를 저장할 변수를 선언합니다.

class CMainFrame : public CMDIFrameWnd
{
      /// 생략
      // Attributes
 
public:
      int screenY;
      int screenX;
      /// 생략
}



초기화는 요렇게..

// 현재 해상도 보관
screenX = GetSystemMetrics(SM_CXSCREEN) ;
screenY = GetSystemMetrics(SM_CYSCREEN) ;

인자로 받은 해상도로 변경하는 함수를 만듭니다.

void CMainFrame::ChangeDisplay(int Width, int Height)
{
    DEVMODE mode ;
    mode.dmBitsPerPel = 16;   // 16비트 칼라로 변경
    mode.dmPelsWidth  = Width;
    mode.dmPelsHeight = Height;
    mode.dmFields     = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
    ChangeDisplaySettings(&mode, CDS_FULLSCREEN );
    ShowWindow( SW_SHOWMAXIMIZED );
}

전 16비트 칼라로만 변경하도록 했는데, 저것두 인자로 받아서 처리하면 원하는 칼라로 바꿀수 있겠죠??? 그리구 원하는 곳에서 함수를 호출~~~

    // 1024 x 768 로 변경    
    ((CMainFrame*)AfxGetMainWnd())->ChangeDisplay(1024, 768) ;    

프로그램이 종료할때 이전 해상도로 복귀하도록 해야 겠죠? 이건 DestroyWindow() 에서 처리하면 됩니다.

BOOL CMainFrame::DestroyWindow()
{
    if(screenX != GetSystemMetrics(SM_CXSCREEN) ||
        screenY != GetSystemMetrics(SM_CYSCREEN))
        ChangeDisplay(screenX, screenY) ;
    return CFrameWnd::DestroyWindow();
}

이전 해상도를 저장해뒀으니까 그걸로 다시 복귀한거죠.

암튼, 위에서처럼 작업하면 원하는 해상도로 변경시켜서 작업할 수 있죠.



참, 도움말을 뒤져보니깐 윈도우 메시지중에 WM_DISPLAYCHANGE 라는게 있더군요.

근데 이 메시지는 클래스위저드에 들어있질 않네요.

99999999WM_DISPLAYCHANGE 메세지는 직접 호출할 수는 없을 겁니다.

다만 OS가 화면 해상도가 변경되었을 때 모든 Window에게로

보내주는 Message일 겁니다.



해상도 복원시에 변수로 해상도를 가지고 있다가 복원할 필요 없이 그냥

    ChangeDisplaySettings(NULL, 0);

이라고만 하면 화면 복원 됩니다. 이 함수는 해상도 변경만 하지 디스플레이

등록정보 자체를 바꾸진 않기 때문에 위 방식대로 함수를 호출하면 디스플레이

등록정보에 있는 해상도로 그대로 바꿔주기 때문에 더 안정적입니다.



원하는 해상도를 모니터가 받아줄수 있는지 테스트 하시고 바꿔야죠.

그 부분이 빠졌군요.

EnumDisplaySettings를 이용하시면 됩니다.



< 화면 지우기>

1) OnDraw 와 InvalidateRect 이용

VC++ 에는 Basic의 CLS 명령과 같은 간단히 화면을 지우는 명령이 없습니다. 이때문에 다음과 간은 방법을 사용합니다. 예를 들면

CDC::Rectangle(x1,y1,x2,y2);

으로 사각형을 그린후 안을 배경색으로 모두 채우면 지운 것과 같은 효과를 내게 됩니다. windows는 외관상 화면에 문자출력이 되어 있어도 내부적으로는 모두 이미지를 그리기 때문에 이 명령으로 화면을 지울수가 있습니다.

OnDraw 함수와 InvalidateRect 함수를 사용하는 것으로도 간단하게 화면을 지울수 있습니다.

1 OnDraw 함수에 if문을 나누어 놓아서 아무 것도 표시하지 않는 기능을 넣습니다.

2 CWnd::GetclientRect 함수에서 클라이언트 영역 크기를 알아 낸다.

3 그 크기를 파라미터로 하여 InvalidateRect 함수를 실행하면 됩니다.

그럼 좀더 자세히 알아 보기로 하겠습니다.

CRect myRECT;                // x1,y1,x2,y2 정보를 지정하는 오브젝트를 확보
GetClientRect(&myRECT);  // 클라이언트 영역의 x1,y1,x2,y2를 myRECT에 설정.
InvalidateRect(&myRECT); // 이 사각형 영역을 다시 그리기 설정

이 처리는 클라이언트 영역 전체의 크기를 myRECT에 설정하여 그 myRECT에서 나타내는 범위를 다시 그리고 있습니다. 혹시 부분적으로 다시 그리기를 한다면,

myRECT.SelRect(0,0,200,200);//클라이언트 영역의 (0,0,)-(200,200)을 지정
9InvalidateRect(&myRECT);//그 사각형 영역을 다시 그리기 지정.

과 같이 하면 되구요, 더욱이 "클라이언트 영역만"을 지우려고 할때에는 사전에 크기를 알아 놓지 않더라도 다음과 같이 아주 간단하 방법으로 지울수 있습니다.

InvalidateRect(NULL);



2) ExtTextOut 이용

제가 알고 있기론 화면 지우는 방법 중에서 가장 빠른 걸로 알고 있습니다.

처음은 똑같습니다.

CRect mRect;
GetClientRect(&mRect);

이 다음 InvalidateRect가 아닌.. ExtTextOut을 씁니다.

  텍스트의 백그라운드 색깔을 칠하고 싶은 화면색깔로 정하고, 빈문자열을 쓰라는 겁니다. 즉(파라미터로 CDC *pDC를 받는다는 가정하에)

pDC->SetBkColor(필요한 색깔);
pDC->ExtTextOut(mRect.left, mRect.top, ETO_OPAQUE, &mRect, "", 0, NULL);

이렇게 빈문자열을 죽~ 라이팅하면 문자열의 배경색으로만 칠해집니다.

말하자면 화면을 지우는 효과이죠.

디폴트를 사용하시겠다면 SetBkColor는 사용하지 않아도 됩니다.

화면을 단일색으로 지우는데는 그 이상 빠른 방법이 없죠.

MFC의 CDC::FillSolidRect도 내부 소스를 보면 ExtTextOut을 사용합니다.

다만 ExtTextOut으로 사각형을 칠했을 때는 DC의 배경색이 바뀐다는 사실을 잊으면 안됩니다.



3) PatBlt 이용

WM_PAINT에서

CPaintDC dc(this);
CRect rect;
GetClientRect(rect);

dc.PatBlt(0, 0, rect.Width(), rect.Height(), WHITENESS);



- the end of this article -

No comments:

Post a Comment