Visual C++ tutorial
Control ตอน 4 (กลไก DDX และ DDV)

กลไก DDV
วันนี้เราจะมาพูดถึงกลไก DDV ต่อนะคะ จากบทความตอนที่แล้ว เราได้กล่าวไปบ้างแล้วว่า DDV หรือ Dialog Data Validation คือกระบวน การตรวจสอบความถูกต้อง ของข้อมูลที่ป้อนให้ กับ control ซึ่งส่วนใหญ่แล้วจะเป็นการตรวจสอบ ความถูกต้องในการป้อนข้อมูล ให้กับ Edit box นั่นเอง เพราะเป็น Edit box เป็น control ที่สามารถรับข้อมูล จากผู้ใช้งานได้หลากหลายที่สุด ผู้ใช้สามารถพิมพ์ข้อความ, ตัวเลข, หรือรหัสสัญลักษณ์อื่น ๆ เข้าไปใน Edit box ได้ ดังนั้นถ้าไม่ทำการ ตรวจสอบให้ดีแล้ว ผู้ใช้อาจป้อนข้อมูลผิดชนิดมาให้ หรือป้อนตัวเลขที่มีค่า มากหรือน้อยกว่าที่ต้องการ ซึ่งอาจเป็นผลให้โปรแกรมทำงานผิดพลาดได้ เราลองมาดูตัวอย่างโปรแกรมง่าย ๆ กันสักโปรแกรมหนึ่งกันนะคะ ในที่นี้หนูแจ๋วขอยกตัวอย่าง โปรแกรมคำนวณพื้นที่ของรูปสี่เหลี่ยมขึ้นมา โดยเราจะทำโปรแกรมให้เป็นแบบ Dialog base ดังแสดงในรูปต่อไปนี้ค่ะ


โดยสร้าง Dialog box ขึ้นมา เก็บไว้ในรีซอร์ทไฟล์ชื่อ resource.rc แล้วกำหนดค่า ID ของ Dialog เป็น IDD_DIALOG1 และเพิ่ม control เข้าไปใน Dialog box โดยให้มีค่า caption และ ID ดังแสดงในตารางต่อไปนี้
ชนิดของ Control
Caption
หมายเลข ID
ลักษณะเพิ่มเติม
Edit box
Width IDC_WIDTH
-
Edit box
Height IDC_HEIGHT
-
Edit box
Area IDC_AREA
read only
Button
Calculation IDC_CAL
-

จากนั้นก็สร้างไฟล์ใหม่ ขึ้นมา 4 ไฟล์ คือไฟล์ myapp.h และ myapp.cpp ใช้เก็บการประกาศคลาส และเก็บรายละเอียดของคลาส CMyApp ซึ่งเป็นคลาส ควบคุมการทำงาน ของโปรแกรมของเรา และไฟล์ maindialog.h และ maindialog.cpp ใช้เก็บการประกาศคลาส และเก็บรายละเอียดของคลาส CMainDialog ซึ่งเป็นคลาสควบคุม Dialog หลักของโปรแกรม ของเรา ดังมีรายละเอียดดังนี้ค่ะ

myapp.h
class CMyApp:public CWinApp
{
public:
	BOOL InitInstance();
};

myapp.cpp
#include <afxwin.h>
#include "myapp.h"
#include "maindialog.h"

CMyApp myapp;

BOOL CMyApp::InitInstance()
{
	CMainDialog maindlg;
	m_pMainWnd = &maindlg;
	maindlg.DoModal();

	return FALSE;
}

maindialog.h
class CMainDialog:public CDialog
{
	public:
		int width; // ตัวแปรชนิด integer ใช้เก็บค่าใน Edit box width, height, area
		int height;
		int area;

		CMainDialog();	// function constructor

	protected:
		void DoDataExchange(CDataExchange* pDX); // ประกาศฟังก์ชั่น DoDataExchange เพื่อ Override
		afx_msg void OnCal(); // ประกาศ ฟังก์ชั่น OnCal()

	public:

		DECLARE_MESSAGE_MAP()
};

maindialog.cpp
#include <afxwin.h>
#include "maindialog.h"
#include "resource.h"

BEGIN_MESSAGE_MAP(CMainDialog,CDialog)
 ON_BN_CLICKED(IDC_CAL,OnCal) // เรียกใช้ฟังก์ชั่น OnCal() เมื่อมีการคลิ๊กปุ่ม IDC_CAL
