<CListCtrl에 컬럼 넣기>

윈도우 탐색기를 보면 크기, 종류, 변경한 날짜 라는 컬럼이 있는데, 제 프로그램에도 이런 식으로 컬럼을 만들고 싶습니다.

 먼저 'LV_COLUMN lvcomumn'이라고 변수를 지정합니다.

▶ LV_COLUMN의 구조

typedef struct _LV_COLUMN {
        UINT mask;
        int fmt;
        int cx;
        LPSTR pszText;
        int cchTextMax;
        int iSubItem;
} LV_COLUMN

 이렇게 정의된 LV_COLUMN 구조체의 첫 번째 인자인 'UINT mask'로 구조체 멤버변수의 유효여부를 지정할 수 있는데, 이때 값은 하나 이상일 수도 있고, 아무런 값을 갖지 않을 수도 있습니다. 두 번째 'int fmt'는 멤버변수에 따라 정렬방식 (LVCFMT_CENTER : 가운데 정렬, LVCFMT_LEFT : 왼쪽 정렬, LVCFMT_RIGHT : 오른쪽 정렬)을 결정할 수 있습니다. 리스트 뷰 컨트롤의 맨 왼쪽에 있는 컬럼은 왼쪽 정렬입니다.
 그리고 'int cx'는 컬럼 넓이로 픽셀 단위이고, 'LPSTR pszText'는 컬럼의 맨 위에 들어가는 컬럼명을 가진 스트링 포인터입니다. 'int cchTextMax'는 pszText에서 얻은 버퍼의 크기를 지정해 줄 수 있는데 꼭 지정할 필요는 없습니다. 그리고 'int iSubItem'은 컬럼의 인덱스입니다. 여기서 iSubItem을 변경하면 이름, 크기, 종류, 변경한 날짜의 순서를 바꿀 수 있습니다.
 이렇게 구조체의 내용을 넣은 다음 CListCtrl의 멤버함수인 int InsertColumn(int nCol, const LV_COLUMN* pColumn)을 사용하면 컬럼을 삽입할 수 있습니다.

void CMyList::FillColumn()
{
        CRect rect;
        LV_COLUMN lvcolumn;
        TCHAR rgtsz[4][10] = {_T("이름"), _T("크기"), _T("종류"), _T("변경날짜")};
        m_listctrl.GetClientRect(&rect);
        for(int i=0; i<4; i++) {  // 4는 컬럼의 개수
                lvcolumn.mask = LVCF_FMT | LVCF_SUBITEM |
                        LVCF_TEXT | LVCF_WIDTH;
                lvcolumn.fmt = LVCFMT_LEFT;
                lvcolumn.pszText = rgtsz[i];
                lvcolumn.iSubItem = i;
                lvcolumn.cx = rect.Width() / 4;    // 4는 컬럼의 개수
                m_listctrl.InsertColumn(i, &lvcolumn);
                // CListCtrl m_listctrl 으로 선언해 준다.
        }
}               

 구조체를 사용하지 않고 다음처럼 직접 값을 넣어도 됩니다.

int InsertColumn(int nCol, LPCTSTR lpszColumnHeading,
        int nFormat = LVCFMT_LEFT, int nWidth = ?, int nSubItem = ?)
 

< 다이얼로그에서 뷰 포인터 액세스 >

비주얼 C++로 폼뷰에서 버튼을 하나 만들고 버튼을 누르면 데이터베이스 프로그램이 실행되도록 구성하고자 합니다. 이 다이얼로그에 데이터 레코드를 표시하기 위해 다이얼로그 클래스에서 폼뷰의 포인터를 얻을 수 있는 함수를 알고 싶습니다.

 두 가지 단계를 거쳐야 합니다. 먼저 AfxGetMainWnd()를 사용해 프레임 윈도우의 포인터를 찾아야 합니다. 이 경우 cast 연산자에 따라 프로젝트에서 생성된 멤버 변수가 사용 가능한지 아닌지가 결정됩니다. 두 번째로 이 포인터로부터 GetActiveView() 나 GetActiveDocument() 로부터 뷰와 도큐먼트의 포인터를 얻을 수 있습니다. SDI에서는 항상 동일한 값이 넘어오게 되며, MDI에서는 현재 활성화된 도큐먼트와 뷰가 넘어오게 됩니다. 앞단계와 마찬가지로 cast 연산자에 신경을 쓰면 됩니다.

   CFrameWnd *pFr = (CFrameWnd *)AfxGetMainWnd();
   CSampleView *pView = (CSampleView *)(pFr->GetActiveView());

 

