Aplikace ve Win32 API - 'průzkumník' (pokračování)
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu
Reklama
Reklama

Aplikace ve Win32 API - 'průzkumník' (pokračování)Aplikace ve Win32 API - 'průzkumník' (pokračování)

 

Aplikace ve Win32 API - 'průzkumník' (pokračování)

Google       Google       29. 3. 2010       16 986×

Jednoduchou aplikaci vytvořenou v minulém díle poněkud rozšíříme, aby alespoň trochu lahodila oku. Přidáme si menu, tlačítkovou lištu, stavový řádek a trochu funkce.

Reklama
Reklama

V předchozí části jsme si vytvořili jednoduchou aplikaci, "průzkumníka", která je opravdu velmi strohá. V tomto díle si přidáme menu, ze kterého budeme ovládat zobrazení tlačítkové lišty a stavového řádku. Na tlačítkovou lištu umístíme jedno tlačítko pro ukončení aplikace a druhé pro pohyb "o úroveň výše". Do stavového řádku budeme vypisovat aktuální úplnou cestu adresáře, jehož obsah se právě v ListView zobrazuje. A ještě si přidáme možnost procházení adresářové struktury přímo z ListView, tj. otevření podadresáře a možnost zobrazit obsah nadřazeného adresáře.

Ze všeho nejdříve si položky v ListView setřídíme. Ve výsledku by měl být první "adresář", který umožní zobrazit obsah nadřazeného adresáře (".."), dále by měly být adresáře seřazeny podle abecedy a následovat by je měly soubory (také setříděné podle abecedy). Obsah ListView lze nechat setřídit automaticky nastavením vlastnosti LVS_SORTASCENDING nebo LVS_SORTDESCENDING pro setřídění vzestupně, resp. sestupně podle jména při vytváření instance ListView. To bohužel není náš případ, my potřebujeme položky ještě rozlišovat podle typu (adresář/soubor). Proto si musíme třídicí funkci napsat sami (a také ji sami ve vhodnou dobu volat).

Třídění bude tedy probíhat nejprve podle typu položek a následně podle jména položek. Na uživatelské třídění položek slouží volání funkce:

ListView_SortItems(HWND hwnd, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)

Neboli v našem případě funkce z třídy CMyListView:

BOOL CMyListView::SortItems(PFNLVCOMPARE pfnCompare, LPARAM lParamSort)

Parametr pfnCompare je adresa třídicí funkce a lParamSort je parametr, který je této funkci předán při každém volání této funkce (často se předává this).

Třídicí funkce musí mít následující tvar:

int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)

Parametr lParamSort je ten, který jsme předali při volání SortItems. Parametry lParam1 a lParam2 jsou data přiřazená každé položce v ListVIew. Tedy nikoliv indexy nebo jména, ale data přiřazená jednotlivým položkám. Způsobů, jak dosáhnout potřebného výsledku, je mnoho. V tomto případě (z ukázkových účelů nevyužijeme C++ list) použijeme ukládání informací o jednotlivých položkách do spojitého jednosměrného seznamu a každé položce v ListView předáme pointer na buňku s informacemi o ní.

Každá buňka seznamu bude tedy obsahovat jméno položky (řetězec) a typ položky (číslo - stačí byte). Definice struktury bude tedy následující:

typedef struct _LIST_VIEW_STRUCT {
	// jmeno polozky
	wchar_t				wzName[MAX_PATH];
	// typ polozky (0x00 - prazdna, 0x01 - soubor, 0xFF - adresar)
	BYTE				nType;
	// ukazatel na dalsi polozku seznamu (nebo NULL)
	_LIST_VIEW_STRUCT*	pNext;

} LIST_VIEW_STRUCT;

Vzhledem k tomu, že obsah ListView bude vždy jen jeden, tak nám stačí před každým naplněním ListView (a tedy i buněk tohoto seznamu) obsah seznamu vyčistit a nechat znovu naplnit. Omezíme tím neustálé alokace paměti. Seznam definujeme jako členskou proměnnou třídy panelu ListView (ListViewPanel):

