DDX/DDV工作內幕 (轉)
DDX/DDV工作內幕 (轉)[@more@]DDX/DDV工作內幕
DDX(動態資料)和DDV(動態資料驗證)看起來好象是在對話方塊中某和
某成員變數之間建立連線,自動實現控制元件和變數之間的資料轉移.但這只是一個幻
覺.它的實際工作方式是這樣的:當你用ClassWizard把某變數和控制元件連線起來時
(透過Member Variables選項卡),它在資料對映中建立一個入口.實際上也就是
在對話方塊的DoData中新增一個入口函式(DoDataExchange函式是
Class Wizard產生和維護的函式).當你UpdateData(FALSE)時,MFC呼叫
DoDataExchange 函式,Class Wizard放於DoDataExchange中的實現程式碼將把
來自變數的資料複製到對應的控制元件.如果呼叫UpdateData(TRUE),MFC反過來把
資料複製回變數(並且可能同時進行資料驗證)
應該注意到,CDialog經常在OnInitDialog函式中呼叫UpdateData(FALSE),
這樣當對話方塊顯示時你的成員變數就會神秘的出現在對話方塊中.同樣OnOK函式也
呼叫UpdateData,但引數是TRUE.這樣模態對話方塊看起來自己處理自己了.你可以
編寫類似下面的程式碼:
CNameDlg dlg;
dlg.m_name="New Name";
if ( dlg.odal() == IDOK ) MessageBox(dlg.m_name,"Greetings");
來實現模態對話方塊的自動處理.
下面考慮一下使用非模態對話方塊的情況吧.對話方塊仍處理OnInitDialog訊息,
因此資料初始傳輸正常.但是非模態對話方塊一般不等到按OK按鈕來處理它們的數
據,這就意味這我們必須自己處理資料傳輸,具體請看『DDX』.
好了,現在來看看DDV動態資料驗證.你在使用DDX動態資料交換的同時,也可
以使用資料驗證.典型的,驗證可以保證一個字串的字元數小於給定的數目,或
者數字在一定範圍之內.
不過資料驗證通常並不能滿足我們的期望,這是因為驗證只在控制元件到變數的
資料傳輸時才發生.這通常意味著在輸入了所有資料,單擊OK,然後就收到
一個錯誤訊息.
不過我們可以改進一下,使用『現場資料驗證』.
改進DDX/DDV
快速DDX
有幾種情況我們需要使用快速DDX,比如你編寫一個電子,使用者在對
話框中輸入名稱和地址,你需要一個按鈕使應用程式可以在使用者輸入完之後得到
郵件的名稱和地址.或者考慮一下模態對話方塊的"應用"按鈕的實現吧.
當然要實現上面的任務,我們可以直接呼叫GetDlgItemText獲取編輯框資料,
但為什麼不使用DDX呢?這樣至少可以使對話方塊看起來有點自動化.可以呼叫
UpdateData(TRUE)把資料傳送到變數,反過來填充地址時可以呼叫UpdateData(FALSE).
要想得到每個控制元件的狀態以確定何時需要進行資料交換,可以過載CDialog類
的OnCommand函式,因為一般的傳統控制元件都用WM_COMMAND訊息來提示狀態的改變,
當然對於使用WM_NOTIFY訊息的新型控制元件可以一樣的處理OnNotify函式.下面是
使用該技術的一個簡單例子,當你在對話方塊中輸入資料的同時主視窗中資料也進
行相應的改變.
程式清單:快速DDX
// LiveDialog.cpp : implementation file
//
#include "stdafx.h"
#include "Custom.h"
#include "LiveDialog.h"
#ifdef _DE
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CLiveDialog dialog
CLiveDialog::CLiveDialog(CWnd* pParent /*=NULL*/)
: CDialog(CLiveDialog::IDD, pParent)
{
//{{AFX_DATA_INIT(CLiveDialog)
m_e = _T("");
m_name = _T("");
//}}AFX_DATA_INIT
m_pView=NULL; // 應用程式視窗視的指標
}
void CLiveDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CLiveDialog)
DDX_Text(pDX, IDC_, m_email);
DDX_Text(pDX, IDC_NAME, m_name);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CLiveDialog, CDialog)
//{{AFX_MSG_MAP(CLiveDialog)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CLiveDialog message handlers
BOOL CLiveDialog::OnCommand(WPARAM wParam, LPARAM lParam)
{
BOOL fOk= CDialog::OnCommand(wParam, lParam);
// Don't do if this command destroyed us or we are initializing
if ( ::IsWindow(m_hWnd) && !in_init )
{
UpdateData(); // Update on any change
ASSERT(m_pView !=NULL );
m_pView->GetDocument()->UpdateAllViews(NULL);
}
return fOk;
}
BOOL CLiveDialog::OnInitDialog()
{
in_init=TRUE; // 正在初始化
CDialog::OnInitDialog();
in_init=FALSE; // 初始化結束
return TRUE;
}
在視窗視類中用下面的方法呼叫對話方塊:
void CLiveView::OnGo()
{
m_dlg.m_pView=this; // 設定m_dlg.m_pView指向當前視
m_dlg.DoModal();
}
有關該程式還有幾個細節要注意:
1.WM_COMMAND訊息有破壞對話方塊,並使之無效的機會,那就是為什麼在
::IsWindow(m_hWnd)返回FALSE時不呼叫UpdateData的原因.
2.如果你在該段程式碼的某部分呼叫UpDateData(FALSE)時,控制元件可能在那時啟用
命令訊息,這樣在你遞迴呼叫UpDateDate(FALSE)時將產生一個斷言.也就是說,
在你呼叫UpDateDate之前必須確定你不是正在資料,這就是為什麼在
OnInitDialog函式中設定in_ini標誌的原因(OnInitDialog中呼叫了
UpDateDate(FALSE)),同樣在其它任何呼叫UpDateDate的地方都要這樣處理.
3.主視必須知道何時需要更新,在這個例子中先儲存指向主視的指標,然後在某種
事情發生時呼叫主視的文件的UpdateAllView實現視的更新.你可能說我們可以找
到對話方塊的父視窗而不需要事先儲存主視指標,但這是無法實現的,因為對話方塊的
父視窗永遠不可能是視(對話方塊的父視窗必須是頂級視窗).
現場資料驗證
實現DDV資料驗證的函式其實就是DoDataExchange中以DDV_開頭的函式,
如DDV_MinMaxInt()用來驗證整數的範圍、DDV_MaxChars用來驗證字串的字
符個數,呼叫UpdateData()函式就可以引起這些函式的了。
要想改進資料驗證,比如你想在資料輸入框中資料的改變時驗證大小是否
合適而不是等到按OK之後得到一個錯誤對話方塊(就象DDV_MaxChars一樣在資料
改變的同時進行字串個數的驗證),我們可以過載對話方塊的OnCommand函式以
截獲所有的命令訊息,從中篩選(提取wParam的高位字)符合要求的訊息(比如
EN_CHANGE就意味著編輯框中的內容發生了改變),這裡也就是我們驗證域中值
的好時機。
還有一個問題是我們並不想驗證所有的東西,這有幾種方法可以解決,
比如手工更改資料對映:
第一步,新增一個成員變數UINT m_vid,在建構函式中把它置為0(當為0時執行
常規的延遲資料驗證),在OnCommand中儲存當前要驗證的控制元件ID(提取wParam的
底位字).
BOOL CAboutDlg::OnCommand(WPARAM wParam,LPARAM lParam)
{
if ( HI(wParam) == EN_CHANGE &&
!m_fIsUpdating ) // 檢驗標誌,是否正在進行資料驗證或更新
{
UpdateData();
m_vid=0;
}
return CDialog::OnCommand(wParam,lParam);
}
第二步,修改資料對映。把DoDataExchange中的所有資料對映移出Class Wizard
的特別註釋,並按下面的方法修改程式碼:
void CLiveDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
m_fIsUpdating=TRUE; // 是否正在進行資料傳遞或驗證的標誌,設定它
if ( !m_vid ||m_vid==IDC_LOG )
{
DDX_Text(pDX, IDC_LOG, m_log);
DDV_MaxChars(pDX, m_log, 10);
}
if ( !m_vid || m_vid ==IDC_NUM )
{
DDX_Text(pDX, IDC_NUM, m_num);
DDV_MinMaxInt(pDX, m_num, -10, 10);
}
m_fIsUpdating=FALSE; // 清除標誌
}
這裡我們還會碰到前一個例子中的第2個問題,也就是你在驗證一個特殊的
域時,必須確信你沒有正在驗證,不然你會得到一個斷言。當然可以採用和上面
的例子相似的辦法,即在進行資料驗證之前先設定m_fIsUpdating=TRUE;(正在進
行資料傳遞或驗證),驗證完之後再設為FALSE,在呼叫UpdateData之前判斷該標誌.
不過這裡會有一個小小的問題,如果資料驗證失敗,MFC會發出一個異常,以放
棄DoDataExchange,這樣你設定的標誌就不靈了.最簡單的解決辦法是在
UpdateData呼叫之後重新把m_fIsUpdating標誌設為FALSE(必須是所有的
UpdateData的呼叫之後,當然包括MFC內部程式碼中的呼叫,如
CDialog::OnInitdialog中),另一種方法是捕獲該異常,然後清除標誌,具體請
看下面的程式碼:
void CLiveDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
m_fIsUpdating=TRUE; // 是否正在進行資料傳遞或驗證的標誌,設定它
try
{
if ( !m_vid ||m_vid==IDC_LOG )
{
DDX_Text(pDX, IDC_LOG, m_log);
DDV_MaxChars(pDX, m_log, 10);
}
if ( !m_vid || m_vid ==IDC_NUM )
{
DDX_Text(pDX, IDC_NUM, m_num);
DDV_MinMaxInt(pDX, m_num, -10, 10);
}
m_fIsUpdating=FALSE; // 清除標誌
}
catch (...) // 捕獲異常
{
m_fIsUpdating=FALSE; // 清除標誌
throw; // 丟擲異常
}
}
還有一種方法是參考MFC原碼寫的,在OnCommand中做如下修改:
BOOL CAboutDlg::OnCommand(WPARAM wParam,LPARAM lParam)
{
if ( HIWORD(wParam) == EN_CHANGE && !m_fIsUpdating )
// prevent control notifications from being dispatched during UpdateData
{
m_vid=LOWORD(wParam);
_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
HWND hWndOldLockout = pThreadState->m_hLockoutNotifyWindow;
if (hWndOldLockout != m_hWnd) // must not recurse
UpdateData();
m_vid=0;
}
return CDialog::OnCommand(wParam,lParam);
}
關於資料對映
應該認識到,所謂的資料對映只是一個函式,它展現了許多可能性(也就是可擴
展性),另外你還可以定製自己的資料驗證,比如你想定製一個郵政編碼的資料驗
證.具體實現方法請看下面的定製DDX/DDV部分.
還有一點要注意到的,一定要在對應的DDX呼叫之後立即新增驗證程式碼,否則,當
驗證失敗時,你的程式或許不會正確識別哪個域有問題.
定製DDX/DDV
現在你可以嘗試編寫自己的資料交換和資料驗證過程了.你要知道交換和
驗證函式只是一些知道如何處理CDataExchange的全域性函式而已,沒有什
麼特殊的.
下面來看看具體做法:
對於資料交換,需要編寫一個帶有引數CDataExchange指標、一個控制元件ID和對
某變數引用的全域性函式,儘管可以不在函式前面新增DDX_字首,但是為了可以和
Class Wizard整合,最好忍住你的這種念頭(後面你會看到為什麼了).
在交換函式中,可以檢查CDataExchange指標,以瞭解你所須的細節.下面就
來看看CDataExchange類的成員
成員
描述
m_bSaveAndValidate
對應於你提供給UpdateData的引數,當為TRUE時資料從控制元件傳遞到變數
m_pDlgWnd
控制視窗或對話方塊的控制程式碼
PrepareCtrl(int nIDC)
呼叫該函式,以標識當前控制元件(如不是編輯框)
PrepareEditCtrl(int nIDC)
呼叫該函式,以標識當前控制元件(如是編輯框)
Fail()
產生一個對控制元件的驗證失敗(你可以在DDX或者DDV中呼叫該函式),丟擲一個異
常,破壞DoDataExchange函式的執行
一般的,編寫交換函式,,你首先要檢查m_bSaveAndValidate的值以確定資料
的傳遞方向,如果傳遞失敗,你有必要呼叫PrepareEditCtrl(適於編輯控制元件)或者
PrepareCtrl(適於所有控制元件),在做此呼叫之後,任何對Fail的呼叫將導致把焦
點交回給該控制元件,即使其它過程(比如一起的驗證過程)釋出同樣的失敗,也是這樣
編寫一個驗證函式,跟編寫一個交換函式差不多,差別只是引數的不同.函式
以DDV_為字首,引數可以接受一個CDataExchange指標、合適型別的一個只值、
一個或兩個引數.
它的工作很簡單,如果m_bSaveAndValidate為TRUE時,一定要保證值是合法
的(通常認為從程式傳遞到控制元件的值是正確的).如果資料正常,則從該函式返回,
如果資料不正常,則呼叫Fail函式.前一個資料交換函式已經標識了當前操作的
是哪個控制元件(這也就是為什麼必須要在對應的DDX呼叫之後立即新增DDV的驗證代
碼的原因了).
下面的例子演示瞭如何定製自己的資料驗證:
程式清單:使用定製的DDX/DDV
// validView.cpp : implementation of the CValidView class
//
#include "stdafx.h"
#include "valid.h"
typedef float Currency; // used for DDV
#include "validDoc.h"
#include "validView.h"
#include "customdd.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CValidView
IMPLEMENT_DYNCREATE(CValidView, CFormView)
// Class Wizard won't put this here because it thinks
// Dialog boxes handle OnOK. They do, but this is a
// foview, not a dialog box
BEGIN_MESSAGE_MAP(CValidView, CFormView)
//{{AFX_MSG_MAP(CValidView)
ON_COMMAND(IDOK,OnOK)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CValidView construction/destruction
CValidView::CValidView()
: CFormView(CValidView::IDD)
{
validating=FALSE;
vid=0;
//{{AFX_DATA_INIT(CValidView)
m_age = 18;
m_name = _T("");
m_wager = 1.0;
m_btnenable = TRUE;
//}}AFX_DATA_INIT
// TODO: add construction code here
}
CValidView::~CValidView()
{
}
void CValidView::DoDataExchange(CDataExchange* pDX)
{
CFormView::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CValidView)
DDX_Text(pDX, IDC_AGE, m_age);
DDV_MinMaxInt(pDX, m_age, 18, 150);
DDX_Text(pDX, IDC_NAME, m_name);
DDV_MaxChars(pDX, m_name, 64);
DDX_Text(pDX, IDC_WAGER, m_wager);
DDV_MinMaxCurrency(pDX, m_wager, 1.f, 100.f);
DDX_EnableWindow(pDX, IDOK, m_btnenable);
//}}AFX_DATA_MAP
}
BOOL CValidView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
return CFormView::PreCreateWindow(cs);
}
/////////////////////////////////////////////////////////////////////////////
// CValidView diagnostics
#ifdef _DEBUG
void CValidView::AssertValid() const
{
CFormView::AssertValid();
}
void CValidView::Dump(CDumpContext& dc) const
{
CFormView::Dump(dc);
}
CValidDoc* CValidView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CValidDoc)));
return (CValidDoc*)m_pDocument;
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// CValidView message handlers
void CValidView::OnOK()
{
if (UpdateData(TRUE))
{
MessageBox("Wager placed");
m_btnenable=FALSE;
UpdateData(FALSE);
}
}
程式清單:定製的DDX/DDV過程
#include
#include "customdd.h"
// Custom Exchange
void DDX_EnableWindow(CDataExchange *pDX, int id, BOOL &flag)
{
CWnd *ctl=pDX->m_pDlgWnd->GetDlgItem(id);
if (pDX->m_bSaveAndValidate)
flag=ctl->IsWindowEnabled();
else
ctl->EnableWindow(flag);
}
// Custom validator
void DDV_MinMaxCurrency(CDataExchange *pDX, float val, float min, float max)
{
CWnd *editctl=CWnd::FromHandle(pDX->m_hWndLastControl);
CString s;
int n;
if (pDX->m_bSaveAndValidate)
{
// Using math to dec if anything is left over is bad because of rounding
// errors, so use a string method instead
editctl->GetWindowText(s);
n=s.Find('.');
if (n!=-1 && n+3Fail();
}
DDV_MinMaxFloat(pDX,val,min,max); // let the existing one do the job
}
}
與Class Wizard整合
如果你只想把某定製過程應用於一個專案的話,可以把它新增到該專案的
CLW.你也可以在包含mfcclwz.dll檔案的BIN目錄(我的是
... CommonMSDev98Bin)下建立一個DDX.CLW檔案,
然後你的DDX過程就可以應用到所有專案了.
下面來看一看怎樣寫CLW檔案:
首先新增一個名為[ExtraDDX]的區段,看起來象INI檔案的區段,但是這裡的名
稱是區分大小寫的.
[ExtraDDX]
ExtraDDXCount=2
ExtraDDX1=E;;Value;Currency;0.0;Text;Floating Point Currency;MinMaxCurrency;Mi&nimum;f;Ma&ximum;f
ExtraDDX2=bBECcRLIMNn;;Enable State;BOOL;TRUE;EnableWindow;Window Enabled Status
這些程式碼是什麼意思呢?
第二行
ExtraDDXCount=x
的x表示專案的數目(這裡是2),然後接下來的一行以ExtraDDX1=開頭,再下來是
ExtraDDX2=、ExtraDDX3=等等。等號右邊的程式碼被分成7個、10個或者12個域,
具體倚賴於你所想實現的目標,每個域以分號分隔,下面的表格列出了這些域的意思:
域
描述
1
DDX應用的空間型別(比如E=編輯框)
2
未使用
3
屬性型別(經常是值,對應著Class Wizard的第一個組合框)
4
變數的資料型別
5
初始值
6
沒有DDV_字首的DDV過程名
7
註釋
8
沒有DDV_字首的DDV過程名
9
第一個DDV引數的名稱(可選)
10
第一個DDV引數的型別(比如,f=float;可選)
11
第二個DDV引數的名稱(可選)
12
第二個DDV引數的型別(比如,f=float;可選)
你不必指定任何DDV過程,你可以把一個新的驗證過程與標準的交換函式混合
在一起(也就象上面ExtraDDX1那一行所做的一樣).注意,這些DDX和DDV函式名並
沒有以DDX_和DDV_開頭,但是Class Wizard確實往它產生的程式碼中新增了這些前
綴.這也就是用這些字首命名函式的原因了。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-988973/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 螢幕取詞核心內幕 (轉)
- Nimda程式設計內幕 (轉)程式設計
- Linux開機程式內幕(轉)Linux
- MFC技術內幕簡結 (轉)
- Linux 核心偵錯程式內幕(轉)Linux
- Socket API,CAsyncSocket,CSocket內幕及其用法 (轉)API
- 揭秘Linux核心偵錯程式之內幕(轉)Linux
- Git 內幕(一)Git
- LangChain內幕指南LangChain
- WebKit技術內幕WebKit
- Object Pascal中String型別的內幕探討 (轉)Object型別
- 揭祕JAVA JVM內幕JavaJVM
- zendframeworker命名規則內幕Framework
- Slackware Linux技術內幕之--包管理機制(轉)Linux
- 深入理解Linux網路內幕-PART I -通用背景(轉)Linux
- JSP標籤庫介紹(1)定製標籤內幕 (轉)JS
- 被詛咒的油畫 圖片病毒技術的內幕(轉)
- ShowMeBug 核心技術內幕
- 讀《etcd 技術內幕》
- HTTPS 背後的內幕HTTP
- 揭祕《Arduino技術內幕》UI
- Google“員工”大曝內幕Go
- ubuntu螢幕旋轉Ubuntu
- Linux 引導過程內幕Linux
- 簡述Spring技術內幕Spring
- Kafka流處理內幕詳解Kafka
- 淺談Java String內幕(1)Java
- Redis 字串型別實現內幕Redis字串型別
- 軟體業黑暗內幕:年齡歧視
- 內控工作體系
- 螢幕上內容究竟是怎樣畫出來的 —— Android View 工作原理詳解AndroidView
- 獲取各種螢幕高度寬度(工作)
- Mybatis技術內幕(2.3.3):反射模組-InvokerMyBatis反射
- Mybatis技術內幕(2.3.4):反射模組-ObjectFactoryMyBatis反射Object
- Mybatis技術內幕(2.3.1):反射模組-ReflectorMyBatis反射
- Mybatis技術內幕(1):Mybatis簡介MyBatis
- [Mysql技術內幕]Innodb儲存引擎MySql儲存引擎
- PostgreSQL技術內幕(七)索引掃描SQL索引