2009년 4월 25일 토요일

[TreeCtrl] 아이템 영역, 비 아이템 영역에 따른 마우스 오른쪽 버튼Context 메뉴 출력

아이템 영역, 비 아이템 영역에 따른 마우스 오른쪽 버튼 Context 메뉴 출력

 트리 컨트롤에서 트리 아이템 위에서 마우스 오른쪽 버튼에 의한 Context 메뉴 출력,  트리 아이템 영역이
아닌 곳에서 마우스 오른쪽 버튼에 의한 Context 메뉴 출력은 리스트 컨트롤과 크게 다르지 않다
참조) http://six605.tistory.com/302

 하지만 리스트 컨트롤과 조금 다른 점이 있다면 아이템 영역이 어디까지 인가 하는 것이다.
아래와 같이 트리 컨트롤 아이템이 출력되어 있는 경우




"응용 프로그램" 트리 아이템의 영역은 그 라인 전체가 된다, 즉 가로길이 끝까지.



특정 경우에 "응용 프로그램" 위에서만 마우스 오른쪽 버튼에 의한 Context 메뉴를 출력 시키고 싶을 때가 있다.



void CLogTreeView::OnNMRClick(NMHDR *pNMHDR, LRESULT *pResult)
{
    UNUSED_ALWAYS(pNMHDR);

    CPoint CurrentPosition;
    ::GetCursorPos(&CurrentPosition);

    CTreeCtrl &TreeCtrl = GetTreeCtrl();

    TreeCtrl.ScreenToClient(&CurrentPosition);

    m_hTreeItem = TreeCtrl.HitTest(CurrentPosition);
    if (NULL == m_hTreeItem)
        return;

    CRect rect;
    TreeCtrl.GetItemRect(m_hTreeItem, &rect, TRUE);
    if (!rect.PtInRect(CurrentPosition))
        return;

    m_sMouseRPopupLogName = TreeCtrl.GetItemText(m_hTreeItem);

    ::GetCursorPos(&CurrentPosition);
    CMenu MenuTemp;
    CMenu *pContextMenu = NULL;
    MenuTemp.LoadMenu(IDR_MENU_LOGTREE);
    pContextMenu = MenuTemp.GetSubMenu(0);
    pContextMenu->TrackPopupMenu(TPM_LEFTALIGN, CurrentPosition.x, CurrentPosition.y, ::AfxGetMainWnd());

    *pResult = 0;
}


소스 코드의 16 ~ 19 라인의 코드 처럼
CTreeCtrl::GetItemRect() 함수로 텍스트 영역만 얻고자 하는 트리 아이템 핸들을 전달한다.
그런 다음 CRect::PtInRect() 함수를 이용하여 마우스 포인터의 위치가 트리 아이템의 텍스트 영역에 있는지 판별하면 된다.

2009년 4월 24일 금요일

[ListCtrl] 아이템 영역, 비 아이템 영역에 따른 마우스 오른쪽 버튼Context 메뉴 출력

아이템 영역, 비 아이템 영역에 따른 마우스 오른쪽 버튼 Context 메뉴 출력



  트리컨트롤과 마찬가지로 리스트 컨트롤에서 출력되어 있는 아이템 위에서 마우스 오른쪽 버튼을
클릭 하였을때 팝업 되는 Context 메뉴와 아이템 영역이 아닌 곳에서 클릭 하였을 경우 팝업되는
Context 메뉴가 다르게 하고 싶은 경우가 있습니다.

예를 들어 Windows 의 이벤트 뷰어를 확인해 보면 두가지 상황에 따라 다른 Context 메뉴를 출력하고 있습니다.

- 아이템 위에서 Context 메뉴 출력



- 아이템 영역이 아닌 곳에서 Constext 출력