LIST_VIEW_STRUCT		m_sItemList;

Tím se dostáváme i k funkcím nad tímto seznamem. Budeme potřebovat vložení, vymazání a nakonec celkové uvolnění paměti:

LIST_VIEW_STRUCT* CListViewPanel::InsertListItem(LPCWSTR pwzName, BYTE nType)
{
	LIST_VIEW_STRUCT*	pNext	= &m_sItemList;
	LIST_VIEW_STRUCT*	pLast;
	// projdi polozky seznamu
	while(pNext != NULL) {
		pLast = pNext;
		// zjisti, ktera je prazdna, a do ni to uloz
		if (pNext->nType == 0) {
			wcscpy_s(pNext->wzName, MAX_PATH, pwzName);
			pNext->nType = nType;
			return(pNext);
		}

		pNext = pNext->pNext;
	}
	// pokud nebyla zadna prazdna, tak v pLast je posledni polozka seznamu a za ni pridej dalsi
	if ((pLast->pNext = new LIST_VIEW_STRUCT) == NULL) return(NULL);
	// a do ni to uloz
	pNext		= pLast->pNext;
	pNext->pNext	= NULL;
	pNext->nType	= nType;
	wcscpy_s(pNext->wzName, MAX_PATH, pwzName);

	return(pNext);
}


void CListViewPanel::ClearList()
{
	LIST_VIEW_STRUCT*	pNext	= &m_sItemList;
	// projdi vsechny polozky seznamu a vycisti je
	while(pNext != NULL) {
		wcscpy_s(pNext->wzName, MAX_PATH, L"");
		pNext->nType = 0;
		pNext = pNext->pNext;
	}
}


void CListViewPanel::FreeList()
{
	LIST_VIEW_STRUCT*	pNext	= m_sItemList.pNext;
	LIST_VIEW_STRUCT*	pTemp;
	// pojdi vschny polozky seznamu
	while(pNext != NULL) {
		// ukladej si nasledovniky
		pTemp = pNext->pNext;
		// uvolnuj pamet soucasny polozkam
		delete pNext;
		// a pokracuj na ulozenem nasledovnikovi
		pNext = pTemp;
	}
	// samozrejme vynuluj i prvni (statickou) polozku - jako v konstruktoru
	m_sItemList.nType	= 0;
	m_sItemList.pNext	= NULL;
	wcscpy_s(m_sItemList.wzName, MAX_PATH, L"");
}

A samozřejmě nesmíme zapomenout seznam inicializovat v konstruktoru:

CListViewPanel::CListViewPanel() : CMyPanel()
{		
	m_sItemList.nType	= 0;
	m_sItemList.pNext	= NULL;
	wcscpy_s(m_sItemList.wzName, MAX_PATH, L"");
}

Nyní máme seznam, do kterého vždy uložíme zobrazené položky. Pointer na uložené informace (buňku) přiřadíme každé položce ListView jako její data. Rozlišuje vložené položky na adresáře a soubory. Seznam položek v daném adresáři projdeme stejně jako v předchozím díle, pouze doplníme uložení dat jednotlivým položkám seznamu a přestaneme odfiltrovávat speciální adresář "..":

LIST_VIEW_STRUCT*	pItem;

...