<응용 프로그램의 위치를 보존하려면>

응용 프로그램의 마지막 크기와 위치를 보존하는 프로그램을 만들고 싶습니다.

 CMDIFrameWnd에서 상속된 CMDIRememberFrame을 추가하면 윈도우 프로그램이 실행될 때마다 프로그램 창의 크기와 실행위치를 제어할 수 있습니다. OnDestroy, OnClose, Active Frame 멤버로 구성된 이 클래스는 이전 INI 파일에 저장된 값을 읽어들이고 분석하므로 위치보존이 가능해집니다. 또한 창의 출력위치와 상태를 결정하기 위해 SetWindowPlacement()에 필요한 구조체인 WINDOWPLACEMENT를 생성하기 위해 파일에 저장된 값을 부분적으로 사용합니다.

 

<디버깅으로 실행 진행을 보려면>

비주얼 C++ 4.1에서 클래스 위저드로 SDI 프로그램을 만들었습니다. 별도의 코드 추가없이 단계적으로 실행 결과를 보고 싶은데, 어느 곳에 브레이크 포인터를 찍는 것이 가장 좋습니까?

 디버거를 동작시킬 때 적절한 자리에 브레이크 포인터를 찍지 않으면 프로그램은 동작하지만 디버거는 활성화되지 않습니다. 또한 WM_PAINT 메시지를 잡지 못하기 때문에 디버거가 활성화되어도 프로그램의 실행모습은 볼 수 없습니다.
 만약 실행할 때 MFC의 구조를 보길 원한다면 프로젝트에서 CWinApp로부터 상속받은 'C프로젝트명App'식으로 만들어진 (파일명 : 프로젝트명app.cpp) 클래스의 'C프로젝트명app theApp;' 라고 되어있는 부분에 브레이크 포인터를 찍고 'step into'를 하면 MFC의 처음 동작부터 쫒아갈 수 있습니다.

 

<비트맵을 움직이게 하려면>

윈도우의 비트맵(혹은 아이콘)을 움직이게 하는 방법에 대해 알고 싶습니다.

 윈도우에서 애니메이션을 수행하려면 우선 오브젝트를 배경 또는 움직임에 따라 이전 상태로 복원해야 합니다.
 여러 가지 복원방법이 있는데, 이 중 이미지가 배경 위에 출력되기 전에 'offscreen' 비트맵에 이 배경을 캡처해서 이미지가 그 영역을 벗어날 때 그 배경을 다시 복원하는 방법이 가장 간단합니다. 또 다른 방법은 일정한 정방향 또는 사각형의 모양이 아닌 이미지를 애니메이션으로 구현한 것으로, 윈도우 API 함수인 SetBkMode()를 사용해 그리기 모드를 투명 또는 불투명으로 맞추는 방법입니다. 하지만 이 모드는 비트맵 연산을 사용할 수 없으므로 투명한 부분을 갖는 비트맵을 생성하는 유일한 방법은 mask 비트맵과 함께 BitBlt()fm 여러번 수행하는 것입니다.
 애니메이션을 수행할 때 고려해야 할 마지막 단계는 애니메이션을 시간을 맞추는 방법인데, 단순한 루프에서 애니메이션 함수를 호출하는 것보다는 타이머를 권하고 싶습니다. 참고로 SetTimer로 초기화되는 시스템 타이머는 WM_TIMER 메시지를 다른 모든 메시지가 처리된 후에야 발생합니다.

 

<프로그램을 완성한 후 스크롤바를 추가하려면>

