• Thứ Tư, 05/05/2004 14:32 (GMT+7)

    Câu hỏi :
    Một số ứng dụng hiển thị menu dropdown với 1 hình chữ nhật màu xanh kéo dài từ trên xuống dưới. Xin chỉ cách tạo menu như vậy bằng VB6.0.

    Trả lời :

     Mặc dù Windows cho phép ứng dụng tạo và hiển thị các menu theo đặc thù riêng của mình, nhưng các môi trường lập trình trực quan như VB 6.0, VC++ không cho phép người lập trình có thể thiết kế trực quan hệ thống menu có cách hiển thị đặc thù. Do đó nếu bạn muốn hiển thị menu theo cách đặc thù (thí dụ có hình chữ nhật màu xanh kéo dài từ trên xuống dưới ở phía trái của 1 menu dropdown), bạn phải tự viết code gọi các hàm API thực hiện yêu cầu này. Về mặt viết code, Windows cho phép bạn khai báo sự hiển thị menu dropdown theo nhiều mức độ khác nhau:

    Hiển thị từng "menu item" theo chế độ text hay bitmap hay tổng hợp cả 2 chế độ.

    Định nghĩa icon đồ họa để hiển thị kèm theo "menu item" ở chế độ chọn/không chọn item đó.

    Giao phó cho ứng dụng tự hiển thị lấy từng menuItem theo đặc thù riêng của mình.

    Trong 3 mức độ hiển thị menuItem trên, mức độ giao phó cho ứng dụng hiển thị là tổng quát nhất, người ta gọi chế độ này là "owner-drawn menu item". Để dùng chế độ hiển thị menu này, ta sẽ thực hiện 3 bước sau:

    1. Tạo mới từng menu item với chế độ hiển thị "owner-drawn". Nếu menu item đã được thiết kế trực quan rồi thì ta có thể gọi hàm SetMenuItemInfo() để thiết lập lại chế độ hiển thị "owner-drawn" cho nó.

    2. Viết code phục vụ sự kiện WM_MEASUREITEM để thiết lập kích thước cho từng "menu item".

    3. Viết code phục vụ sự kiện WM_DRAWITEM để tự hiển thị menu item theo yêu cầu riêng.

    Việc viết code quản lý và hiển thị các menu Item sẽ phải gọi nhiều hàm API của Windows và xử lý nhiều cấu trúc dữ liệu phức tạp của Windows nên dùng ngôn ngữ VC++ sẽ thích hợp hơn và đơn giản hơn nhiều so với dùng VB. Để giúp bạn thấy cụ thể việc tạo 1 menu dropdown đặc thù, chúng tôi sẽ lấy thí dụ về 1 ứng dụng VC++ dùng giao diện SDI (Single Document Interface) có 1 thanh menubar, trong đó menu đầu tiên của nó là menu File có sự hiển thị đặc thù như sau :

    Qui trình điển hình để tạo ứng dụng VC++ có menu File như trên:

    1. Chạy trình VC++ 6.0.

    2. Chọn menu File.New để hiển thị cửa sổ New, chọn tab "Project", chọn mục "MFC AppWizard (exe)", chọn vị trí thư mục trên đĩa để chứa các thành phần của Project ở comboBox "Location", nhập tên project ở ComboBox "Project name" (thí dụ tên là MyBitmapMenu), chọn button OK để tiếp tục.

    3. Trong cửa sổ "MFC AppWizard Step 1", đánh dấu chọn checkbox "Single Document", chọn button "Finish" để hoàn thành việc tạo Project với các thông số mặc định còn lại.

    4. Chọn tab "Resource View" trong cửa sổ Project (thường nằm phía trái màn hình), ấn vào dấu cộng của phần tử gốc "MyBitmapMenu resources" để hiển thị chi tiết các

    tài nguyên giao diện của ứng dụng, ấn vào dấu cộng của mục "Menu" để hiển thị chi tiết các menu của ứng dụng, lúc này chỉ có 1 menu mặc định với tên là IDR_MAINFRAME, đây là menu chính do VC++ tạo tự động cho ứng dụng. Ấn kép chuột vào mục IDR_MAINFRAME để hiển thị trực quan menu hầu hiệu chỉnh lại nó. Các thành phần ban đầu của menubar như sau (ta chỉ quan tâm đến menu File):

    5. Chọn mục "Save As" rồi ấn vào icon "Cut" trên Toolbar để xóa nó, tiếp tục xóa các option còn lại cho đến option Exit thì dừng lại. Lúc này menu File chỉ còn 4 phần tử như hình bên.

    6. Dùng trình "Screen Saver" nào đó, copy vùng đồ họa hình chữ nhật miêu tả các option của menu File, chạy ứng dụng xử lý đồ họa mạnh nào đó (thí dụ CorelDraw), dán hình bitmap vừa copy vào cửa sổ làm việc của nó rồi hiệu chỉnh lại theo đặc thù riêng của bạn, thí dụ thêm thanh hình chữ nhật xanh lợt dần về phía dưới như hình trên. Sau khi đã có hình ảnh tổng thể về menu cần hiển thị, bạn chọn từng vùng một, mỗi vùng bitmap miêu tả 1 menuItem, cắt nó và dán lên 1 cửa sổ khác rồi cất lên file dạng *.bmp. Cuối cùng ta được 4 file b0.bmp, b1.bmp, b2.bmp, b3.bmp, mỗi file chứa ảnh bitmap của 1 menuItem. Lưu ý là nên chỉnh kích thước giống nhau cho 4 file ảnh miêu tả 4 menuItem. Tạo thêm 1 file ảnh khác (thí dụ b4.bmp) chứa ảnh bitmap có cùng kích thước với ảnh bitmap của menuItem nhưng chỉ toàn pixel đen, ảnh bitmap này sẽ được dùng làm "mask" cho hoạt động đảo ngược màu menuItem được chọn. Copy 5 file ảnh vừa tạo được vào thư mục của Project ứng dụng rồi quay lại cửa sổ VC++.

    7. Chọn menu View.ClassWizard để hiển thị cửa sổ ClassWizard, chọn tab "Message Map", chọn CMainFrame trong listbox "Class name", chọn mục CMainFrame trong listbox "Object IDs", duyệt và chọn mục "WindowProc" trong listbox "Messages", ấn button "Add function" để tạo hàm xử lý các sự kiện của cửa sổ MainFrame, ấn button "Edit Code" để mở cửa sổ soạn code cho hàm WindowProc() vừa tạo. Viết đoạn code sau cho hàm WindowProc():

    // CMainFrame message handlers

    LRESULT CMainFrame::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {

        // TODO: Add your specialized code here and/or call the base class

        switch (message) {

            case WM_CREATE:

                if (!MyOnCreate(m_hWnd))

                    return -1;

                break;

            case WM_MEASUREITEM:

                MyOnMeasureItem(m_hWnd, (LPMEASUREITEMSTRUCT) lParam);

                return TRUE;

            case WM_DRAWITEM:

                MyOnDrawItem(m_hWnd, (LPDRAWITEMSTRUCT) lParam);

                return TRUE;

                }

                return CFrameWnd::WindowProc(message, wParam, lParam);

    }

    8. Viết tiếp các hàm được gọi từ hàm WindowProc như sau (nên để đoạn code sau nằm trước hàm WindowProc):

    // khai báo các hằng và biến cần dùng

    #define IDB_ITEMMASK 4

    HBITMAP hbmList[5];

    HINSTANCE hInst;

    // hàm thiết lập lại thuộc tính onwer-drawn cho các menuItem của menu File

    BOOL WINAPI MyOnCreate(HWND hwnd) {

    int i;

    HMENU hMenuBar = GetMenu(hwnd);

    HMENU hFileMenu = GetSubMenu(hMenuBar,0);

    MENUITEMINFO mii;

       hInst = (HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE);

       // Load các bitmap cho các menuItem

       hbmList[0] = (HBITMAP)LoadImage(hInst,"b0.bmp",IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

       hbmList[1] = (HBITMAP)LoadImage(hInst,"b1.bmp",IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

       hbmList[2] = (HBITMAP)LoadImage(hInst,"b2.bmp",IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

       hbmList[3] = (HBITMAP)LoadImage(hInst,"b3.bmp",IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

       hbmList[4] = (HBITMAP)LoadImage(hInst,"b4.bmp",IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

       for (i = 0 ; i <4; i++) {

           // Chuẩn bị các thông số cho record

           mii.cbSize = sizeof(MENUITEMINFO);

           mii.fType = MFT_OWNERDRAW;

           mii.fMask = MIIM_TYPE | MIIM_DATA;

           mii.dwItemData = (DWORD) i;

           // thiết lập lại thông số cho menuItem

           SetMenuItemInfo(hFileMenu, i, TRUE, &mii);

       }

        return TRUE;

    }

    // Hàm vẽ menu item tương ứng với trạng thái selected/noselected

    BOOL PASCAL FDrawMaskHBitmap(HDC hDC, LPPOINT lppt, HBITMAP hBitPaint, HBITMAP hBitMask) {

    HDC     hMemDC1;

    HDC     hMemDC2;

    HBITMAP hBitTemp;

    BITMAP  bm;

    WORD    cx, cy; //biến tạm làm code dễ đọc

        hMemDC1=CreateCompatibleDC(hDC);

        hMemDC2=CreateCompatibleDC(hDC);

        SetMapMode(hMemDC1, GetMapMode(hDC));

        SetMapMode(hMemDC2, GetMapMode(hDC));

        GetObject(hBitPaint, sizeof(BITMAP), (LPSTR)&bm);

        cx=bm.bmWidth;

        cy=bm.bmHeight;

        hBitTemp=CreateCompatibleBitmap(hDC, cx, cy);

        // Vẽ mask

        SelectObject(hMemDC1, hBitMask);

        SelectObject(hMemDC2, hBitTemp);

        BitBlt(hMemDC2, 0, 0, cx, cy, hDC, lppt->x, lppt->y, SRCCOPY);

        BitBlt(hMemDC2, 0, 0, cx, cy, hMemDC1, 0, 0, SRCAND);

        SelectObject(hMemDC1, hBitPaint);

        BitBlt(hMemDC2, 0, 0, cx, cy, hMemDC1, 0, 0, SRCPAINT);

        BitBlt(hDC, lppt->x, lppt->y, cx, cy, hMemDC2, 0, 0, SRCCOPY);

        DeleteDC(hMemDC1);

        DeleteDC(hMemDC2);

        DeleteObject(hBitTemp);

        return TRUE;

    }

    // hàm phục vụ sự kiện WM_MEASUREITEM

    // chức năng : thiết lập kích thước cho menuItem

    void WINAPI MyOnMeasureItem(HWND hwnd, LPMEASUREITEMSTRUCT lpItem) {

    BITMAP      bm;

    WORD        wIDBitmap;

    WORD        cxCheck;

        wIDBitmap=LOWORD(lpItem->itemData);

        cxCheck=LOWORD(GetMenuCheckMarkDimensions());

        GetObject(hbmList[wIDBitmap], sizeof(BITMAP), (LPVOID)&bm);

        lpItem->itemWidth=bm.bmWidth-cxCheck;

        lpItem->itemHeight=bm.bmHeight;

    }

    // hàm phục vụ sự kiện WM_DRAWITEM

    // chức năng : vẽ menuItem

    void WINAPI MyOnDrawItem(HWND hwnd, LPDRAWITEMSTRUCT lpItem) {

    BITMAP      bm;

    WORD        wIDBitmap;

    POINT       pt;

        if (ODA_DRAWENTIRE & lpItem->itemAction){

            wIDBitmap=LOWORD(lpItem->itemData);

            GetObject(hbmList[wIDBitmap], sizeof(BITMAP), (LPVOID)&bm);

            pt.x=0;

            pt.y=lpItem->rcItem.top;

            // Vẽ bitmap miêu tả menuItem

            FDrawMaskHBitmap(lpItem->hDC, &pt, hbmList[wIDBitmap], hbmList[IDB_ITEMMASK]);

            //nếu menuItem đang được chọn, đảo bitmap của nó.

            if (ODS_SELECTED & lpItem->itemState)

                InvertRect(lpItem->hDC, &(lpItem->rcItem));

        } else {

            // Selection state is being changed, so just invert.

            if (ODA_SELECT & lpItem->itemAction)

                InvertRect(lpItem->hDC, &(lpItem->rcItem));

        }

    }

    9. Chọn menu Build.Execute MyBitmapMenu.exe để chạy thử ứng dụng, chọn menu File bạn sẽ thấy kết quả như mong muốn (như hình dưới đây):

    Bạn có thể liên hệ tòa soạn PCWorld để copy toàn bộ project VC++ của ứng dụng demo này (tên là VCMyBitmapMenu).

     

    Chuyên mục: Lập trình