END_MESSAGE_MAP()

// function constructor ซึ่งจะถูกเรียกใช้โดยอัตโนมัติ เมื่อมีการสร้าง object ของคลาส CMainDialog
CMainDialog::CMainDialog():CDialog(IDD_DIALOG1,NULL)
{
	width = 0; // กำหนดค่าเริ่มต้นให้กับตัวแปร width, height, area
	height = 0;
	area = 0;
}

// ฟังก์ชั่นนี้จะถูกเรียกใช้ โดยอัตโนมัติเมื่อมีการเรียกใช้ฟังก์ชั่น UpdateData() void CMainDialog::DoDataExchange(CDataExchange* pDX) { DDX_Text(pDX,IDC_WIDTH,width); // เชื่อมโยงตัวแปรเข้ากับ Edit box DDX_Text(pDX,IDC_HEIGHT,height); DDX_Text(pDX,IDC_AREA,area); CDialog::DoDataExchange(pDX); } // ฟังก์ชั่นนี้ ถูกเรียกเมื่อมีการคลิ๊กปุ่ม Calculate (IDC_CAL) void CMainDialog::OnCal() { UpdateData(); // นำค่าจาก control ไปเก็บในตัวแปร area = width*height; // คำนวณหาพื้นที่จาก ความกว้าง X สูง UpdateData(FALSE); // นำค่าจากตัวแปร ไปแสดงออกที่ control }

อย่าลืม Set project ให้ใช้ MFC ด้วยนะคะ จากนั้นให้ save ไฟล์ทั้งหมด แล้วทำการ Build จากนั้นก็ลอง Execute โปรแกรมดู ก็จะได้ โปรแกรมแบบ Dialog base ดังแสดงในรูป



เพื่อน ๆ ลองป้อนค่าตัวเลข ลงในช่อง Edit box Width และ Height แล้วคลิ๊กปุ่ม Calculation ก็จะได้ผลลัพธ์ ออกมาที่ช่อง Area ดังแสดงในรูปค่ะ
ดู ๆ แล้ว โปรแกรมก็ทำงานได้ดีใช่ไม๊คะ ไม่น่ามีอะไรผิดพลาด แต่คราวนี้เพื่อน ๆ ลองป้อนค่า อะไรก็ได้ลงไปในช่อง Width และ Height ที่ทำให้ได้ผลลัพธ์ มากกว่า 2147483647 เช่น 46341 คูณกับ 46341 จะได้ผลลัพธ์ ดังแสดงในรูปค่ะ



จะเห็นว่าผลลัพธ์เป็น -2147479015 ซึ่งเป็นค่าลบ!!! ที่เป็นเช่นนี้เพราะ เราประกาศชนิดของตัวแปร area ซึ่งเป็นผลลัพธ์ของตัวแปร width คูณด้วย height และตัวแปร area เป็นชนิด int (integer) ซึ่งในที่นี้ ก็คือตัวเลขชนิดจำนวนเต็มแบบคิดเครื่องหมายขนาด 32 บิต ซึ่งมีค่าได้สูงสุด คือ 7FFFFFFF (ฐานสิบหก) หรือ 2147483647 (ฐานสิบ) นั่นเอง คราวนี้เมื่อใดก็ตาม ที่ผลลัพธ์มากกว่านี้ ก็จะทำให้ บิตข้อมูลสูงสุด นั้นเซตเป็น 1 ซึ่งในตัวแปร int นี้ ถ้าบิตสูงสุดมีค่า เป็น 1 เมื่อใด จะหมายความว่า ตัวแปรนั้นมีค่าติดลบ ดังนั้นจึงทำให้ผลลัพธ์ที่ได้ มีค่าเป็นลบ เพื่อหลีกเลี่ยง ข้อผิดพลาดนี้ เราจึงควรกำหนด ค่าสูงสุดของ ตัวแปร width และ height ให้ไม่เกิน 46340 เพื่อว่าเมื่อนำมาคูณกันแล้ว จะได้ให้ผลลัพธ์ที่ ไม่เกินข้อจำกัดนี้ ส่วนค่าต่ำสุด เรากำหนดให้เป็น 0 เพื่อป้องกันไม่ให้ผู้ใช้ ป้อนค่าที่ติดลบให้ กับตัวแปร width หรือ height เพราะผลลัพธ์ ที่ได้ จะต้องไม่ติดลบ (พื้นที่มีค่าเป็นลบไม่ได้)
เราสามารถใช้ ฟังก์ชั่น DDV ในการตรวจสอบ ค่าที่ป้อน ให้กับ Edit box ไม่ให้มีค่าเกินที่กำหนด ได้โดย แก้ไขไฟล์ maindialog.cpp เป็นดังต่อไปนี้ค่ะ

