技術演進中的開發沉思-36 MFC系列: 對話框

MFC這個章節里,不能忽視的是對話框的開發。如果把 MFC 程序比作一棟辦公樓,那對話框就是「會客室」—— 它是程序與用戶面對面交流的地方:用戶在這里輸入數據,程序在這里展示信息,彼此的互動都從這個空間開始。今天圍繞這個「會客室」,講透了它的設計、交流規則、數據傳遞,以及如何讓它真正「用起來」。

一、對話框編輯器

記得第一次打開 VC++ 的對話框編輯器時,我總想起老家木匠做衣柜的場景 —— 木匠先在紙上畫好隔板位置、抽屜大小,對話框編輯器就像這張圖紙:拖個按鈕像釘個掛鉤,放個輸入框像留個抽屜,調整布局像擺家具。你不用寫一行代碼,就能用鼠標拖曳出對話框的模樣:標題欄是「會客室門牌」,按鈕是「呼叫鈴」,輸入框是「留言本」。

這種「所見即所得」的設計,在當年可是個新鮮事。上世紀 90 年代末,我剛接觸編程時,寫一個簡單的輸入框界面,得用純 API 函數一行行定義坐標、尺寸,就像用文字描述衣柜的每個部件尺寸,稍不注意就會出現按鈕重疊、文字超出邊框的情況。有次為了讓一個查詢框居中顯示,調試了一下午坐標計算代碼,最后發現是把屏幕分辨率的寬高搞反了。而有了對話框編輯器后,鼠標拖動就能對齊控件,還能直接在界面上預覽字體大小,效率提升不止一倍。

編輯器里還有個很貼心的功能 —— 控件對齊工具。就像給家具安裝定位器,選中幾個按鈕,點一下「左對齊」,它們就整整齊齊排成一列。這在做數據錄入界面時特別有用,那些密密麻麻的輸入框和標簽,靠手動調整永遠會有細微偏差,用對齊工具處理后,界面瞬間變得清爽規整。我記得當年做一個員工信息登記系統,光是對話框里的控件就有三十多個,全靠這個功能節省了大半天時間。

對話框的消息處理函數:會客室里的應答規則

但光有房間還不夠,得有人回應客人的需求。用戶點擊按鈕、輸入文字時,對話框會收到「消息」—— 就像客人按鈴、遞紙條,程序必須知道該怎么接話。比如用戶點了「確定」按鈕,對話框會收到BN_CLICKED消息,我們寫的OnOK()函數就是「應答話術」。

消息處理有個很關鍵的邏輯 —— 消息映射。它就像會客室的服務指南,明確記錄著「客人按紅色按鈕要送茶水,按藍色按鈕要拿紙筆」。MFC 把這個過程封裝得很巧妙,我們不用去管底層的消息分發機制,只需在 ClassWizard 里關聯控件和函數就行。

這讓我想起早年做項目時的趣事:有次忘了寫關閉按鈕的消息處理,用戶點了十幾次都沒反應,最后打電話來問「是不是程序凍住了」。后來排查時發現,按鈕確實放在了對話框上,但沒給它綁定消息處理函數,就像按鈴后鈴鐺響了,卻沒人聽到。還有一次更烏龍,把「確定」和「取消」的消息處理函數寫反了,用戶點「確定」沒保存數據,點「取消」反而保存了,測試人員拿著打印出來的操作記錄來找我時,我臉都紅了。從那以后,每次寫完消息處理,我都會像檢查門窗是否鎖好一樣,逐個按鈕點一遍才放心。

其實消息處理背后是 MFC 的消息循環機制,就像會客室門口的接待員,不斷接收客人的請求并指引給對應的服務員。這個機制在當時比其他開發框架要高效得多,有些早期的編程工具處理消息時會出現卡頓,就像接待員同時接了太多電話,顧此失彼,而 MFC 能有條不紊地處理多個消息,哪怕用戶快速點擊多個按鈕,也能按順序響應。

二、對話框數據交換與校驗

用戶在對話框里填的信息(比如年齡、郵箱),怎么傳到程序里?又怎么確保填的是有效信息?這就得靠 DDX(對話框數據交換)和 DDV(對話框數據校驗)。

