Thứ Tư, 29 tháng 1, 2014

[WINAPI-C++]bài 3: Vòng lặp tin nhắn - What is a Message?

Giờ ngồi cứ nghĩ nghĩ, mình đến tuổi này rồi(24 tuổi) mà vẫn bị mấy đứa em chê.. Ừ thì đúng là kém nên phải chấp nhận thôi.. Suy cho cùng thì làm gì cũng phải tự tin và đi đến cùng và không ẩu, không làm dở chừng, thì mới thành công được.
Lằng nhằng rồi, tut này sẽ trình bày cho các bạn hiểu về vòng lặp tin nhắn. Vậy vòng lặp tin nhắn là gì?
Trong C/C++ thì một thông báo trả về 1 giá trị nguyên. He he, ở đời khi bạn muốn báo cho ai biết điều gì vd như báo bạn bè bạn có bạn gái hay báo bạn phải đi đổ vỏ thì bạn hô hào, truyền miệng. Nhưng máy tính thì nó chỉ hiểu số nên chung quy lại báo cáo thực chất là trả về số. Nhưng đọc 1 số và xem nó là báo cáo gì thì rất khó.
Và trong tập lệnh API thì người ta định nghĩa sẵn cho dễ lập trình, kiểu như:
#define WM_INITDIALOG                   0x0110
#define WM_COMMAND                      0x0111
#define WM_LBUTTONDOWN                  0x0201

Dạng: #define <tên thông báo> <giá trị tương ứng>
Để gửi tin nhắn chúng ta dùng  2 lệnh PostMessage() or SendMessage()
PostMessage() : đưa tin nhắn vào hang đợi và thực hiện khi đến lượt
SendMessage(): đưa tin nhắn vào cửa sổ và trả về cho đến khi thực hiện
ví dụ: Khi bạn muốn đóng cửa sổ có thể dùng nút close trên cửa sổ, hoặc gửi thông điệp PostMessage(hwnd, WM_CLOSE, 0, 0)
Thành phần tin nhắn gồm: wParam and lParam
wParam:là biến nguyên 16bit
lParam:là biến nguyên 32bit
Trong 1 số tin nhắn thì có thể dùng cả 2 giá trị này, hoặc không dùng để 0 như lệnh tắt cửa sổ ở trên.
wParam bao gồm 2 giá trị: HIWORD để thông báo tin nhắn(nếu có) và LOWORD để điều khiển hay chứa id tin nhắn.
lParam là cửa sổ tin nhắn, có thể nhận NULL nếu như không phải là tin nhắn điều khiển.
(HIWORD và LOWORD là 2 macro chứa biến nguyên high word 32 bit 0xFFFF0000) và low word tương ứng(0x0000FFFF))
Cấu trúc vòng lặp tin nhắn:
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
}

Hàm GetMessage để đợi tin nhắn của bạn, bất cứ thao tác chuột, bàn phím... Nó trả về giá trị không nếu gặp tin nhắn WM_QUIT và giá trị <0 (-1 ?)khi gặp lỗi nào đó. Bình thường thì giá trị  >0
Hàm TranslateMessage để dịch các dạng tin nhắn như phím ảo..vvv.. (?)
Hàm DispatchMessage gửi tin nhắn về cửa sổ để xử lý..









[WINAPI-C++]bài 2: Thêm một xử lý cho cửa sổ - Handling Messages

Oki, hôm qua mình viết tut xong(29/1/2014) thì có gửi lên cho anh em chém gió. Nhận được nhiều phê bình mình rất vui, nghĩa là anh em đã quan tâm tới các tut của mình. Hôm nay mình lại hào hung viết tut tiếp, mặc dù âm dương là 29 tết rồi, chuẩn bị đón năm mới ^_^
Bài này mình thêm 1 xử lý cho cửa sổ, khi kick vào cửa sổ thì nó hiện thông báo ra ứng dụng đang chạy bao gồm đường dẫn của ứng dung.
 Cái này gọi là Handling Messages: Có nhiều Handling Messages lắm. Đó là các sự kiện của người dung và sau đó là code để xử lý sự kiện đó. Sự kiện kick chuột là  WM_LBUTTONDOWN . Còn khi các bạn muốn xử lý cho sự kiện này thì thêm vào đoạn LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
Code thêm ở đoạn này:

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch(msg)
   {
      case WM_LBUTTONDOWN:    // <-
                              // <-     Chúng ta sẽ add code ở đây      break;                  // <-
      case WM_CLOSE:
         DestroyWindow(hwnd);
      break;
      case WM_DESTROY:
         PostQuitMessage(0);
      break;
      default:
         return DefWindowProc(hwnd, msg, wParam, lParam);
   }
   return 0;
}

Để thêm tập lệnh xử lý cho sự kiện này, các bạn phải đặt code ở cặp lệnh :
// BEGIN NEW CODE và // END NEW CODE thì chương trình mới chạy được.
Tập lệnh như sau:
 char szFileName[MAX_PATH]: Khai báo mảng cha, có độ dài là số ký tự tối đa của đường dẫn. Tiếng anh: Path : đường dẫn.
