MFC – Lession3:Tree Control và List Control

Tree control và list control là hai control được dùng phổ biến trong các bài toán về hiển thị dữ liệu, truy xuất, quản lý tài nguyên, thư mục,…

Trong bài này tôi sẽ trình bày cách sử dụng kết hợp hai control này để hiển thị dữ liệu.

Download sourcecode (316KB)

TreeListDemo

TreeListDemo

Nội dung liên quan đến các phần chính sau:

–          Import resource vào dự án

–          Thêm và lưu trữ icon với ImageList

–          Thêm và hiển thị dữ liệu lên Tree control và List control

–          Xử lý sự kiện chọn các mục trên Tree control và List control

Thiết kế giao diện

Tạo dự án mới với tên TreeListDemo và thiết kế như sau:

Design

Đối tượng Thuộc tính Giá trị Biến
Tree Control ID 

Styles > Has buttons

Styles > Has lines

Styles > Lines add root

IDC_TREE1 

Checked

Checked

Checked

List Control ID 

Styles > View

IDC_LIST1 

List

CListCtrl m_cListCtrl
Static Text ID IDC_STATIC_LIST1

Khi đánh dấu chọn các mục trong Styles của Tree control, bạn hãy để ý để sự thay đổi kiểu hiển thị của control này trên form. Việc chọn các giá trị này chỉ có mục đích giúp cây nhìn trực quan hơn.

Trong bài này ta sẽ dùng hai cách để làm việc với control. Với tree control ta sẽ dùng hàm GetDltItem(), còn với list control ta dùng ClassWizard để tạo biến kiểu control để làm việc.

Sử dụng CImageList

Ta sẽ tạo một đối tượng CImageList  chứa các icon để hiển thị lên các mục trong tree control và list control.

–          Đầu tiên bạn add các icon vào dự án bằng cách nhấn phải chuột vào mục Icon trong ResourceView và chọn Insert (hoặc nhấn Ctrl+R). Trong hộp thoại Insert Resource mở ra chọn mục Icon và nhấn nút Import. Bạn hãy chọn 6 icon để import vào.

–          Sau khi insert, các icon sẽ có định danh tự động là IDI_ICON1, IDI_ICON2, IDI_ICON3… Bạn có thể nhấn phải vào từng icon chọn Properties để đổi tên nếu muốn.

–          Thêm vào lớp CTreeListDemoDlg biến private có kiểu CImageList và tên là m_imageList

–          Thêm vào lớp CTreeListDemoDlg hàm private có kiểu trả về void với tên InitImageList và viết code cho hàm như sau:


void CTreeListDemoDlg::InitImageList()

{

// Tao mang icon

HICON hIcon[6];

 

m_imageList.Create(16, 16, ILC_MASK, 6, 6);

CWinApp* pApp=(CWinApp*)AfxGetApp();

 

hIcon[0] = pApp->LoadIcon(IDI_ICON1);

hIcon[1] = pApp->LoadIcon(IDI_ICON2);

hIcon[2] = pApp->LoadIcon(IDI_ICON3);

hIcon[3] = pApp->LoadIcon(IDI_ICON4);

hIcon[4] = pApp->LoadIcon(IDI_ICON5);

hIcon[5] = pApp->LoadIcon(IDI_ICON6);

 

// Them cac icon vao m_imageList

for (int i = 0; i < 6; i++) {

m_imageList.Add(hIcon[i]);

}

&nbsp;

// Gan m_imageList cho list control

m_cListCtrl.SetImageList(&m_imageList, LVSIL_SMALL);

}

Giải thích:

–          Đầu tiên ta phải tạo một đối tượng image list bằng cách hàm CImageList::Create(), hàm này được định nghĩa như sau:

BOOL Create(
int cx,
int cy,
UINT nFlags,
int nInitial,
int nGrow
);

–          Trong đó cx và cy là kích thước của icon, ta đặt hai giá trị này là 16 để tạo icon nhỏ. Tham số nFlag chỉ ra kiểu của image list sẽ tạo ra. Ta dùng giá trị ILC_MASK để icon hiện ra không có nền đen. Thuộc tính nInitial xác định số phần tử sẽ thêm vào và nGrow là số phần tử mà image list có thể thay đổi kích thước để chứa thêm. Vì ta chỉ thêm các icon vào image list một lần duy nhất nên cả hai sẽ đặt chung giá trị là 6, tức là số icon mà ta đã import vào lúc đầu.

–          Để lấy về các handle của icon trong resource, đầu tiên ta dùng AfxGetApp() để lấy về con trỏ của đối tượng CWinApp hiện tại, và dùng hàm LoadIcon() của đối tượng này để tạo các phần tử cho mảng hIcon.

–          Hàm CWinApp::LoadIcon() này yêu cầu tham số là ID của icon trong resource và đối tượng trả về là một HICON.

–          Sau khi thêm các icon vào image list thông qua vòng lặp, bạn phải gán đối tượng m_imaegList này cho list control. Vì ta đã tạo một biến m_cListCtrl trong phần thiết kế nên chỉ việc gọi hàm SetImageList() để làm việc này.

–          Vì ban đầu ta thiết lập thuộc tính Styles > View của list control là List, tức là chỉ hiện thị các icon ở dạng nhỏ nên tham số thứ 2 của SetImageList() là LVSIL_SMALL, nếu bạn muốn icon lớn hơn hãy thay tham số này bằng LVSIL_NORMAL.

Thêm node vào Tree control

Đối tượng tree control chứa nhiều node với cấp độ khác nhau, mỗi node này là một đối tượng kiểu HTREEITEM. Để thêm một node mới vào tree control, bạn cần gọi hàm CTreeCtrl::InsertItem(), nó có một vài overload, trong bài này ta sẽ dùng 2 phiên bản sau:

HTREEITEM InsertItem(
LPCTSTR lpszItem,
HTREEITEM hParent = TVI_ROOT,
HTREEITEM hInsertAfter = TVI_LAST
);
HTREEITEM InsertItem(
LPCTSTR lpszItem,
int nImage,
int nSelectedImage,
HTREEITEM hParent = TVI_ROOT,
HTREEITEM hInsertAfter = TVI_LAST
);

Ở đây bạn gặp một kiểu dữ liệu với tên khá khó hiểu là LPCTSTR, đây chỉ đơn giản là một hằng con trỏ chuỗi hỗ trợ Unicode, chứa nội dung sẽ hiển thị của node trên tree control. Hai tham số cuối (hParent và hInsertAfter) của hai phiên bản trên là optional vì chúng được thiết lập giá trị mặc định, tức là bạn có thể bỏ qua không cần truyền tham số vào đó.

Tham số hParent chỉ đến node cha của node sắp thêm vào (mặc định là TVI_ROOT tức là thêm vào node gốc của cây) và hInsertAfter chỉ node sẽ đứng trước node sắp được thêm (mặc định TVI_LAST tức là thêm vào sau node cuối cùng).

Tham số nImage chỉ ra index của icon trong image list sẽ hiển thị bên cạnh node, và nSelectedImage là index của icon trong image list sẽ xuất hiện khi bạn chọn node (giống như Windows Explorer khi bạn chọn một thư mục ở cây thư mục thì icon của thư mục đó sẽ chuyển từ thư mục đóng thành thư mục mởs).

Ta bắt đầu thêm các node vào cây theo các bước sau:

–          Thêm một hàm vào lớp CTreeListDemoDlg hàm private có kiểu void và tên là InitTreeControl.

–          Viết mã lệnh cho hàm InitTreeControl:


void CTreeListDemoDlg::InitTreeCtrl()

{

CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem(IDC_TREE1);

// Gan image list

pTree->SetImageList(&m_imageList, TVSIL_NORMAL);

HTREEITEM hTree, hCompany;

// index cua icon trong image list

int nSelImage=5;

// root

hTree = pTree->InsertItem("Books",1,nSelImage, TVI_ROOT);

hCompany = pTree->InsertItem("MS Office",2,nSelImage, hTree);

pTree->InsertItem("MS Word", hCompany);

pTree->InsertItem("MS Excel", hCompany);

hCompany = pTree->InsertItem("Programming",3,nSelImage, hTree);

pTree->InsertItem("C#", hCompany);

pTree->InsertItem("C++", hCompany);

pTree->InsertItem("Java", hCompany);

hCompany = pTree->InsertItem("Design",4,nSelImage, hTree);

pTree->InsertItem("CorelDRAW", hCompany);

pTree->InsertItem("Photoshop", hCompany);

// Mo rong node root
pTree->Expand(hTree,TVE_EXPAND);
}

Ta đã viết hai hàm InitImageList và InitTreeControl để thêm dữ liệu cho image list và tree control, để hai hàm này được thực thi, ta sẽ gọi chúng trong hàm CTreeListDemoDlg::OnInitDialog().

BOOL CTreeListDemoDlg::OnInitDialog()
{
[…]
// TODO: Add extra initialization here
InitImageList();
InitTreeCtrl();
return TRUE;  // return TRUE  unless you set the focus to a control
}

Bâ giờ bạn có thể chạy thử và xem kết quả hiển thị trên tree control.

Liên kết Tree control và List control

Mục đích trong phần này là xử lý để khi chọn một node trên tree control, các node con của node được chọn sẽ hiển thị vào list control. Bạn cần thêm sự kiện TVN_SELCHANGED bằng cách dùng ClassWizard với tên hàm là OnSelchangedTree1:


void CTreeListDemoDlg::OnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult)

{

NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

// TODO: Add your control notification handler code here

CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem(IDC_TREE1);

m_cListCtrl.DeleteAllItems();

// node duoc chon tren tree control

HTREEITEM hSelected = pNMTreeView->itemNew.hItem;

if (hSelected != NULL) {

// Kiem tra node dang chon co node con khong

if (pTree->ItemHasChildren(hSelected))

{

HTREEITEM hNextItem;

// Lay node con dau tien cua node dang chon

HTREEITEM hChildItem = pTree->GetChildItem(hSelected);

int count=0;

while (hChildItem != NULL)

{

// Lay text cua node con va insert vao list control

CString strItemText = pTree->GetItemText(hChildItem);

m_cListCtrl.InsertItem(count++,strItemText,0);

// Lay node tiep theo ben duoi hChildItem

hNextItem = pTree->GetNextItem(hChildItem, TVGN_NEXT);

// Cap nhat vi tri cua hChildItem sang node ke tiep

hChildItem = hNextItem;

}

}

}

*pResult = 0;

}

pNMTreeView là một struct kiểu NM_TREEVIEW chứa itemNew là node mới được chọn trên tree control.

Đọc đoạn code trên bạn có thể hình dung được phần nào phương pháp thực hiện. Để lặp qua tất cả các node bạn phải dùng hàm GetNextItem() với tham số thứ hai TVGN_NEXT để lấy node kế tiếp cùng cấp với node được truyền vào (hChildItem). Nếu không tìm thấy node nào, hàm này sẽ trả về NULL.

Bạn hãy chú ý dòng lệnh insert vào list control:

m_cListCtrl.InsertItem(count++,strItemText,0);

Tham số đầu tiên là vị trí cần chèn, tham số thử hai là text hiển thị và tham số cuối là index của icon trong image list. Ở đây tôi để là 0 tương đương với việc bạn bỏ qua tham số này, tức là mặc định list control sẽ lấy icon đầu tiên để hiển thị. Tôi đặt là 0 để bạn có thể thay đổi trong trường hợp cần thiết.

Bạn hãy chạy thử và chọn một vài node trên tree control, để ý xem list control có hiện đúng những gì mà bạn dự đoán không.

Sự kiện chọn item trong List control

Phần này bạn sẽ thực hiện việc hiển thị item được chọn trong list control lên static text bên dưới. Bạn dùng ClassWizard để thêm sự kiện LVN_ITEMCHANGED với tên OnItemChangedList1:

void CTreeListDemoDlg::OnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
// TODO: Add your control notification handler code here
int nSelected = pNMListView->iItem;
CString strItem = m_cListCtrl.GetItemText(nSelected,0);
SetDlgItemText(IDC_STATIC_LIST1, strItem);
*pResult = 0;
}

Hàm GetItemText() của CListCtrl nhận 2 tham số là: một là index của item cần lấy text và hai là vị trí subItem của item đó. Nếu bạn chuyển kiểu hiển thị của list control sang Report  thì bạn có thể thấy rằng mỗi item của list control có nhiều cột, và mỗi cột chia các item ra nhiều subItem. Ở đây ta đặt tham số thứ hai là 0 tức là lấy text của item.

Trong bài này bạn thấy mặc dù tôi nói ta tạo biến để sử dụng giống cơ chế DDX/DDV cho list control nhưng lại không thấy dùng hàm UpdateData() để liên kết dữ liệu. Nguyên nhân là do biến m_cListCtrl không phải là kiểu value mà là kiểu control, do đó việc gọi UpdateData() cũng không làm thay đổi giá trị của nó. Biến m_cListCtrl chỉ đơn giản là một biến lưu thể hiện của đối tượng CListCtrl với mục đích gọi các hàm của nó để thao tác với control.

Phần kết

Trong khuôn khổ bài viết tôi chỉ giới thiệu cách sử dụng cơ bản nhất Tree control và List control, còn rất nhiều chức năng và tùy chọn khác khi làm việc với hai control này. Bạn có thể dễ dàng tự nghiên cứu thông qua thư viện MSDN với đầy đủ hướng dẫn và định nghĩa.

https://yinyangit.wordpress.com

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s