<2개의 툴바를 한줄에>

MFC로 2개의 툴바를 만들었습니다. 그런데 이 툴바가 한줄씩 따로따로 놓이는군요. 물론 특별히 문제가 되는 것은 아니지만 개인적으로는 툴바를 모두 한줄에 놓고 싶습니다. 나름대로 문서를 찾아보기도 했지만 뾰족한 방법이 없군요. 가령 다음과 같은 코드에서 툴바를 한줄에 모으려면 어떻게 해야 합니까?

class CMainFrame : public CFrame
{
protected:
    CToolBar wnd_myToolBar;
    CToolBar wnd_othToolBar;
    ...
};

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    ...
    wnd_myToolBar.Create(...);
    wnd_othToolBar.Create(...);
    ...
}

DockControlBarLeftOf 함수를 이용하면 가능합니다. DockControlBarLeftOf(CToolBar* Bar, CToolBar* LeftOf)처럼 두 개의 툴바를 인수로 받기 때문입니다. 따라서 Bar와 LeftOf 툴바는 한줄에 출력될 것입니다. 다음 코드를 참고하기 바랍니다.

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    ...
    EnableDocking(CBRS_ALIGN_ANY);

    m_wndToolBar.SetWindowText(_T("myToolBar"));
    m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
    DockControlBar(&m_wndToolBar, AFX_IDW_DOCKBAR_TOP);

    wnd_othToolBar.SetWindowText(_T("otherToolBar"));
    wnd_othToolBar.EnableDocking(CBRS_ALIGN_ANY);
    DockControlBarLeftOf(&m_wndToolBar, &wnd_othToolBar);
   ...
}

void CMainFrame::DockControlBarLeftOf(CToolBar* Bar,CToolBar* LeftOf)
{
    CRect rect;
    DWORD dw;
    UINT n;
    // get MFC to adjust the dimensions of all docked ToolBars
    // so that GetWindowRect will be accurate
    RecalcLayout();
    LeftOf->GetWindowRect(&rect);
    rect.OffsetRect(1,0);

    dw=LeftOf->GetBarStyle();
    n = 0;
    n = (dw&CBRS_ALIGN_TOP) ? AFX_IDW_DOCKBAR_TOP : n;
    n = (dw&CBRS_ALIGN_BOTTOM && n==0) ? AFX_IDW_DOCKBAR_BOTTOM : n;
    n = (dw&CBRS_ALIGN_LEFT && n==0) ? AFX_IDW_DOCKBAR_LEFT : n;
    n = (dw&CBRS_ALIGN_RIGHT && n==0) ? AFX_IDW_DOCKBAR_RIGHT : n;
    // When we take the default parameters on rect, DockControlBar will dock
    // each Toolbar on a seperate line.  
    // By calculating a rectangle, we in effect
    // are simulating a Toolbar being dragged to that location and docked.
    DockControlBar(Bar,n,&rect);
}

<현재 디렉토리의 정보를 알아내는 법>

현재 디렉토리에 있는 파일 이름이나 하위 디렉토리 등의 정보를 알아내는 방법을 알고 싶습니다.

다음 소스 프로그램은 현재 디렉토리의 모든 서브 디렉토리에 있는 파일을 찾아서 파일 이름을 모두 소문자로 바꾸는 루틴입니다. 자세한 내용은 각 함수별로 도움말을 참조하기 바랍니다.

void lookup()
{
    char new_filename[100];
    int leng;
    struct _finddata_t c_file;
    long hFile;
    int temp = 0;

    if ((hFile = _findfirst("*.*",&c_file)) == -1L) {
     cout << "error" << endl;
    }

    for (; temp == 0; temp = _findnext(hFile,&c_file)){
        leng = strlen(c_file.name);
        if ((c_file.attrib & _A_SUBDIR) == _A_SUBDIR){
            if (c_file.name[0] != '.'){
                _chdir(c_file.name);
                lookup();
                _chdir("..");
            }
        continue;
        }
        else if ((c_file.attrib & _A_ARCH) != _A_ARCH) continue;
        for(int i = 0;i < leng;i++){
            new_filename[i] = tolower(c_file.name[i]);
        }
        new_filename[i] = '\0';
        rename(c_file.name, new_filename);
    }
    _findclose(hFile);
}

