| 運用 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);
} |
||
| 步驟九 | 調整設計選單 |
||
| 步驟十 | 設計滑鼠右鍵選單 |
||
| 步驟十一 | 更改應用程式的圖示 (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 |