maindialog.cpp
#include <afxwin.h>
#include "maindialog.h"
#include "resource.h"

BEGIN_MESSAGE_MAP(CMainDialog,CDialog)
 ON_BN_CLICKED(IDC_CAL,OnCal) // เรียกใช้ฟังก์ชั่น OnCal() เมื่อมีการคลิ๊กปุ่ม IDC_CAL
END_MESSAGE_MAP()

// function constructor ซึ่งจะถูกเรียกใช้โดยอัตโนมัติ เมื่อมีการสร้าง object ของคลาส CMainDialog
CMainDialog::CMainDialog():CDialog(IDD_DIALOG1,NULL)
{
	width = 0; // กำหนดค่าเริ่มต้นให้กับตัวแปร width, height, area
	height = 0;
	area = 0;
}

// ฟังก์ชั่นนี้จะถูกเรียกใช้ โดยอัตโนมัติเมื่อมีการเรียกใช้ฟังก์ชั่น UpdateData()
void CMainDialog::DoDataExchange(CDataExchange* pDX)
{
	DDX_Text(pDX,IDC_WIDTH,width); // เชื่อมโยงตัวแปรเข้ากับ Edit box
	DDV_MinMaxInt(pDX,width,0,46340); // ตรวจสอบให้ตัวแปรชนิด int ชื่อ width ให้มีค่าอยู่ระหว่าง 0-46340
	DDX_Text(pDX,IDC_HEIGHT,height);
	DDV_MinMaxInt(pDX,height,0,46340); // ตรวจสอบให้ตัวแปรชนิด int ชื่อ height มีค่าอยู่ระหว่าง 0-46340
	DDX_Text(pDX,IDC_AREA,area);

	CDialog::DoDataExchange(pDX);
}

// ฟังก์ชั่นนี้ ถูกเรียกเมื่อมีการคลิ๊กปุ่ม Calculate (IDC_CAL)
void CMainDialog::OnCal()
{
	UpdateData();  // นำค่าจาก control ไปเก็บในตัวแปร

	area = width*height; // คำนวณหาพื้นที่จาก ความกว้าง X สูง

	UpdateData(FALSE); // นำค่าจากตัวแปร ไปแสดงออกที่ control
}

คราวนี้เราลอง Build โปรแกรม แล้วลอง Execute ดูนะคะ เพื่อน ๆ จะเห็นว่า เมื่อใดเราป้อน ค่าที่สูงหรือต่ำกว่าที่กำหนด ให้ Edit box โปรแกรม จะยังไม่แจ้งข้อผิดพลาดอะไรออกมา แต่เมื่อไหร่ที่เพื่อนคลิ๊กปุ่ม Calculate จะทำให้ ไปเรียกใช้ฟังก์ชั่น OnCal() และภายใน ฟังก์ชั่นนี้ มีการเรียกใช้ฟังก์ชั่น UpdateData() ซึ่งจะไปเรียกใช้ DoDataExchange() อีกต่อหนึ่ง ทำให้มีการตรวจสอบ ค่าภายใน Edit box ทั้งสอง (width และ height) เมื่อพบว่า ค่าที่ป้อนเข้าไป มีค่ามากหรือน้อยกว่าช่วงที่กำหนด ก็จะทำให้มี message box แจ้งข้อผิดพลาด ออกมาดังแสดงในรูป



