DDX และ DDV คืออะไร?
DDX และ DDV คือกลไกที่ MFC ได้จัดเตรียมให้ เพื่ออำนวยความสะดวก ในการโอนย้ายข้อมูล
และตรวจสอบ ความถูกต้อง ของข้อมูล ระหว่าง control และ ตัวแปร ที่ใช้เก็บข้อมูล
ของ control นั้น โดย
หลักการง่าย ๆ ก็คือ control แต่ละตัว จะมีตัวแปรประจำ control นั้น ๆ ซึ่งตัวแปรนี้ ทำหน้าที่ ในการเก็บค่า ข้อมูลหรือสภาวะของ control นั้น ๆ ไว้ และจะมีฟังก์ชั่นหนึ่ง ที่ทำหน้าที่เป็นตัวเชื่อม (link) ตัวแปรที่กำหนดไว้เข้ากับ control ที่ต้องการ รวมถึงกำหนด การตรวจสอบ ความถูกต้อง ของข้อมูล และท้ายที่สุด เมื่อต้องการจะโอนย้าย ข้อมูล ก็จะมีการเรียกใช้ฟังก์ชั่นเพื่อให้มีการโอนย้ายข้อมูล โดยส่งพารามิเตอร์กำหนดทิศทาง ของ การโอนย้ายข้อมูล ไปด้วย ก็จะทำให้การโอนย้ายข้อมูลสำเร็จ ได้โดยง่าย
ขั้นตอนการเขียนโปรแกรม ให้ใช้กระบวนการ DDX และ DDV
1. กำหนดตัวแปรประจำ control
ตัวแปรนี้ จะใช้เก็บข้อมูลประจำ control นั้น โดยเราจะกำหนดตัวแปรนี้ ในตอนประกาศคลาสของ
Dialog box ที่มี control นั้น วางอยู่ ซึ่งชนิดของ ตัวแปร จะขึ้นอยู่กับชนิดของ
control ด้วย โดยถ้าเป็น Check box ก็จะมีตัวแปรที่เก็บข้อมูลเป็นชนิด int, ถ้าเป็น
Edit control ก็จะมีตัวแปรประจำ เป็นชนิด CString หรือ int เป็นต้น สมมติว่า Dialog
box ของเรา มี control ที่ต้องการทำ DDX อยู่ 3 ชนิด ดังแสดงในรูป
class CMyDialog:public CDialog
{
public:
CString EditData; // ตัวแปรที่ใช้เก็บข้อความใน Edit box
int CheckStatus1; // ตัวแปรใช้เก็บสภาวะของ Check box 1
int CheckStatus2; // ตัวแปรใช้เก็บสภาวะของ Check box 2
int RadioStatus; // ตัวแปรใช้เก็บหมายเลขสภาวะของกลุ่ม Radio button 1, 2, 3
//.......................... // ประกาศ ค่าอื่น ๆ ในคลาส
DECLARE_MESSAGE_MAP() // ประกาศการ Map message
};
เพื่อน ๆ จะสังเกตเห็นว่า Edit box และ check box แต่ละตัว จะมีตัวแปรประจำอยู่
แต่สำหรับ Radio button 3 ตัว จะมีตัวแปรเก็บสภาวะ เพียงตัวเดียวคือ RadioStatus
ที่เป็นเช่นนี้เพราะ ในขณะใดขณะหนึ่ง Radio button ทั้ง 3 ตัวนี้ จะมีเพียงตัวเดียว
เท่านั้น ที่จะอยูในสภาวะ check ได้ ดังนั้น จึงไม่จำเป็น ที่จะต้องตรวจสอบ สภาวะของ
Radio button ทุกตัว ขอเพียงรู้ว่า Radio button ตัวไหน อยู่ในสภาวะ check ก็เพียงพอแล้ว
เพราะแสดงว่า Radio button ตัวอื่น ๆ ที่เหลือ ต้องอยู่ในสภาวะ clear อย่างแน่นอน
ดังนั้นใช้ตัวแปร เก็บสภาวะ RadioStatus เพียงตัวเดียว ก็เพียงพอแล้ว โดยถ้า RadioStatus
= 0 แสดงว่า Radio button 1 อยู่ในสภาวะ check , แต่ถ้า RadioStatus = 1 ก็แสดงว่า
Radio button 2 อยู่ในสภาวะ check, เป็นเช่นนี้ไปเรื่อย ๆ แต่ถ้าต้องการ ให้ Radio
button ทุกตัว อยู่ในสภาวะ clear ก็ให้กำหนดค่า RadioStatus = -1 เสีย
2. ประกาศฟังก์ชั่น DoDataExchange() และทำการ override
ฟังก์ชั่นนี้
ฟังก์ชั่น DoDataExchange() จะเป็นเหมือน ฟังก์ชั่นที่ใช้บรรจุ ข้อมูลที่บอกว่า
ตัวแปรตัวไหนจะสัมพันธ์กับ control ตัวไหน โดยเราจะประกาศ ฟังก์ชั่นนี้ ในตอนประกาศคลาส
ของ Dialog box ดังโค้ดต่อไปนี้
class CMyDialog:public CDialog
{
public:
CString EditData; // ตัวแปรที่ใช้เก็บข้อความใน Edit box
int CheckStatus1; // ตัวแปรใช้เก็บสภาวะของ Check box 1
int CheckStatus2; // ตัวแปรใช้เก็บสภาวะของ Check box 2
int RadioStatus; // ตัวแปรใช้เก็บหมายเลขสภาวะของกลุ่ม Radio button 1, 2, 3
//.......................... // ประกาศ ค่าอื่น ๆ ในคลาส
public:
void DoDataExchange(CDataExchange* pDX); // ประกาศฟังก์ชั่น DoDataExchange()
DECLARE_MESSAGE_MAP() // ประกาศการ Map message
};
จากนั้น ในตอนประกาศรายละเอียด ของคลาส CMyDialog เราก็จะเขียน รายละเอียดของโค้ด ที่ใช้เชื่อมโยงตัวแปร ที่ต้องการเข้ากับ control ที่กำหนด ซึ่งจะกำหนดไว้ในฟังก์ชั่น DoDataExchange() ดังต่อไปนี้
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
DDX_Text (pDX,IDC_EDIT1,EditData); // เชื่อมโยงตัวแปร EditData เข้ากับ Edit control
DDX_Check (pDX,IDC_CHECK1,CheckStatus1); // เชื่อมโยงตัวแปร CheckStatus1 เข้ากับ Check box control1
DDX_Check (pDX,IDC_CHECK2,CheckStatus2); // เชื่อมโยงตัวแปร CheckStatus2 เข้ากับ Check box control2
DDX_Radio (pDX,IDC_RADIO1,RadioStatus); // เชื่อมโยงตัวแปร RadioStatus เข้ากับกลุ่มของ Radio button
CDialog::DoDataExchange(pDX);
}
| DDX_Text | เชื่อมโยงตัวแปรชนิด int, float, หรือ CString เข้ากับ Edit control | ||
| DDX_Check | เชื่อมโยงตัวแปรชนิด int เข้ากับ Check box control | ||
| DDX_Radio | เชื่อมโยงตัวแปรชนิด int เข้ากับ กลุ่มของ Radio button controls | ||
| DDX_LBIndex | เชื่อมโยงตัวแปรชนิด int เข้ากับ List box control | ||
| DDX_CBIndex | เชื่อมโยงตัวแปรชนิด int เข้ากับ Combo box control | ||
| DDX_Scroll | เชื่อมโยงตัวแปรชนิด int เข้ากับ Scroll bar | ||
| void AFXAPI DDX_Function( CDataExchange* pDX, int nIDC, int& value ); | |||
| โดยที่ | Function | คือฟังก์ชั่น DDX ที่แสดงในตารางข้างต้น | |
| pDX | ตัวแปรชนิด pointer ที่จะชี้ไปยังวัตถุเป้าหมายของคลาส CDataExchange | ||
| nIDC | คือหมายเลข ID ของ control ที่ต้องการ | ||
| value | คือตัวแปร ที่ต้องการให้เชื่อมโยงกับ control นั้น ๆ | ||
| BOOL UpdateData( BOOL bSaveAndValidate = TRUE ); | |||
| โดยที่ | bSaveAndValidate | คือ แฟลกที่จะกำหนดทิศทางของการโอนย้ายข้อมูล | |
|
UpDateData(FALSE);
|
![]() |
|
UpDateData(TRUE);
|
![]() |