<BMP 파일에 투명색을 지정하는 법>

비주얼 C++를 이용해 BMP 파일을 처리하는데 BMP 파일을 읽어 투명색을 지정하려면 어떻게 해야 하나요. 참고로 현재 스크린 세이버를 제작중입니다.

비주얼 C++에서 자동으로 이 문제를 지원하는 방법은 없고, 다만 이에 대한 함수가 공개되어 있을 뿐입니다.
MSJDrawTransparentBitmap이라는 함수가 마이크로소프트의 knowledgebase 079212에 나와있습니다. 다음 소스 코드를 보면 이 함수가 하는 일은 비트맵을 불러오고 페인팅 표면을 투명하게 칠하기 위해 마스크로 보내는 작업을 한다는 것을 알 수 있습니다(자세한 내용은 MSJ 한국판 98년 1월호 108쪽 참고).

// MSJDrawTransparentBitmap
// 함수는 복사되었고 Knowledgebase 기사 Q79212으로부터 적용되었다.
// 제목 : 투과되는 비트맵 그리기
void MSJDrawTransparentBitmap(CDC* pDC, CBitmap* pBitmap, int xStart,
    int yStart, COLORREF cTransparentColor)
{
    CBitmap bmAndBack, bmAndObject, bmAndMem, bmSave;
    CDC dcMem, dcBack, dcObject, dcTemp, dcSave;
    dcTemp.CreateCompatibleDC(pDC);
    dcTemp.SelectObject(pBitmap); //비트맵 선택하기
    BITMAP bm;
    pBitmap->GetObject(sizeof(BITMAP), (LPSTR)&bm);
    CPoint ptSize;
    ptSize.x = bm.bmWidth;         // 비트맵의 폭
    ptSize.y = bm.bmHeight;        // 비트맵의 높이
    dcTemp.DPtoLP(&ptSize, 1);     // 논리 포인트로부터 장치 포인트로 변환
    // 임시 데이터를 보관하기 위해 몇몇 DC를 생성
    dcBack.CreateCompatibleDC(pDC);
    dcObject.CreateCompatibleDC(pDC);
    dcMem.CreateCompatibleDC(pDC);
    dcSave.CreateCompatibleDC(pDC);
    // 각 DC를 위해 비트맵을 생성. DC는 GDI의 숫자만큼 필요하다.
    functions.
    // 모노크롬 DC
    bmAndBack.CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
    // 모노크롬 DC
    bmAndObject.CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
    bmAndMem.CreateCompatibleBitmap(pDC, ptSize.x, ptSize.y);
    bmSave.CreateCompatibleBitmap(pDC, ptSize.x, ptSize.y);
    // 각 DC는 픽셀 데이터를 저장하기 위해 비트맵 객체를 선택해야 한다.
    CBitmap* pbmBackOld = dcBack.SelectObject(&bmAndBack);
    CBitmap* pbmObjectOld = dcObject.SelectObject(&bmAndObject);
    CBitmap* pbmMemOld = dcMem.SelectObject(&bmAndMem);
    CBitmap* pbmSaveOld = dcSave.SelectObject(&bmSave);
    // 적당한 맵핑 모드를 설정한다.
    dcTemp.SetMapMode(pDC->GetMapMode());
    // 이리로 보내진 비트맵을 저장한다.
    dcSave.BitBlt(0, 0, ptSize.x, ptSize.y, &dcTemp, 0, 0, SRCCOPY);
    // 투영되는 비트맵에 포함되는 색으로 소스 DC의 배경색을 설정한다.
    COLORREF cColor = dcTemp.SetBkColor(cTransparentColor);
    // 소스 비트맵에서 모노크롬 비트맵까지 BitBlt를 수행해서
    // 비트맵용 오브젝트 마스크를 생성한다.
    dcObject.BitBlt(0, 0, ptSize.x, ptSize.y, &dcTemp, 0, 0, SRCCOPY);
    // 원래 색으로 되돌리는 소스 DC의 배경색을 설정한다.
    dcTemp.SetBkColor(cColor);
    // 객체 마스크를 반전시킨다.
    dcBack.BitBlt(0, 0, ptSize.x, ptSize.y, &dcObject, 0, 0, NOTSRCCOPY);
    // 목적지에 메인 DC의 배경을 복사한다.
    dcMem.BitBlt(0, 0, ptSize.x, ptSize.y, pDC, xStart, yStart, SRCCOPY);
    // 비트맵이 놓여지는 위치를 마스크한다.
    dcMem.BitBlt(0, 0, ptSize.x, ptSize.y, &dcObject, 0, 0, SRCAND);
    // 비트맵의 투명 색상 픽셀을 마스크한다.
    dcTemp.BitBlt(0, 0, ptSize.x, ptSize.y, &dcBack, 0, 0, SRCAND);    
    // 목적지 DC상에서 배경색과 비트맵을 XOR 연산한다.
    dcMem.BitBlt(0, 0, ptSize.x, ptSize.y, &dcTemp, 0, 0, SRCPAINT);
    // 스크린에 목적지를 복사한다.
    pDC->BitBlt(xStart, yStart, ptSize.x, ptSize.y, &dcMem, 0, 0, SRCCOPY);
    // 보내진 비트맵으로 원래 비트맵을 대체한다.
    dcTemp.BitBlt(0, 0, ptSize.x, ptSize.y, &dcSave, 0, 0, SRCCOPY);
    // 메모리 비트맵을 리셋시킨다.
    dcBack.SelectObject(pbmBackOld);
    dcObject.SelectObject(pbmObjectOld);
    dcMem.SelectObject(pbmMemOld);
    dcSave.SelectObject(pbmSaveOld);
    // 메모리 DC와 비트맵 객체들은 자동으로 삭제된다.
}

