My Opera is closing 1st of March

QUANG HOANG'S BLOG

Welcome to everybody !

Xử lý các message bàn phím, mouse và time trong VC++

, ,

Hoàng Đăng Quang

Bài 1: Viết ứng dụng hiển thị một chuỗi trên vùng client, sau đó có thể dùng các phìm mũi tên để di chuyển chuỗi này.

1. Tạo 2 biến int m_x, m_y để lưu tọa độ chuỗi trong lớp CBaiTap1View.
2. Trong hàm OnDraw() dùng ham CDC::TextOut() để xuất chuỗi tại vị trí m_x, m_y.
3. Ánh xạ message OnActiveView:
- Vào menu View --> ClassWizard… (hay nhấn Ctrl + W)
- Chọn OnAcviteView trong listbox Messages --> Add Function --> Edit Code



4. Viết code cho hàm OnActiveView():
void CBaiTap1View::OnActivateView(BOOL bActivate, CView* pActivateView, CView* DeactiveView) 
{
   // TODO: Add your specialized code here and/or call the base class
   CRect rect;
   GetClientRect(&rect);
   m_x = rect.Width() / 2;
   m_y = rect.Height() / 2;
   CView::OnActivateView(bActivate, pActivateView, pDeactiveView);
}


5. Ánh xạ message WM_KEYDOWN, và viết code cho hàm OnKeyDown():
switch(nChar)
      {
            case VK_LEFT:     m_x--; break;
            case VK_RIGHT:    m_x++; break;
            case VK_UP:       m_y--; break;
            case VK_DOWN:     m_y++; break;
      }
      Invalidate();  //Chập nhật lại vùng client --> Gọi hàm OnDraw()



Bài 2: Viết ứng dụng khi nhấn bất kỳ phím nào thì trên vùng cilent xuất hiện các thông tin: Mã scan code, mã virtual key, mã ASCII.

1. Ánh xạ message WM_KEYDOWN.
2. Dùng hàm UINT MapVirtualKey( UINT uCode, UINT uMapType ); để lấy mã scan và mã ASCII của phím (Xem MSDN để hiểu cách sử dụng hàm này).

void CBaiTap2View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
   // TODO: Add your message handler code here and/or call default   
   int code;
   char buffer[100]; 
   CDC *pDC = GetDC();     
   //Mã scan của phím
   code = MapVirtualKey(nChar, 0);
   sprintf(buffer, "Scan code: %d   ", code);
   pDC->TextOut(0, 0, buffer);
   //Virtual key của phím
   sprintf(buffer, "Virtual key: %d   ", nChar);
   pDC->TextOut(0, 20, buffer);
   //Mã ASCII của phím
   code = MapVirtualKey(nChar, 2);
   sprintf(buffer, "ASCII code: %d   ", code);
   pDC->TextOut(0, 40, buffer);
   CView::OnKeyDown(nChar, nRepCnt, nFlags);
}



Bài 3: Viết ứng dụng hiển thị các thông tin khi nhấn một phím bất kỳ.
Xem sách Programming Windows Fifth Edition trang 193.


Bài 4: Viết ứng dụng cho phép soạn thảo một dòng văn bản đơn giản trên vùng client (không được dùng lớp CEditView) xử lý các phím chức năng như right, left, delete, backspace.

1. Khai báo một số biến sử dụng trong chương trình: (Khai báo trong CBaiTap4View)
CString m_fontName; //Lưu tên font sử dụng
int m_fontSize; //Lưu kích thước font sử dụng.
POINT m_textPosition; //Lưu vị trí (bắt đầu) của font.
CString m_text; //Lưu nội dung chuỗi nhập vào.
int m_carectIndex; //Lưu vị trí của carect trong chuỗi.

int m_carectWidth;  //Chiều rộng và chiều cao của Carect (con trỏ)
int m_carectHeight; //(Xem tài liệu trang 94 để hiểu về Carect)


2. Khởi tạo các biến trong hàm CBaiTap4View():
      m_textPosition.x = 10;        m_textPosition.y = 10;
      m_carectIndex = 0;            m_carectWidth = 2;      m_carectHeight = 50;
      m_fontName = "VNI-Times";     m_fontSize = 300;


3. Tạo và hiển thị carect:
- Ánh xạ message WM_SETFOCUS.
- Viết code cho hàm OnSetFocus():