可以把 DDX 看作「傳送員」:用戶在輸入框里寫了「25」,DDX 會把這個數字「搬」到程序的變量里;程序要顯示「余額 100 元」,DDX 又會把變量里的數字「貼」到顯示框里。而 DDV 是「安檢員」—— 如果用戶在年齡框里填「abc」,DDV 會彈出提示「請輸入數字」;如果填「200」,它會提醒「年齡過大」。

當年做社保系統時,就是靠 DDV 擋住了無數無效數據。有次測試人員故意填「-5」當年齡,DDV 立刻報錯,我們都笑說:「這安檢員比火車站的還嚴。」但它也不是死板的,我們可以自定義校驗規則。比如做圖書管理系統時,要求 ISBN 編號必須是 13 位數字,我們就給 DDV 加了個自定義校驗函數,像給安檢員培訓新的檢查標準,確保輸入的編號格式正確。

DDX 的交換過程其實很智能,它會自動識別數據類型。如果綁定的是字符串變量,就把輸入框里的文字原樣傳過去;如果是整數變量,就自動做類型轉換。但有次我把一個只能輸入數字的編輯框綁定到了字符串變量,結果用戶輸入「123a」也能通過,后來才明白,DDX 只是負責搬運,數據格式的把關還得靠 DDV。這就像傳送員只負責把包裹送到,包裹里的東西是否符合規定,還得安檢員來檢查。

現在想來,DDX 和 DDV 的設計理念影響了后來很多開發框架,包括現在 Web 開發里的表單綁定和驗證,其實和它們的思路很像,只是換了種實現方式。

三、對話框的調用

設計好的對話框,總得讓用戶能打開。調用對話框就像「推門進會客室」:用DoModal()打開的是「封閉式會客室」—— 用戶必須處理完這里的事(點確定或取消),才能回到主程序;用Create()打開的是「開放式會客室」—— 用戶可以在對話框和主程序間來回切換,就像隨時能進出的休息室。

最常用的是DoModal(),比如登錄框:必須輸入賬號密碼,否則進不了系統。早年寫財務軟件時,每次用戶登錄,都是DoModal()彈出登錄框,那聲「咚」的彈出音效,現在想起來還很熟悉。有次為了讓登錄框更友好,我們在DoModal()調用前加了個判斷,如果用戶勾選了「記住密碼」,就自動填充賬號密碼,減少用戶操作。

Create()則適合那些需要長期顯示的對話框,比如圖像處理軟件里的調色板面板。用戶可以一邊在主窗口畫畫,一邊在調色板里選顏色,不用反復開關。但Create()有個需要注意的地方,它創建的是非模態對話框,得手動管理生命周期,就像開放式會客室要安排人隨時打掃整理,否則容易出現內存泄漏。我早年就犯過一個錯,頻繁創建非模態對話框卻沒及時銷毀,程序運行久了就越來越卡,最后通過內存檢測工具才找到問題所在。

兩種調用方式各有適用場景,就像去銀行辦業務,取號時的排隊叫號窗口是「封閉式」的,必須輪到你才能辦理;而旁邊的自助查詢機是「開放式」的,隨時可以去查余額。在實際開發中,我們會根據功能需求選擇合適的調用方式,讓用戶操作更順暢。

四、利用 ClassWizard 連接對話框與類

對話框、控件、消息、變量,怎么把它們串起來?ClassWizard 就是「管家」—— 它能自動生成連接代碼:你選個輸入框,告訴它「關聯到 m_age 變量」,它就把 DDX 代碼寫好;你選個按鈕,說「點它要執行 OnSave」,它就把消息映射做好。

剛用 ClassWizard 時,我總懷疑「這東西真能行?」畢竟之前手寫這些代碼時,光是消息映射宏的格式就經常搞錯。有次手寫ON_BN_CLICKED宏時,把控件 ID 寫錯了,結果按鈕怎么點都沒反應,查了半天才發現是少寫了個數字。而 ClassWizard 生成的代碼就像打印出來的標準答案,格式工整、沒有遺漏,大大減少了這類低級錯誤。