<프린트 용지 크기 제어>

CFormView에서 프린트 셋업 다이얼로그를 띄우지 않고 프린트가 가능한 프로그램을 만들려고 합니다. 프린트하는 것까지는 아무 문제없이 해결할 수 있었습니다만 용지의 크기를 바꾸는 데서 막혀 더 이상 진전이 없습니다. DEVMODE를 변경하면 된다는 것까지는 알겠는데 어디서 어떻게 바꿔줘야 할지 막막하기만 합니다. 참고로 문제가 되는 제 소스 코드는 다음과 같습니다.

   CPrintDialog *pMyDlg = new CPrintDialog(FALSE);
   DOCINFO di={ sizeof(DOCINFO),"Print1: Test",NULL};
   pMyDlg->GetDefaults();

   HDC hdc= pMyDlg->GetPrinterDC();
   ::StartDoc(hdc,&di);
   ::StartPage(hdc);

   ::TextOut(hdc, 0,  10, str, str.GetLength());

   ::RestoreDC(hdc,-1);
   ::EndPage(hdc);
   ::EndDoc(hdc);
   delete pMyDlg;

일반적인 CView에서 프린트 셋업 다이얼로그를 오버라이드해서 용지의 크기를 바꾸는 방법도 있습니다. 질문과 같이 DeviceMode를 바꾸고 싶으면 다음과 같이 해보기 바랍니다.

///////////////////
// nPaperSize - 변경하려는 문서 규격 (미리 정의되어 있음)
// nOrient    - 문서의 종횡
void CMyDoc::UpdatePaperSize(int nPaperSize, int nOrient)
{
    PRINTDLG PrtDlg;

    if (!AfxGetApp()->GetPrinterDeviceDefaults(&PrtDlg))
    {
        TRACE0 ("Error - Not initialized default print.\n");
        return;
    }

    LPDEVMODE pDevMode = (LPDEVMODE) ::GlobalLock(PrtDlg.hDevMode);
    if (NULL == pDevMode)
    {
        TRACE0 ("failed to GlobalLock DEVMODE\n");
        return;
    }

    pDevMode->dmPaperSize   = nPaperSize;   //DMPAPER_A4, ...
    pDevMode->dmOrientation = nOrient;      //DMORIENT_PORTRAIT  (1) - 세로
                                            //DMORIENT_LANDSCAPE (2) - 가로
    ::GlobalUnlock(PrtDlg.hDevMode);
}
///////////////////

