MFC 對話框資料交換 (DDX) 在 CDialog::DoDataExchange 中進行,標準流程:
UpdateData(TRUE):控制項 → 變數 (驗證 + 交換)。UpdateData(FALSE):變數 → 控制項 (顯示)。標準 DDX_Text、DDX_Check 等宏對應 AFX_DDX 函數。自定義 DDX 需:
DoDataExchange 中呼叫。CDataExchange 方向與驗證。
// MyDialog.h
#include // inet_addr, INADDR_NONE
#pragma comment(lib, "ws2_32.lib")
// 自定義 DDX 函數宣告
void AFXAPI DDX_IPAddress(CDataExchange* pDX, int nIDC, UINT& value);
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_IPAddress(pDX, IDC_EDIT_IP, m_uServerIP); // m_uServerIP 為 UINT 成員
}
void AFXAPI DDX_IPAddress(CDataExchange* pDX, int nIDC, UINT& value)
{
HWND hWndCtrl = pDX->PrepareEditCtrl(nIDC); // 取得編輯控制項 HWND,處理 Focus
CString strIP;
::GetWindowText(hWndCtrl, strIP.GetBufferSetLength(255), 255);
strIP.ReleaseBuffer();
if (pDX->m_bSaveAndValidate) { // 控制項 → 變數 (驗證階段)
// 1. 字元合法性檢查:只允許數字與點
for (int i = 0; i < strIP.GetLength(); ++i) {
TCHAR ch = strIP[i];
if (ch != '.' && !_istdigit(ch)) {
AfxMessageBox(_T("IP 位址只能包含數字與點號"));
pDX->Fail(); // 設定焦點回控制項
return;
}
}
// 2. 結構驗證:四段、各段 0-255、無前導零 (可選)
// 簡化版:直接用 inet_addr 驗證
value = inet_addr(CT2A(strIP)); // 轉為網路位序 (大端序)
if (value == INADDR_NONE) { // 255.255.255.255 也會返回此值,需額外判斷
AfxMessageBox(_T("無效的 IP 位址格式"));
pDX->Fail();
return;
}
// 額外檢查 "255.255.255.255" (廣播地址通常不作為伺服器 IP)
if (value == 0xFFFFFFFF && strIP != _T("255.255.255.255")) {
AfxMessageBox(_T("無效的 IP 位址"));
pDX->Fail();
}
}
else { // 變數 → 控制項 (顯示階段)
// 將網路位序 UINT 轉為點分十進位字串
in_addr addr; addr.s_addr = value;
const char* psz = inet_ntoa(addr); // 返回靜態緩衝區
if (psz) {
CString strDisp(CA2T(psz));
::SetWindowText(hWndCtrl, strDisp);
}
}
}
bool ValidateIPStrict(const CString& strIP)
{
// 分割四段
int nStart = 0, nCount = 0;
CString token;
while (AfxExtractSubString(token, strIP, nCount++, '.')) {
if (token.IsEmpty() || token.GetLength() > 3) return false;
// 無前導零 (除非單獨 "0")
if (token.GetLength() > 1 && token[0] == '0') return false;
// 全數字
for (int i = 0; i < token.GetLength(); ++i)
if (!_istdigit(token[i])) return false;
// 數值範圍
int val = _ttoi(token);
if (val < 0 || val > 255) return false;
}
return nCount == 4; // 必須剛好四段
}
Windows 內建 IP 位址控制項 (Common Control 6.0,SysIPAddress32),四欄位分別編輯,自動驗證:
// 資源編輯器加入 "IP Address Control" (IDC_IPADDRESS)
// 對話框成員變數
CIPAddressCtrl m_ipCtrl;
// DoDataExchange
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_IPADDRESS, m_ipCtrl); // 關聯控制項
if (pDX->m_bSaveAndValidate) {
DWORD dwIP = 0;
m_ipCtrl.GetAddress(dwIP); // 取得網路位序 DWORD
if (dwIP == 0) { AfxMessageBox(_T("請輸入 IP")); pDX->Fail(); }
m_uServerIP = dwIP;
}
else {
m_ipCtrl.SetAddress(m_uServerIP); // 顯示
}
}
優點:原生 UI 體驗、自動限制輸入、無需自寫驗證。缺點:需 Common Control 6.0 (Manifest)、樣式較固定。
| 問題 | 原因 | 解決 |
|---|---|---|
| inet_addr 返回 INADDR_NONE 但字串合法 | "255.255.255.255" 合法但值為 -1 | 額外字串比對排除 |
| DDX 失敗後焦點不回控制項 | 未呼叫 pDX->Fail() | 驗證失敗分支必加 pDX->Fail() |
| Unicode 專案 inet_addr 編譯錯誤 | 需轉 ANSI: CT2A(str) 或 inet_addr(CA2T(...)) | 使用 atlconv.h 轉換宏 |
| IPv6 支援 | inet_addr 僅 IPv4 | 改用 InetPton(AF_INET6, ...) (Vista+),或第三方庫 |
自定義 DDX_IPAddress 展示 MFC DDX 機制的擴展性:PrepareEditCtrl → 取得文字 → 方向判斷 → 驗證/轉換 → Fail/設定文字。結合 inet_addr/ntoa 實現 IPv4 點分十進位與網路位序雙向轉換。現代專案優先考慮原生 CIPAddressCtrl,自定義 DDX 適用於舊版相容、特殊格式需求或輕量化場景。