if ((sFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) {
	if  ((wcscmp(sFindData.cFileName, L".") != 0)) {
		pItem = InsertListItem(sFindData.cFileName, 0xFF);
		m_hListView.InsertItem(nCount++, sFindData.cFileName, 0, (LPVOID)pItem);
	}
}
else {
	pItem = InsertListItem(sFindData.cFileName, 0x01);
	m_hListView.InsertItem(nCount++, sFindData.cFileName, 1, (LPVOID)pItem);
}

A po naplnění seznamu je vhodná chvíle na setřídění položek, tj. zavolání SortItems. V našem případě nepotřebujeme předávat žádný parametr (proto NULL), ale jak jsem zmínil výše, často se předává this. To se dělá z toho důvodu, aby se třídící funkce dostala k instančním proměnným a funkcím dané třídy, neboť třídicí funkce musí být definována jako statická (v době překladu musí být známa její adresa). Třídění tedy spustíme zavoláním SortItems a jako parametr předáme naši třídicí funkci:

m_hListView.SortItems(CompareFunc, NULL);

Třídicí funkce není nijak složitá. Funguje na podobném principu jako funkce strcmp (wcscmp), tj. vrací 0, pokud jsou položky stejné, -1, pokud má první položka předcházet druhou, a 1, pokud je tomu naopak. Musíme dodržet 3 zásady:

  • první je vždy speciální adresář ".."
  • adresáře vždy předcházejí soubory
  • adresáře/soubory jsou setříděné podle abecedy (bez rozlišení velikosti písmen)

Třídicí funkce bude tedy vypadat následovně:

int CALLBACK CListViewPanel::CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
	LIST_VIEW_STRUCT*	pItem1	= (LIST_VIEW_STRUCT*)lParam1;
	LIST_VIEW_STRUCT*	pItem2	= (LIST_VIEW_STRUCT*)lParam2;

	// adresare jsou vzdy nad soubory
	if (pItem1->nType < pItem2->nType) return(1);
	if (pItem1->nType > pItem2->nType) return(-1);	

	// specialni adresar ".." je vzdy prvni
	if (wcscmp(pItem1->wzName, L"..") == 0) return(-1);
	if (wcscmp(pItem2->wzName, L"..") == 0) return(1);

	// jinak tridime podle jmena souboru/adresaru
	return(_wcsicmp(pItem1->wzName, pItem2->wzName));
}

Nyní máme položky setříděné a ještě tomuto seznamu rychle vdechneme život. Budeme zachytávat notifikaci při aktivaci (dvojklik apod.) položky ListView - LVN_ITEMACTIVATE. Využijeme toho, že v datech položky je její typ i název. U adresářových položek přepošleme jejich název do hlavní okna (rodičovského) opět jako WM_COMMAND:

if (pItem->nType == 0xFF) {
	::SendMessage(GetParent(m_hWnd), WM_COMMAND, MAKEWPARAM(ID_LIST_VIEW, 1), (LPARAM)pItem->wzName);
}

V hlavním okně tuto zprávu předáme do TreeView, kde na ni budeme reagovat podobně, jako by došlo k požadavku na prohlížení obsahu adresáře přímo z TreeView. V případě, že se jedná o speciální adresář "..", tak provedeme výběr nadřazené položky:

hItem = m_hTreeView.GetSelection();

if (wcscmp(pwzItem, L"..") == 0) {
	hNext = m_hTreeView.GetParent(hItem);
	m_hTreeView.SelectItem(hNext);
	return;
}

Pro ostatní adresáře expandujeme aktuální výběr, projdeme všechny podsložky a vybereme tu, pro kterou byla změna adresáře z ListView volána:

m_hTreeView.Expand(hItem, TVE_EXPAND);

hNext = m_hTreeView.GetChild(hItem);
while(hNext != NULL) {
	sItem.mask			= TVIF_TEXT | TVIF_HANDLE;
	sItem.hItem			= hNext;
	sItem.pszText		= wzItem;
	sItem.cchTextMax	= MAX_PATH;

	if ((m_hTreeView.GetItem(&sItem)) && (wcscmp(wzItem, pwzItem) == 0)) {
		m_hTreeView.SelectItem(hNext);
		return;
	}
	
	hNext = m_hTreeView.GetNextSibling(hNext);
}

Nyní se lze pohybovat v adresářové struktuře i přes ListView. Tuto oblast teď opustíme a dáme se do přidávání menu, tlačítkové a stavové lišty.

Nejjednodušší je menu, které stačí definovat jako součást resource (.rc) souboru a následně předat jeho ID (IDC_MAIN_MENU) při vytváření hlavního okna aplikace. Funkce menu bude v tomto případě úzce spjata s tlačítkovou a stavovou lištou. Mimo ovládání zobrazení těchto dvou lišt bude z menu možné už jenom aplikaci uzavřít.