스크롤바를 클래스 위저드를 이용해 처음부터 만드는 것이 아니라 필요에 따라 나중에 추가하고 싶습니다.

 나중에 별도로 스크롤바를 추가하고 싶다면 CWinApp의 m_lpCmdLine에 멤버 변수를 사용하면 됩니다. 그리고 public으로 선언되어 있으므로 AfxGetApp()->m_lpCmdLine 와 같은 방법의 참조도 가능합니다. CScrollView에서 상속받지 않고 프로젝트를 만들었다면 CWnd의 PreCreateWindow 가상함수를 오버라이드(Override)한 후 인자로 넘어오는 CREATESTRUCT 구조체의 style 변수에 WS_HSCROLL과 WS_VSCROLL을 다음과 같이 추가해 주면 됩니다.

 만약 프로그램 작성 중간에 만들고 싶다면 GetWindowLong 함수로 현재의 스타일을 얻은 후 같은 방식으로 SetWindowLong 함수에서 윈도우 스타일에 스크롤 속성을 추가하면 됩니다.

 가장 손쉬운 방법은 min 값과 max 값이 같으면 스크롤바는 나타나지 않으므로 일단 만들어 놓은 후 SetScrollRange 함수를 사용하는 것입니다.

 

<에디터 컨트롤을 마음대로>

일반적으로 컨트롤의 포커스를 이동시키기 위해 <Tab>키를 사용하는데, <Enter>키를 누르면 입력이 끝나고 다른 창으로 이동시킬 수 있게 하고 싶습니다.

 다이얼로그 박스에서 <Tab>키는 컨트롤 간의 전환키로, <Esc>키는 취소키로, 그리고 <Enter>키는 확인키로 사용됩니다.

 일반적으로 <Esc>키로 다이얼로그 박스를 종료하는 것을 막기 위해 OnCancel 메시지를 가로채는데, 이 경우 'Cancel' 버튼이 없는 경우와 같은 방법으로 처리하면 됩니다. <Enter>키도 이런 식으로 OnOK 메시지를 발생할 때 이를 가로채서 처리하면 됩니다.

 

<윈도우 프로그램 시작할 때 메시지>

윈도우 프로그램 실행 준비가 완료된 후에 발생되는 메시지는 어떤 것인지 알고 싶습니다.

 윈도우가 생성되면 WM_CREATE 메시지가 발생한 후 UpdateWindow()가 불리고 윈도우가 그려지면서 많은 메시지가 생성됩니다.

 그 중 대표적인 것이 WM_PAINT와 WM_NCPAINT입니다. WM_PAINT는 클라이언트 영역을 그리는 메시지로 보통 프로그래머가 이것을 가로채게 됩니다. WM_NCPAINT는 타이틀바 같은 비클라이언트 영역을 칠하라는 것으로 DefaultWinProc가 수행합니다.

 

< MFC에서 상대좌표 구하는 방법 >

 MFC에서 스크롤을 했을 경우 좌표변환이 생겨 포인터를 찍을 경우 인식을 못하는데 상대좌표를 구하는 방법을 알고 싶습니다.

 원래 SetPoint 같이 디바이스 컨텍스트(Device Context)에 쓰는 GDI 함수들은 모두 논리적 좌표를 사용하므로 프로그래머는 스크롤의 움직임에 관여할 필요가 없습니다.

 그리기 루틴을 추가해줄 때 WM_PAINT 메시지가 전달되면 적절하게 윈도우가 화면에 나타나긴 하는데, 문제는 보이지 않았을 뿐 현재 윈도우 좌표가 아닌 부분에 대해서도 실행이 된다는 것입니다.

 그러므로 마우스에서 좌표를 전달받는 디바이스 좌표(device coordination)를 논리적 좌표로 변경해 줘야 합니다. 이를 DPtoLP 로 쉽게 전환할 수 있고, 역시 역으로 LPtoDP 로 환원할 수 있습니다. 물론 DPtoLP 전에 DC를 생성하고, 클라이언트에 대한 정보를 얻어야 합니다.

 

< 마지막 메시지를 얻으려면>

윈도우에 전달된 마지막 메시지를 처리하고 싶습니다.

현재 처리중인 MSG 구조체의 포인터를 얻으면 됩니다. 클래스 위저드를 사용해 하나의 공통 함수에 여러 메뉴 항목의 처리 함수를 연결한 후 선택된 메뉴 항목을 확인하기 위해 GetCurrentMessage를 호출하면 되는 거죠.

void CMainFrame::OnCommonMenuHandler()
{
     TRACE("Menu is item %u was selected.", GetCurrentMessage()->wParam);
}

 

- the end of this article -