它的操作過程也很直觀,就像在填表:第一步選對話框資源,第二步輸入類名,第三步勾選要關聯的控件和消息,確定后,一個完整的對話框類就生成了。我記得第一次用它生成類時,看著自動出現的DoDataExchange函數和消息處理函數,有種「原來復雜的事情可以這么簡單」的感慨。

但 ClassWizard 也不是萬能的。有次我們想給一個按鈕加雙擊消息處理,發現它默認只支持單擊消息,后來查資料才知道,需要手動在消息映射里添加對應的宏。這就像管家能處理日常事務,但遇到特殊需求時,還得自己動手。不過總體來說,它把程序員從重復的代碼編寫中解放出來,讓我們有更多精力去思考業務邏輯。


// 登錄對話框類(ClassWizard自動生成框架)class CLoginDlg : public CDialog{public:CLoginDlg(CWnd* pParent = nullptr);// 關聯的變量(DDX用)CString m_strUser; // 用戶名CString m_strPwd; // 密碼int m_nAge; // 年齡(示例)// 對話框數據enum { IDD = IDD_LOGIN_DLG }; // 對話框資源IDprotected:virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV核心// 消息映射(ClassWizard生成)DECLARE_MESSAGE_MAP()public:afx_msg void OnBnClickedOk(); // 確定按鈕消息處理afx_msg void OnBnClickedCancel(); // 取消按鈕消息處理};// DDX&DDV實現(ClassWizard輔助生成)void CLoginDlg::DoDataExchange(CDataExchange* pDX){CDialog::DoDataExchange(pDX);// 把IDC_USER_EDIT控件和m_strUser變量綁定DDX_Text(pDX, IDC_USER_EDIT, m_strUser);// 把IDC_PWD_EDIT控件和m_strPwd變量綁定DDX_Text(pDX, IDC_PWD_EDIT, m_strPwd);// 把IDC_AGE_EDIT控件和m_nAge變量綁定,并校驗范圍18-60DDX_Text(pDX, IDC_AGE_EDIT, m_nAge);DDV_MinMaxInt(pDX, m_nAge, 18, 60);// 校驗用戶名不為空DDV_NotEmpty(pDX, m_strUser);}// 確定按鈕消息處理void CLoginDlg::OnBnClickedOk(){// 更新數據(從控件到變量)UpdateData(TRUE);// 模擬密碼驗證if (m_strPwd != _T("123456")){AfxMessageBox(_T("密碼錯誤,請重新輸入"));return;}CDialog::OnOK();}// 取消按鈕消息處理void CLoginDlg::OnBnClickedCancel(){if (AfxMessageBox(_T("確定要取消登錄嗎?"), MB_YESNO) == IDYES){CDialog::OnCancel();}}// 調用對話框(在主程序中)void CMainFrame::OnLogin(){CLoginDlg dlg;// 如果有記住的用戶名,預先填充dlg.m_strUser = m_strSavedUser;// 彈出對話框,用戶點擊確定后返回IDOKif (dlg.DoModal() == IDOK){// 獲取用戶輸入的數據CString strInfo;strInfo.Format("歡迎,%s!年齡:%d", dlg.m_strUser, dlg.m_nAge);AfxMessageBox(strInfo);// 保存用戶名(模擬記住功能)m_strSavedUser = dlg.m_strUser;}}

最后小結:

對話框是 MFC 里最「接地氣」的部分 —— 它不聊高深的底層原理,只關心「用戶怎么用得順手」。從拖控件的編輯器到自動生成代碼的 ClassWizard,MFC 把復雜的交互邏輯藏在背后,讓我們能專注于「和用戶對話」。

后來在?Web時代 和移動互聯網時代,總想起 MFC 對話框的設計哲學:好的技術,就該像會客室的服務員 —— 默默把一切打理好,讓用戶和程序的交流自然又順暢。現在的前端框架里,表單組件的設計和 MFC 對話框有著異曲同工之妙,比如 React 的表單控件綁定,就像簡化版的 DDX,而各種表單驗證庫,也和 DDV 的思路相通。

