運用 Visual Studio 來製作 MFC GUI 程式需要適當的計畫 | |||
---|---|---|---|
步驟一 | 第一個步驟當然是想像/設計程式的功能以及使用者的操作界面 這個程式我們運用先前完成的複數 Complex 類別, 設計一個圖形化的界面來輸入兩個 複數, 然後計算 加/減/乘/除 的結果以 圖形及文字 顯示在視窗中 圖形界面如下圖所示:
當然一般你在開始設計這種圖形化界面的應用程式時, 只會有一個大概設計的界面圖形, 不會有上面這個完整畫面的... |
||
下面這些步驟只是摘要, 很多部份需要有相當的說明與練習你才知道遇見問題時該怎樣克服,
雖然說照著做有機會完成, 不過你應該會發現你需要更清楚視窗系統的基本運作機制, 你才有辦法自己獨立設計一個應用程式 , 每一個單一的功能你可以在
Google 上找到很多相關的建議, 可是和你的環境都不見得相同, 所以都不一定能夠順利運作!! 你都需要依照你對於 視窗系統的了解、物件導向機制的了解來判斷和調整...
以下原則上每一個步驟都需要編譯測試, 才會繼續下一步驟的功能, 細節請參考 詳細程式製作過程 |
|||
步驟二 | 在 VC2010 中產生 MFC 應用程式專案, 使用 SDI 界面, 編譯測試 |
||
步驟三 | 更改視窗屬性, 使得使用者無法更改視窗大小, 使最大化按鈕失效BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWndEx::PreCreateWindow(cs) ) return FALSE; // TODO: 在此經由修改 CREATESTRUCT cs // 達到修改視窗類別或樣式的目的 cs.style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); //cs.cx = 1000; // SDI 程式無效 //cs.cy = 500; return TRUE; } 調整視窗大小SDI 應用程式 + CView 視窗, 比較難處理, MDI 應用程式或是 CFormView 都可以用 ResizeParentToFit()void CGUIComplexCalcView::OnInitialUpdate() { CView::OnInitialUpdate(); // TODO: 在此加入特定的程式碼和 (或) 呼叫基底類別 CClientDC dc(this); dc.SetMapMode(MM_LOENGLISH); CSize squareInch(1000,1000); dc.LPtoDP(&squareInch); m_cxInch = squareInch.cx/10.0; // 邏輯上每一英吋的 pixel 數 m_cyInch = squareInch.cy/10.0; // TRACE("%f %f\n", m_cxInch, m_cyInch); // 108.4 108.9 // short cxInch = dc.GetDeviceCaps(LOGPIXELSX); // 傳回資料不正確 // short cyInch = dc.GetDeviceCaps(LOGPIXELSY); // TRACE("%d %d\n", cxInch, cyInch); // 96,96 CFrameWnd *pMainWnd = GetParentFrame(); // 視窗寬 4.4 英吋, 高 6 英吋 (含標題列, 選單) pMainWnd->SetWindowPos(0,0,0 , (int)(4.4*m_cxInch+0.5), (int)(6*m_cyInch+0.5), SWP_NOMOVE|SWP_NOZORDER); pMainWnd->ShowWindow(SW_SHOW); } 取消工具列以及狀態列將 m_wndMenuBar, m_wndToolBar, m_wndStatusBar 相關的都註解掉 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWndEx::OnCreate(lpCreateStruct) == -1) return -1; // BOOL bNameValid; // 根據持續值設定視覺化管理員和樣式 OnApplicationLook(theApp.m_nAppLook); /* if (!m_wndMenuBar.Create(this)) { TRACE0("無法建立功能表列\n"); return -1; // 無法建立 } m_wndMenuBar.SetPaneStyle(m_wndMenuBar.GetPaneStyle() | CBRS_SIZE_DYNAMIC | CBRS_TOOLTIPS | CBRS_FLYBY); // 防止功能表列在啟動時取得焦點 CMFCPopupMenu::SetForceMenuFocus(FALSE); if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndToolBar.LoadToolBar(theApp.m_bHiColorIcons ? IDR_MAINFRAME_256 : IDR_MAINFRAME)) { TRACE0("無法建立工具列\n"); return -1; // 無法建立 } CString strToolBarName; bNameValid = strToolBarName.LoadString(IDS_TOOLBAR_STANDARD); ASSERT(bNameValid); m_wndToolBar.SetWindowText(strToolBarName); CString strCustomize; bNameValid = strCustomize.LoadString(IDS_TOOLBAR_CUSTOMIZE); ASSERT(bNameValid); m_wndToolBar.EnableCustomizeButton(TRUE, ID_VIEW_CUSTOMIZE, strCustomize); // 允許使用者定義的工具列作業: InitUserToolbars(NULL, uiFirstUserToolBarId, uiLastUserToolBarId); if (!m_wndStatusBar.Create(this)) { TRACE0("無法建立狀態列\n"); return -1; // 無法建立 } m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)); // TODO: 如果不希望工具列和功能表列為可停駐,請刪除這 5 行 m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY); m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockPane(&m_wndMenuBar); DockPane(&m_wndToolBar); // 啟用 Visual Studio 2005 樣式停駐視窗行為 CDockingManager::SetDockingMode(DT_SMART); // 啟用 Visual Studio 2005 樣式停駐視窗自動隱藏行為 EnableAutoHidePanes(CBRS_ALIGN_ANY); // 啟用工具列和停駐視窗功能表取代 EnablePaneMenu(TRUE, ID_VIEW_CUSTOMIZE, strCustomize, ID_VIEW_TOOLBAR); // 啟用快速 (Alt+拖曳) 工具列自訂 CMFCToolBar::EnableQuickCustomization(); if (CMFCToolBar::GetUserImages() == NULL) { // 載入使用者定義的工具列影像 if (m_UserImages.Load(_T(".\\UserImages.bmp"))) { CMFCToolBar::SetUserImages(&m_UserImages); } } // 啟用功能表個人化 (最近使用的命令) // TODO: 定義您自己的基本命令,確定每個下拉式功能表都至少有一個基本命令。 CList<UINT, UINT> lstBasicCommands; lstBasicCommands.AddTail(ID_FILE_NEW); lstBasicCommands.AddTail(ID_FILE_OPEN); lstBasicCommands.AddTail(ID_FILE_SAVE); lstBasicCommands.AddTail(ID_FILE_PRINT); lstBasicCommands.AddTail(ID_APP_EXIT); lstBasicCommands.AddTail(ID_EDIT_CUT); lstBasicCommands.AddTail(ID_EDIT_PASTE); lstBasicCommands.AddTail(ID_EDIT_UNDO); lstBasicCommands.AddTail(ID_APP_ABOUT); lstBasicCommands.AddTail(ID_VIEW_STATUS_BAR); lstBasicCommands.AddTail(ID_VIEW_TOOLBAR); lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2003); lstBasicCommands.AddTail(ID_VIEW_APPLOOK_VS_2005); lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2007_BLUE); lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2007_SILVER); lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2007_BLACK); lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2007_AQUA); lstBasicCommands.AddTail(ID_VIEW_APPLOOK_WINDOWS_7); CMFCToolBar::SetBasicCommands(lstBasicCommands); */ return 0; } 設定應用程式標題void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle) { // TODO: 在此加入特定的程式碼和 (或) 呼叫基底類別 CFrameWndEx::OnUpdateFrameTitle(bAddToTitle); // order is important SetWindowText(_T("GUI Complex Calculator")); // overrides document title } |
||
步驟四 | 設定 mapping mode 為 MM_LOENGLISH (座標軸一單位是 1/100 英吋)void CGUIComplexCalcView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo) { // TODO: 在此加入特定的程式碼和 (或) 呼叫基底類別 GetClientRect(&m_rectClient); //裝置座標 pDC->SetViewportOrg(CPoint(m_rectClient.right/2, m_rectClient.bottom/2-(int)(0.55*m_cyInch))); //裝置座標 pDC->SetMapMode(MM_LOENGLISH); CView::OnPrepareDC(pDC, pInfo); } 計算複數平面區域的範圍void CGUIComplexCalcView::OnInitialUpdate() { ... int cx_border = GetSystemMetrics(SM_CXFRAME); int cy_border = GetSystemMetrics(SM_CYFRAME); int cy_caption = GetSystemMetrics(SM_CYCAPTION); CRect window_rect; pMainWnd->GetWindowRect(&window_rect); CPoint client_top_left(0, 0); pMainWnd->ClientToScreen(&client_top_left); int menu_height = client_top_left.y - window_rect.top - cy_caption - cy_border; // TRACE("cy_caption=%d menu_height=%d client_top_left.y=%d cy_border=%d\n", // cy_caption, menu_height, client_top_left.y, cy_border); CRect rectClient; GetClientRect(&rectClient); //裝置座標 int xmargin = (int)((4.4-4.12)*m_cxInch - 2*cx_border)/2; int ymargin = (int)(((6-4.12)*m_cyInch - 2*cy_border - cy_caption - menu_height)/2.0 - 0.55*m_cyInch) ; // TRACE("xmargin=%d ymargin=%d right=%d bottom=%d\n", // xmargin, ymargin, rectClient.right, rectClient.bottom); m_rectHit = CRect(xmargin, ymargin, xmargin+(int)(4.1*m_cxInch), ymargin+(int)(4.1*m_cyInch)); }4.12 = 4 + 2*0.04 + 2*0.02 |
||
步驟五 | 繪製座標軸void CGUIComplexCalcView::OnDraw(CDC* pDC) { int i; CGUIComplexCalcDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: 在此加入原生資料的描繪程式碼 ... // x-axis CPen bAxisPen(PS_SOLID, 2, RGB(255, 0, 0)); pDC->SelectObject(&bAxisPen); pDC->MoveTo(-200, 0); pDC->LineTo(200, 0); pDC->MoveTo(190,5); pDC->LineTo(200, 0);pDC->LineTo(190,-5); for (i=-200; i<=200; i+=100) { pDC->MoveTo(i, -6); pDC->LineTo(i, 6); } // y-axis pDC->MoveTo(0, -200); pDC->LineTo(0, 200); pDC->MoveTo(-5,190); pDC->LineTo(0,200);pDC->LineTo(5,190); for (i=-200; i<=200; i+=100) { pDC->MoveTo(-6, i); pDC->LineTo(6, i); } ... 標示參考點// labels pDC->TextOut(5,-3, CString("(0+0i)")); pDC->TextOut(168,-5, CString("(2+0i)")); pDC->TextOut(5,198, CString("(0+2i)")); 繪製複數平面區域// bounding rectangle CPen bPen(PS_SOLID, 3, RGB(0, 0, 255)); pOldPen = (CPen*) pDC->SelectObject(&bPen); pDC->Rectangle(CRect(-204, 204, 204,-204)); 繪製參考單位圓及2單位圓// reference circle CBrush *pOldBr = (CBrush*) pDC->SelectStockObject(NULL_BRUSH); CPen bPen2(PS_DASH, 1, RGB(200, 200, 200)); CPen *pOldPen = (CPen*) pDC->SelectObject(&bPen2); pDC->Ellipse(-100, 100, 100, -100); pDC->Ellipse(-200, 200, 200, -200); |
||
步驟六 | 加入 Complex.h 以及 Complex.cpp在 Complex.cpp 的第一列加入 #include "stdafx.h" 加入一個界面 convertPoint() 將 Complex 型態的複數點轉換為 CPoint 型態的座標點 CPoint Complex::convertPoint() { return CPoint((int)(m_real*100), (int)(m_imaginary*100)); } 繪製 3 個點以及畫面下方的說明文字void CGUIComplexCalcView::OnDraw(CDC* pDC) { ... // end point pDC->MoveTo(m_endPt.convertPoint()+CPoint(-5,5)); pDC->LineTo(m_endPt.convertPoint()+CPoint(5,-5)); pDC->MoveTo(m_endPt.convertPoint()+CPoint(5,5)); pDC->LineTo(m_endPt.convertPoint()+CPoint(-5,-5)); ... // start point pDC->MoveTo(m_startPt.convertPoint()+CPoint(-5,5)); pDC->LineTo(m_startPt.convertPoint()+CPoint(5,-5)); pDC->MoveTo(m_startPt.convertPoint()+CPoint(5,5)); pDC->LineTo(m_startPt.convertPoint()+CPoint(-5,-5)); // text descriptions pDC->TextOutW(-200, -210, CString("請在上面複數平面中以滑鼠左鍵及右鍵選擇兩點")); ostrstream sstr; m_startPt.print(sstr); sstr<<ends; CString line("起點: "); line += sstr.str(); sstr.freeze(false); pDC->SetTextColor(RGB(0,0,255)); pDC->TextOutW(-190, -240, line); ostrstream sstr2; m_endPt.print(sstr2); sstr2<<ends; CString line2("終點: "); line2 += sstr2.str(); sstr2.freeze(false); pDC->SetTextColor(RGB(255,0,0)); pDC->TextOutW(-190, -260, line2); CString line3; m_resultPt = m_startPt; m_resultPt.add(m_endPt); line3 = "起點 + 終點 = "; CPoint pt = m_resultPt.convertPoint(); pDC->LPtoDP(&pt); ostrstream sstr3; m_resultPt.print(sstr3); sstr3<<ends; line3 += sstr3.str(); sstr3.freeze(false); pDC->SetTextColor(RGB(192,0,128)); if (!m_rectHit.PtInRect(pt)) line3 += " (outside the box)"; pDC->TextOutW(-190, -290, line3); // result point pt = m_resultPt.convertPoint(); CPen bResultPen(PS_SOLID, 3, RGB(192, 0, 128)); pDC->SelectObject(&bResultPen); pDC->MoveTo(pt+CPoint(-6,0)); pDC->LineTo(pt+CPoint(6,0)); pDC->MoveTo(pt+CPoint(0,6)); pDC->LineTo(pt+CPoint(0,-6)); CRect resultRect(pt.x-6, pt.y-6, pt.x+7, pt.y+7); pDC->Ellipse(&resultRect); pDC->SelectObject(pOldPen); pDC->SelectObject(pOldBr); ... } |
||
步驟七 | 測試游標是否位於複數平面區間, 如果游標在區間內則更改游標形狀處理 WM_SETCURSOR 訊息 BOOL CGUIComplexCalcView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { // TODO: 在此加入您的訊息處理常式程式碼和 (或) 呼叫預設值 CPoint point; GetCursorPos(&point); pWnd->ScreenToClient(&point); // TRACE("point=(%d,%d)\n", point.x, point.y); if (m_rectHit.PtInRect(point)) { ::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_CROSS)); return TRUE; } return CView::OnSetCursor(pWnd, nHitTest, message); } |
||
步驟八 | 處理滑鼠左鍵以及右鍵的輸入處理 WM_LBUTTONUP 及 WM_RBUTTONUP 兩個訊息 void CGUIComplexCalcView::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: 在此加入您的訊息處理常式程式碼和 (或) 呼叫預設值 // TRACE("point.x=%d point.y=%d\n", point.x, point.y); if (m_rectHit.PtInRect(point)) { m_startPt = pointToComplex(point); Invalidate(); } CView::OnLButtonUp(nFlags, point); } void CGUIComplexCalcView::OnRButtonUp(UINT nFlags, CPoint point) { // TODO: 在此加入您的訊息處理常式程式碼和 (或) 呼叫預設值 if (m_rectHit.PtInRect(point)) { m_endPt = pointToComplex(point); Invalidate(); CView::OnRButtonUp(nFlags, point); } else { ClientToScreen(&point); OnContextMenu(this, point); } } // 將平面上一個 CPoint 點轉換為 Complex 複數 Complex CGUIComplexCalcView::pointToComplex(CPoint point) { CPoint origin((int)((m_rectHit.left+m_rectHit.right+0.5)/2.0), (int)((m_rectHit.top+m_rectHit.bottom+0.5)/2.0)); return Complex((point.x - origin.x) / m_cxInch, -(point.y - origin.y) / m_cyInch); } |
||
步驟九 | 調整設計選單處理選單訊息 IDM_ADD, IDM_SUBTRACT, IDM_MULTIPLY, IDM_DIVIDEvoid CGUIComplexCalcView::OnAdd() { // TODO: 在此加入您的命令處理常式程式碼 m_typeOfOperation = 0; Invalidate(); } void CGUIComplexCalcView::OnSubtract() { // TODO: 在此加入您的命令處理常式程式碼 m_typeOfOperation = 1; Invalidate(); } void CGUIComplexCalcView::OnMultiply() { // TODO: 在此加入您的命令處理常式程式碼 m_typeOfOperation = 2; Invalidate(); } void CGUIComplexCalcView::OnDivide() { // TODO: 在此加入您的命令處理常式程式碼 m_typeOfOperation = 3; Invalidate(); } void CGUIComplexCalcView::OnDraw(CDC* pDC) { ... CString line3; m_resultPt = m_startPt; switch (m_typeOfOperation) { case 0: line3 = "起點 + 終點 = "; m_resultPt.add(m_endPt); break; case 1: line3 = "起點 - 終點 = "; m_resultPt.subtract(m_endPt); break; case 2: line3 = "起點 * 終點 = "; m_resultPt.multiply(m_endPt); break; case 3: line3 = "起點 / 終點 = "; m_resultPt.divide(m_endPt); } CPoint pt = m_resultPt.convertPoint(); pDC->LPtoDP(&pt); ... } void CGUIComplexCalcView::OnUpdateAdd(CCmdUI *pCmdUI) { // TODO: 在此加入您的命令更新 UI 處理常式程式碼 pCmdUI->SetCheck(m_typeOfOperation == 0); } void CGUIComplexCalcView::OnUpdateSubtract(CCmdUI *pCmdUI) { // TODO: 在此加入您的命令更新 UI 處理常式程式碼 pCmdUI->SetCheck(m_typeOfOperation == 1); } void CGUIComplexCalcView::OnUpdateMultiply(CCmdUI *pCmdUI) { // TODO: 在此加入您的命令更新 UI 處理常式程式碼 pCmdUI->SetCheck(m_typeOfOperation == 2); } void CGUIComplexCalcView::OnUpdateDivide(CCmdUI *pCmdUI) { // TODO: 在此加入您的命令更新 UI 處理常式程式碼 pCmdUI->SetCheck(m_typeOfOperation == 3); } |
||
步驟十 | 設計滑鼠右鍵選單已經自動顯示右鍵選單, 但是還需要設定只有當滑鼠移到視窗下方指定區間才能動作void CGUIComplexCalcView::OnContextMenu(CWnd* /* pWnd */, CPoint point) { // TRACE("point.x=%d point.y=%d\n", point.x, point.y); #ifndef SHARED_HANDLERS CPoint sPoint(point); ScreenToClient(&sPoint); if (!m_rectHit.PtInRect(sPoint)) theApp.GetContextMenuManager()-> ShowPopupMenu(IDR_POPUP_EDIT, point.x, point.y, this, TRUE); #endif } 處理選單訊息 ID_APP_EXIT 及 ID_APP_ABOUTvoid CGUIComplexCalcView::OnAppExit() { // TODO: 在此加入您的命令處理常式程式碼 AfxGetMainWnd()->PostMessageW(WM_CLOSE); } void CGUIComplexCalcView::OnAppAbout() { // TODO: 在此加入您的命令處理常式程式碼 ((CGUIComplexCalcApp*)AfxGetApp())->OnAppAbout(); } |
||
步驟十一 | 更改應用程式的圖示 (icon) |
||
完整程式製作過程 (android com.adobe.flashplayer.apk) | |||
範例程式碼 |
回
C++ 物件導向程式設計課程
首頁
製作日期: 04/03/2014 by 丁培毅 (Pei-yih Ting) E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615 海洋大學 電機資訊學院 資訊工程學系 Lagoon |