เมื่อเราคลิ๊ก OK บน message box ก็จะทำให้ แถบกระพริบ (caret) ย้ายไปกระพริบในช่อง ป้อนค่า Height เพื่อให้เรา สามารถป้อนค่า ที่ถูกต้องลงไปได้เลย ที่เป็นเช่นนี้ เป็นเพราะเราได้วางคำสั่ง DDV ไว้หลังคำสั่ง DDX ที่เกี่ยวข้องกัน ดังนั้นเราควรจะต้องวางคำสั่ง DDV ไว้หลัง คำสั่ง DDX ที่เกี่ยวข้องกันเสมอ เพื่อให้โฟกัสไปอยู่ตรง control ที่บรรจุค่าที่ผิดพลาดอยู่ ได้อย่างถูกต้อง เพื่อจะได้สะดวก ต่อการแก้ไข

ตอนนี้เพื่อน ๆ คงเห็น ประโยชน์ของคำสั่ง DDV กันบ้างแล้วนะคะ ซึ่งคำสั่ง DDV นี้มีหลายคำสั่งด้วยกัน แต่ที่สำคัญได้แสดง ดังตารางข้างล่างนี้ค่ะ
DDV_MinMaxByte ตรวจสอบว่าตัวแปรชนิด BYTE มีค่าอยู่ในช่วงที่กำหนดหรือไม่
DDV_MinMaxInt ตรวจสอบว่าตัวแปรชนิด int มีค่าอยู่ในช่วงที่กำหนดหรือไม่
DDV_MinMaxLong ตรวจสอบว่าตัวแปรชนิด long มีค่าอยู่ในช่วงที่กำหนดหรือไม่
DDV_MinMaxFloat ตรวจสอบว่าตัวแปรชนิด float มีค่าอยู่ในช่วงที่กำหนดหรือไม่
DDV_MinMaxDouble ตรวจสอบว่าตัวแปรชนิด double มีค่าอยู่ในช่วงที่กำหนดหรือไม่
DDV_MaxChars ตรวจสอบว่าตัวแปรชนิด Char มีจำนวนตัวอักขระมากกว่าค่าที่กำหนดหรือไม่

โดยมีรูปแบบของฟังก์ชั่น ดังแสดงต่อไปนี้
DDV_Function(CDataExchange* pDX , value , minVal , maxVal );
โดย Function คือฟังก์ชั่นดังแสดงในตารางข้างบน
pDX คือตัวชี้ไปยัง วัตถุเป้าหมายของคลาส CDataExchange
value คือตัวแปรที่ต้องการตรวจสอบ
minVal คือ ค่าต่ำสุดที่ยอมรับได้
maxVal คือ ค่าสูงสุดที่ยอมรับได้

แต่มีข้อยกเว้นข้อหนึ่งคือ ฟังก์ชั่น DDV_MaxChars ซึ่งมีรูปแบบดังนี้
DDV_MaxChars( CDataExchange* pDX, CString const& value, int nChars );
โดยที่ pDX คือตัวชี้ไปยัง วัตถุเป้าหมายของคลาส CDataExchange
value คือตัวแปรที่ต้องการตรวจสอบ
nChars คือจำนวนตัวอักขระ ที่มากที่สุดที่ยอมรับได้

จากตัวอย่างข้างต้น เพื่อน ๆ จะเห็นว่า ฟังก์ชั่น DDX_Text( ) นั้นสามารถ เชื่อมโยงตัวแปร ชนิด ตัวเลข int, float, double, ฯลฯ เข้ากับ Edit control ได้ ไม่จำกัดเฉพาะตัวแปรชนิด CString เท่านั้น โดยฟังก์ชั่น DDX_Text จะทำการแปลงค่าตัวอักขระ ใน Edit control ให้เป็น ตัวเลขชนิด int, float, double โดยอัตโนมัติ ก่อนส่ง ไปเก็บไว้ในตัวแปรที่สัมพันธ์กัน และเมื่อต้อง การนำค่าจาก ตัวแปรชนิด int, float, double ไปแสดงยัง Edit control ฟังก์ชั่น DDX_Text ก็จะทำการ แปลงค่าเหล่านั้น ให้เป็น CString โดยอัตโนมัติ ก่อนจะส่ง ออกไปแสดงยัง Edit control เช่นกัน ดังนั้นจะเห็นว่า ฟังก์ชั่น DDX_Text() เพียงฟังก์ชั่นเดียว ก็สามารถ จัดการกับตัวแปรใน Edit control ได้เกือบทุกชนิด ทำให้สะดวกต่อ การเขียนโปรแกรมเป็นอย่างมาก

ก่อนจะจบบทนี้ หนูแจ๋วขอแนะนำ คำสั่ง DDX ที่สำคัญมาก อีกคำสั่งหนึ่งคือ ซึ่งใช้เชื่อมโยง วัตถุเป้าหมาย ของคลาสที่ควบคุม control เข้ากับ control ใน Dialog ทำให้สามารถ ใช้ฟังก์ชั่นของคลาสนั้น ๆ ในการกำหนดค่าเริ่มต้น หรือกำหนดการทำงาน เฉพาะอย่าง ให้กับ control ที่ต้องการได้ คำสั่งนั้นคือ DDX_Control() นั่นเอง ซึ่งมีรูปแบบดังนี้
DDX_Control( CDataExchange* pDX, int nIDC, CWnd& rControl );
โดยที่ pDX คือตัวชี้ไปยัง วัตถุเป้าหมายของคลาส CDataExchange
nIDC คือ ID ของ control ที่ต้องการ
rControl คือ วัตถุเป้าหมาย ของคลาสของ control ที่เกี่ยวข้อง

คอนโทรลบางตัว ต้องมีการกำหนด การทำงานเฉพาะอย่าง ให้ เช่น ComboBox ซึ่งจะมีลักษณะเหมือน Edit box รวมกับ List box ดังแสดงในรูป


คลาสที่ควบคุม การทำงานของ ComboBox ก็คือคลาส CComboBox ซึ่งภายในคลาสนี้ จะมีฟังก์ชั่นสมาชิก ที่ใช้ควบคุมการทำงาน ของ ComboBox อยู่หลายตัว แต่หนูแจ๋ว ขอยกมาเป็นตัวอย่าง เพียงตัวเดียวก่อน คือฟังก์ชั่น AddString() ที่ใช้สำหรับ เพิ่มข้อความ ลงไปตอนท้ายของ รายการใน ComboBox ซึ่งมีรูปแบบ ดังนี้
int AddString( LPCTSTR lpszString );
โดยที่ lpszString คือ ตัวชี้ ไปยังข้อความ (string) ที่ต้องการเพิ่มเติมเข้าไปในรายการของ Combo box
ฟังก์ชั่นนี้จะ ส่งคืนค่าตำแหน่ง (index) ของข้อความที่เพิ่มเข้าไปในรายการของ Combo box กลับมา

ปกติแล้ว เราจะเพิ่มข้อความ เข้าไปในรายการ ของ Combo box โดยการเพิ่มโค้ดคำสั่ง เข้าไปใน ฟังก์ชั่น OnInitDialog() ซึ่งจะถูกเรียก ทุกครั้ง เมื่อมีการสร้าง Dialog box ขึ้นบนจอภาพ โดยรูปแบบของ โค้ดจะเป็นดังนี้ (สมมติว่า Combo box ของเรา มีหมายเลข ID เป็น IDC_MYCOMBO)
BOOL  CMainDialog::OnInitDialog( )
{
   CDialog::OnInitDialog( ); // เรียกฟังก์ชั่น OnInitDialog() ของ Base class
   
   // รับค่าตัวชี้ไปยัง Combo box IDC_MYCOMBO มาเก็บไว้ที่ตัวแปร mycombo;
   CComboBox*  mycombo = (CComboBox*) GetDlgItem(IDC_MYCOMBO);

   // ใช้ฟังก์ชั่น AddString เพิ่มข้อความลงไปในรายการ ของ Combo box
   mycombo -> AddString("picture 1");
   mycombo -> AddString("picture 2");
   mycombo -> AddString("picture 3");
  }

แต่ถ้าเราใช้ความสามารถของ DDX & DDV แล้ว เราจะมีรูปแบบ การเขียนโค้ดแตกต่างไป โดยจะมีการ เพิ่มเติมโค้ดลงไป 3 ที่ ดังนี้

maindialog.h
class CMainDialog: public CDialog
{
  protected:
          //  (1)ประกาศ object ของคลาส CComboBox ไว้ในตอนประกาศคลาศของ Dialog box
          CComboBox  mycombo; 
           
          // ประกาศตัวแปร แบบ protected อื่น ๆ
          // ..........................................................
   public:
          CMainDialog( );
          void DoDataExchange(CDataExchange*  pDX);
          DECLARE_MESSAGE_MAP( )
};