MFC 對話框的這些設計,在當時是開創性的,它讓程序員不用再為界面交互的基礎邏輯耗費精力,能更快地做出用戶需要的功能。這或許就是 MFC 能火那么多年的原因 —— 它懂技術,更懂開發者和用戶的需求。而對于我們這些老程序員來說,每次想起用對話框編輯器拖控件、用 ClassWizard 生成代碼的日子,就像想起20多年前斌嗎的日子.......

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/89684.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/89684.shtml
英文地址,請注明出處:http://en.pswp.cn/web/89684.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

(李宏毅)deep learning(五)--learning rate

一,關于learning rate的討論:(1)在梯度下降的過程中,當我們發現loss的值很小的時候,這時我們可能以為gradident已經到了local min0(低谷),但是很多時候,loss很小并不是因…

pytorch:tensorboard和transforms學習

tensorboard:可視化數據 在anaconda安裝: pip install tensorboard2.12.0最好使用這個版本 不然后面調用會報錯 因為版本過高的原因 然后還碰到了安裝的時候 安裝到C盤去了 但是我用的虛擬環境是在E盤:此時去C盤把那些新安裝的復制過來就好了 附錄我C盤的…

常用的100個opencv函數

以下是OpenCV中最常用的100個函數及其作用與注意事項的全面整理,按功能模塊分類,結合官方文檔與工業實踐優化排序。各函數均標注Python(cv2)和C(cv::)命名,重點參數以加粗突出: &…

【C++】紅黑樹,詳解其規則與插入操作

各位大佬好,我是落羽!一個堅持不斷學習進步的大學生。 如果您覺得我的文章有所幫助,歡迎多多互三分享交流,一起學習進步! 也歡迎關注我的blog主頁: 落羽的落羽 一、紅黑樹的概念與規則 紅黑樹是一種更加特殊的平衡二…

Camera相機人臉識別系列專題分析之十七:人臉特征檢測FFD算法之libhci_face_camera_api.so 296點位人臉識別檢測流程詳解

【關注我,后續持續新增專題博文,謝謝!!!】 上一篇我們講了: 這一篇我們開始講: Camera相機人臉識別系列專題分析之十七:人臉特征檢測FFD算法之libhci_face_camera_api.so 296點位人臉識別檢測流程詳解 目錄 一、背景 二、:FFD算法libhci_face_camera_api.s…

PostgreSQL 16 Administration Cookbook 讀書筆記:第7章 Database Administration

編寫一個要么完全成功要么完全失敗的腳本 事務(transaction)可以實現all or nothing。不過這里指的是psql的-和--single-transaction選項。可以實現transaction wrapper: 此選項只能與一個或多個 -c 和/或 -f 選項組合使用。它會導致 psql 在…

DeepSeekMath:突破開源語言模型在數學推理中的極限

溫馨提示: 本篇文章已同步至"AI專題精講" DeepSeekMath:突破開源語言模型在數學推理中的極限 摘要 數學推理由于其復雜且結構化的特性,對語言模型構成了重大挑戰。本文介紹了 DeepSeekMath 7B,該模型在 DeepSeek-Code…

實體類序列化報錯:Caused by: java.lang.NoSuchMethodException: com.xx.PoJo$Item.<init>()