<수동으로 메시지 맵에 연결하기>

클래스 위저드에는 나타나지 않지만 원하는 메시지를 받아 처리하려면 어떻게 해야 하는지 알고 싶습니다.

임의의 메시지에 반응하는 핸들러를 추가하려면 ON_MESSAGE라는 매크로를 이용해서 메시지 핸들러를 지정해야 합니다. 헤더와 소스에서 메시지 맵을 뒤져 수동으로 입력해주면 되는데, 예를 들어 WM_MSG000이라는 메시지를
CYourView::OnMsg000이라는 멤버 함수가 받게 하고 싶다면 다음과 같이 하면 됩니다.

① 헤더의 적당한 곳에다 메시지를 정의한다.

        #define  WM_MSG000     (WM_USER+200)     ← 적당한 값을 준다.

② YourView.h에서 CYourView 클래스의 멤버로 OnMsg000 함수를 선언한다.
      //{{AFX_VIRTUAL 등으로 표시된 구역에는 쓰지 않는다.

        afx_msg LONG OnMsg000(UINT wParam, LONG lParam);

③ YourView.cpp 파일에서 메시지 맵을 찾아 OnMsg000 함수를 등록한다.

BEGIN_MESSAGE_MAP(CYourView, CView)
    //{{AFX_MSG_MAP(CMeshViewView)
    ....                                  ← 여기에 쓰지 말고
    //}}AFX_MSG_MAP
    ON_MESSAGE(WM_MSG000, OnMsg000)       ← 밖에 써야된다.
END_MESSAGE_MAP()

④ YourView.cpp 파일에서 멤버 함수 OnMsg000을 정의한다.

        afx_msg LONG OnMsg000(UINT wParam, LONG lParam)  {
       // 여기다가 코드를 써넣는다
        }

이제 WM_MSG000 메시지가 날아오면 CYourView::OnMsg000 함수가 호출됩니다. 다른 메시지도 같은 방법으로 하면 됩니다. 또한 ClassWizard도 같은 방법으로 메시지 맵을 붙여줍니다.

See Also
"ON_MESSAGE", "User-defined Message Handlers"

 

<파일을 링크드 리스트 형식으로 저장하려면>

어떤 데이터를 저장하려고 하는데 데이터를 중간에 써넣을 경우가 있기 때문에 링크드 리스트 방식으로 구현하는데 문제가 됩니다. 파일을 링크드 리스트 형식으로 저장하는 방법이 무엇인지 궁금합니다.

파일과 메모리의 링크드 리스트는 사실 별 차이가 없습니다. 파일에도 파일 위치라는 것이 존재하므로 그것을 메모리 주소라고 생각하고 코딩하면 됩니다.
보통 여러 가지 방법이 있겠지만 제 경우는 다음과 같이 구성했습니다. 우선 파일의 맨 앞부분에는 첫째 데이터의 블럭의 위치를 표시하는 Header와 마지막 데이터 블럭을 지정하는 Tail로 할당합니다. 물론 데이터 블럭에는 다음 데이터 블럭의 위치를 지정하는 포인터를 데이터에 추가해야 합니다.

HEADER - TAIL - DATA1 - DATA2 - DATA3  - . . . .

크게 보면 대충 이런 방법으로 구성됩니다. 메모리의 링크드 리스트와 흡사하다는 것을 한눈에 알 수 있습니다.
새로운 데이터를 추가하는 과정은 다음과 같습니다.
 
① 새로운 데이터 블록을 삽입하게 되면 파일의 맨 마지막에 새로 추가한다.
 ② TAIL의 마지막 데이터 블럭 파일 위치를 읽어들여 그 위치로 이동한다.
 ③ 마지막 데이터 블록의 오른쪽 포인터가 삽입된 파일 위치를 지정하도록 갱신한다.
 ④ 새로 삽입된 데이터의 왼쪽 포인터가 바로 이전 마지막 데이터 블럭의 파일 위치를
     지정하도록 한다.
 ⑤ TAIL의 포인터를 새롭게 삽입된 데이터 블럭의 파일 위치값으로 갱신한다
      (즉 새롭게 추가된 데이터가 맨 마지막 데이터가 된다).

