View Single Post
Old 21-06-2006, 03:37 PM   #7
Hồ sơ
LGi
Banned
 
LGi's Avatar
 
Tham gia ngày: May 2006
Số bài viết: 34
Tiền: 25
Thanks: 0
Thanked 12 Times in 10 Posts
LGi is an unknown quantity at this point
Default

BÀI 2: THU NHỎ ỨNG DỤNG XUỐNG KHAY HỆ THỐNG SYSTEM TRAY</span>
<a href=\'http://www.thuvientinhoc.com/forums/lofiversion/index.php/t1091.html\' target=\'_blank\'><span style=\'color:red\'>My Webpage</a>
Chúng ta trở lại với ứng dụng dạng hộp thoại dialog đơn giản ở bài 1. Bài 2 này chúng ta sẽ bắt đầu đề cập 1 số kĩ thuật phức tạp hơn của việc lập trình trên môi trường Windows, đó là tạo 1 ứng dụng có thể thu nhỏ dạng icon ở system tray và tạo menu popup đáp ứng khi người dùng nhấn phải chuột lên icon ở system tray; đồng thời cũng đề cập đến kĩ năng viết code kết hợp với MFC của VC++ 6.0.
1. Thu nhỏ ứng dụng xuống system tray
Ở ví dụ bài 1 chúng ta đã có 1 nút bấm "Hiện Messagebox", ta sẽ thêm 1 nút bấm khác để phục vụ khi người dùng nhấn vào nó để thu nhỏ ứng dụng xuống system tray. Bạn hãy làm tương tự như ở bài 1, ở khung WorkSpace bên trái, chọn tab Resource, sau đó chọn phần Dialog và nhấp đúp vào ID có tên IDD_SAMPLE1_DIALOG để vào chế độ soạn resource cho dialog ứng dụng của chúng ta. Sau đó bạn chọn hình nút bấm trên thanh công cụ bên phải và nhấp chuột vào giữa hình dialog, bạn nhấp chuột phải lên button vừa tạo và chọn Properties, kế tiếp chọn tab General, gõ vào ô Caption nội dung sau: "Minimize on tray". Lưu ý trước khi tạo button này, bạn dùng chuột kéo button trước đó (Hien Messagebox) sang trái để chúng ko đè lên nhau.
Kết quả bạn có thể thấy như sau:
<img src=\'http://www.whatvn.com/images/vc/sample1/31.gif\' border=\'0\' alt=\'user posted image\' />
Tương tự bài 1, ta tạo event handler cho sự kiện nhấp chuột lên button, bạn nhấp đúp chuột lên button vừa tạo để mở hộp thoại tạo mới hàm xử lý, gõ vào tên hàm xử lý sự kiện là OnMinimize và nhấn OK. Như vậy một hàm mới tên là OnMinimize() sẽ được tạo trong code của bạn nhưng chưa có lệnh xử lý, chúng ta sẽ thêm mã lệnh sau.
Code:
Void CSample1Dlg::OnMinimize() 
{
 //TODO:... 
}
Chúng ta sẽ bắt đầu viết mã lệnh phục vụ khi nhấn vào button thì thực hiện thu nhỏ ứng dụng xuống system tray.

Để phục vụ cho việc thu nhỏ, ta dùng hàm Shell API có tên Shell_NotifyIcon. API viết tắt của Application Programming Interface, hay gọi là Giao diện lập trình ứng dụng trên Windows. Nói một cách đơn giản API là tập hợp các hàm, các cấu trúc dữ liệu, thư viện mà hệ thống hỗ trợ cho các lập trình viên tạo nên các ứng dụng. Các API thường được đặt trong các tập tin thư viện như .dll nằm trong thư mục cài đặt Windows.
Hàm Shell_NotifyIcon làm nhiệm vụ gửi thông điệp yêu cầu (message) có chức năng cụ thể nào đó tới system tray. Ví dụ yêu cầu tạo tray icon, xóa tray icon... Khai báo hàm như sau:
Code:
BOOL Shell_NotifyIcon(
  DWORD dwMessage, 
  PNOTIFYICONDATA lpdata
);
Lưu ý:[/b] Bạn cần 1 tài liệu tham khảo lập trình VC++ hoặc thư viện MSDN để có thể tham khảo các API, cấu trúc,cách sử dụng của chúng...khi cần vì ko thể nào nhớ hết được.