maindialog.cpp
void CMainDialog:: DoDataExchange(CDataExchange* pDX)
{
   // (2) เชื่อมโยง object ชื่อ mycombo เข้ากับ Combo box ที่มีหมายเลข ID เป็น IDC_MYCOMBO
   DDX_Control(pDX, IDC_MYCOMBO, mycombo);
   
   // ฟังก์ชั่น DDX & DDV สำหรับ control ตัวอื่น ๆ
   // ............................................................................
  
   CDialog:: DoDataExchange(pDX); // เรียกฟังก์ชั่น DoDataExchange() ของ base class
}

BOOL  CMainDialog:: OnInitDialog( )
{
   CDialog::OnInitDialog( ); // เรียกฟังก์ชั่น OnInitDialog( ) ของ base class
   
   // (3) เรียกใช้ฟังก์ชั่น AddString( ) ของ คลาส Combo box 
   mycombo.AddString("picture 1");
   mycombo.AddString("picture 2");
   mycombo.AddString("picture 3");
}

คราวนี้ เราลองมาเขียนโปรแกรม เพื่อเพิ่มพูน ความเข้าใจ เกี่ยวกับฟังก์ชั่น DDX_Control และการใช้งาน Combo box กันสัก 1 โปรแกรม นะคะ โดยตอนแรก ให้เพื่อน ๆ สร้างโปรเจคท์ ขึ้นมาใหม่ ให้เป็นแบบ Win 32 application แล้วกำหนดให้ใช้ MFC ด้วย จากนั้นให้ ทำการเพิ่ม dialog box เข้าไปเพื่อให้เป็น dialog หลักของโปรแกรม (หนูแจ๋ว ขออนุญาตเขียนโปรแกรม แบบ Dialog base ก่อนนะคะ เพื่อให้ง่าย ต่อการเขียนโค้ด แล้วบทต่อ ๆ ไป เราค่อยกลับไปเขียน โปรแกรมแบบที่ใช้ Frame window เป็นหน้าต่างหลัก ของโปรแกรมเหมือนเดิม) โดยมีขั้นตอนดังนี้ค่ะ

1. สร้างไฟล์รีซอสท์ สำหรับใช้เก็บรีซอสท์ของโปรแกรม (เช่น dialog box หรือภาพ bitmap) โดยเลือกเมนู Project -> Add to Project แล้วคลิ๊ก New เลือกหัวข้อ สร้าง Resource script แล้วพิมพ์ชื่อ ไฟล์รีซอสท์เป็น resource.rc แล้วคลิ๊ก OK

2. แทรกรีซอสท์ เข้าไปในไฟล์รีซอสท์ โดยเลือกเมนู Insert -> Resource แล้วคลิ๊ก New เลือกที่จะแทรก Dialog แล้วคลิ๊ก New จะเข้าสู่ Dialog editor จากนั้นให้เพื่อน ๆ ลบปุ่มกด OK ทิ้งไป แล้วเพิ่ม control ลงไป 2 ตัว คือ picture control และ combo box ดังแสดงในรูป ค่ะ


โดย เราจะกำหนดหมายเลข ID ของสิ่งต่าง ๆ ไว้ดังตารางต่อไปนี้
ชนิดของ Dialog และ control
หมายเลข ID
dialog หลัก (caption = "Picture Selection") IDD_DIALOG1
picture control IDC_PIC
combo box IDC_COMBO1

จากนั้นเราจะแทรกรีซอสท์ ชนิดรูปบิตแมป ลงไป โดยเลือกเมนู Insert -> Resource คลิ๊ก New เลือกแทรก Bitmap จากนั้น ให้คลิ๊กปุ่ม Import เพื่อที่จะนำไฟล์ภาพบิตแมป (.bmp) มาทำเป็น resource ให้กับโปรแกรมของเรา จะได้หน้าต่างของไฟล์ที่จะ import เข้ามาดังรูป ให้เพื่อน ๆ คลิ๊กเลือก folder ที่เก็บรูป แล้วเลือกชนิด ของไฟล์เป็น All file จากนั้นให้คลิ๊กเลือกไฟล์บิตแมปที่ต้องการ แล้วคลิ๊กปุ่ม Import ดังแสดงในรูป โดยในที่นี้ หนูแจ๋วได้เลือกรูป bitmap มา 3 รูป ซึ่งควรจะมีขนาด ๆ เท่า ๆ กัน