삭제의 경우도 마찬가지로 데이터의 좌, 우 포인터 파일 위치로 앞뒤 데이터를 찾아서 데이터 블럭의 파일 위치만 갱신해 주면 됩니다(삭제시 실제 데이터를 파일에서 삭제하는 것이 아니라 단지 삭제된 데이터의 연결고리를 끊어주는 것이다). 그리고 Header는 처음으로 데이터 블럭이 삽입되거나, 첫째 데이터 블럭이 삭제될 경우에만 갱신해주면 됩니다. 이렇게 구성하면 줄줄이 데이터들이 엮어 집니다.
만일 단일 링크라고 가정하면 구조체나 클래스의 크기를 알아내는 것은 매우 쉽게 해결할 수 있고, 또한 크기를 알면 파일을 연 후에 앞뒤로 이동할 수도 있습니다(C의 fseek 같은).
메모리에서 정보의 링크 필드가 필요하듯이 파일에 저장할 때도 링크 필드를 대체할 무엇인가가 필요합니다. unsigned int만 써도 16비트에서는 65535개의 링크가 조절 가능합니다. 어쨌든 첫 구조체가 들어갈 때는 인덱스 0을 갖게 되고(0부터 시작이라고 할 때) 둘째나 셋째도 해당 인덱스를 가지고 들어가게 됩니다. 첫째 저장은 이로 인해서 차례차례 저장됩니다. 인덱스를 1부터라고 하면(0부터 시작이라고 해서) 이때 인덱스는 다음 인덱스를 기록한다고 가정합니다. 찾을 때는 구조체 크기 단위로 파일에서 읽어 들이고, 다음 구조체는 인덱스×구조체 크기만큼 처음부터 가게 됩니다. 이로써 파일을 순차적으로 읽는 것이 아닌 링크 필드를 이용한 접근이 완성됩니다.
수정시에는 이 구조체가 파일의 어느 부분인지 당연히 알 수 있게 됩니다. 따라서 파일의 그 위치에 덮어쓰면 수정이 됩니다. 문제가 되는 것은 링크가 변경되었을 때인데, 먼저 새로운 구조체가 중간에 끼어들어갈 경우를 생각해 봅시다. 간단히 이 새로운 구조체를 그저 파일의 끝에 추가하면 됩니다. 그리고 이 구조체를 가리켜야 하는 정보로부터 다음 인덱스를 얻어야 합니다. 그리고 나서 이를 저장해두고 얻어온 정보의 인덱스를 새로운 구조체의 인덱스로 저장합니다. 다음은 새로운 구조체의 인덱스에다 정보에서 얻어온 인덱스(다음 구조체)를 기록합니다. 이런 식으로 접근과 수정 그리고 링크 필드의 추가(끼워 넣기) 등이 해결됩니다.
마지막으로 고려해야 할 부분이 삭제할 때입니다. 삭제가 되면 그저 인덱스 접근에서 빼버립니다. 즉 파일의 그 구조체 위치로 가게 하는 것이 없으면 된다는 말이죠. 하지만 문제가 되는 것은 삭제가 많이 발생하면 쓸데없이 파일이 커집니다. 이를 방지하기 위해서는 쓰레기 청소 루틴이 있어야 합니다.
총 파일 크기를 얻어온 후 구조체의 크기로 나누면 파일에 저장된 구조체의 개수를 알게 됩니다. 그럼 실제 등록된 구조체의 개수와 비교한 후 10개 혹은 100개 이상 차이가 나면, 임시 파일을 생성한 후 원래 파일에서 링크 필드를 쫓아가면서 읽어서 임시 파일에 쓰게 합니다. 그런 후에 원래 파일을 삭제하고, 임시 파일을 원래 파일의 이름으로 변경합니다. 앞에서 설명한 방법은 메모리가 부족한 경우에 메모리에 링크를 구성하지 않고 파일의 링크를 구성하고자 할 때만 유용합니다. 이 방법의 장점은 필요한 메모리 공간이 항상 구조체의 서너 배 정도만 있으면 된다는 것입니다.