Trong đó, dwMessage chỉ định kiểu yêu cầu. Ở đây ta quan tâm đến 2 loại message là:
- NIM_ADD : Tạo 1 icon ở system tray hay còn gọi là khu vực trạng thái taskbar
- NIM_DELETE : Xóa 1 icon khỏi system tray

lpdata là 1 con trỏ đến 1 cấu trúc dữ liệu chứa các thông tin cần thiết cho icon hoạt động ở system tray, như hình icon, mã điều khiển, thông điệp hệ thống sẽ nhận được khi user kích hoạt vào icon... Ta xem cấu trúc NOTIFYICONDATA và chú thích sau dấu //
Code:
typedef struct _NOTIFYICONDATA { 
  DWORD cbSize; // kích thước của cấu trúc
  HWND hWnd; // window handle của cửa sổ ứng dụng
  UINT uID; // mã điều khiển của trayicon 
  UINT uFlags; // mặt nạ cờ chứa các option đặc biệt
  UINT uCallbackMessage; // thông điệp ta sẽ tự định nghĩa cho trayicon
  HICON hIcon; // hình biểu tượng cho trayicon
  #if (_WIN32_IE < 0x0500)
    TCHAR szTip[64]; // tooltip trợ giúp nhanh khi di chuyển chuột lên trayicon
  #else
    TCHAR szTip[128];
  #endif
  #if (_WIN32_IE >= 0x0500)
    DWORD dwState; 
    DWORD dwStateMask; 
    TCHAR szInfo[256]; 
    union {
      UINT uTimeout; 
      UINT uVersion; 
    } DUMMYUNIONNAME;
    TCHAR szInfoTitle[64]; 
    DWORD dwInfoFlags; 
  #endif
  #if (_WIN32_IE >= 0x600)
    GUID guidItem;
  #endif
} NOTIFYICONDATA, *PNOTIFYICONDATA;
Các bạn chú ý 1 số chú thích trong cấu trúc NOTIFYICONDATA để hiểu sơ công dụng vì ta sẽ dùng đến chúng.

Như vậy yêu cầu để thực hiện hàm Shell_NotifyIcon là ta điền đầy đủ các tham số và gọi hàm, ta sẽ đi từng tham số một. Với tham số đầu tiên, vì ta muốn tạo ứng dụng dạng trayicon, ta sẽ dùng NIM_ADD. Cấu trúc ở tham số thứ hai phức tạp hơn, chúng ta cần chuẩn bị 1 số thông tin cho cấu trúc này.
Trước tiên ta cần vẽ 1 icon dùng để hiện ở system tray. Đây là dấu hiệu nhận biết ứng dụng đang ở system tray. Bạn trở lại Resource tab, chọn vào phần Icon, nhấn chuột phải và chọn Insert Icon, Resource Editor sẽ tự tạo cho bạn 1 icon có ID là IDI_ICON1, bạn nhấn chuột phải lên ID chọn Properties để thay đổi ID thành IDI_TRAY1. Sau đó ở phần vẽ icon bên phải, bạn dùng thanh công cụ vẽ 3 hình tròn như hình sau:
<img src=\'http://www.whatvn.com/images/vc/sample1/32.gif\' border=\'0\' alt=\'user posted image\' />
Sau đó bạn nhấn nút Save để lưu lại icon mình vừa tạo.
Đến đây ta tiếp tục với 1 vấn đề khác, đó là khi đã thu nhỏ dạng trayicon, icon này cần đáp ứng khi có sự kiện nhấp chuột lên nó. Tham số thứ 2 lpdata của hàm Shell_NotifyIcon yêu cầu truyền vào 1 cấu trúc NOTIFYICONDATA. Cấu trúc này cho phép ta định nghĩa thêm 1 windows message kiểu người dùng đặt vào 1 thuộc tính tên là uCallbackMessage. Như vậy ta sẽ định nghĩa 1 message như sau:
Code:
#define WM_NOTIFYOFINCONONTRAY (WM_USER+1) //WM_USER
là 1 hằng số được định nghĩa sẵn

