Re: [問題] mfc不知如何下手

看板C_and_CPP (C/C++)作者 (purpose)時間13年前 (2011/04/07 06:39), 編輯推噓16(1601)
留言17則, 16人參與, 最新討論串3/5 (看更多)
※ 引述《steven70101 (老人)》之銘言: : 最近剛入公司,要學習MFC的用法 : c++跟win32 api都是略懂(程式越學越不敢說懂...) : 有用 win32 api 寫出俄羅斯方塊的經驗 : 遊戲畫面 : http://ppt.cc/QY_N : 但是現在在看MFC時有種找不到下手點的感覺 : 會按照範例寫出最基本的一個空視窗 : 也看了另一個範例教你怎麼拉功能表 : 但是看了看卻忽然感覺,那我應該怎麼下筆囧 : 是用那基本的空視窗在新增 .rc 然後去拉嗎? : 那程式碼部分應該怎麼寫? 我也是新手,提供一點看法。 不管怎麼教、怎麼學 MFC,有些最基本的東西都要掌握住。 要搞懂什麼是 Dialog-Based,什麼是 SDI、MDI。 因為每次開新的 MFC 專案,第一個要選的就是這件事情。 可以先從既有的軟體去比較,如果這個軟體跟你要寫的類似,那再分析它是 用 Dialog 還是 SDI 或 MDI 寫。 如果對方也用 MFC 寫,又有 Open Source 那你當然直接開他的 .sln 看最快。 版友之前貼過的,比較知名的 MFC 開源專案: http://www.codeproject.com/KB/cpp/OpenSource_VC_MFC.aspx MFC 裡有很多比較方便的功能,都得使用 DOC/VIEW 才有。 換言之,至少得選 SDI、MDI 專案才能使用這些功能。 問題是 Dialog 一開始就能用拖曳的方式加入控件 (or Control or 控制項), 而 SDI 與 MDI 一開始沒地方加! 這是一個常見問題。 解決的方法是開 SDI 專案時,自己更改設定,把 View 類從預設的繼承自 CView 改成繼承自 CFormView。 那這個 SDI 的客戶區就會跟 Dialog 一樣,隨便你用滑鼠拖曳去添加控件了。 當你拖曳好軟體介面後,接下來要考慮的兩件事,來決定程式碼怎麼寫: 1. 要處理哪些事件 為什麼需要處理事件,這理由就不需要多談。 當使用者敲下某個鍵盤按鍵,Windows 會告知發生 "WM_KEYDOWN" 給有 focus 的視窗。 然後看此視窗的決定,是要如何處理掉此事件。 如果該視窗選擇「不處理」,那就「到此為止」。這是由 Windows 定下的規則。 可是 MFC 對於 message 的傳遞有自己的路線,不再死守 Windows 規定。 (進階版的事件處理) 如果是 DOC/VIEW 程式,則會看有 focus 的視窗打不打算處理這個 "WM_KEYDOWN", 這個 focus 視窗通常是第一線的 View 類視窗。 如果 View 類不處理,那麼就將訊息轉送給 Doc 類。 如果 Doc 類又不想處理,那就再轉交給框架視窗 (CFrameWnd), 而框架視窗又不想處理,就交給 CWinApp 類。 比如針對一個 F7 按鍵,我們可以在這幾個 class 裡,都分別寫好對於 F7 的 事件處理程式碼。 等事件真的發生時,再從目前的程式狀態,去決定要交給哪個 class 負責。 2. 現有的圖形介面,是否需要修改 主要的問題通常在控件上。也就是所謂的 "Custom Control" 議題。 舉例來說,控件裡最強大、最麻煩的 List Control,預設的 Report 型態下, 每個 item 的背景顏色固定只有一種。 如果想要單數 item 背景是白色,雙數 item 的背景是紅色,那就要自己改寫。 對於 Win32 API 開發者而言,熟悉的方案就是用 "SubClassing" 解決。 MFC 也有 SubClassing,只是跟 Win32 不完全等價。 前者是改掉 WndClass,後者是改掉真的 C++ Class。 Edit 控件之所以是 Edit,是因為在 CreateWindow() 時,其 WndClass 參數選擇 了一個叫 Edit 的 WndClass。 又每個已經對 OS 註冊過的 WndClass 都有自己獨一無二的 WndProc 函數。 已經註冊完畢的 WndClass,就已經將 WndProc 寫死掉,沒辦法修改內容。 所以 Win32 API 開發者們,會在 CreateWindow() 建立過控件後,再這樣做: oldWndProc = SetWindowLong(目標視窗的 hWnd, GWL_WNDPROC, 自訂的新 WndProc); 此時雖然偵測目標視窗的 WndClass 還是沒有改變,但是其 WndProc 已經在 Runtime 被修改成自訂的版本了。然後在自訂的新 WndProc 裡,再用 switch 挑選出想要 自己處理的訊息,改寫訊息處理程式碼後,將其它不打算改的狀況都丟給: CallWindowProc(oldWndProc, 目標視窗的 hWnd, uMsg, wParam, lParam); 參考資料: http://msdn.microsoft.com/en-us/library/bb773183.aspx 回到前面提的 List Control,其 WndClass 叫 SysListView32,如果依造 Win32 API 開發者的那套 SubClassing 機制,那就是要改寫 WM_PAINT 的處理程式碼。 改寫 WM_PAINT 的工作量非常非常大,於是 Windows 又提供了新的機制,用來針對 想要 "Custom Control" 的開發者,主要有兩個: Owner Draw Custom Draw 參考資料:http://msdn.microsoft.com/en-us/library/ms364048.aspx 不是每個控件都有提供上面這兩個機制,但至少 SysListView32 有提供。 其想法是,整個控件的外框繪製還是由 SysListView32 的既有 WM_PAINT 去 處理,但是內部每個 item 的繪製工作就不管了。一律傳送通告訊息給父視窗, 當父視窗收到後,再去手動取得 DC 手動用 GDI 函數畫 item。所以我們想要拿單數、 雙數的 item 有不同背景色,就可以這樣做。 這裡的父視窗顯然就是控件的 Owner,所以才叫 Owner Draw。 至於 Custom Draw 當成新版的 Owner Draw 就好,同樣也是由控件發送通告訊息, 去告知控件的父視窗,目前準備要繪製哪個 item,由你負責繪製用的程式碼吧! 這兩個做法是不牽涉到 SubClassing 的,因為父視窗通常就是我們軟體的視窗, 其 WndClass (WndProc) 本來就是全新自訂的。 ### MFC 的 SubClassing 已經不需要 SetWindowLong 再 CallWindowProc 了。 一些都以 C++ 類別為核心。 以 Edit Control 為例,當在對話方塊拉好一個 Edit 控件,然後在上面按右鍵, 選擇『加入變數』。 最右邊 (VC 2008 為例) 會有個「類別」,如果選擇 Value,且「變數型別」 為 CString,則表示將這個 Edit 控件跟一個 CString 物件綁定。 每當執行 UpdateData() 後,螢幕畫面上的字就會跟 CString 物件的值同步。 如果『加入變數』時,選擇「類別 = Control」,這時候「變數型別」就是一個關鍵, 當你選擇預設的 CEdit,變數名稱叫 myedit,那你只是多了一個 CEdit myedit, 可以方便你寫 myedit.SetWindowText() 這類操作,該控件的 WndProc 還是沒有改變。 因為 MFC 一切都是以類別為單位,所以要先用 class view 或者你用 VC6 就是用 什麼類別精靈?先去新增一個 CMyEdit 類別,並繼承自 CEdit。 然後在「加入變數」設定「變數型別」時,選擇 CMyEdit,不要選 CEdit。 就這樣選完後,MFC 就自動幫你做好 SubClassing 工作了。 比如在父視窗的 class 裡幫你加入資料成員 CMyEdit myed; 的程式碼,會自動產生。 所以 MFC 再怎麼爛,還是比直接寫 Win32 API 方便、快速一點。 就算退一萬步,你也還是能夠掛著 MFC 的皮,去搞 Win32 API 那套 SubClassing。 當新增好 CMyEdit 後,因為這個 class 是你建的,所以愛怎麼改就怎麼改吧, 不會像 CEdit 的程式碼一樣,都已經被寫死。 繼續回去用 List Control 當 "Custome Control" 例子,假設已經新增 好 CMyListCtrl mylist。 首先在 class view (類別檢視) 點選 CMyListCtrl 讓他維持選中的狀態, 然後打開「屬性視窗」,會有幾個分頁。 如果選「事件」分頁,表示你要處理來自 CMyListCtrl 的子視窗發來的通告訊息。 以此例來說只有 CMyDialog 才有可能擁有子視窗,所以這裡不用碰。 如果選「覆寫」(override),就是改寫 CMyListCtrl 裡的一些虛擬函數。 因為 MFC 在每個控件都有額外設計一些虛擬函數,你首先要知道他們的功能, 才需要考慮要不要做 override。不知道功能的就不要動,不會影響整個程式。 像 PreSubClassWindow 這個虛擬函數,因為前面的 SubClassing 動作都是由 MFC 自動 處理。如果想要在 SubClassing 之前,做一些額外的動作,就可以在這裡進行。 舉例來說,原本按照預設值,控件都是自己 Draw 外觀,你必須明確加上 Style 告知說你想要做 Owner Draw,則控件才會在某些時段,準備資料傳送通告訊息給父 視窗說,該輪到你 Draw 內容了。這就可以在 PreSubClassWindow 去更改 Style: void CMyListCtrl::PreSubclassWindow() { // 加入下面這個 Style,才能使 List Control 進入 Owner Draw 模式 this->ModifyStyle(0,LVS_OWNERDRAWFIXED); CListCtrl::PreSubclassWindow(); } 如果選「訊息」(message),那就是主要的處理重點。 比如選 WM_PAINT,那就真的整個 List Control 的繪製工作都可以由你來寫。 如果我們 SubClassing 之後想要讓 List Control 做 Owner Draw,則要注意 這裡不是去寫一個處理 WM_DRAWITEM 的處理函數,去動 CMyListCtrl::OnDrawItem() 是錯誤的。這樣寫表示當 List Control 的子視窗想要做 Owner Draw 並且送來通告 訊息時,做為父視窗的 CMyListCtrl 去幫他畫。 所以應該改用 CMyListCtrl 的父視窗,或者用 MFC 的術語講,就是改用其父類別 CMyDialog 去處理,即 CMyDialog::OnDrawItem()。 但是這個方法有個缺點,就是 CMyListCtrl 會跟 CMyDialog 綁死。 你下次開的 MFC 專案時,為了重複使用這次的有 Owner Draw 功能的 List Control, 那你就要把 CMyListCtrl 跟 CMyDialog 都複製過去。 所以 MFC 有另外一個方案,就是做 override,改寫掉 CMyListCtrl::DrawItem() 這個虛擬函數。把想要做的 Owner Draw 程式碼都寫在裡面就好。 下次開新專案時,只要把這個 CMyListCtrl 複製過去,設定好加入變數的 MFC SubClassing 動作,不用寫一行程式碼,就可以重複使用。 : 因為之前 api 有提供基本空視窗的程式碼 : 然後我要抓啥資料或是多啥功能就是去寫程式就好 : 結果MFC的工具書裡面一堆圖反而讓我無所適從無法下手(汗) 書就前篇講的《Programming Windows with MFC》 雖然很舊,但重要觀念都有講。其他書我也看了一些,雖然可能細節寫得更詳細, 或者中文書看起來比較快,但是在教導觀念的部份,還是這本好。 另外補充一點,雖然現在版本的 MFC Class Hierarchy Chart 都大到誇張,小螢幕 的根本不能一次看完,還是可以找 VC6 比較小的圖,盡量讓自己可以隨時打開來看, 或者印出來看,怎樣都好。 因為大家的記憶都有限,不可能記住 MFC 每個類別的功能,一定是用到時再查。 比如想到要做工具列,那工具列的可能英文是什麼?想好後,開這張圖,去查 toolbar 相關的類別,並看與他相關的父類、子類是什麼。 以 CView 來說,如果你看過他的 Hierarchy Chart,就知道還有很多子類,有些 可以支援捲軸的一些功能,有些可以像 Dialog 一樣拖曳控件上去。 而 CView 僅只是最垃圾,功能最少的那種。你沒這樣查圖,一直用最基本的類別 只會覺得,怎麼什麼東西都沒有! -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 124.8.132.241 ※ 編輯: purpose 來自: 124.8.132.241 (04/07 06:41)