■ ListView 에서의 구현
void ChRmtEvwrView::OnNMRClick(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast(pNMHDR);
    UNREFERENCED_PARAMETER(pNMItemActivate);

    CPoint CurrentPosition;
    ::GetCursorPos(&CurrentPosition);

    INT nIndex = -1;
    CListCtrl &ListCtrl = GetListCtrl();
    
    ListCtrl.ScreenToClient(&CurrentPosition);
    nIndex = ListCtrl.HitTest(CurrentPosition);

    if (-1 == nIndex)
    {
        // 아이템 영역이 아닌 곳에서 마우스 오른쪽 버튼을 선택한 경우
    }
    else
    {
        // 아이템 영역에서 마우스 오른쪽 버튼을 선택한 경우
    }
...

    ::GetCursorPos(&CurrentPosition);
    CMenu MenuTemp;
    CMenu *pContextMenu = NULL;
    MenuTemp.LoadMenu(IDR_MENU1);
    pContextMenu = MenuTemp.GetSubMenu(0);
    pContextMenu->TrackPopupMenu(TPM_LEFTALIGN, CurrentPosition.x, CurrentPosition.y, ::AfxGetMainWnd());

    *pResult = 0;





::GetCursorPos() 함수를 이용하여 마우스 커서의 Screen 영역에서의 마우스  위치를 구하게 됩니다.
리스트 컨트롤에서 마우스로 선택한 곳이 아이템이 있는 영역인지 확인 하기 위해서는 CListCtrl::HitTest()
함수를 사용합니다. CListCtrl::HitTest() 함수는 인수로 Screen 영역에서의 마우스 위치가 아닌 Client 영역
에서의 마우스 위치를 전달해야 하므로 CListCtrl::ScreenToClient() 함수로 변경해 주어 인수로 전달해야
합니다.

이렇게 하여 CListCtrl::HitTest() 함수의 리턴값으로 마우스가 위치한 곳의 아이템의 인덱스가 리턴이 됩니다.
만약 마우스가 위치한 곳에 아이템이 없다면 -1 을 리턴하게 됩니다.





cf) Screen 좌표, Client 좌표

2009년 4월 22일 수요일

응용 프로그램의 side-by-side 구성이 잘못되어 응용 프로그램을 시작하지 못했습니다.

응용 프로그램의 side-by-side 구성이 잘못되어
응용 프로그램을 시작하지 못했습니다.





 Visual Studio 2008 로 작성한 프로그램을 Vista 에서 실행 시키려 하니까 다음과 같은 에러 창이 뜬다.




경고 창의 지시대로 이벤트 뷰어로 어떤 로그가 남겨져 있는지 확인해 보았다.



무슨 소리인지 알겠는가??  종속 어셈블리 관련 어떤 파일이 없는 것 같다.


MSDN 에 위와 같은 문제를 해결 할 수 있는 방법이 나와있는 페이지가 있다.
http://msdn.microsoft.com/ko-kr/library/ms235512.aspx


가장 간단한 방법은 Visual C++ 재배포 가능 패키지를 응용프로그램이 동작할 환경에 설치 하는 것이다.
응용프로그램을 개발한 환경에 맞는 패키지를 선택해서 설치 하면 된다.
http://six605.tistory.com/256


아!! 그리고 하나 더
당연히 Debug 모드로 컴파일한 실행 파일은 실행이 안될 것이다.
배포 할 때는 Release 모드로 컴파일한 실행 파일을!!

2009년 4월 18일 토요일

INT_PTR, __int64

INT_PTR, __int64




  책에 있는 소스 코드를 보다가 INT_PTR 이라는 데이터 타입을 보게 되었다.
MSDN 의 Windows Data Type 을 찾아 보면 다음과 같이 나온다.



 포인터의 정밀성을 위한 부호있는 정수형 타입(Signed integer type) 이다.
포인터 연산을 수행하기 위한 정수형 타입으로의 캐시팅시 사용된다.
이 타입은 BaseTsd.h 에 정의 되어 있다.


 정의된 선언을 보면 _WIN64 플래그가 선언되어 있으면(64비트 환경에서) INT_PTR 은 __int64 형과 같다.
_WIN64 가 아닌 환경 (32비트 환경) 에서는 int 형과 같다.




1. 그렇다면 64비트 환경과 32비트 환경에서 포인터의 정밀도에 차이가 있는 것일까?

  지금까지의 환경, 즉 32비트 환경에서 프로그래밍을 할 때 포인터의 크기는 4Byte 라고 공식처럼
알고 있다. 따라서 포인터 값을 4Byte 변수에 저장 하여도 아무 문제가 없다.
#class MyClass* pMyPointer;
DWORD dwValue = (DWORD) pMyPointer;


하지만 64비트 환경에서 위와 같은 코드는 문제를 일으킬 수 있다.
DWORD 값은 32비트 크기의 변수지만 pMyPointter 포인터 값은 64비트 값을 갖기 때문이다.
강제 캐스팅 이니까 포인터 값의 값이 잘릴테고 dwValue 는 엄한 곳을 가리키기 때문에
잘못된 메모리 영력 침범으로 프로세스가 종료될 것이다.


그래서 등장한 것이 INT_PTR

class MyClass* pMyPointer;
INT_PTR dwValue = (INT_PTR) pMyPointer;


 32비트, 64비트 환경에 관계없이 dwValue 변수에는 pMyPointer 포인터 값을 저장할 수 있다.

 결국 INT_PTR 의 사용 목적은 int 형의 포인터 값을 32비트와 64비트에서 그 값이 달라질 수 있을때