原實體類代碼EqualsAndHashCode(callSuper true) Data public class Pojo extends BaseBean {private static final long serialVersionUID -4291335073882689552L;ApiModelProperty("")private Integer id;......private List<Item> list;AllArgsConstructo…

基于單片機病床呼叫系統/床位呼叫系統

傳送門 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品題目速選一覽表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品題目功能速覽 概述 該系統是以單片機STM32F103為核心的基于無線網絡的醫院病房呼叫系統&#xff0c;分為從機和主機兩…

[黑馬頭條]-登錄實現思路

需求分析在黑馬頭條項目中&#xff0c;登錄有兩種方式&#xff1a;一種是用戶輸入賬號密碼后登錄&#xff0c;這種方式登陸后的權限很大&#xff0c;可以查看&#xff0c;也可以進行其他操作&#xff1b;另一種方式就是用戶點擊不登錄&#xff0c;以游客的身份進入系統&#xf…

了解.NET Core狀態管理:優化技巧與常見問題解決方案

前言 歡迎關注dotnet研習社&#xff0c;今天我們聊聊“ .NET Core 中的狀態管理”。 在Web應用程序中&#xff0c;管理和維持狀態是一個非常重要的主題&#xff0c;尤其是在無狀態的環境中&#xff0c;如 HTTP 協議和 RESTful API。對于基于 .NET Core 構建的應用程序&#xff…

504網關超時可能是哪些原因導致?

在網絡訪問中&#xff0c;504 網關超時&#xff08;Gateway Timeout&#xff09;如同一個突然亮起的警示燈&#xff0c;打斷用戶的瀏覽或操作流程。這個 HTTP 狀態碼意味著服務器作為網關或代理時&#xff0c;未能在規定時間內收到上游服務器的響應。引發504錯誤的核心因素有哪…

ComfyUI 常見報錯問題解決方案合集(持續更新ing)

前言&#xff1a; 本文匯總了 5 大高頻問題 及其解決方案&#xff0c;涵蓋&#xff1a; HuggingFace 認證修復&#xff08;Token 申請 手動下載指南&#xff09; ComfyUI 版本更新&#xff08;完整命令 依賴管理&#xff09; 自啟動配置&#xff08;Conda 環境 權限修復&…

完美解決Linux服務器tomcat開機自啟動問題

經過多次測試終于徹底解決tomcat開機自啟動的問題了 PID3ps aux | grep /home/server/shichuan/ | grep java | awk {print $2} if [ -n "$PID3" ]; then 這個判斷pid的方式還是可能出現啟動失敗的情況 # tail -n 1 /home/server/shichuan/logs/catalina.out |grep…

kotlin部分常用特性總結

<h3>Kotlin中類和對象初始化</h3><ul> <li>添加open關鍵字代表可以被繼承</li> <li>Any 是所有類的父類,類似Object,包含 equals() hashCode() toString()方法</li> <li>constructor 關鍵字代表構造函數, constructor關鍵字可…

PHP 就業核心技能速查手冊

# PHP 就業核心技能速查手冊 > 高效聚焦市場所需&#xff0c;快速提升競爭力 --- ## 一、語法基礎&#xff08;必會&#xff01;&#xff09; php // 1. 變量與數據類型 $price 19.99; // 浮點型 $isStock true; // 布爾型 // 2. 流程控制 foreach ($…

從混沌到秩序:數據科學的熱力學第二定律破局——線性回歸的熵減模型 × 最小二乘的能量最小化 × 梯度下降的負反饋控制系統,用物理定律重構智能算法的統一場論

目錄 一、機器學習是什么&#xff1f; 1.1 什么是機器學習&#xff1f; 1.2 機器學習的三大類型 二、線性回歸是什么&#xff1f; 2.1 通俗理解 2.2 數學表達 三、最小二乘法&#xff08;Least Squares Method&#xff09; 3.1 什么是損失函數&#xff1f; 3.2 什么是最小…

BI 數據可視化平臺建設(3)—首頁性能提升實踐

作者&#xff1a; vivo 互聯網大數據團隊- Wang Lei 本文是vivo互聯網大數據團隊《BI 數據可視化平臺建設》系列文章第3篇。 隨著越來越多代碼的堆積&#xff0c;平臺的運行加載性能也在逐步下降&#xff0c;在不同程度上極大地影響了用戶體驗&#xff0c;從而導致用戶流失。本…

基于Python的畢業設計選題管理系統設計與實現

基于Python的畢業設計選題管理系統設計與實現摘要本論文詳細闡述了一個基于Python的畢業設計選題管理系統的設計與實現過程。該系統采用了Python的Tkinter庫構建圖形用戶界面&#xff0c;使用SQLite數據庫存儲數據&#xff0c;實現了高校畢業設計選題過程中的教師出題、學生選題…

如何在HTML5頁面中嵌入視頻

在HTML5中嵌入視頻主要使用<video>標簽&#xff0c;這是一種簡單且標準的方式。以下是詳細步驟和示例&#xff1a; 基礎實現 <!DOCTYPE html> <html> <head><title>視頻嵌入示例</title> </head> <body><!-- 基礎視頻播放器…