Obě lišty vykazují jisté společné vlastnosti (jde o "proužek" v aplikaci, lze je schovávat/zobrazovat atd.), takže si pro ně vytvoříme společnou třídu (CMyBar), která zapouzdří společné vlastnosti a vytvoří společné rozhraní. Jedná se o vytvoření lišty, změnu a detekce stavu viditelnosti a zrušení lišty. Dále si do naší hierarchie tříd přidáme dceřiné třídy CMyToolbar a CMyStatusbar, které budou obsahovat obecné věci týkající se daných lišt. Všechno ostatní musíme implementovat v dceřiných třídách (v aplikaci), které budou obsahovat i iniciliazaci lišt apod. Z kódu třídy CMyBar si zde uvedeme jako příklad změnu stavu viditelnosti (s výpočtem výšky lišty):

BOOL CMyBar::ChangeViewStatus()
{
	if (m_hBar == NULL) return(FALSE);
	// je-li vyska > 0, pak je lista zobrazena -> musime ji skryt
	if (m_nHeight > 0) {		
		ShowWindow(m_hBar, SW_HIDE);
		m_nHeight = 0;
	}
	// jinak neni zobrazena -> musime ji zobrazit
	else {
		RECT rtBar;
		ShowWindow(m_hBar, SW_SHOW);
		GetWindowRect(m_hBar, &rtBar);
		m_nHeight = rtBar.bottom - rtBar.top;
	}
	return(TRUE);
}

Z třídy CMyStatusbar a CMyToolbar si zde jako příklad uvedeme nastavení textu ve stavovém řádku a změnu stavu tlačítka:

BOOL CMyStatusbar::SetBarText(LPCWSTR pwzText)
{
	if (m_hBar == NULL) return(FALSE);	

	return(SendMessage(m_hBar, SB_SETTEXT, (WPARAM)0, (LPARAM)pwzText));
}

...

void CMyToolbar::EnableButton(UINT nButtonID, BOOL bEnabled)
{
   if (m_hBar == NULL) return;

   SendMessage(m_hBar, TB_ENABLEBUTTON, (WPARAM)nButtonID, (LPARAM)MAKELONG(bEnabled, 0));
}

Všechny další specializované věci (dané aplikací) je nutné implementovat až v aplikaci. Z těchto tříd si zde jako příklad uvedeme vytvoření statvové lišty, kterou v tomto případě tvoří jedna část přes celou délku stavového řádku:

BOOL CStatusbar::BarCreate()
{
	INT  			nParts[1];
	HINSTANCE	hInstance	= (HINSTANCE)::GetWindowLong(m_hParentWnd, GWL_HINSTANCE);

	if (m_hParentWnd == NULL) return(FALSE);

	InitCommonControls();

	m_hBar = CreateStatusWindow(WS_CHILD | WS_VISIBLE, L"", m_hParentWnd, IDC_SB_STATUSBAR);

	if (m_hBar == NULL) return(FALSE);

	// az do konce -> -1
	nParts[0]	= -1;	

	SendMessage(m_hBar, SB_SETPARTS, (WPARAM)1, (LPARAM)&nParts);

	return(TRUE);
}

Vytvoření lišt provedeme, stejně jako ostatní prvky, v OnCreate. Celková velikost zobrazené plochy se zmenší o velikost jednotlivých lišt. To musíme zohlednit i v reakci na změnu velikost okna:

rtRect.top += m_hToolbar.GetBarHeight();
rtRect.bottom -= m_hStatusbar.GetBarHeight();

Samozřejmě s tím, že tlačítková lišta je zobrazena v horní části a stavový řádek v dolní části.

Změnu viditelnosti (ovládanou z menu) můžeme provést přes společné rozhraní dané CMyBar. Musíme změnit stav dané lišty, změníme zaškrtnutí v menu a zavoláme přepočítání velikosti okna:

void CMainView::OnShowBar(CMyBar* pBar, UINT nMenuID)
{	
	HMENU	hMenu	= GetMenu(m_hWnd);

	pBar->ChangeViewStatus();
			
	CheckMenuItem(hMenu, nMenuID, pBar->IsShowed());

	OnSize(0, 0, 0);
}