หลังจากเราได้แทรกทั้ง Dialog box และภาพบิตแมป 3 ภาพ ลงในไฟล์รีซอสท์แล้ว เมื่อคลิ๊กดูใน resource view แล้ว ก็จะเห็นว่า รีซอสท์ของโปรแกรมของเรา จะประกอบด้วย Dialog box ที่มี ID เป็น IDD_DIALOG1 และ Bitmap ที่มี ID เป็น IDB_BITMAP1 ถึง 3 ดังแสดงในรูป

3. ต่อไปก็สร้างไฟล์ ที่ใช้ประกาศคลาส และอธิบายรายละเอียดของคลาส ที่ควบคุมการทำงานของโปรแกรมเรา คือไฟล์ myapp.h และ myapp.cpp ดังต่อไปนี้ค่ะ

myapp.h
class CMyApp:public CWinApp
{
public:
	BOOL InitInstance();
};

myapp.cpp
#include <afxwin.h>
#include "myapp.h"
#include "maindialog.h"

CMyApp myapp;

BOOL CMyApp::InitInstance()
{
	CMainDialog dlg;
	m_pMainWnd = &dlg;

	dlg.DoModal();

	return FALSE;
}

4. จากนั้น ก็สร้างไฟล์ที่ควบคุมการทำงานของ Dialog หลัก ของโปรแกรมเรา ซึ่งก็คือไฟล์ maindialog.h และ maindialog.cpp ดังแสดงต่อไปนี้ค่ะ

maindialog.h
class CMainDialog:public CDialog
{
protected:
	CComboBox  m_combo; // ประกาศ object ของคลาส CComboBox เพื่อใช้ควบคุม combo box ต่อไป
	int index;                                // ตัวแปรที่ใช้เก็บค่าตำแหน่ง index ของ รายการที่ถูกเลือกใน combo box
  CStatic   pic;                        // ประกาศ object ของคลาส CStatic เพื่อใช้ควบคุม picture control

public:
	CMainDialog();
	BOOL OnInitDialog();     // ประกาศฟังก์ชั่น OnInitDialog() เพื่อใช้ในการ Override
	void DoDataExchange(CDataExchange* pDX); // ประกาศฟังก์ชั่นที่จะถูกเรียกใช้ในกระบวนการ DDX&DDV
	void OnSelection();       // ประกาศฟังก์ชั่นที่จะถูกเรียก เมื่อมีการเลือกรายการใน Combo box

	DECLARE_MESSAGE_MAP()
};

maindialog.cpp
#include <afxwin.h>
#include "maindialog.h"
#include "resource.h"

BEGIN_MESSAGE_MAP(CMainDialog,CDialog)
  // ดักจับแมสเสจ CBN_CLOSEUP แล้วเรียกใช้ฟังก์ชั่น OnSelection
  ON_CBN_CLOSEUP(IDC_COMBO1,OnSelection) 
END_MESSAGE_MAP()

CMainDialog::CMainDialog():CDialog(IDD_DIALOG1,NULL)
{
	index = 0;   // กำหนดค่า index ของรายการเลือกของ combo box ให้เป็น 0
}

// ฟังก์ชั่นนี้จะถูกเรียกโดยอัตโนมัติ เมื่อมีการเรียกฟังก์ชั่น UpdateData()
void CMainDialog::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);

	DDX_Control(pDX,IDC_COMBO1,m_combo);  // เชื่อมโยง object m_combo เข้ากับ Combo box (IDC_COMBO1)
	DDX_CBIndex(pDX,IDC_COMBO1,index);        // เชื่อมโยง ตัวแปร index เข้ากับ Combo box (IDC_COMBO1)

  DDX_Control(pDX, IDC_PIC, pic);                        // เชื่อมโยง object pic เข้ากับ picture control (IDC_PIC)
}

// ฟังก์ชั่นนี้จะถูกเรียกโดยอัตโนมัติ เมื่อมีการสร้าง dialog box 
BOOL CMainDialog::OnInitDialog()
{
	CDialog::OnInitDialog();     // เรียกใช้ฟังก์ชั่น OnInitDialog( ) ของ base class

	m_combo.AddString("picture 1"); // เพิ่มข้อความ เข้าไปใน Combox box control
	m_combo.AddString("picture 2");
	m_combo.AddString("picture 3");

	return TRUE;
}