04/07 08:07, , 1F
推一個。
04/07 08:07, 1F

04/07 08:31, , 2F
m一個 >///<
04/07 08:31, 2F

04/07 08:57, , 3F
有M有推 其實是自己不懂這個啦 >___<
04/07 08:57, 3F

04/07 09:00, , 4F
推一個 很詳細, 希望原po可照著瞭解, 都試過
04/07 09:00, 4F

04/07 09:04, , 5F
推推
04/07 09:04, 5F

04/07 09:25, , 6F
好文推
04/07 09:25, 6F

04/07 09:57, , 7F
推p大
04/07 09:57, 7F

04/07 10:30, , 8F
靠 這啥假新手XD
04/07 10:30, 8F

04/07 10:35, , 9F
樓上太中肯 再推一個 XDDD
04/07 10:35, 9F

04/07 10:46, , 10F
只好推樓樓上了 LOL
04/07 10:46, 10F

04/07 13:20, , 11F
偽新手 XDDDDDDDD
04/07 13:20, 11F

04/07 14:30, , 12F
好強大的新手
04/07 14:30, 12F

04/07 15:20, , 13F
這新手也太強了吧XD 推一個
04/07 15:20, 13F

04/07 20:39, , 14F
推強大的新手XD
04/07 20:39, 14F

04/08 00:00, , 15F
好強大的新手 XDDD
04/08 00:00, 15F

04/08 21:35, , 16F
好強大的新手 ><
04/08 21:35, 16F

04/13 23:00, , 17F
真的是假新手XD
04/13 23:00, 17F
文章代碼(AID): #1DdEkAWJ (C_and_CPP)
討論串 (同標題文章)
本文引述了以下文章的的內容:
以下文章回應了本文
完整討論串 (本文為第 3 之 5 篇):
文章代碼(AID): #1DdEkAWJ (C_and_CPP)