Ještě nám zbývá reagovat na událost od tlačítka "adresář výše" a do stavového řádku vypisovat adresář, jehož obsah je v ListView aktuálně zobrazen. Reakce na tlačítko bude velmi jednoduchá. Opět využijeme stávajícího kódu, tj. do TreeView odešleme jako parametr speciální adresář "..":

m_hTreeViewPanel.ChangeItem(L"..");

Stejně jednoduché to bude i v případě stavové lišty. Ve chvíli předávání aktuálního adresáře z TreeView do ListView (přes MainView) můžeme tento adresář vypsat:

m_hStatusbar.SetBarText((LPCWSTR)lParam);

To je pro tentokráte vše. Zdrojový kód aplikace je ke stažení zde. V dalším díle si aplikaci ještě trochu rozšíříme - vytvoříme si záložky na "otevřené" soubory, tj. použijeme TabCtrl.

×Odeslání článku na tvůj Kindle

Zadej svůj Kindle e-mail a my ti pošleme článek na tvůj Kindle.
Musíš mít povolený příjem obsahu do svého Kindle z naší e-mailové adresy kindle@programujte.com.

E-mailová adresa (např. novak@kindle.com):

TIP: Pokud chceš dostávat naše články každé ráno do svého Kindle, koukni do sekce Články do Kindle.

2 názory  —  2 nové  
Hlasování bylo ukončeno    
0 hlasů
Google
Autor se věnuje programování za peníze :)

Nové články

Obrázek ke článku NEWTON Media prohledá 200  milionů mediálních zpráv během sekund díky Cisco UCS

NEWTON Media prohledá 200 milionů mediálních zpráv během sekund díky Cisco UCS

Česká společnost NEWTON Media provozuje největší archiv mediálních zpráv ve střední a východní Evropě. Mezi její zákazníky patří například ministerstva, evropské instituce nebo komerční firmy z nejrůznějších oborů. NEWTON Media rozesílá svým zákazníkům každý den monitoring médií podle nastavených klíčových slov a nabízí online službu, kde lze vyhledat mediální výstupy v plném znění od roku 1996.

Reklama
Reklama
Obrázek ke článku Delphi 10.1.2 (Berlin Update 2) – na co se můžeme těšit

Delphi 10.1.2 (Berlin Update 2) – na co se můžeme těšit

Touto roční dobou, kdy je zem pokrytá barevným listím a prsty křehnou v mrazivých ránech, se obvykle těšíme na zbrusu novou verzi RAD Studia. Letos si však ale budeme muset počkat na Godzillu a Linux až do jara. Vezměme tedy za vděk alespoň updatem 2 a jelikož dle vyjádření pánů z Embarcadero se budou nové věci objevovat průběžně, pojďme se na to tedy podívat.

Obrázek ke článku Konference: Moderní datová centra pro byznys dneška se koná už 24. 11.

Konference: Moderní datová centra pro byznys dneška se koná už 24. 11.

Stále rostoucí zájem o cloudové služby i maximální důraz na pružnost, spolehlivost a bezpečnost IT vedou k výrazným inovacím v datových centrech. V infrastruktuře datových center hraje stále významnější roli software a stále častěji se lze setkat s hybridními přístupy k jejich budování i provozu.

Obrázek ke článku Konference: Mobilní technologie mají velký potenciál pro byznys

Konference: Mobilní technologie mají velký potenciál pro byznys

Firmy by se podle analytiků společnosti Gartner měly  rychle přizpůsobit skutečnosti, že mobilní technologie už zdaleka nejsou horkou novinkou, ale standardní součástí byznysu. I přesto - nebo možná právě proto - tu nabízejí velký potenciál. Kde tedy jsou ty největší příležitosti? I tomu se bude věnovat již čtvrtý ročník úspěšné konference Mobilní řešení pro business.

loadingtransparent (function() { var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; po.src = 'https://apis.google.com/js/plusone.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s); })();
Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032016 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý