MFC 프로그래밍에 대한 Q&A입니다
1. 유니코드와 ANSI 문자열간의 타입 변환 방법
BSTR 타입으로 표현되는 유니코드와 ANSI 문자열간의 변환 방법을 알고 싶습니다.
API를 이용해 변환할 수도 있는데 MFC의 CString 클래스를 사용한다면 변환은 아주 쉽습니다. 먼저 ANSI 문자열에서 BSTR 문자열로의 변환은 // str은 CString 타입이다. BSTR bstr = str.AllocSysString(); 반대로의 변환은 CString str = bstr;
2. 리스트박스 컨트롤에서의 여러 항목 선택
리스트박스 컨트롤에서 여러 항목을 동시에 선택하는데 Shift키나 Ctrl 키를 사용하려고 하는데 어떻게 해야할지 모르겠습니다.
이는 별도의 메소드등을 사용하여 해결되는 문제가 아니고 리스트박스 컨트롤의 스타일로 Extended를 선택해야 합니다.
3. 프로그램을 하나만 실행하기
제가 만든 프로그램을 항상 하나만 실행하도록 하려면 어떻게 해야할까요 ? 두 번째 실행시에는 먼저 실행되어 있던 프로그램을 앞으로 띄우고 싶습니다.
여러가지 방법이 있겠지만 파일맵핑 객체를 이용하도록 하겠습니다. 처음 뜨는 프로그램이 정해진 이름의 파일 맵핑 객체를 만들어 놓으면(여기서는 MyTestMap) 나중에 뜨는 프로그램들이 이 이름의 파일 맵핑 객체가 있는지 살펴보고 있으면 실행을 중지하는 방법입니다. 이 것이 가능한 이유는 파일 맵핑 객체는 시스템 전역 객체이기 때문입니다. 먼저 항상 하나만 떠 있 게 하는 방법을 알아보고 다음으로 기존의 프로그램을 앞으로 띄우는 코드를 알아보도록 하겠습니다.
- 항상 하나만 떠 있게 하는 방법(윈도우의 캡션이 고정되어 있을 경우에는 FindWindow를 사용하는 것이 더 유리합니다). 다음 코드를 InitInstance의 선두에 넣습니다. HANDLE hMapping; hMapping = CreateFileMapping( (HANDLE) 0xffffffff, NULL, PAGE_READONLY, 0, 4, "MyTestMap" ); if( hMapping ) { if( GetLastError() == ERROR_ALREADY_EXISTS ) { // 프로그램이 이미 실행 중임을 알리는 메시지를 띄운다. MessageBox( NULL, "이미 실행 중입니다.", "경고", MB_OK ); ExitProcess(1); } }
- 위의 코드는 하나의 실행파일을 실행할 수 있는 방법이지만 기존의 실행되어 있는 프로그램을 앞으로 띄우는 일까지 할 수는 없습니다. 이를 하려면 InitInstance의 코드를 다음과 같이 수정합니다. HANDLE hMapping; hMapping = CreateFileMapping( (HANDLE) 0xffffffff, NULL, PAGE_READWRITE, 0, 32, "MyTestMap" ); if( hMapping ) { if( GetLastError() == ERROR_ALREADY_EXISTS ) { // 프로그램이 이미 실행 중임을 알리는 메시지를 띄운다. MessageBox( NULL, "이미 실행 중입니다.", "경고", MB_OK ); ExitProcess(1); } // 기존 파일맵핑 객체를 연다. 기존 파일 맵핑 객체의 선두에 이전 프로그램의 메인 윈도우 핸들이 들어있다. hMapping = OpenFileMapping(FILE_MAP_WRITE, FALSE, "MyTestMap"); LPDWORD lpDword = (LPDWORD)MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 4); HWND hWnd = (HWND)*lpDword; // 윈도우를 전면으로 내세우기 전에 먼저 아이콘 상태에 있을지도 모르는 윈도우를 원래 크기로 되돌린다. // IsIconic 함수를 호출하여 아이콘 상태 여부를 확인해도 될 것이다. ShowWindow(hWnd, SW_SHOW); SetForegroundWindow(hWnd); // 파일 맵핑 객체를 닫는다. UnmapViewOfFile(lpDword); ExitProcess(1); } ..... // Dispatch commands specified on the command line if (!ProcessShellCommand(cmdInfo)) return FALSE; // 다음을 추가한다. LPDWORD lpDword = (LPDWORD)MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, 4); *lpDword = (DWORD)m_pMainWnd->m_hWnd; UnmapViewOfFile(lpDword); // ------------------- m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow();
4. OLE Property Page에서 확인 혹은 적용 버튼 처리하기
COlePropertyPage 클래스에서 사용자가 Property Sheet의 OK 버튼이나 적용 버튼을 누른 것을 감지하는 방법은 무엇입니까 ?
CPropertyPage 클래스에서는 이를 알 수 있는 명시적인 이벤트 처리함수들이 정의되어 있지만 COlePropertyPage 클래스에는 이런 것이 없습니다. 따라서 이를 처리하려면 약간의 편법을 사용해야 합니다. COlePropertyPage 클래스를 보면 DoDataExchange라는 함수가 있는데 이 함수는 프로퍼티 페이지에서 OK 버튼을 누르거나 Apply 버튼을 눌렀을 때 호출됩니다. 단 먼저 SetModifiedFlag 함수를 TRUE를 인자로 호출해야 DoDataExchange 함수가 호출된다는 점을 주의하기 바랍니다. DoDataExchange 함수의 끝 부분에 다음과 같은 코드를 추가합니다.
..... //}}AFX_DATA_MAP DDP_PostProcessing(pDX); if (pDX->m_bSaveAndValidate) { // 요 부분에 원하는 코드를 집어넣는다.5. ODBC 클래스를 사용할 때의 예외상황 처리
ODBC 작업 중에 발생하는 예외 상황을 처리하고 싶습니다. 이 때 어떤 클래스를 사용할 수 있습니까 ?
CDBException를 사용하면 됩니다. 발생한 에러코드는 이 클래스의 m_nRetCode로 들어오고 그 에러에 대한 설명은 m_strError로 들어옵니다. 다음의 예제 코드는 ODBC 레코드셋을 오픈하는 과정에서 발생하는 에러를 처리하는 방법을 보여줍니다.
CRecordset* CSectionView::OnGetRecordset() { if ( m_pSet != NULL ) return m_pSet; // Recordset already allocated m_pSet = new CSectionSet( NULL ); try // 에러의 가능성이 있는 코드를 try문 사이에 두고 { m_pSet->Open( ); } catch( CDBException* e ) // 에러 발생시 처리 코드를 catch문에 둔다. { AfxMessageBox( e->m_strError, MB_ICONEXCLAMATION ); // 에러가 발생한 레코드셋을 삭제한다. delete m_pSet; m_pSet = NULL; e->Delete(); } return m_pSet; }6. 하나의 세션으로 여러 개의 레코드셋을 사용하기
ODBC 클래스를 사용하면서 하나의 DSN에 대해 하나의 세션만을 사용해 여러 개의 레코드셋을 사용하고 싶습니다. 그 방법은 알고 싶습니다.
레코드셋 객체를 만들 때 어떤 CDatabase 객체를 사용하느냐에 따라 달라집니다. 레코드셋 객체의 생성자로 기존의 CDatabase 객체를 주면 이를 이용해 레코드셋을 오픈합니다. 즉, 세션이 별도로 열리지 않는 것입니다. 레코드셋 객체의 생성자로 NULL을 주면 레코드셋에서 알아서 CDatabase 객체를 하나 만들어 사용합니다. 즉, 세션이 별도로 열리는 것입니다.
그런데 MFC 프로그램의 경우 모든 레코드셋을 후자의 방법으로 만들어 놓기 때문에 (물론 수정은 가능합니다) 레코드셋마다 세션이 열리게 됩니다. 예를 들어 하나의 레코드셋을 만들어 이를 다른 클래스의 멤버 변수 형태로 사용하면 다음과 같은 형식이 됩니다.
public: CTifTestSet m_tifTestSet;이 경우 레코드셋의 생성자로 아무 것도 넘어가지 않기 때문에 레코드셋에서 별도의 CDatabase 객체를 만들게 되고 따라서 세션이 하나 더 생성됩니다. 이를 해결하려면 먼저 전역 CDatabase 객체를 하나 멤버 변수로 선언해두고 각 레코드셋 변수도 그냥 변수가 아닌 포인터 변수로 선언해야 합니다.
public: CDatabase m_globalDB; CTifTestSet *m_pTifTestSet;이렇게 한 다음 레코드셋을 오픈한 필요가 있을 때 다음과 같은 절차를 거칩니다.
m_pTifTestSet = new CTifTestSet(&m_globalDB);다른 레코드셋을 오픈할 때도 위와 같은 CDatabase 객체를 사용하면 세션을 하나만 사용할 수 있습니다. 물론 동적으로 할당하기 때문에 나중에 delete하는 것을 잊으면 안 됩니다.
7. 다이얼로그상의 특정 컨트롤의 색상 변경
다이얼로그 박스상의 특정 컨트롤의 색상을 변경하고 싶습니다. 어떻게 해야 합니까 ?
이는 VB나 델파이에서는 아주 쉽게 할 수 있는 일이지만 다이얼로그 박스 클래스에서 좀 복잡합니다. 일단 다이얼로그 클래스의 WM_CTLCOLOR 메시지에 대한 처리 함수를 정의합니다. 그 안의 코드를 다음과 같이 변경합니다. 참고로 IDC_TEXTOUT은 색상을 변경하고자 하는 스태틱 컨트롤의 ID라고 하겠습니다. HBRUSH CTifTestView::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CRecordView::OnCtlColor(pDC, pWnd, nCtlColor); if (pWnd->GetDlgCtrlID() == IDC_TEXTOUT) { pDC->SetTextColor(RGB(255, 0, 0)); // 빨강색으로 출력하고자할 경우에 사용한다. } return hbr; }
8. 툴바버튼 누를 때 모양 이상
툴바 버튼이 눌렸을 때 다이얼로그 박스를 띄우려고 합니다. 그런데 이 순간에 툴바의 버튼이 두 개가 눌려진 형태가 되는데 이를 해결하는 방법은 무엇입니까 ?
해결 방법은 다이얼로그 박스를 띄우기 전에 App 객체의 OnIdle을 호출하면 됩니다.
AfxGetApp()->OnIdle(0); // 다이얼로그 박스 호출...9. 다이얼로그상에 비트맵 올리기
다이얼로그위에 비트맵을 올리려고 합니다. 다이얼로그에서 비트맵 버튼의 크기를 줄 때 비트맵의 크기와 동일하게 주었는데 크기가 맞지 않습니다.. 이유가 무엇일까요 ?
일단 이 문제는 다이얼로그에서 위치나 크기를 줄 때 사용하는 단위가 일반 픽셀 단위가 아니기 때문에 발생한 것입니다. 먼저 다이얼로그 박스위에 어떤 컨트롤이든지 비트맵을 올릴 수 있는 컨트롤을 올렸다고 가정하겠습니다. 그리고나서 다이얼로그 박스의 WM_INITDIALOG 메시지 처리부에서 비트맵이 올라간 컨트롤의 크기를 다음과 같이 변경합니다. IDC_BITMAPCTRL은 바로 그 컨트롤의 ID라고 가정하겠습니다.
XXXX::OnInitDialog() { // .... RECT rc; GetDlgItem(IDC_BITMAPCTRL)->GetWindowRect(&rc); ScreenToClient(&rc); GetDlgItem(IDC_BITMAPCTRL)->MoveWindow(rc.left, rc.right, 비트맵의 폭, 비트맵의 높이, TRUE); return TRUE; }10. 프린트 다이얼로그를 안 띄우고 인쇄하기
프린트 다이얼로그를 안 띄우고 기본 프린터로 인쇄하고 싶습니다. 방법을 알려주세요.
기본적으로 인쇄는 인쇄 다이얼로그를 띄우는 절차를 필요로 합니다. MFC의 인쇄 다이얼로그 클래스는 CPrintDialog인데 이 클래스를 생성할 때 인자를 잘 주면 다이얼로그가 뜨지 않고 기본 프린터에 대한 정보만 얻어오게 됩니다. 다음 코드는 인쇄 다이얼로그를 띄우지 않고 프린터에 출력하는 예제입니다.
// CPrintDialog를 이용해 프린터의 디폴트 설정을 읽어온다. CPrintDialog dlg(TRUE, PD_RETURNDEFAULT); // DoModal을 실행하면 원래는 다이얼로그가 떠야되지만 인자로 PD_RETURNDEFAULT를 // 주었기 때문에 정보만 얻어오고 바로 리턴한다. dlg.DoModal(); // 종이를 가로 방향으로 찍고 싶다든지 여러 매를 찍고 싶다면 아래의 코드를 수정하여 // 사용한다. DEVMODE 구조체를 공부해보기 바란다. 다음 예는 가로 방향으로 설정하는 예이다. DEVMODE *pDevMode = (DEVMODE *)::GlobalLock(dlg.m_pd.hDevMode); if (pDevMode) { // 프린터의 인쇄 방향 설정을 변경한다. pDevMode->dmOrientation = DMORIENT_LANDSCAPE; // 3매를 찍도록 한다. pDevMode->dmCopies = 3; ::GlobalUnlock(dlg.m_pd.hDevMode); } CDC dc; // 프린터 설정을 dc 변수에 반영한다. if (dc.Attach(dlg.CreatePrinterDC())) { DOCINFO di; // 새로 생성되는 프린트 잡의 이름을 준다. memset(&di, 0x00, sizeof(DOCINFO)); di.cbSize = sizeof(DOCINFO); di.lpszDocName = "InstPhoto Output"; // 프린트 잡을 하나 연다. dc.StartDoc(&di); dc.StartPage(); // dc에 원하는 출력을 수행한다. .... // 출력이 종료되었으면 마무리 작업을 한다. dc.EndPage(); dc.EndDoc(); dc.DeleteDC(); }11. CStatic 클래스를 이용한 비트맵 출력
MFC 프로그래밍을 하는데 CStatic 클래스를 이용해 비트맵을 출력하고 있습니다. 동적으로 상황에 따라 비트맵의 내용을 변경하고 싶은데 SetBitmap 함수로 잘 안됩니다. 이를 해결하려면 어떻게 해야합니까 ?
CStatic 클래스에서 비트맵을 출력하는데 사용되는 것이 CStatic의 멤버 함수인 SetBitmap 함수입니다. 이를 사용하기 전에 해야할 일이 있습니다. 예를 들어 CStatic 클래스의 객체 이름이 pic이고 변경하고자 하는 새로운 모양의 비트맵이 들어있는 CBitmap 타입의 변수를 bm이라고 가정하면 pic.SetBitmap(bm)이라고 하면 비트맵의 내용이 변경되어야할 것 같은데 그렇게 되지 않습니다. 다음과 같은 좀더 복잡한 과정을 거쳐야 합니다.
pic.SetBitmap(NULL); pic.SetBitmap(bm); // bm은 비트맵이 로드되어 있는 CBitmap 클래스의 객체나 HBITMAP 핸들값 pic.ShowWindow(SW_SHOW); pic.UpdateWindow();12. 응용프로그램의 정보를 레지스트리에 보관하기
응용프로그램에서 사용중인 정보(예를 들면 메인 윈도우의 위치 등등)를 레지스트리에 보관했다가 다음 실행할 때 읽어들이고자 레지스트리 관련 API를 사용하지 않고 좀 더 쉽게 처리할 수 있는 방법은 없을까요 ?
원래 MFC에는 INI 파일 I/O를 위해 다음과 같은 종류의 함수들을 CWinApp 클래스의 멤버 함수로 제공해 줍니다.
GetProfileInt, GetProfileString, WriteProfileInt, WriteProfileString
이 함수들은 원래는 INI 파일하고만 관련된 것이지만 CWinApp의 SetRegistryKey 함수를 사용하면 레지스트리 관련 I/O를 사용하는데 사용할 수도 있습니다. SetRegistryKey를 호출할 때 "회사이름/프로그램이름"의 형식을 갖는 문자열을 지정해 주는 것이 좋습니다. AppWizard에서는 디폴트로 SetRegistryKey("Local AppWizard-Generated Applications")를 호출합니다. 아무튼 SetRegistryKey에 지정한 값이 HKEY_CURRENT_USER\Software\에 붙어 사용됩니다. 예를 들어 다음과 같은 코드를 생각해 보겠습니다.
// 응용프로그램 클래스에서 SetRegistryKey("Samsung\Doore"); .... WriteProfileString("Main Window","Type", "Normal"); // 그러면 HKEY_CURRENT_USER\Software\Samsung\Doore\Main Window\Type의 값이 Normal로 지정됩니다.VB에서 레지스트리 관련 조작을 하는 함수를 알고 싶다면 이는 다음을 참고하기 바랍니다.
13. CFile 클래스를 이용한 파일 I/O
CFile 클래스를 이용한 파일 I/O의 간단한 코드 예제를 보고 싶습니다.
다음 예제 코드를 참조하세요. API를 이용한 코드 예제를 참고하고 싶으시면 요기를 클릭하세요. 파일의 크기를 알고 싶은 경우에는 CFile의 GetLength 멤버 함수를 호출하고 파일 포인터를 이동하고 싶으시면 Seek 멤버 함수를 호출하세요.
14. 파일 열기 다이얼로그 띄우기
파일 열기 다이얼로그를 띄우려면 어떻게 해야 합니까 ?
파일 열기를 하는 용도로 CFileDialog를 사용할 수 있습니다. 예제 코드는 다음과 같습니다.
CFileDialog fd(TRUE); if (fd.DoModal() == IDOK) { // 사용자가 선택한 파일의 전체 경로가 다 필요하다면 GetPathName 멤버 함수를 사용한다. // 파일 이름만 필요하다면 GetFileName 멤버 함수를 사용한다. CString fileName = fd.GetPathName(); AfxMessageBox(fileName); }15. ActiveX 컨트롤의 다이얼로그에서 ActiveX 컨트롤 띄우기
제가 만든 ActiveX 컨트롤에서 다이얼로그 박스를 하나 만들고 그 위에 이미 등록되어 있는 ActiveX 컨트롤(graph그리는 컨트롤)을 하나 올리면 아무 것도 보이지 않습니다. 그런데, 기본적으로 제공되는 리소스의 다이얼로그 컨트롤들(예로 에디터박스나 버턴등)만 사용하면 괜찮은데, 등록되어 있는 모든 ActiveX 컨트롤중 하나만 삽입해도 문제입니다
해당 프로그램의 App 클래스의 InitInstance 함수에서 AfxEnableControlContainer()을 선두에 삽입하기 바랍니다.
16. 팝업 메뉴 띄우기
뷰 윈도우에서 오른쪽 마우스 버튼을 누르면 팝업 메뉴를 띄우고 싶습니다. CMenu 클래스를 이용했는데 잘 안 됩니다.
다음 코드는 오른쪽 마우스 버튼이 클릭되었을 때 그 위치에다 팝업 메뉴를 띄우는 코드입니다.
void Cxxx::OnRButtonDown(UINT nFlags, CPoint point) { CMenu menu; // 마우스의 좌표가 현재 리스트 컨트롤에서 어느 위치에 있는지 살펴본다. ::ClientToScreen(m_hWnd, &point); // 메뉴를 띄운다. menu.LoadMenu(IDR_CHATMENU); CMenu *pRMenu = menu.GetSubMenu(0); // TrackPopupMenu의 네 번째 인자로 명령을 받을 윈도우의 포인터를 지정한다 pRMenu->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, GetParent()); }여기서 중요한 것은 IDR_CHATMENU가 가리키는 메뉴 리소스가 다음과 같은 그림과 같이 탑 메뉴 항목이 첫 번째 탑 메뉴 항목의 서브 메뉴로 구성해 놓아야 한다는 점입니다.
17. 리치 에디트 컨트롤에서 문자열을 한 줄씩 추가하는 방법
리치 에디트 컨트롤을 이용해서 간단한 채팅 창을 만들고 있습니다. 그런데 글꼴과 색상을 바꿔가며 한 줄씩 추가하는 것이 잘 안됩니다.
다음 함수는 m_reChat라는 이름의 리치 에디트 컨트롤에 strTextIn으로 지정한 메시지를 추가합니다. crNewColor로 지정한 색상과 sSize로 지정한 크기와 lpszFontName으로 지정한 글꼴로 추가되는 메시지를 만들어 추가합니다.
void Cxxx::AddText(CString &strTextIn, COLORREF &crNewColor, int sSize, LPCSTR lpszFontName) { // m_reChat은 채팅 컨트롤 int iTotalTextLength = m_reChat.GetWindowTextLength(); m_reChat.SetSel(iTotalTextLength, iTotalTextLength); m_reChat.ReplaceSel((LPCTSTR)strTextIn); int iStartPos = iTotalTextLength; CHARFORMAT cf; cf.cbSize = sizeof(CHARFORMAT); cf.dwMask = CFM_COLOR | CFM_UNDERLINE | CFM_BOLD | CFM_FACE | CFM_SIZE; cf.dwEffects = (unsigned long)~( CFE_AUTOCOLOR | CFE_UNDERLINE | CFE_BOLD); cf.crTextColor = crNewColor;//RGB(0, 0, 0); cf.yHeight = sSize * 20; strcpy(cf.szFaceName, lpszFontName); int iEndPos = m_reChat.GetWindowTextLength(); m_reChat.SetSel(iStartPos, iEndPos); m_reChat.SetSelectionCharFormat(cf); m_reChat.HideSelection(TRUE, FALSE); m_reChat.LineScroll(1); }18. 에디트 컨트롤 뒤에 내용 추가하기
에디트 컨트롤 뒤에 새로운 내용을 추가하려면 어떻게 해야합니까 ?
m_editCtrl이 CEdit 타입의 객체라고 하고 lpMsg가 추가되어야할 문자열이라면 다음과 같은 코드를 수행하면 됩니다.
int len = m_editCtrl.GetWindowTextLength(); m_editCtrl.SetSel(len, len); m_editCtrl.ReplaceSel(lpStr);'WindowsPrograming' 카테고리의 다른 글
리치에디트 컨트롤 사용법 (2) | 2007.03.11 |
---|---|
RichEditView에서 File Filter설정방법? (0) | 2007.03.11 |
vc++ mfc에서 CRichEdit 클래스에서 폰트색 변경 방법 (0) | 2007.03.11 |