và đặt dòng #define này ở đầu file .cpp
Ta cũng cần chỉ định window handle của cửa sổ cần thu nhỏ, ta sẽ dùng biến m_hWnd là thuộc tính có sẵn của tất cả các ứng dụng dạng window dùng MFC.
Kết quả ta sẽ khai báo cấu trúc như sau:
Code:
NOTIFYICONDATA tnid; 
  HICON hicon; //handle đến icon của ứng dụng trayicon
  hicon = AfxGetApp()->LoadIcon(IDI_TRAY1); // ta dùng hàm LoadIcon để load icon có sẵn trong resource mà ta tạo lúc trước

  tnid.cbSize = sizeof(NOTIFYICONDATA); //khai báo trứơc kích thước buffer
  tnid.hWnd = m_hWnd; //window handle
  tnid.uID = IDI_TRAY1; //ta tận dụng luôn id của trùng với icon đã tạo ở trên
  tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; 
  tnid.uCallbackMessage = WM_NOTIFYOFINCONONTRAY; // chỉ định window message người dùng
  tnid.hIcon = hicon; //handle tray icon
  _tcscpy(tnid.szTip, _T("Right click to show menu")); // tạo 1 tooltip
Lưu ý thuộc tính uFlags ở trên gồm 3 giá trị kết hợp OR với nhau:
- NIF_MESSAGE: cho phép dùng window message với thuộc tính uCallbackMessage
- NIF_ICON: cho phép chỉ icon vào thuộc tính hIcon
- NIF_TIP: cho phép dùng tooltip với thuộc tính szTip. Ta dùng hàm _tcscpy để copy chuỗi vào biến szTip.
Để biết thêm chi tiết các thuộc tính trên, bạn tham khảo MSDN.

Như vậy, sau khi thiết lập các thông tin cần thiết cho cấu trúc NOTIFYICONDATA, ta chỉ việc gọi hàm Shell_NotifyIcon với tham số NIM_ADD để thu nhỏ xuống systray:
Code:
  Shell_NotifyIcon(NIM_ADD, &tnid);
Lưu ý tham số thứ 2 yêu cầu 1 con trỏ, tức là cần truyền vào địa chỉ của cấu trúc NOTIFYICONDATA nên ta cần để biến CODE
tnid
với dấu & đằng trước.
Khi ứng dụng đã xuất hiện ở system tray, ta ko cần hiện dạng dialog trên màn hình, do đó ta gọi hàm ShowWindow để ẩn ứng dụng (nếu ko ẩn thì ta sẽ có cả ứng dụng dialog hiện hành và trayicon).
Code:
 ShowWindow(SW_HIDE);
Bạn có thể làm tương tự khi muốn xóa icon khỏi system tray với tham số NIM_DELETE truyền vào hàm Shell_NotifyIcon.

Hãy xem hàm đầy đủ như sau kết hợp cả hai tính năng thêm và xóa icon ở system tray, với tham số truyền vào kiểu BOOLEAN.
Code:
//Hàm thu nhỏ/xóa TrayIcon
//If this code works, it's written by W_Hat Zorro. If not, I don't know who wrote it.
//Nếu bMinimize=TRUE: thu nhỏ
//  bMinimize=FALSE: xóa trayicon
BOOL CSample1Dlg::MinimizeOnTray(BOOL bMinimize)
{
  BOOL res; 
  NOTIFYICONDATA tnid; 
  HICON hicon;

//Xóa trayicon
  if(!bMinimize)
  {
tnid.cbSize = sizeof(NOTIFYICONDATA); 
tnid.hWnd = m_hWnd; 
tnid.uID = IDI_TRAY1;    
res = Shell_NotifyIcon(NIM_DELETE, &tnid); 

ShowWindow(SW_SHOW);

return TRUE;
  }
// thu nhỏ ứng dụng xuống system tray
  hicon = AfxGetApp()->LoadIcon(IDI_TRAY1);
  if(hicon==NULL)
  {
 return FALSE;
  }

  tnid.cbSize = sizeof(NOTIFYICONDATA); 
  tnid.hWnd = m_hWnd; 
  tnid.uID = IDI_TRAY1; 
  tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; 
  tnid.uCallbackMessage = WM_NOTIFYOFINCONONTRAY; 
  tnid.hIcon = hicon; 
  _tcscpy(tnid.szTip, _T("Right click to show menu")); 

  res = Shell_NotifyIcon(NIM_ADD, &tnid); 

  if(hicon!=NULL) 
  {
    DestroyIcon(hicon);
  }
  
  ShowWindow(SW_HIDE);

  return TRUE;
}
Bạn xem lại project trong Visual C++ vừa tạo để xem code thật sự của phần trên.
Như vậy ta đã thực hiện xong phần thu nhỏ và xóa trayicon. Phần kế tiếp ta sẽ viết hàm nhận thông điệp khi người dùng nhấp chuột phải lên trayicon và từ đó cho hiện menu lựa chọn.

2. Tạo menu popup cho biểu tượng system tray
Như đã đề cập ở phần trên, ta đã định nghĩa 1 user defined message có tên là WM_NOTIFYOFINCONONTRAY. Khi người dùng thực hiện các thao tác nhấn phím, rê chuột lên trayicon hoặc nhấp chuột lên nó, Windows sẽ gửi đến ứng dụng chúng ta 1 message chính là message được gán vào thuộc tính tnid.uCallbackMessage ở trên. Vì đây là message tự định nghĩa nên ta phải tự viết hàm xử lý biến cố cho nó, ta sẽ phải tuân theo chuẩn của Windows khi viết hàm này. Với MFC, hàm xử lý message sẽ có dạng sau:
Code:
 afx_msg void MessageProc(WPARAM wParam, LPARAM lParam);
Với ứng dụng chúng ta sẽ đặt tên là:
Code:
afx_msg void OnNotifyOfIconOnTray(WPARAM wParam, LPARAM lParam)
;
Ở đây afx_msg là macro được MFC định sẵn chỉ định hàm xử lý thông điệp. Thông số WPARAM sẽ cho biết giá trị uID đã được gán trước trong cấu trúc NOTIFYICONDATA, LPARAM sẽ là giá trị cụ thể thông điệp hệ thống (nhấn phím nào, chuột trái, phải...)
Trong cửa sổ WorkSpace, bạn chọn tab ClassView và nhấp đúp và tên class CSample1Dlg để mở file header của class. Sau đó bạn gõ thêm dòng định nghĩa hàm ở trên, kết quả như hình vẽ sau:
<img src=\'http://www.whatvn.com/images/vc/sample1/33.gif\' border=\'0\' alt=\'user posted image\' />
Kế đến ta cần viết thêm mã để hàm OnNotifyOfIconOnTray có hiệu lực. Trên cửa sổ ClassView bạn nhấp đúp vào 1 hàm nào đó của class CSample1Dlg để mở cửa sổ code của class này (nằm trong file sample1Dlg.cpp). Tìm đến phần BEGIN_MESSAGE_MAP(CSample1Dlg, CDialog) và thêm dòng ON_MESSAGE( WM_NOTIFYOFINCONONTRAY, OnNotifyOfIconOnTray) như hình vẽ:
<img src=\'http://www.whatvn.com/images/vc/sample1/34.gif\' border=\'0\' alt=\'user posted image\' />
Đến đây ta sẽ cần tạo 1 menu popup cho trayicon, bạn trở lại phần Resource tab, nhấn chuột phải lên mục Menu và chọn Insert Menu. Resource Editor sẽ tự động tạo cho bạn 1 menu có id là IDR_MENU1 và 1 menu item rỗng có viền lấm chấm sẽ được tạo ở phần editor bên phải, bạn nhấn chuột phải lên nó và chọn Properties như hình bên dưới:
<img src=\'http://www.whatvn.com/images/vc/sample1/35.gif\' border=\'0\' alt=\'user posted image\' />
Trong hộp thoại Properties, bạn nhập vào ô Caption chuỗi "test" và nhấn Enter. Sau khi nhấn Enter trình editor tự động chuỗi bạn vừa nhập vào menuitem và tạo thêm 1 menuitem rỗng ngay bên dưới. Bạn làm tương tự để tạo thêm 2 item sổ xuống lần lượt là "&Show" và "&Exit". Kết quả như hình sau:
<img src=\'http://www.whatvn.com/images/vc/sample1/36.gif\' border=\'0\' alt=\'user posted image\' />
Vì menuitem đầu tiên có caption là "Test" nên trình Resource Editor của VC++ sẽ tự tạo cho các item của nó những số ID duy nhất có phần bắt đầu dạng ID_TEST_Caption. Ví dụ như ID_TEST_SHOW, ID_TEST_EXIT.

Tương tự như các nút bấm trên dialog ở trên, mỗi menuitem cần 1 hàm xử lý sự kiện cho nó khi người dùng chọn item đó trên menu. Bước này ta sẽ dùng trình ClassWizard của VC++.
Bạn nhấn chuột phải lên menuitem có tên "Show" và chọn ClassWizard...từ menu sổ xuống.
Một hộp thoại hiện ra hỏi bạn "Adding a class", ta chưa dùng chức năng này nên bạn chọn Cancel để bỏ qua.
Trong hộp thoại MFC ClassWizard, ở listbox Object IDs, bạn chọn ID có tên ID_TEST_SHOW và ô bên phải có tên "Messages", bạn nhấp đúng vào mục COMMAND. Hộp thoại Add Member Function sẽ hiện ra, bạn nhập tên hàm xử lý là OnTestShow (thường thì tên hàm được chọn sẵn cho bạn), bạn nhấn OK. Bạn làm tương tự với ID_TEST_EXIT. Như hình sau:
<img src=\'http://www.whatvn.com/images/vc/sample1/37.gif\' border=\'0\' alt=\'user posted image\' />
Sau khi tạo xong 2 hàm, ở phần Member functions bạn nhấp đúp vào hàm OnTestShow và sẽ được đưa trở lại cửa sổ code, ở hàm OnTestShow bạn nhập thêm dòng MinimizeOnTray(FALSE);, tham số FALSE truyền vào hàm MinimizeOnTray ta đã tạo ở trên sẽ dùng để xóa icon khỏi systemtray và hiện lại cửa sổ dialog (bạn xem lại hàm MinimizeOnTray ở trên). Tương tự với hàm OnTestExit, bạn thêm dòng OnOK();, đây là hàm ngầm định của ứng dụng dạng dialog dùng để kết thúc ứng dụng.
Code:
void CSample1Dlg::OnTestShow() 
{
  MinimizeOnTray(FALSE);
}
void CSample1Dlg::OnTestExit() 
{
  OnOK();
}
Nhân tiện bạn viết thêm code cho hàm OnMinimize ở trên ta đã tạo sẵn 1 hàm gọi MinimizeOnTray với tham số bằng TRUE để khi nguời dùng nhấn nút sẽ thu nhỏ ứng dụng:
Code:
void CSample1Dlg::OnMinimize() 
{
  MinimizeOnTray(TRUE);
}
Như vậy ta đã xong phần viết lệnh xử lý cho menu, bây giờ bạn về cuối cửa sổ code và viết thêm hàm OnNotifyOfIconOnTray như ta đã đề cập ở trên, code như sau:
Code:
void CSample1Dlg::OnNotifyOfIconOnTray(WPARAM wParam, LPARAM lParam)
{
  UINT uID; 
  UINT uMouseMsg; 

  uID = (UINT) wParam; 
  uMouseMsg = (UINT) lParam; 

  if(uMouseMsg == WM_RBUTTONUP && uID == IDI_TRAY1)
  { 
CPoint mouse;
GetCursorPos(&mouse); //lấy vị trí nhấp chuột
this->SetFocus(); //set focus cho ứng dụng

CMenu lMenu;
lMenu.LoadMenu(IDR_MENU1);
CMenu* pMenu = lMenu.GetSubMenu(0);
pMenu->TrackPopupMenu(TPM_RIGHTALIGN|TPM_RIGHTBUTTON, mouse.x, mouse.y, this, NULL);
  } 
}
Như đã đề cập, hàm xử lý message OnNotifyOfIconOnTray có 2 tham số wParam, lParam. Để sử dụng chúng ta sẽ ép kiểu (type-cast) về kiểu UINT (unsigned integer - số nguyên không dấu) với wParam chứa ID của trayicon và lParam chứa thông điệp Windows gửi cho ứng dụng.
Ở đây ta chỉ cho hiện menu khi người dùng nhấp chuột phải lên trayicon nên bạn sẽ thấy lệnh if với điều kiện uMouseMsg == WM_RBUTTONUP, WM_RBUTTONUP là windows message có sẵn, bạn xem MSDN để biết thêm các message khác và với ID là IDI_TRAY1 như ta đã thiết lập khi gọi hàm Shell_NotifyIcon ở trên, ta sẽ cho hiện menu popup.
Cách thức sử dụng 1 menu dạng popup như ta đã tạo bên trên, bạn dùng lớp MFC có tên CMenu với phương thức LoadMenu và tham số truyền vào là ID của menu đã tạo, ở đây là IDR_MENU1. Lưu ý đây là dạng menubar có 1 item "Test" và sổ xuống 1 popup, nên ta cần dùng hàm GetSubMenu để lấy đúng menu popup, tham số 0 chỉ item đầu tiên của menubar (lưu ý trong C/C++, PHP, Java... các phần tử trong 1 mảng, list bắt đầu từ 0), hàm này sẽ trả về 1 con trỏ đến menupop up, nên bạn cần khai báo thêm 1 biến con trỏ để lưu giữ menu đó.
Code:
CMenu* pMenu = lMenu.GetSubMenu(0);
Công việc cuối cùng là gọi hàm TrackPopupMenu của lớp CMenu, hàm này được khai báo như sau:
Code:
BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL );
nFlags chỉ định kiểu menu xuất hiện. Giá trị TPM_RIGHTALIGN chỉ menu sẽ hiện canh lề bên phải vị trí nhấp chuột, TPM_RIGHTBUTTON chỉ định right-click
int x, int y chỉ định vị trí nhấp chuột, ta sẽ dùng hàm GetCursorPos để lấy vị trí này
CWnd* pWnd chỉ định con trỏ đến ứng dụng sẽ nhận thông điệp xử lý chuột, ta dùng con trỏ định trước this. Đây là dạng con trỏ chỉ đến cửa sổ hiện tại
LPCRECT lpRect = NULL chỉ định phạm vi hiệu lực của menu. Giá trị ngầm định của tham số này là NULL, chỉ định nếu người nhấp chuột ngoài vùng này thì menu tự động đóng lại.
Lưu ý bạn cần thêm hàm this->SetFocus(); để chỉ định điểm nhập focus cho ứng dụng trayicon hỗ trợ thêm cho việc nếu người nhấp chuột ngoài vùng này thì menu tự động đóng lại.

Để tiết kiệm thời gian, bạn copy phần code của hàm OnNotifyOfIconOnTray tôi đã tạo sẵn ở trên và dán vào cuối vùng code của CSample1Dlg.
Đến đây là hoàn thành phần tạo ứng dụng có khả năng thu nhỏ xuống system tray. Bạn nhấn Ctrl-F5 để biên dịch và chạy ứng dụng, nhấn lên nút "Minimize on tray" để thu nhỏ và nhấp chuột phải lên trayicon để sử dụng menu.

Toàn bộ project VC++6.0 tôi đã tạo sẵn các bạn có thể download tại đây:
<a href=\'http://www.whatvn.com/data/sample1.zip\' target=\'_blank\'>http://www.whatvn.com/data/sample1.zip</a>
LGi is offline   Trả Lời Với Trích Dẫn