CreateSolidCaret(m_carectWidth, m_carectHeight);
ShowCaret();


4. Hũy carect: (trong hàm ~CBaiTap4View())
DestroyCaret();


5. Thêm hàm void UpdateCarect() vào lớp CBaiTapView: (Cập nhật vị trí cho carect)
Dựa vào biến m_carectIndex và dùng hàm CDC:: GetTextExtent() để xác định chiều dài của text (tính đến vị trí của carect) --> vị trí của carect.

size = pDC->GetTextExtent(m_text.Left(m_carectIndex));


6. Xử lý các phím chức năng như right, left, delete, backspace:
- Ánh xạ message WM_KEYDOWN.
- Viết code cho hàm OnKeyDown():

switch(nChar)
{
   case VK_BACK: 
         if (m_carectIndex > 0)
         {
               m_text.Delete(m_carectIndex-1);
               m_carectIndex--;
               Invalidate();
         }
         break;
   case VK_DELETE: 
         if (m_carectIndex >= 0 && m_carectIndex < m_text.GetLength())
         {                       
               m_text.Delete(m_carectIndex);                         
               Invalidate();
         }
         break;
   case VK_LEFT:
         if (m_carectIndex > 0)
         {
               m_carectIndex--;
               UpdateCarect();
         }
         break;
   case VK_RIGHT:
         if (m_carectIndex < m_text.GetLength())
         {
               m_carectIndex++;
               UpdateCarect();
         }
         break;
   //Tiếp tục xử lý thêm các phím khác (VK_HOME, VK_END…) ở đây.
}


7. Xử lý các phím ký tự:
- Ánh xạ message: WM_CHAR.
- Viết code cho hàm OnChar():

if (nChar >= 32)  //32 = SpaceSpace  (Nếu là ký tự chữ cái)
   {
         m_text.Insert(m_carectIndex, CString(nChar));         
         m_carectIndex ++; 
         Invalidate();
   }


8. Xuất chuỗi với font và vị trí được lưu trong biến: m_fontName, m_fontSize, m_Position trong hàm OnDraw():
CFont font;
   font.CreatePointFont(m_fontSize,m_fontName);
   CFont *pOldFont = pDC->SelectObject(&font);
   HideCaret();
   pDC->TextOut(m_textPosition.x, m_textPosition.y, m_text);
   UpdateCarect();
   pDC->SelectObject(pOldFont);
   font.DeleteObject();  



Bài 5: Viết ứng dụng cho phép người dùng vẽ đoạn thẳng với mouse. Để vẽ đoạn thẳng, nhấn nút trái của mouse bất kỳ nơi nào trên vùng client và rê cursor với nút trái của mouse vẫn ở trạng thái nhấn (lúc này hình dạng cursor thay đổi), khi mouse di chuyển, một đoạn thẳng di chuyển theo quanh điểm neo. Khi nút trái của mouse nhã ra, một đoạn thẳng được vẽ giữa điểm neo và trị trí cursor mà tại đó nút trái của mouse được nhã ra. Pen, độ dày của đoạn thẳng lấy ngẫu nhiên. Khi vẽ đoạn thẳng mới, đoạn thẳng cũ không được xóa. Ứng dụng luôn hiển thị các đoạn thẳng đã vẽ.

1. Khai báo một số biến sử dụng trong chương trình: (Khai báo trong CBaiTap4View)

BOOL m_bTracking; //Kiểm tra có đang vẽ đường thẳng không ?
CPoint m_ptFrom; //Điểm bắt đầu của đường thẳng
CPoint m_ptTo; // Điểm kết thúc của đường thẳng


2. Khởi tạo các biến trong hàm CBaiTap4View():
m_bTracking = FALSE;


3. Thêm hàm void DrawLine(CDC *pDC, CPoint P1, CPoint P2) vào CBaiTap5View:
void CBaiTap5View::DrawLine(CDC *pDC, CPoint P1, CPoint P2)
{
      int nOldMode = pDC->SetROP2(R2_NOTXORPEN); //Xem tài liệu trang 65
      pDC->MoveTo(P1);
      pDC->LineTo(P2);
      pDC->SetROP2(nOldMode);  
}