INT_PTR로 호환성을 맞쳐주는데 목적이 있다고 생각이 된다.


※ 근데 포인터 값을 굳이 다른 정수형 타입으로 저장해야 하는 경우가 있을까?
   아직 겪어보지 못했는데...




2. 32비트 환경에선 포인터 값의 크기는 항상 4Byte??
  포인터의 크기는 컴파일러 정책에 따라 달라질 수 있다. 즉 항상 4Byte 값을 나타내지는 않는다.
cf) http://kldp.org/node/39915?page=1
  32비트 환경에서 CPU는 데이터 처리의 단위가 4Byte 일 때 가장 좋은 성능을 가지므로
그렇게 정한 것이라고 생각 된다.




3. __int64??
  부끄럽게도 __int64 라는 데이터 타입을 대학교때는 한번도 보질 못했다 -_-;;;
64bit signed integer 값을 가지며 값의 표현 범위는 –9223372036854775808 ~ 9223372036854775807 이다.

2009년 4월 14일 화요일

View(뷰) 간의 활성화 뷰 변경 - CFrameWnd::SetActiveView()

View(뷰) 간의 활성화 뷰 변경 - CFrameWnd::SetActiveView()
 아마도 분할 윈도우로 나누었을 경우 특정 순간에 현재 뷰에서 다른 뷰를 활성화(포커스) 해주어야 할 경우가 있다.
예로 트리뷰에서 Tab 키 를 입력 받으면 리스트 뷰로 포커스를 이동해야 하는 경우가 있을 것이다.

이때는 CFrameWnd:;SetActiveView() 함수를 이용하면 된다.
CMainFrame 클래스가 CFrameWnd 의 파생 클래스니까 CMainFrame 에서 호출하면 된다.


// 분할 윈도우에서 활성하 시키고자 하는 뷰의 포인터를 얻는다.
CWnd *pWnd = m_wndSplitter2.GetPane(0, 0);
ChRmtEvwrView *pView = DYNAMIC_DOWNCAST(ChRmtEvwrView, pWnd);

// 지정한 뷰를 활성화 시킨다.
SetActiveView(pView);



cf ) CFrameWnd::SetActiveView()

2009년 4월 7일 화요일

error LNK2005: "..." .obj에 이미 정의되어 있습니다.

 해당 에러가 나는 경우는
Visual C++에서 CRT 라이브러리와 MFC 라이브러리가 잘못된 순서로 링크되면 LNK2005 오류가 발생한다.

참고)
http://support.microsoft.com/kb/148652/ko

하지만 나의 경우는 그런것 같지 않고...
CAaa 라는 클래스를 CBbb 클래스의 정의 파일(Bbb.h) 파일에 넣어두고
프로젝트에는 CAaa.cpp, CAaa.h 파일을 그대로 놔둬서 생기는 문제였다.
컴파일 타임 링크 에러.

프로젝트에서 CAaa.cpp, CAaa.h 파일을 삭제 시켜주니까 해결 되었다.

2009년 4월 6일 월요일

프로그램 중복 실행 방지

 별다른 설정을 하지 않았다면 생성한 프로그램은 기본적으로 중복 실행이 가능하다.
중복 실행을 방지하기 위해선 CXXApp 클래스의 InitInstance() 함수에 다음과 같이 작성한다.



BOOL ChRmtEvwrApp::InitInstance()
{
    HANDLE hEvent = NULL;


    hEvent = ::CreateEvent(NULL, FALSE, TRUE, ::AfxGetAppName());
    if (NULL == hEvent)
       return FALSE;

    if (ERROR_ALREADY_EXISTS == ::GetLastError())
       return FALSE;
   
     ...
  


   return TRUE;
}



::CreateEvent() 함수는 스레드간의 작업 순서나 시기를 조정해 주는 동기화 객체이다.
정확히 말해선 Kernel Object 이다.

함수의 첫번째 인자로는 보안특성을 설정해 준다. NULL을 주면 해당 Event Kernel Object의
핸들을 자식 프로세스에게 상속하지 않는다는 것이다. 여기선 이벤트 핸들을 가지고 특별한
작업을 하는 것은 아니므로 NULL을 준다.

두번재 인자로는 이벤트 종류를 설정하는데 FALSE값을 주어 수동 리셋 이벤트 객체를
만들어 주었다. (여기선 그리 중요한 설정 사항이 아니다.)

 세번째 인자로는 이벤트 객체의 처음 상태를 설정 하는데 TRUE로 줌으로써 신호상태 이벤트
를 만들어 주었다.

 네번째 인자가 중요하다. 네번째 인자로는 이벤트 객체의 이름을 지정해 준다. 이 이름은
나중에 이벤트 객체를 열 때 사용한다.

 이렇게 처음 프로그램을 실행 시키면 ::CreateEvent() 함수는 이벤트 객체를 리턴하며 아래
문장을 실행하고 TRUE를 리턴한다.

 프로그램을 실행 시킨 상태에서 또 이 프로그램을 실행 시키면 ::CreateEvent() 함수는
네번째 인수로 이벤트 객체를 만들려고 시도한다. 하지만 네번째 인자로 주어지는 이름이
처음 프로그램을 실행시켰을 때 이벤트 객체를 만든 이름과 동일한 이름으로 이벤트 객체를
만들려고 하므로 ::CreateEvent() 함수는 처음 프로그램이 만든 이벤트 객체를 리턴한다.

이 때 ::GetLastError() 함수를 호출하면 ERROR_ALREADY_EXISTS 를 리턴한다.
이 값일 때 프로그램을 바로 종료하게 만들면 프로그램의 중복 실행을 막을 수 있다.
어떻게 보면 프로세스간 동기화 기법인 이벤트를 이용한 것이지만 이벤트 객체가
Kernel Object 라는 것을 아는게 더 선수 지식 같다.

Kernel Object 는 프로세스간 공유될 수 있지만 각 프로세스에서 동일한 핸들 값을 갖지는
않는다. (프로세스 한정적)

2009년 4월 1일 수요일

Warning: calling DestoryWindow in CWnd::~CWnd OnDestroy orPostNcDestroy in derived class will not be called

Warning: calling DestoryWindow in CWnd::~CWnd
OnDestroy or PostNcDestroy in derived class will not be called



 윈도우 객체(대화상자 등)를 delete로 삭제 하려고 했을때
위와같은 경고 메시지가 발생한다.

 윈도우 객체를 파괴하기 위해 delete 를 사용하면 안되고,
윈도우 객체를 파괴하기 위해선 DestoryWindow() 를 사용해야 한다.


cf) http://ssmhz.tistory.com/176

모달리스(Modeless) 대화상자의 삭제

모달리스(Modeless) 대화상자의 삭제


 Modeless 대화상자를 만들었다.
보통 아래와 같이 생성할 것이다.


m_pRecvWaitingDlg = new CRecvWaitingDlg;
m_pRecvWaitingDlg->Create(IDD_RECV_WAITING);
m_pRecvWaitingDlg->ShowWindow(SW_SHOW);


대화상자 종료 및 메모리 해제


if (NULL == m_pRecvWaitingDlg)
        return;

m_pRecvWaitingDlg->PostMessage(WM_CLOSE);
m_pRecvWaitingDlg = NULL;


해당 대화상자에서 WM_CLOSE 메시지 처리를 다음과 같이 구현해 주면
대화상자가 알아서 자기한테 할당된 메모리를 해제시켜 준다.



void CRecvWaitingDlg::OnClose()
{
    m_ProgressRecvWaiting.SetPos(m_nMaxRange);
    KillTimer(PROGRESS_TIMER_ID);
    DestroyWindow();
}

void CRecvWaitingDlg::PostNcDestroy()
{
    delete this;

    CDialog::PostNcDestroy();
}




But! Memory leak 이 발생...
다른때는 잘 되다가 이번엔 왜 안될까?

별 수 없이 WM_CLOSE 와 직접 DestoryWindow() 를 호출해 주었다.

if (NULL == m_pRecvWaitingDlg)
        return;

m_pRecvWaitingDlg->PostMessage(WM_CLOSE);
m_pRecvWaitingDlg->DestroyWindow();m_pRecvWaitingDlg = NULL;

error C2065: 'IDD_RECV_WAITING' : 선언되지 않은 식별자입니다.

error C2065: 'IDD_RECV_WAITING' : 선언되지 않은 식별자입니다.



 그닥 코딩을 잘 못한것 같지는 않지만
특정 헤더파일을 include 하면 위와 같이 에러가 발생한다.
말 그대로 선언이 되어 있지 않다는 소리인데...
잘 되다가 뜸금없이 안된다니...

 헤더파일의 include 시 발생 했으니까 include 순서에 의해 발생한 것 같다.
자세한 이유는 모르겠다..



■ 해결 방법
에러가 발생한 헤더 파일에
#include "Resource.h"
를 추가시켜 주면 된다.