// ฟังก์ชั่นนี้จะถูกเรียกทุกครั้ง เมื่อ Maindialog ได้รับแมจเสจ CBN_CLOSEUP จาก Combo box
void CMainDialog::OnSelection()
{
	UpdateData();  // นำค่าจาก control ไปเก็บไว้ในตัวแปร


	CDC *picDC = pic.GetDC(); // รับค่า Device context ของ picture control ไปเก็บในตัวชี้ picDC
	CDC memDC;                          // สร้างวัตถุเป้าหมาย ของคลาส CDC ขึ้นมา
	CBitmap picbitmap;               // สร้างวัตถุเป้าหมาย ของคลาส CBitmap ขึ้นมา

	memDC.CreateCompatibleDC(picDC); // สร้าง Device context ที่เหมือนกับ DC ของ picture control มาเก็บไว้

  // เปรียบเทียบค่า index ของรายการ combo box ที่ถูกเลือก เพื่อโหลดภาพ bitmap ที่สัมพันธ์กัน
	switch(index)
	{
		case 0:	picbitmap.LoadBitmap(IDB_BITMAP1);
				break;
		case 1: picbitmap.LoadBitmap(IDB_BITMAP2);
				break;
		case 2: picbitmap.LoadBitmap(IDB_BITMAP3);
	}

	memDC.SelectObject(&picbitmap); // นำภาพบิตแมปไปเก็บไว้ใน memDC
	picDC->BitBlt(0,0,50,65,&memDC,0,0,SRCCOPY); // ย้ายภาพจาก memDC ไปยัง picture control
}

5. จากนั้น ลอง Build แล้ว Execute โปรแกรมดู ซึ่งเมื่อเปิดโปรแกรมขึ้นมาครั้งแรก จะได้หน้าจอดังแสดงในรูป


เมื่อเราคลิ๊กที่ปุ่มลูกศรชี้ลง ข้าง ๆ Combo box ก็จะมี list box แสดงออกมาข้างล่างของ combox box ดังแสดงในรูป ซึ่งเราสามารถ ใช้เมาส์ เลื่อนไปเลือกรายการใด ๆ ใน list box นี้ก็ได้

เมื่อเราคลิ๊กที่รายการเลือกใด ๆ บน list box นี้ ก็จะทำให้ list box นี้หายไป แต่ข้อความใน list box ที่ถูกเลือก จะถูกนำไป แสดงใน Edit box ของ combo box ด้วย พร้อม ๆ กันนั้น จะมีการส่งแมจเสจเตือน (notification message) ที่ชื่อ CBN_CLOSEUP ไปยังหน้าต่างแม่ ของ combo box นี้ ซึ่งก็คือหน้าต่างของ main dialog นั้นเอง เพื่อบอกว่า ส่วน list box ของ combo box นี้ ถูกปิดไป ซึ่งโปรแกรมของเรา ได้ทำการ ดักจับแมสเสจนี้ ด้วยมาโคร ON_CBN_CLOSEUP( ) ซึ่งจะไปเรียกใช้ ฟังก์ชั่น OnSelection( ) อีกทีหนึ่ง ภายในฟังก์ชั่น OnSelection( ) นี้ ก็จะทำการตรวจสอบค่า index ของรายการที่ถูกเลือกภายใน combo box และไปทำการเลือก ภาพบิตแมปที่สัมพันธ์กัน มาแสดงใน picture control นั่นเอง ดังแสดงในรูป

เป็นยังไงบ้างคะ เพื่อน ๆ บทความตอนนี้ อาจจะดูยุ่งเหยิงอยู่บ้าง เพราะ หนูแจ๋วต้องการจะสรุปเนื้อหา เกี่ยวกับ DDX และ DDV ให้จบ ภายในตอนนี้ ให้ได้ แต่ถ้าเพื่อน ๆ อ่านอย่างละเอียด และศึกษาจาก MSDN เพิ่มเติมแล้ว หนูแจ๋วคิดว่าเพื่อน ๆ จะสามารถเข้าใจ เนื้อหาในบทนี้ ได้อย่างไม่ยากเลยค่ะ

แล้วพบกันใหม่นะคะ

หนูแจ๋ว (คนหน้าเดิม)

Hosted by www.Geocities.ws

1