HINSTANCE hInstance = GetModuleHandle(NULL):

            GetModuleFileName(hInstance, szFileName, MAX_PATH);
            MessageBox(hwnd, szFileName, "Chuong trinh nay la:", MB_OK | MB_ICONINFORMATION);


Rồi, đến đây mình sẽ giải thích cho các bạn biết những gì mà chúng ta đã làm ở tut 1. Hi nếu các bạn đủ kiên nhẫn thì đọc tiếp nhé:
(COPY và cho nó chạy. Nếu không chắc copy chuẩn thì các bạn lấy code ở đây nhé)

#include <windows.h> //thu vien cho lap trinh window
const char g_szClassName[] = "myWindowClass";
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg) //THong diep quan ly khi ban bam chuot, tat chuong trinh,....
    {
        case WM_LBUTTONDOWN:  //Khi ban kick chuot
        {
            char szFileName[MAX_PATH];
            HINSTANCE hInstance = GetModuleHandle(NULL);

            GetModuleFileName(hInstance, szFileName, MAX_PATH);
            MessageBox(hwnd, szFileName, "Chuong trinh nay la:", MB_OK | MB_ICONINFORMATION);
        }
        break;
        case WM_CLOSE://Khi ban tat man hinh
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY://Khi man hinh bi tat
            PostQuitMessage(0);
        break;
        default://Mac dinh
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;//Lop luu tru ve thong tin icon, mau, ten, menu cua chuong trinh
    HWND hwnd;//Handle cho window
    MSG Msg;//Thong diep cho window

    wc.cbSize        = sizeof(WNDCLASSEX);//Dat kich co cho lop, tuc la cap bo nho cho no
    wc.style         = 0;//Kieu cua so mac dinh
    wc.lpfnWndProc   = WndProc;//Thu tuc cua cua so, kiem tra cac thong diep tac dong den cua so
    wc.cbClsExtra    = 0;//Phan mo rong cua cua so, de mac dinh 0
    wc.cbWndExtra    = 0;//nhu tren
    wc.hInstance     = hInstance;//Instance tam thoi va co the co nhieu instance
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);//icon chuong trinh va logo
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);//Lay cursor mac dinh
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))//Neu dang ky that bai
    {
        MessageBox(NULL, "Dang ky cua so that bai!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "Tieu de cua so",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Tao cua so that bai!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}


Vậy là xong tut 2. Bạn muốn yên tâm thì copy code ở đây nhé. Còn nếu các bạn vẫn bị mắc lỗi khi chạy chương trình thì cho mình xem lỗi, mình sẽ sửa lại cho.
thank all!







Thứ Ba, 28 tháng 1, 2014

[WINAPI-C++]bài 1: Giới thiệu về lớp

Rảnh rỗi mình ngồi viết tut, mong các bạn ủng hộ nhé.
Chúng ta sẽ bắt đầu "động chạm" tới WINAPI, mà dân lập trình cứ hay gọi nó là lập trình WINDOW, khá là phức tạp, khó nhớ và nhanh nản.
Hồi mới học C mình cũng nghĩ là khi học xong sẽ làm được các ứng dụng này nọ, nhưng chỉ học console thì chắc chả làm được cái gì ngoài code mấy bài giải phương trình bậc 2, vì trình mình kém không đủ kiên nhẫn học tiếp cũng như chả có ai định hướng cho cả. Sau đó mình chia tay C/C++ một thời gian rồi chuyển lung tung qua C#, android hay java. Thật sự bây giờ nhìn lại mình thấy thế này: C# là ngôn ngữ dễ học, cứ kéo thả ầm ầm, viết 1 cái ứng dụng nhanh lắm vì nó hỗ trợ nhiều, nhưng mà vì dễ học nên số lượng coder về cái này cũng khá là nhiều, nhan nhản :D
java thì đồ chùa, nghĩa là không phải lo vấn đề bản quyền lắm khi bạn viết 1 ứng dụng đem đi bán, nó là mã mở mà. Nó học cũng khá hay, hiện tại làm game mạnh mẽ nhất có lẽ là thằng java này. Mình ít xài java vì mặc dù thích chơi game, nhưng ghét code game.
Mình chán mấy thứ đó, mà có lẽ do tính mình nhanh nản, học gì cũng học được 1 tý chứ chả chuyên sâu gì, vậy nên nếu các bạn đang có tâm lý như mình thì nên thay đổi đi. Phải kiên định các bạn ạ :)
Mình viết tút về lập trình WINAPI, có mục đích là muốn quay lại với em C/C++ khó xơi này. Thứ hai muốn học từ đầu và không muốn lan man nhiều ngôn ngữ nữa, mình cũng thích mình giỏi về cái này. Bạn nào đọc đến đây thấy nản thì thôi nhé.
hihi, lan man nhiều quá. Ta bắt đầu với chương trình helloWorld luôn nhé.
Đầu tiên mình cần các bạn cài Dev-C cho mình, vì mình cũng đang dung cái này để viết tut. Tất nhiên các bạn có thể dung Cfree(thực ra là không free), hay visual C,. nhưng thôi cứ theo mình nhé :D
Các bạn cài đặt Dev-C cho mình nhé. Nếu bạn nào cài Win8 thì cài đúng bản giành cho Win8 vì bản cài đơn thuần bị lỗi không chạy được thư viện g++ đâu nhé. Link thì nhấn đây để tải nhé :v

Oki rồi, bây giờ mình có 1 đoạn code như sau rồi anh em cùng phân tích xem nó viết cái gì nhé:

#include <windows.h>
const char g_szClassName[] = "myWindowClass";
// Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    //Step 1: Registering the Window Class
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // Step 2: Creating the Window
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Step 3: The Message Loop
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

Nhìn đoạn code màu xanh trên hơi nản đúng không anh em? Mình cũng vậy, đúng là nó lạ hoắc và lại dài loằng ngoằng. Nói that mình đã bỏ cuộc khi nhìn thấy nó trong 1 thời gian. Bây giờ anh em nào còn kiên trì thì đọc nốt nhé. Mình sẽ phân tích từng đoạn 1. Anh em copy đoạn màu xanh rồi cho vào dev-C, nhấn F9 cho nó chạy được file exe ở cùng mục code luôn.
Còn bây giờ thì đọc phân tích này:
#include <windows.h>
Đoạn khai báo thư viện windows.h để anh em có thể dung những tiện ích mà thư viện này mang lại. Tương tự như khai báo conio thì dung getch, stdio thì dung printf đó anh em!

const char g_szClassName[] = "myWindowClass";
Đoạn này là khai báo 1 kiểu hàm char - kiểu mảng ký tự và chưa biết có bao nhiêu ký tự nên người ta đặt tên mảng là szClassName có [], sau đó gán xâu  "myWindowClass" cho mảng này luôn.

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}