|
ชนิดของ Control
|
Caption
|
หมายเลข ID
|
| Edit box | Edit box | IDC_EDIT1 |
| Check box | Check box 1 | IDC_CHECK1 |
| Check box | Check box 2 | IDC_CHECK2 |
| Radio button | Radio button 1 | IDC_RADIO1 |
| Radio button | Radio button 2 | IDC_RADIO2 |
| Radio button | Radio button 3 | IDC_RADIO3 |
| Button Control | Input Dialog | IDC_INPUT |
ต่อไป เป็นขั้นตอนสำคัญ คือการกำหนดกลุ่มให้ Radio button อยู่ในกลุ่มเดียวกัน
เพื่อให้สามารถใช้ตัวแปร เพียงตัวเดียว บอกถึง สภาวะของ Radio button ทั้ง 3 ตัวได้ว่า
ตัวไหนอยู่ใน สภาวะ Check โดยการจะทำเช่นนี้ได้ เราจะต้องรู้ถึง tab order ของ
control ใน dialog box นี้เสียก่อน โดยให้เพื่อน ๆ เลือกเมนู LayOut -> Tab
Order ก็จะได้ลำดับของ tab order แสดง เป็นตัวเลขติดอยู่กับ control ดังแสดงในรูป
เราสามารถเปลี่ยนลำดับของ Tab order เสียใหม่ก็ได้ โดยการเอาเมาส์ ไปคลิ๊กที่ control
เรียงลำดับกันใหม่ control เหล่านั้น ก็จะมีหมายเลข Tab order เรียงกันไป ตามลำดับของเมาส์ที่เรานำไปคลิ๊กที่
control แต่ในตอนนี้ เราจะไม่เปลี่ยน Tab order ของ control เหล่านี้นะคะ (แต่
control ของเพื่อน ๆ อาจมี Tab order ที่ไม่ตรงกับรูปก็ได้ค่ะ ก็ไม่ต้องกังวลใจนะคะ
ของเพียงแต่ให้ Tab order ของ Radio button ทั้งกลุ่ม เรียงลำดับกันก็พอแล้วค่ะ)
|
ชนิดของ Control
|
Caption
|
หมายเลข ID
|
| Edit box | Edit box | IDC_EDIT2 |
| Check box | Check box 1 | IDC_CHECK3 |
| Check box | Check box 2 | IDC_CHECK4 |
| Radio button | Radio button 1 | IDC_RADIO4 |
| Radio button | Radio button 2 | IDC_RADIO5 |
| Radio button | Radio button 3 | IDC_RADIO6 |