4. Xử lý khi nhấn nút trái chuột: --> bắt đầu vẽ đường thẳng
- Ánh xạ message WM_LBUTTONDOWN.
- Viết code cho hàm OnLButtonDown():
m_ptFrom = point;    //Khởi tạo điểm đầu và điểm cuối của đoạn thẳng
m_ptTo = point;
m_bTracking = TRUE;  //Bật trạng thái: Bắt đầu vẽ
::SetCursor(::LoadCursor(NULL,IDC_CROSS));   //Đổi con chỏ chuột: +


5. Xử lý khi rê chuột: --> Cập nhật điểm kết thúc
- Ánh xạ message WM_MOUSEMOVE.
- Viết code cho hàm OnMouseMove():
if (m_bTracking)
{
   CDC *pDC = GetDC();                 //Lấy DC
   DrawLine(pDC, m_ptFrom, m_ptTo);    //Xóa đoạn thẳng đã vẽ trước đó
   DrawLine(pDC, m_ptFrom, point);     //Vẽ đoạn thẳng mới
   m_ptTo = point;                     //Cập nhật lại điểm kết thúc
}


6. Xử lý khi nhã nút trái chuột: --> kết thúc vẽ đường thẳng
- Ánh xạ message WM_LBUTTONUP.
- Viết code cho hàm OnLButtonUp():
      if (m_bTracking)
      {
            m_bTracking = FALSE;
            CDC *pDC = GetDC();
            DrawLine(pDC, m_ptFrom, m_ptTo);
            CPen pen; 
            pen.CreatePen(PS_SOLID, rand() % 10, RGB(rand() % 256, rand() % 256, rand() % 256));
            CPen *pOldPen = pDC->SelectObject(&pen);
            pDC->MoveTo(m_ptFrom);
            pDC->LineTo(m_ptTo);
            pDC->SelectObject(pOldPen);
            pen.DeleteObject();
            ::SetCursor(::LoadCursor(NULL, IDC_ARROW));
      }


Bài 6: Tương tự như bài 5, chúng ta dùng mouse để vẽ hình chữ nhật, sau khi nhả mouse hình chữ nhật được fill với màu ngẫu nhiên.

Tương tự bài 5.


Bài 7: Viết ứng dụng chia client thành một ma trận nxn. Nếu chúng ta click mouse vào một trong các hình chữ nhật này, hình chữ nhật đó được đánh dấu với một chữ X, nếu chúng ta click một lần nữa dấu X bị xóa.
1. Trong file CBaiTap7View.h:
- Khai báo hằng (số ô hình chữ nhật trên vùng client):
#define MaxN 5

- Khai báo mảng dánh dấu:
int m_nCheck[MaxN][MaxN];

2. Trong hàm OnDraw():
- Lấy kích thước vùng client --> Tính ra kích thước của từng ô nhỏ.
- Vẽ các ô nhỏ này, kết hợp với mảng m_nCheck để biết ô nào được đánh dấu.
3. Ánh xạ message WM_LBUTTONDOWN, viết code cho hàm OnLButtonDown():
CRect rect;
      GetClientRect(&rect);
      int w = rect.right / MaxN;
      int h = rect.bottom / MaxN;
      m_nCheck[point.y / h][point.x / w] = !m_nCheck[point.y / h][point.x / w];
      Invalidate();


Bai 8: Viết một ứng dụng sau một khoảng thời gian nào đó, vùng client thay đổi màu (phát sinh ngẫu nhiên)

1. Ánh xạ message WM_CREATE, viết code cho hàm OnCreate():
SetTimer(1, 1000, NULL); //1000 = 1 giây

2. Ánh xạ message WM_DESTROY, viết code cho hàm OnDestroy():
KillTimer(1);

3. Ánh xạ message WM_TIMER, viết code cho hàm OnTimer():
- Lấy con trỏ của lớp CDC.
- Lấy kích thước vùng client.
- Tạo brush với màu ngẫu nhiên.
- Dùng hàm CDC::Rectangle() để vẽ.

Bài 9: Viết ứng dụng hiển thị thời gian như sau:

Viết hàm HorizontalLine(CDC *pDC, int x, int y, int w): vẽ nét ngang của con số
int w2 = w/2;
int kc = 2;
for (int i=0; i < w2; i++)
{
   pDC->MoveTo(x + w2 + i + kc, y + w2 -i);
   pDC->LineTo(x +w*4 - w2 -i - kc, y + w2 - i);
   pDC->MoveTo(x + w2 + i + kc, y + w2 + i);
   pDC->LineTo(x + w*4 - w2 - i - kc, y + w2 + i);
}