Toàn bộ thủ tục này hơi loằng nhoằng nhưng anh em bình tĩnh nào. Thủ tục có tên là CALLBACK do windows định nghĩa sẵn cấu trúc rồi, phía trong có lệnh switch để xử lý từng sự kiện. Thủ tục này chạy được thì cần những thông số sau:HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam

 nhưng anh em chưa vội quan tâm vì bài mở đầu nên mình cũng chả muốn dọa ai cả. cứ biết nó cần là được. Các bài sau code nhiều một tý là anh em sẽ hiểu ra thôi.
 switch(msg)
    {
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }

cấu trúc switch có lẽ anh em biết cả rồi. Nó là lựa chọn với biến msg - biến thông điệp khi người dung làm gì đó với cái cửa sổ mà anh em vừa tạo bang code. Ở đây case WM_CLOSE  nghĩa là khi anh em chọn vào nút đóng cửa sổ, nó sẽ làm DestroyWindow(hwnd) nghĩa là tắt cái windows luôn và đóng chương trình. với WM_DESTROY thì sẽ   PostQuitMessage(0);   - thông báo thoát chương trình. Mặc định ngoài hai sự kiện trên mà không xảy ra thì nó sẽ liên tục vẽ lại cửa sổ  DefWindowProc(hwnd, msg, wParam, lParam)

Bài tiếp mình sẽ giới thiệu 1 số thông điệp khác mà anh em có thể dùng.

 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    //Step 1: Registering the Window Class
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // Step 2: Creating the Window
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Step 3: The Message Loop
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

Anh em lại ngứa rồi đúng không, nhưng bình tĩnh ta sẽ gỡ từng cái một.
//Step 1: Registering the Window Class
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

Đoạn này là Window đang đăng ký với hệ điều hành các thông số để khởi tạo cửa sổ. Như là kích thước cửa sổ, kiểu cửa sổ, icon ứng dụng, con trỏ, kiểu menu, kiểu hình nền....
Thiếu bất cứ cái nào cũng không chạy được.
    if(!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

Thằng if trên nó tạo ra 1 cái hộp thông báo có dòng tiêu đề là error(lỗi), thông báo là đăng ký window bị lỗi!
  hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "The title of my window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
        NULL, NULL, hInstance, NULL);

    if(hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

Khi anh em đăng ký không bị lỗi thì bắt đầu "vẽ" cái cửa sổ ra, vẽ bang code bao gồm kích thước, kiểu cửa sổ,... Và nếu như vẽ không được thì nhã lỗi cho người lập trình xem.
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

 Hiển thị cửa sổ lên và cập nhật liên tục cửa sổ.
    // Step 3: The Message Loop
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return

Vòng lặp tin nhắn:
Hàm GetMessage là để lấy thông tin từ người dung như bàn phím, click chuột, rê chuột....và nếu như nó trả về gt>0 thì bắt đầu thực hiện 2 câu lệnh:
dịch tin nhắn để hiểu nó là tin nhắn gì. Và thứ 2 là gửi tin nhắn ra cửa sổ thông điệp đã được gửi đến.
Bài 1 kết thúc tại đây. Nếu anh em thắc mắc bài 1 có thể xem bài 2 tiếp theo của mình. Anh em nào muốn hỏi cũng có thể inbux tại: quynhlxbkhn@gmail.com
(28/1/2014)