// myapp.h
class CMyApp:public CWinApp
{
public:
BOOL InitInstance();
};
|
//myapp.cpp
#include <afxwin.h>
#include "myapp.h"
#include "maindialog.h"
CMyApp app; // สร้างวัตถุเป้าหมายของ Application class เป็นจุดเริ่มต้นทำงานของโปรแกรม
BOOL CMyApp::InitInstance()
{
CMainDialog d;
m_pMainWnd = &d;
d.DoModal();
return FALSE;
}
|
// maindialog.h
class CMainDialog:public CDialog
{
protected:
CString MainString; // ตัวแปรชนิด CString ใช้เก็บข้อความใน Edit box
int Checkbox1_status; // ตัวแปรชนิด int ใช้เก็บสภาวะของ check box 1
int Checkbox2_status; // ตัวเปรชนิด int ใช้เก็บสภาวะของ check box 2
int Radio_Status; // ตัวแปรชนิด int ใช้เก็บสภาวะของ Radio button 1,2,3
public:
CMainDialog(); // function constructor
void DoDataExchange(CDataExchange* pDX); // ประกาศฟังก์ชั่นเพื่อ override
afx_msg void OnInput(); // ฟังก์ชั่นที่ตอบสนองต่อการกดปุ่ม Input Dialog
DECLARE_MESSAGE_MAP()
};
|
// maindialog.cpp
#include <afxwin.h>
#include "maindialog.h"
#include "inputdialog.h"
#include "resource.h"
BEGIN_MESSAGE_MAP(CMainDialog,CDialog)
// ดักจับการกดปุ่ม Input Dialog เพื่อเรียกใช้ ฟังก์ชั่น OnInput()
ON_BN_CLICKED(IDC_INPUT,OnInput)
END_MESSAGE_MAP()
CMainDialog::CMainDialog():CDialog(IDD_DIALOG1,NULL)
{
}
// ฟังก์ชั่นนี้ จะถูกเรียกใช้โดยอัตโนมัติ เมื่อมีการเรียกใช้ฟังก์ชั่น UpdateData()
void CMainDialog::DoDataExchange(CDataExchange* pDX)
{
DDX_Text(pDX, IDC_EDIT1, MainString); // เชื่อมโยงตัวแปร MainString เข้ากับ Edit box1 ของ Dialog 1
DDX_Check(pDX, IDC_CHECK1,Checkbox1_status); // เชื่อมโยงตัวแปร Checkbox1_status เข้ากับ checkbox 1 ของ Dialog1
DDX_Check(pDX, IDC_CHECK2,Checkbox2_status); // เชื่อมโยงตัวแปร Checkbox2_status เข้ากับ checkbox 2 ของ Dialog1
DDX_Radio(pDX, IDC_RADIO1,Radio_Status); // เชื่อมโยงตัวแปร Radio_Status เข้ากับกลุ่มของ Radio button ของ Dialog 1
CDialog::DoDataExchange(pDX); // เรียกใช้ ฟังก์ชั่น DoDataExchange ของ base class ด้วย
}
// ฟังก์ชั่นนี้ตอบสนองต่อการคลิ๊กปุ่ม Input Dialog
afx_msg void CMainDialog::OnInput()
{
UpdateData(); // มีค่า Default เป็น TRUE จึงทำการโอนย้ายข้อมูลจาก control ไปยัง ตัวแปรของ Main Dialog
CInputDialog dialog; // สร้างวัตถุเป้าหมายของ คลาส CInputDialog
// นำค่าตัวแปรต่าง ๆ ของ Main Dialog ส่งไปให้กับตัวแปร ใน Input Dialog
dialog.CheckBox1_Status = Checkbox1_status;
dialog.CheckBox2_Status = Checkbox2_status;
dialog.Radio_Status = Radio_Status;
dialog.inputdata = MainString;
// สั่งให้ Input Dialog ทำงาน เมื่อทำงานเสร็จแล้ว ถ้ามีการส่งค่า IDOK กลับมา ให้ทำงานในวงเล็บ
if(dialog.DoModal() == IDOK)
{
// โอนย้ายค่าจากตัวแปรใน Input Dialog ไปให้กับตัวแปรใน Main Dialog
Checkbox1_status = dialog.CheckBox1_Status;
Checkbox2_status = dialog.CheckBox2_Status;
Radio_Status = dialog.Radio_Status;
MainString = dialog.inputdata;
UpdateData(FALSE); // ทำการโอนย้ายข้อมูลจาก ตัวแปรไปยัง control ของ Main Dialog
}
}
|
// inputdialog.h
class CInputDialog:public CDialog
{
public:
CString inputdata; // ตัวแปรชนิด CString ใช้เก็บข้อความใน Edit box
int CheckBox1_Status; // ตัวแปรชนิด int ใช้เก็บสภาวะของ check box 1
int CheckBox2_Status; // ตัวเปรชนิด int ใช้เก็บสภาวะของ check box 2
int Radio_Status; // ตัวแปรชนิด int ใช้เก็บสภาวะของ Radio button 1,2,3
public:
CInputDialog(); // function constructor
void DoDataExchange(CDataExchange* pDX);// ประกาศฟังก์ชั่นเพื่อ override
DECLARE_MESSAGE_MAP()
};
|
// inputdialog.cpp
#include <afxwin.h>
#include "inputdialog.h"
#include "resource.h"
BEGIN_MESSAGE_MAP(CInputDialog,CDialog)
END_MESSAGE_MAP()
CInputDialog::CInputDialog():CDialog(IDD_DIALOG2,NULL)
{
}
// ฟังก์ชั่นนี้ จะถูกเรียกใช้โดยอัตโนมัติ เมื่อมีการเรียกใช้ฟังก์ชั่น UpdateData()
void CInputDialog::DoDataExchange(CDataExchange* pDX)
{
DDX_Text(pDX, IDC_EDIT2, inputdata); // เชื่อมโยงตัวแปร inputdata เข้ากับ Edit box ของ Dialog 2
DDX_Check(pDX, IDC_CHECK3, CheckBox1_Status); // เชื่อมโยงตัวแปร Checkbox1_Status เข้ากับ checkbox 1 ของ Dialog2
DDX_Check(pDX, IDC_CHECK4, CheckBox2_Status); // เชื่อมโยงตัวแปร Checkbox2_Status เข้ากับ checkbox 2 ของ Dialog2
DDX_Radio(pDX, IDC_RADIO4, Radio_Status); // เชื่อมโยงตัวแปร Radio_Status เข้ากับกลุ่มของ Radio button ของ Dialog 2
CDialog::DoDataExchange(pDX); // เรียกใช้ ฟังก์ชั่น DoDataExchange ของ base class ด้วย
}
|
4. Compile โปรแกรม แล้วลอง Run โปรแกรม จะได้ผลลัพธ์ ดังแสดงต่อไปนี้


เมื่อ input dialog แสดงออกมาแล้ว เราสามารถ เปลี่ยนแปลง และแก้ไข ค่าใน control
ของ input dialog ได้ตามใจชอบ


โค้ดคำสั่ง ส่วนที่ทำกระบวนการ DDX ของ main dialog จะอยู่ในไฟล์ maindialog.cpp
ดังต่อไปนี้ ค่ะ
// ฟังก์ชั่นนี้ตอบสนองต่อการคลิ๊กปุ่ม Input Dialog
afx_msg void CMainDialog::OnInput()
{
UpdateData(); // มีค่า Default เป็น TRUE จึงทำการโอนย้ายข้อมูลจาก control ไปยัง ตัวแปรของ Main Dialog
CInputDialog dialog; // สร้างวัตถุเป้าหมายของ คลาส CInputDialog
// นำค่าตัวแปรต่าง ๆ ของ Main Dialog ส่งไปให้กับตัวแปร ใน Input Dialog
dialog.CheckBox1_Status = Checkbox1_status;
dialog.CheckBox2_Status = Checkbox2_status;
dialog.Radio_Status = Radio_Status;
dialog.inputdata = MainString;
// สั่งให้ Input Dialog ทำงาน เมื่อทำงานเสร็จแล้ว ถ้ามีการส่งค่า IDOK กลับมา ให้ทำงานในวงเล็บ
if(dialog.DoModal() == IDOK)
{
// โอนย้ายค่าจากตัวแปรใน Input Dialog ไปให้กับตัวแปรใน Main Dialog
Checkbox1_status = dialog.CheckBox1_Status;
Checkbox2_status = dialog.CheckBox2_Status;
Radio_Status = dialog.Radio_Status;
MainString = dialog.inputdata;
UpdateData(FALSE); // ทำการโอนย้ายข้อมูลจาก ตัวแปรไปยัง control ของ Main Dialog
}
จะเห็นว่า เมื่อเราคลิ๊กปุ่ม Input dialog บน main dialog จะมีการเรียกฟังก์ชั่น
OnInput() ขึ้นมาทำงาน ซึ่งในตอนแรก จะไปเรียกใช้ ฟังก์ชั่น UpdateData( ); ฟังก์ชั่นนี้
ถ้าไม่ใส่พารามิเตอร์ให้ ก็จะถือว่า มีพารามิเตอร์เป็น TRUE เสมอ ในกรณีนี้ จึงเป็นการสั่งให้
โอนย้ายข้อมูล จาก control ใน main dialog ไปยังตัวแปรของ main dialog
จากนั้นก็จะมีการสร้าง วัตถุเป้าหมาย ของ input dialog แล้วทำการ copy ค่าของตัวแปรของ
main dialog ไปให้กับตัวแปรของ input dialog จากนั้นก็เรียกให้ input dialog ทำงาน
โดยใช้ฟังก์ชั่น DoModal() ฟังก์ชั่น นี้จะไม่คืนการทำงาน กลับมาให้ main dialog
เลย จนกว่า จะปิด input dialog นี้เสียก่อน
เมื่อปิด input dialog ลง จะมีการส่งค่ากลับมาให้ main dialog ด้วย โดยถ้า input
dialog นี้ถูกปิดลง ด้วยการคลิ๊กที่ปุ่ม OK ก็จะมีการส่งค่า IDOK กลับมา แต่ถ้าถูกปิดด้วยการ
คลิ๊กที่ปุ่ม Cancel ก็จะมีการส่งค่า IDCANCEL กลับมาให้ ซึ่ง main dialog จะตรวจสอบว่าค่า
ที่ส่งกลับมาเป็น IDOK หรือไม่? ถ้าเป็น IDOK ก็จะทำการ copy ค่าของตัวแปรใน input
dialog กลับไปให้กับตัวแปร ของ main dialog จากนั้นจึงเรียกใช้ ฟังก์ชั่น UpdateData(FALSE)
เพื่อนำค่าจากตัวแปร ไปแสดงออกที่ control ของ main dialog
ข้อสังเกต เพื่อน ๆ สังเกตเห็นอะไรอย่างหนึ่งไม๊คะ
ว่าในโค้ดโปรแกรมส่วนของ input dialog นั้น ไม่มีการเรียกใช้ ฟังก์ชั่น UpdateData()
เลย แล้วทำไม ค่าในตัวแปรของ input dialog จึงถูกส่งออกไปยัง control ของ dialog
ได้ สาเหตุ เป็นเพราะ เมื่อใดก็ตามที่มี การสร้าง Dialog box ขึ้นมา จะมีการเรียกใช้ฟังก์ชั่น
OnInitDialog() โดยอัตโนมัติ ซึ่งถ้าเราไม่ได้มีการ override ฟังก์ชั่นนี้แล้ว
ก็จะทำให้มีการเรียกใช้ฟังก์ชั่น ของคลาสฐานแทน ซึ่งก็คือ CDialog::OnInitDialog()
นั่นเอง ซึ่งภายในฟังก์ชั่นนี้ จะทำให้มีการโอนย้ายข้อมูลจากตัวแปรไปยัง control
ใน dialog box โดยอัตโนมัติ
เมื่อผู้ใช้คลิ๊ก OK จะทำให้ไปเรียก CDialog::OnOK() ซึ่งจะทำการโอนย้ายข้อมูล
จาก control ไปยังตัวแปรใน dialog box โดยอัตโนมัติเช่นกัน แต่ถ้าคลิ๊ก Cancel
จะไม่ทำให้เกิดการโอนย้ายข้อมูล
ดังนั้น ถ้าเพื่อน ๆ เขียนโค้ดโปรแกรม ให้มีการใช้กลไก DDX นี้ และต้องมีการ override
ฟังก์ชั่น OnInitDialog() และ ฟังก์ชั่น OnOK() ด้วยแล้ว ก็อย่าลืมเพิ่มบรรทัด
การเรียกใช้ฟังก์ชั่น ของ Base class ลงไปด้วยนะคะ ไม่เช่นนั้น จะทำให้ กลไก DDX
ทำงานไม่ถูกต้องได้ค่ะ
BOOL CInputDialog::OnInitDialog()
{
//........เพิ่มการประกาศค่าอื่น ๆ
CDialog::OnInitDialog(); // เรียก ฟังก์ชั่น OnInitDialog() ของ Base class ด้วย
}
afx_msg void CInputDialog::OnOK()
{
//.......เพิ่มการทำงานอื่น ๆ
CDialog::OnOK(); // เรียกฟังก์ชั่น OnOK() ของ Base class ด้วย
}
พูดมาตั้งเยอะ ยังไม่ได้พูดถึง DDV เลยเจ้าค่ะ เอาเป็นว่ายกยอด ไปพูดในตอนหน้าแล้วกันนะคะ
สวัสดีค่ะ
หนูแจ๋ว