Viết hàm VerticalLine(CDC *pDC, int x, int y, int w): vẽ nét đứng của con số
int w2 = w/2;
int kc = 2;
for (int i=0; i < w2; i++)
{
   pDC->MoveTo(x + w2 - i, y + w2 + i + kc);
   pDC->LineTo(x + w2 - i, y + w*4 - w2 - i - kc);
   pDC->MoveTo(x + w2 + i, y + w2 + i + kc);
   pDC->LineTo(x + w2 + i, y + w*4 - w2 - i - kc);
}


Viết hàm DrawNumber(int x, int y, int w, int num): Vẽ con số tại tọa độ (x, y) với độ dầy của nét vẽ con số (nét đứng và nét ngang) là w, với con số là num.
- Tạo mảng int number[10][7] lưu các nét của con số (mỗi con số, tối đa có 7 nét, gồm 10 số từ 0..9). Ví dụ số 0, gồm có 6 nét {1, 0, 1, 1, 1, 1, 1}…
- Dùng mảng này, phối hợp với hàm HorizontalLine() và VerticalLine() để vẽ con số.

if (number[num][0]) HorizontalLine(pDC, x + w2, y + w2, w);
if (number[num][1]) HorizontalLine(pDC, x + w2, y + w2 + w*3, w);
if (number[num][2]) HorizontalLine(pDC, x + w2, y + w2 + w*6, w);
if (number[num][3]) VerticalLine(pDC, x + w2 , y + w2, w);
if (number[num][4]) VerticalLine(pDC, x + w2 , y + w2 + w*3, w);
if (number[num][5]) VerticalLine(pDC, x + w2 + w*3, y + w2 + w*3, w);   
if (number[num][6]) VerticalLine(pDC, x + w2 + w*3, y + w2, w);


Cách tạo timer và lấy thời gian của hệ thống, xem hướng dẫn ơ bài 10.


Bài 10: Viết ứng dụng hiển thị đồng hồ như sau:


1. Khai báo các hằng và các biến:
- Khai báo các hằng:
#define PI        3.14
#define SMALL_DOT 6
#define BIG_DOT   16


- Khai báo các biến: (là mảng lưu hình dạng của kim giây, phút, giờ)
LPPOINT m_SecondHand;
LPPOINT m_MinuteHand;
LPPOINT m_HourHand;


2. Tạo mảng lưu hình dạng cho các kim --> Thêm hàm CreateHands() vào lớp CBaiTap10View --> Viết code cho hàm CreateHands() --> Gọi hàm CreateHands() trong hàm tạo của lớp CBaiTap10View.

Để đồng hồ có kích thước bất kỳ, khi phóng to hay thu nhỏ vùng clinet kích thước các kim cũng sẽ được tính toán lại cho phù hợp. Ta chia bán kính của đồng hồ ra làm 20 phần, xem tâm của đồng hồ là tọa độ (0, 0). Ta chỉ cần vẽ 1 trang thái của kim (nằm ngang, từ trái qua phải), còn các trạng thái khác, ta sẽ tính toán (dùng phép quay của phép biến đổi Affine) để quay kim về dùng vị trí. Ví dụ ở kim giây, ta chỉ dùng 2 Point để biểu diễn: (-4, 0) và (18, 0). Như vậy kim giây sẽ có độ dài = 4/20 + 18/20 = 22/20. Tương tự cho kim phút và kim giờ, dùng 4 điểm để biểu diễn.

//Hình dạng của kim giây
m_SecondHand = new POINT[2];
m_SecondHand[0].x = -4;       m_SecondHand[0].y = 0;
m_SecondHand[1].x = 18;       m_SecondHand[1].y = 0;
//Hình dạng của kim phút
m_MinuteHand = new POINT[4];
m_MinuteHand[0].x = 0;        m_MinuteHand[0].y = -2;
m_MinuteHand[1].x = 18;       m_MinuteHand[1].y = 0;
m_MinuteHand[2].x = 0;        m_MinuteHand[2].y = 2;
m_MinuteHand[3].x = -4;       m_MinuteHand[3].y = 0;
//Hình dạng của kim giờ
m_HourHand = new POINT[4];
m_HourHand[0].x = 0;          m_HourHand[0].y = -3;
m_HourHand[1].x = 12;         m_HourHand[1].y = 0;
m_HourHand[2].x = 0;          m_HourHand[2].y = 3;
m_HourHand[3].x = -4;         m_HourHand[3].y = 0;


3. Thêm hàm void DrawCircle(CDC *pDC, int x, int y, int r): vẽ hình tròn được tô đen
int r2 = r/2;
CBrush brush;
brush.CreateSolidBrush(RGB(0,0,0));
CBrush *pOldBrush = pDC->SelectObject(&brush);
pDC->Ellipse(x - r2, y - r2, x + r2, y + r2);
pDC->SelectObject(pOldBrush);
brush.DeleteObject();


4. Thêm hàm void DrawClock(CDC *pDC, int r, int r1, int r2): Vẽ đồng hồ
Với r là bán kính của đồng hồ, r1 là bán kính của các chấm hình tròn nhỏ , r2 là các chấm hình tròn lớn trên đồng hồ.

int angle = 0;
for (int i=0; i < 60; i++, angle+=6)      
DrawCircle(pDC,r * sin(angle*PI/180), r * cos(angle*PI/180), i % 5 ? r1 : r2);


5. Thêm hàm void DrawHand(CDC *pDC, int r, int number, LPPOINT lpHandPoints, int nNumPoints): Vẽ các kim của đồng hồ.
Với r: bán kính của đồng hồ; number: kim đồng hồ chỉ vào vị trí số; lpHandPoints: mảng lưu hình dạng của kim; nNumPoints: Số lượng điểm của mảng lpHandPoints.

float  x, y;
POINT p[5];
int angle = 90 - number * 6 ;
int k = r / 20;
for (int i=0; i < nNumPoints; i++)
{           
      x = lpHandPoints[i].x * k;
      y = lpHandPoints[i].y * k;
      p[i].x = x * cos(angle * PI / 180) + y * sin(angle * PI / 180);
      p[i].y = y * cos(angle * PI / 180) - x * sin(angle * PI / 180);   
}
p[nNumPoints] = p[0];         
for (i=0; i < nNumPoints; i++)
{
      pDC->MoveTo(p[i]);
      pDC->LineTo(p[i+1]);
}


6. Thêm hàm void Draw3Hands(CDC *pDC, int r, int hour, int minute, int second): Vẽ 3 kim
Với r: bán kính đồng hồ; hour, minute, second: vị trí kim giờ, phút, giây.
DrawHand(pDC, r,  hour*5 + (minute*5/60), m_HourHand, 4); //Kim giờ
DrawHand(pDC, r, minute, m_MinuteHand, 4);      //Kim phút
DrawHand(pDC, r, second, m_SecondHand, 2);      //Kim giây
DrawCircle(pDC,0,0,10); //Vẽ chấm tròn ở giữa đồng hồ


7. Viết code cho hàm OnDraw():
    CRect rect;
   GetClientRect(&rect);                     
   pDC->SetViewportOrg(rect.right / 2, rect.bottom / 2); //Dời trục tọa độ
   int r = (rect.bottom/2) - BIG_DOT;        //Tính toán kích thước đồng hồ
   DrawClock(pDC, r, SMALL_DOT, BIG_DOT);    //Vẽ đồng hồ 
   CTime t = CTime::GetCurrentTime();        //Lấy giờ hiện tại của hệ thống
   Draw3Hands(pDC, r, t.GetHour(), t.GetMinute(), t.GetSecond()); 


8. Khởi tạo và sử dụng bộ timer (tương tự ở bài 8) --> Ánh xạ các message: WM_CREATE, WM_DESTROY, WM_TIMER.
- Viết code cho hàm OnCreate():
SetTimer(1, 1000, NULL);

- Viết code cho hàm OnDestroy():
KillTimer(1);

- Viết code cho hàm OnTimer():
Invalidate();


"Nỗi lòng" con trai !!!Welcome to my website !

Comments

Unregistered user Wednesday, September 3, 2008 8:54:37 AM

Anonymous writes: thank you very much!

Unregistered user Friday, November 26, 2010 5:10:48 PM

Anonymous writes: thanks for sharing, love you so much

Write a comment

New comments have been disabled for this post.

February 2014
S M T W T F S
January 2014March 2014
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28