首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

MFC UpdateData 異常捕獲與自定義驗證:重寫 DDV 機制實現精確錯誤類型獲取

     編輯:程式設計綜合問答

MFC 對話框數據交換(DDX/DDV)機制中,UpdateData(TRUE) 執行驗證失敗時默認彈出通用錯誤提示,無法在代碼中捕獲具體錯誤類型(如「輸入非數字」、「超出範圍」)。本文詳解如何通過重寫 DDV 函數、捕獲 CUserException、自定義驗證邏輯三種方式實現精確異常處理。

UpdateData 內部機制與異常類型

  • UpdateData(TRUE):調用 DoDataExchange → 執行 DDX_* 數據傳輸 → 執行 DDV_* 驗證。
  • 驗證失敗時,DDV 函數拋出 CUserException(繼承自 CException),UpdateData 捕獲後彈出 AfxMessageBox 並返回 FALSE。
  • 關鍵點:UpdateData 內部已捕獲並處理異常,調用者無法直接 catch 到異常對象。

方案一:重寫 DDV 函數(最徹底、MFC 正統做法)

步驟 1:定義自定義 DDV 函數


// MyDDV.h
void AFXAPI DDV_MyInteger(CDataExchange* pDX, int& value, int min, int max, UINT nIDPrompt = 0);
void AFXAPI DDV_MyDouble(CDataExchange* pDX, double& value, double min, double max, UINT nIDPrompt = 0);
void AFXAPI DDV_MyString(CDataExchange* pDX, CString& value, int nMaxChars, UINT nIDPrompt = 0);

步驟 2:實現自定義驗證邏輯


// MyDDV.cpp
#include "stdafx.h"
#include "MyDDV.h"

void AFXAPI DDV_MyInteger(CDataExchange* pDX, int& value, int min, int max, UINT nIDPrompt) {
    if (pDX->m_bSaveAndValidate) {
        // 1. 類型轉換錯誤已由 DDX_Text 捕獲 (非數字輸入)
        // 2. 範圍檢查
        if (value < min || value > max) {
            CString msg;
            if (nIDPrompt) msg.LoadString(nIDPrompt);
            else msg.Format(_T("請輸入 %d 到 %d 之間的整數"), min, max);
            
            // 拋出用戶異常,攜帶控件 ID 以便定位
            CUserException* pEx = new CUserException(msg);
            pEx->m_nIDHelp = pDX->m_idLastControl; // 記錄出錯控件 ID
            throw pEx;
        }
    }
}

// 類似實現 DDV_MyDouble、DDV_MyString...

步驟 3:在對話框中使用自定義 DDV


void CMyDialog::DoDataExchange(CDataExchange* pDX) {
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_EDIT_AGE, m_age);
    DDV_MyInteger(pDX, m_age, 1, 120, IDS_AGE_RANGE_ERROR); // 自定義驗證
    
    DDX_Text(pDX, IDC_EDIT_PRICE, m_price);
    DDV_MyDouble(pDX, m_price, 0.01, 99999.99);
}

步驟 4:在調用處捕獲並解析異常


void CMyDialog::OnBnClickedOk() {
    if (!UpdateData(TRUE)) {
        // UpdateData 內部已捕獲 CUserException 並彈窗,這裡捕獲不到
        // 需改用方案二或三
        return;
    }
    // 驗證通過,處理業務邏輯
    OnOK();
}

方案二:捕獲 CUserException 並解析(需修改 UpdateData 調用方式)

繼承 CDialogEx 重寫 UpdateData,或直接複製 MFC 源碼邏輯並暴露異常:


BOOL CMyDialog::MyUpdateData(BOOL bSaveAndValidate, CUserException** ppException = nullptr) {
    ASSERT(m_hWnd != NULL);
    CDataExchange dx(this, bSaveAndValidate);
    
    try {
        DoDataExchange(&dx);
        dx.UpdateData();
    } catch (CUserException* e) {
        if (ppException) *ppException = e;  // 返回異常指針
        else e->Delete(); // 避免內存洩漏
        return FALSE;
    } catch (CException* e) {
        // 其他異常 (內存不足等)
        e->ReportError();
        e->Delete();
        return FALSE;
    }
    return TRUE;
}

// 調用
void CMyDialog::OnBnClickedOk() {
    CUserException* pEx = nullptr;
    if (!MyUpdateData(TRUE, &pEx)) {
        if (pEx) {
            // 解析異常
            int failedCtrlId = pEx->m_nIDHelp; // 獲取出錯控件 ID
            CString errMsg = pEx->m_strMessage; // 獲取錯誤信息
            // 可以根據 failedCtrlId 設置焦點、高亮提示等
            CEdit* pEdit = (CEdit*)GetDlgItem(failedCtrlId);
            if (pEdit) pEdit->SetFocus();
            pEx->Delete();
        }
        return;
    }
    OnOK();
}

方案三:手動驗證繞過 DDV(靈活、適合複雜業務規則)


BOOL CMyDialog::ValidateInput(CString& errorMsg, int& errorCtrlId) {
    UpdateData(TRUE); // 僅做數據傳輸,不依賴 DDV 驗證
    
    // 手動驗證年齡
    if (m_age < 1 || m_age > 120) {
        errorMsg = _T("年齡必須在 1-120 之間");
        errorCtrlId = IDC_EDIT_AGE;
        return FALSE;
    }
    
    // 手動驗證價格格式 (防止科學記數法等)
    CString priceStr;
    GetDlgItemText(IDC_EDIT_PRICE, priceStr);
    double price;
    if (!_stscanf_s(priceStr, _T("%lf"), &price) || price < 0.01 || price > 99999.99) {
        errorMsg = _T("價格格式無效,範圍 0.01-99999.99");
        errorCtrlId = IDC_EDIT_PRICE;
        return FALSE;
    }
    
    // 正則驗證郵箱、手機號等
    static const CString emailRegex = _T("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");
    // 使用 ATL::CAtlRegExp 或 boost::regex 實現
    
    return TRUE;
}

void CMyDialog::OnBnClickedOk() {
    CString errMsg;
    int errCtrlId;
    if (!ValidateInput(errMsg, errCtrlId)) {
        AfxMessageBox(errMsg);
        GetDlgItem(errCtrlId)->SetFocus();
        return;
    }
    UpdateData(FALSE); // 同步回控件 (可選)
    OnOK();
}

方案對比與選型建議

方案優點缺點適用場景
重寫 DDVMFC 正統、復用性強、與 DDX 無縫集成需維護自定義 DDV 庫、學習成本高大型項目、多對話框統一驗證風格
捕獲 CUserException改動小、能獲取控件 ID 和錯誤信息需重寫 UpdateData、破壞封裝中小項目、需精確定位錯誤控件
手動驗證完全可控、支援正則/跨字段驗證、無 MFC 依賴代碼量大、需自行同步數據複雜業務規則、非標準驗證、現代化重構

進階:實時輸入驗證 (EN_CHANGE + 紅框提示)


// 對話框頭文件
afx_msg void OnEnChangeEditAge();

// 消息映射
ON_EN_CHANGE(IDC_EDIT_AGE, &CMyDialog::OnEnChangeEditAge)

// 實現
void CMyDialog::OnEnChangeEditAge() {
    CString text;
    GetDlgItemText(IDC_EDIT_AGE, text);
    int val = _ttoi(text);
    CEdit* pEdit = (CEdit*)GetDlgItem(IDC_EDIT_AGE);
    
    if (text.IsEmpty() || (val >= 1 && val <= 120)) {
        pEdit->SetBkColor(RGB(255,255,255)); // 白色背景
    } else {
        pEdit->SetBkColor(RGB(255,200,200)); // 紅色背景提示
    }
    pEdit->Invalidate(); // 觸發重繪
}

總結

MFC UpdateData 默認吞沒驗證異常,無法直接捕獲錯誤類型。重寫 DDV 函數是架構最清晰的方案;捕獲 CUserException改動最小;手動驗證靈活性最高。建議新項目採用「手動驗證 + 實時提示」現代化模式,老項目維護用「捕獲 CUserException」快速修復。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved