V předchozím dílu jsme si ukázali tvorbu komponent a nyní je na řadě slíbená první aplikace, která bude "něco dělat" a na které si ukážeme využití komponent: jednoduchá verze průzkumníka.
Průzkumník (z dílny Microsoftu) jako takový má mnoho funkcí, ale my se v tomto díle soustředíme pouze na strohé zobrazení seznamu/stromu disků, adresářů a souborů. Nebudeme vymýšlet nic převratného. Okno vertikálně rozdělíme na dvě části, do levé vložíme stromovou strukturu disků a jejich adresářů. Do pravé části umístíme seznam adresářů a souborů ve vybraném disku/adresáři v levé části. Funkčnost bude dost omezená. Nechceme přece udělat originálnímu Průzkumníku konkurenta (zatím :)).
Nejprve si připravíme komponenty, tj. komponentu pro zobrazení stromové struktury a komponentu pro zobrazení seznamu. Obě komponenty jsou již definovány - stromová struktura se zobrazuje pomocí TreeView (WC_TREEVIEW) a seznam se zobrazuje pomocí komponenty ListView (WC_LISTVIEW). Vzhledem k tomu, že se jedná o komponenty z common control DLL, tak je nutné udělat i jejich inicializaci, kterou můžeme provést přímo v konstruktoru těchto tříd. Pro inicializaci použijeme funkci InitCommonControlsEx.
CMyTreeView::CMyTreeView() : CMyWindow(WC_TREEVIEW)
{
INITCOMMONCONTROLSEX sInitCommonControl;
sInitCommonControl.dwSize = sizeof(INITCOMMONCONTROLSEX);
sInitCommonControl.dwICC = ICC_TREEVIEW_CLASSES;
InitCommonControlsEx(&sInitCommonControl);
}
CMyListView::CMyListView() : CMyWindow(WC_LISTVIEW)
{
INITCOMMONCONTROLSEX sInitCommonControl;
sInitCommonControl.dwSize = sizeof(INITCOMMONCONTROLSEX);
sInitCommonControl.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&sInitCommonControl);
}
Obě komponenty mají seznam zpráv, pomocí kterých se ovládají. Tyto zprávy mají svoje ekvivalenty v předdefinovaných makrech (seznam maker pro TreeView a seznam maker pro ListView). My si do naší komponenty implementujeme pouze volání těch zpráv/maker, která využijeme, ale stejným způsobem by bylo možné implementovat všechna volání. Nejdůležitější pro nás bude vkládání nových položek a mazání.
Nejprve pro TreeView:
BOOL CMyTreeView::DeleteAllItems()
{
if (m_hWnd == NULL) return(FALSE);
return(TreeView_DeleteAllItems(m_hWnd));
}
BOOL CMyTreeView::DeleteItem(HTREEITEM hItem)
{
if (m_hWnd == NULL) return(FALSE);
if (hItem == NULL) return(FALSE);
return(TreeView_DeleteItem(m_hWnd, hItem));
}
HTREEITEM CMyTreeView::InsertItem(LPTVINSERTSTRUCT psItem)
{
if (m_hWnd == NULL) return(NULL);
if (psItem == NULL) return(NULL);
return(TreeView_InsertItem(m_hWnd, psItem));
}
Pro ListView je to velice podobné:
BOOL CMyListView::DeleteAllItems()
{
if (m_hWnd == NULL) return(FALSE);
return(ListView_DeleteAllItems(m_hWnd));
}
BOOL CMyListView::DeleteItem(int nIndex)
{
if (m_hWnd == NULL) return(FALSE);
if (nIndex < 0) return(FALSE);
return(ListView_DeleteItem(m_hWnd, nIndex));
}
int CMyListView::InsertItem(LPLVITEM psItem)
{
if (m_hWnd == NULL) return(-1);
if (psItem == NULL) return(-2);
return(ListView_InsertItem(m_hWnd, psItem));
}
Vzhledem k tomu, že obsah TVINSERTSTRUCT nebo LVITEM je možné různě kombinovat, tak je naše funkce pro vkládání položek přes tyto struktury stejně univerzální. Stejně tak by bylo možné vytvořit funkce, které by byly speciální (jenom text, text a ikona apod.), např.:
int CMyListView::InsertItem(LPWSTR pwzText)
{
LVITEM sItem;
if (m_hWnd == NULL) return(-1);
if (pwzText == NULL) return(-2);
ZeroMemory(&sItem, sizeof(LVITEM));
sItem.mask = LVIF_TEXT;
sItem.pszText = pwzText;
sItem.item = ListView_GetItemCount(m_hWnd);
return(ListView_InsertItem(m_hWnd, &sItem));
}
Nyní máme vše potřebné pro vytvoření GUI stránky aplikace, takže můžeme pomalu naší aplikaci vdechnout život, tj. naprogramovat funkci stromové struktury a po výběru nějaké položky v této stromové struktuře naplnit seznam adresářů a souborů.
Strom naplníme při jeho inicializaci dostupnými diskovými jednotkami. Každé přiřadíme falešnou podpoložku, aby šlo expandovat strom, který ještě není načtený. Při expandování položky stromu pošle komponenta TreeView svému rodičovskému oknu notifikaci TVN_ITEMEXPANDING, na kterou budeme reagovat tak, že pro daný adresář načteme do stromu jeho podadresáře. Abychom mohli tuto notifikaci zachytávat, tak komponentu TreeView (resp. instanci třídy CMyTreeView) zapouzdříme do panelu CTreeViewPanel a v této třídě budeme zachytávat a zpracovávat notifikace. Notifikace posílá komponenta svému rodičovskému oknu, v našem případě to bude právě tento panel.
Druhou notifikací, na kterou budeme v třídě CTreeViewPanel reagovat, je TVN_SELCHANGED. Reakcí na tuto notifikaci (tj. na výběr nové položky) bude změna obsahu seznamu souborů (v ListView). Odešleme tomuto oknu zprávu s pointerem na řetězec obsahující úplnou cestu, kde má soubory/adresáře hledat. Komponenta ListView (resp. instance CMyListView) je opět zapouzdřena v panelové třídě CListViewPanel (nemusela by, protože s obsahem seznamu zatím nic neprovádíme, ale příště budeme).
V obou případech využijeme toho, že celá cesta je vlastně uložená v názvech jednotlivých položek stromu. Celou cestu tedy vytvoříme tak, že budeme odzadu spojovat jednotlivé názvy a prokládat je zpětnými lomítky. A protože máme společnou kořenovou položku (uloženou v m_hRoot), tak ta se stane i zarážkou pro skládání cesty.
// zaciname prazdnym retezcem
wcscpy_s(m_wzPath, MAX_PATH, L"");
// projdeme vsechny az do korene (m_hRoot)
while(hItem != m_hRoot) {
sItem.hItem = hItem;
sItem.mask = TVIF_HANDLE | TVIF_TEXT;
sItem.pszText = wzItem;
sItem.cchTextMax = MAX_PATH;
// do wzItem nacteme nazev polozky
m_hTreeView.GetItem(&sItem);
// pridame ji na konec zpetne lomitko
wcscat_s(wzItem, MAX_PATH, L"\\");
// a pridame k ni aktualni zbytek cesty (nebot ji skladame odzadu)
wcscat_s(wzItem, MAX_PATH, m_wzPath);
// nove vytvorena cesta se stava aktualnim zbytkem cesty
wcscpy_s(m_wzPath, MAX_PATH, wzItem);
hItem = m_hTreeView.GetParent(hItem);
}
// po pruchodu cyklem mame v m_wzPath uplnou cestu
// (nebo nic, pokud se jednalo o kořenovou položku)
Takto získanou cestu přepošleme zprávou WM_COMMAND do rodičovského okna (MainView). Správně bychom měli použít zprávu WM_USER, ale ono je to (a nejenom v tomto případě) skoro jedno. V MainView tuto zprávu zachytíme a předáme pointer na řetězec s úplnou cestou do panelu, který obsahuje ListView.
V panelu ListView tento adresář použijeme jako výchozí adresář pro vyhledání souborů a adresářů. K tomuto účelu jsou ve Win32 funkce FindFirstFile, FindNextFile a FindClose. Projdeme všechny položky v daném adresáři, odfiltrujeme speciální adresáře ("." a "..") a rozdělíme ostatní položky na adresáře a soubory. Použití funkcí Find... v našem kódu je následující:
// v pwzFolder je adresar, ktery je vybrany v TreeView
HANDLE hFind;
wchar_t wzPath[MAX_PATH];
WIN32_FIND_DATA sFindData;
int nCount = 0;
// vymazeme puvodni obsah ListView (vsechny polozky)
m_hListView.DeleteAllItems();
// hledame vse tj. napr. C:\abc\*
wcscpy_s(wzPath, MAX_PATH, pwzFolder);
wcscat_s(wzPath, MAX_PATH, L"*.*");
// zapocneme hledani - najdeme prvni soubor odpovidajici wzPath
// (v sFindData dostaneme informace o nalezenem souboru)
hFind = FindFirstFile(wzPath, &sFindData);
// hFind je HANDLE hledani, pokud je validni, tak muze hledani probihat
if (hFind != INVALID_HANDLE_VALUE) {
do {
// je nalezena polozka adresar?
if ((sFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) {
// adresar "." a ".." vynechej
if ((wcscmp(sFindData.cFileName, L".") != 0) && (wcscmp(sFindData.cFileName, L"..") != 0))
// vloz do seznamu polozku (index bitmapy adresare je 0)
m_hListView.InsertItem(nCount++, sFindData.cFileName, 0);
}
// vloz do seznamu polozku - soubor (index bitmapy souboru je 1)
else m_hListView.InsertItem(nCount++, sFindData.cFileName, 1);
// najdi postupne vsechno
} while(FindNextFile(hFind, &sFindData));
// a nakonec je potreba hledani uzavrit
FindClose(hFind);
}
Tím vyplníme ListView dle obsahu daného adresáře. Jednotlivé položky (soubory i adresáře) jsou napřeskáčku, nejsou setříděné, ale to nám v tuto chvíli nevadí.
A to je pro tentokráte vše. Zdrojový kód aplikace je ke stažení zde. V dalším díle si aplikaci trochu rozšíříme. Jedním z důvodů je to, aby měla lepší a očekávanější funkci (jednoduše řečeno, aby nebyla tak strohá). A druhým důvodem bude ukázka přidání menu, tlačítkové lišty a stavového řádku.