Visual C++
เรื่องน่ารู้ของ Divice Context
Device Context คืออะไร?
จากบทที่ 5 เราจะพบว่า เมื่อต้องการ ส่งข้อความ หรือตัวอักษร หรือรูปกราฟใด ๆ ออกบนพื้นที่ทำงาน
(client area) ของวินโดวส์ หน้าต่างของโปรแกรมเรา จะต้องมีการ สร้างวัตถุเป้าหมาย (object)
ของคลาส CClientDC ขึ้นมาเสียก่อน ซึ่งเมื่อมีการสร้าง วัตถุเป้าหมายขึ้นมา จะมีการรับค่าของ
device context มาโดยอัตโนมัติ เมื่อได้ค่า device context มาเรียบร้อยแล้ว เราก็สามารถติดต่อ
กับพื้นที่ทำงาน ของหน้าต่างวินโดวส์ ของเราได้ทันที ทำให้สามารถใช้ ฟังก์ชั่น TextOut( ) ในการส่ง
ข้อความ ออกไปยังตำแหน่ง ที่ต้องการได้ทันที
แล้ว device context คืออะไร? จริง ๆ แล้ว device context เป็นโครงสร้าง (structure) ที่เก็บค่า
ตัวแปรต่าง ๆ ในการแสดงผล ของวินโดวส์ไว้ เช่น ดีไวซ์ไดรเวอร์ (device driver), ชนิดของ ตัวอักษร
ที่ใช้อยู่, และอื่น ๆ อีกมากมาย การที่จะแสดงผล ออกบนหน้าต่าง วินโดวส์ของโปรแกรมเรา จะต้องรับค่า
device context มาเก็บไว้เสียก่อน
จากตัวอย่าง ในบทที่ 5 เราพบว่าโปรแกรม ที่เขียนขึ้นมีปัญหา อย่างหนึ่งคือ เมื่อหน้าต่างวินโดวส์ ของโปรแกรมเรา
ถูกย่อลง หรือถูกบังโดย หน้าต่างวินโดวส์ ของโปรแกรมอื่น จะทำให้ข้อความ ที่แสดงอยู่ ในหน้าต่างวินโดวส์ ของ
โปรแกรมเราหายไป เพราะวินโดวส์จะไม่ เก็บข้อมูลที่แสดง ในหน้าต่างไว้ เป็นหน้าที่ ของผู้เขียนโปรแกรม ที่จะต้อง
จัดการในส่วนนี้เอง ซึ่งในตอนนี้ เราจะแก้ปัญหา โดยใช้วิธีการง่าย ๆ คือ มีเหตุการณ์อยู่ 3 อย่าง คือ
- เมื่อมีการแสดงหน้าต่างวินโดวส์ ของโปรแกรมขึ้นมาครั้งแรก
- เมื่อหน้าต่างวินโดวส์ ของโปรแกรมเรา ถูกทับโดยหน้าต่างวินโดวส์ ของโปรแกรมอื่น
- เมื่อหน้าต่างวินโดวส์ ของโปรแกรมเราถูก minimize
ซึ่งทั้ง 3 กรณีข้างต้น ระบบปฏิบัติการวินโดวส์ จะรู้ว่าจำเป็นต้อง มีการเขียนหน้าต่าง วินโดวส์ของ
โปรแกรมเราขึ้นมาใหม่ เมื่อมีการคลิ๊กเรียก หน้าต่างของโปรแกรมเรา ออกมาอีกครั้ง
วินโดวส์ก็จะส่ง message ที่ชื่อว่า WM_PAINT มายังโปรแกรมเรา เพื่อบอกให้โปรแกรม ของ
เรารู้ว่า จะต้องมีการเขียน ข้อมูลในพื้นที่ทำงาน (client area) ของวินโดวส์ขึ้นใหม่
ซึ่ง เราจะใช้ฟังก์ชั่นตัวจัดการกับ message WM_PAINT ซึ่งมีชื่อว่า OnPaint( ) มาจัดการ
ในการแสดงข้อความ ออกบนจอภาพ ซึ่งเราจะเขียน ชุดคำสั่งในฟังก์ชั่นนี้ ดังต่อไปนี้
afx_msg void OnPaint( )
{
CPaintDC dc(this); /* รับค่า device context ของหน้าต่างวินโดวส์นี้ */
dc.TextOut(1,1,str,strlen(str));/* ส่งข้อความที่เก็บในตัวแปร str ออกหน้าต่างวินโดวส์ */
}
จะเห็นว่า ในฟังก์ชั่น OnPaint( ) นี้เราจะรับ ค่า Device context มา ด้วยการสร้างวัตถุเป้าหมาย ของคลาส CPaintDC
แทนที่จะเป็นคลาส CClientDC
คราวนี้ เราจะมีดัดแปลงโปรแกรมที่เขียนไป ในบทที่ 5 เพื่อแก้ไขปัญหา ข้อมูลบนหน้าต่างหายไป โดยใช้เทคนิค
WM_PAINT กัน
/* ไฟล์ ชื่อ message.h */
/* ประกาศคลาสของหน้าต่างวินโดวส์ของเรา ที่สืบทอดมาจากคลาส CFrameWnd */
class CMainWin : public CFrameWnd
{
public:
CMainWin( );
afx_msg void OnChar(UINT ch, UINT count, UINT flags);
afx_msg void OnPaint( ); /* ประกาศต้นแบบ ของฟังก์ชั่น ที่ใช้จัดการ กับ message WM_PAINT*/
DECLARE_MESSAGE_MAP( )
};
/* ประกาศคลาสของโปรแกรมเรา ที่สืบทอดมาจากคลาส CWinApp */
class CApp : public CWinApp
{
BOOL InitInstance( );
};
จากนั้นเราก็มาแก้ไขไฟล์ message.cpp ของเรากันให้เป็นดังนี้นะคะ
/* ไฟล์ชื่อ message.cpp */
#include <afxwin.h> /* ต้อง include ไฟล์นี้ทุกครั้งที่เขียนแบบ MFC */
#include <string.h> /* include function ที่จัดการกับ string เข้ามา */
#include "message.h"/* include ไฟล์ที่ประกาศคลาสของเราเข้ามา */
char str[80]= "Text"; /* กำหนดตัวแปร str เป็นอาเรย์ ของตัวแปรชนิด char จำนวน 80 ตัว */
/* ประกาศเป็นตัวแปรชนิด global เพื่อให้เรียกใช้ได้จากทุกแห่งในโปรแกรม */
CMainWin::CMainWin( ) /* function constructor ของคลาสหน้าต่างวินโดวส์เรา */
{
Create(NULL, "Test WM_CHAR message map");
}
/* รายละเอียดของฟังก์ชั่น InitInstance */
BOOL CApp::InitInstance( )
{
m_pMainWnd = new CMainWin;
m_pMainWnd -> ShowWindow(m_nCmdShow);
m_pMainWnd -> UpdateWindow( );
return TRUE;
}
/* ส่วนของ message map ของหน้าต่างวินโดวส์ CMainWin ที่สืบทอดมาจากคลาส CFrameWnd*/
BEGIN_MESSAGE_MAP(CMainWin, CFrameWnd)
ON_WM_CHAR( )
ON_WM_PAINT( ) /* ประกาศมาโคร เพื่อรอรับ message WM_PANIT */
END_MESSAGE_MAP( )
/* รายละเอียดของฟังก์ชั่นที่ใช้จัดการ message WM_CHAR */
afx_msg void CMainWin::OnChar(UINT ch, UINT count, UINT flags)
{
wsprintf(str, "%c", ch); /* แปลงตัวอักขระ ch ไปเก็บไว้ที่ตัวแปรอาเรย์ str */
InvalidateRect(NULL,TRUE);/* บอกให้วินโดวส์ ส่ง message WM_PAINT มายังโปรแกรมเรา */
}
/* รายละเอียดของฟังก์ชั่นที่ใช้จัดการ message WM_PAINT */
afx_msg void CMainWin::OnPaint( )
{
CPaintDC dc(this);/* รับค่า Device context โดยการสร้างวัตถุเป้าหมาย ของคลาส CPaintDC */
dc.TextOut(1, 1, str, strlen(str)); /* เขียนตัวอักขระลงบน client area ตำแหน่งบนซ้ายสุด */
}
CApp myapp; /* สร้างออปเจคท์ของคลาสโปรแกรมของเรา */
จากโปรแกรมข้างต้น จะเห็นว่าขั้นตอน การเขียนโปรแกรม ให้โปรแกรมเรา ตอบสนองต่อ message WM_PAINT เพิ่มขึ้นมา ทำได้ดังนี้
- ประกาศฟังก์ชั่นต้นแบบ คือ afx_msg void OnPaint( ) ไว้ในการประกาศคลาสหน้าต่างวินโดวส์ ของเรา
- เพิ่มมาโคร ON_WM_PAINT( ) ลงใน message map ของเรา
- เขียนรายละเอียด ของฟังก์ชั่น OnPaint( ) ไว้ในไฟล์ ที่เก็บรายละเอียด ของหน้าต่างวินโดวส์ของเรา
เมื่อเขียนโปรแกรม แบบนี้แล้ว ทุกครั้งที่มีการ สร้างหน้าต่างวินโดวส์ของเรา มาครั้งแรก หรือเมื่อหน้าต่างวินโดวส์ ของเรา ต้องมีการวาดใหม่
อันเนื่องมาจาก ถูก minimize ลง หรือถูกหน้าต่างวินโดวส์อื่น ทับอยู่ก็ตาม เมื่อมีการคลิ๊กให้ หน้าต่างวินโดวส์ ของเรา Active ขึ้นมา ก็จะมี
การส่ง message WM_PAINT มาให้กับหน้าต่างวินโดวส์ ของโปรแกรมเรา ซึ่งจะทำให้มีการ เรียกฟังก์ชั่นที่จัดการกับ message นี้ขึ้นมา
ทำงานโดยอัตโนมัติ ซึ่งคือฟังก์ชั่น OnPaint( ) นั่นเอง ในฟังก์ชั่นนี้ ก็จะทำการรับค่า Device Context มา โดยการสร้างวัตถุเป้าหมาย ของ
คลาส CPaintDC ขึ้นมา แล้วจึงนำค่าที่อยู่ ในตัวแปรอาเรย์ str มาแสดงออก ที่หน้าต่างวินโดวส์ ของเรา โดยใช้ฟังก์ชั่น TextOut( ) นั่นเอง
ดังนั้น ในส่วนของฟังก์ชั่น ที่เป็นตัวจัดการ message WM_CHAR ที่จะเกิดขึ้น เมื่อมีการกดคีย์ ก็จะลดจำนวนคำสั่งลง เหลือเพียงคำสั่งที่ ทำการ
แปลงค่าอักขระ ch ของการกดคีย์ มาเป็นตัวแปรอาเรย์ str คือ wsprintf(str,"%c",ch);
แล้วจึงใช้คำสั่ง InvalidateRect( NULL, TRUE); เพื่อบอกให้ วินโดวส์ส่ง message
WM_PAINT มายังหน้าต่างวินโดวส์ ของโปรแกรมของเรา เพื่อทำให้โปรแกรม เราตอบสนองต่อ message WM_PAINT ด้วยการเรียก ฟังก์ชั่น
OnPaint( ) มาทำงาน เพื่อแสดงตัวอักษร ออกทางพื้นที่ทำงาน (Client Area) ของหน้าต่างวินโดวส์ ของเราต่อไป
ลองแก้ไข โปรแกรมตามข้างต้น แล้วลอง compile แล้ว run โปรแกรมดูนะคะ เพื่อน ๆ ก็จะได้หน้าต่างวินโดวส์ เหมือนกับในบทที่แล้ว เพียงแต่ว่า
คราวนี้ เมื่อเราลอง minimize วินโดวส์ลง หรือคลิ๊กให้มีหน้าต่าง ของโปรแกรมอื่น มาทับหน้าต่างวินโดวส์ ของโปรแกรมเรา เมื่อคลิ๊กกลับมา ที่โปรแกรมของเรา
ก็จะมีตัวอักษร แสดงอยู่เหมือนเดิม ไม่หายไปเหมือนคราวที่แล้ว
เป็นยังไงบ้างคะ? งงหรือเปล่า ถ้าไม่เข้าใจฟังก์ชั่นตัวไหน ก็ให้อ่านเพิ่มเติมได้ใน CD ของ MSDN นะคะ เพราะมีบางฟังก์ชั่นบางฟังก์ชั่น ที่หนูแจ๋วอธิบายไม่ละเอียด
เช่น ฟังก์ชั่น OnChar(UINT ch, UINT count, UINT flags) ) ที่ใช้จัดการกับ message WM_CHAR ว่าทำไมมีพารามิเตอร์ ให้มาหลายตัว โดยที่หนูแจ๋วอธิบายไป
ตัวเดียวคือ ch ว่า เป็นค่าตัวอักขระของปุ่มที่ถูกกด ส่วนพารามิเตอร์ count และ flags ก็ให้เพื่อน ๆ ไปศึกษาเพิ่มเติมเองนะคะ ฟังก์ชั่นที่น่าสนใจ ในบทนี้ ก็มี
- InvalidateRect( LPCRECT lpRegion, BOOL Erase=TRUE);
- wsprintf(str, "%c",ch);
- TextOut(x,y,str,strlen(str));
ส่วน message อื่น ๆ ของวินโดวส์ที่น่าสนใจ และน่านำไปใช้ ก็มี
- WM_LBUTTONDOWN ซึ่งจะถูกส่งมาให้ เมื่อมีการคลิ๊กปุ่มซ้ายของ เมาส์ ในบริเวณ client area ของหน้าต่างวินโดวส์เรา
- WM_RBUTTONDOWN ซึ่งจะถูกส่งมาให้ เมื่อมีการคลิ๊กปุ่มซ้ายของ เมาส์ ในบริเวณ client area ของหน้าต่างวินโดวส์เรา
เพื่อน ๆ ลองไปศึกษาเพิ่มเติมดูนะคะ จะได้ช่วยเพิ่มความเข้าใจ เกี่ยวกับการเขียนโปรแกรม บนวินโดวส์ได้ดีขึ้นด้วย ส่วนในบทที่ 7
คราวหน้า หนูแจ๋วจะพูดเกี่ยวกับ การสร้าง message box และ menu ของโปรแกรม ที่เขียนด้วยภาษาซีกันนะคะ
สวัสดีเจ้าค่ะ หนูแจ๋ว