2 14 Karty i arkusze właściwości (2)


Rozdział 14.
Karty i arkusze właściwości


W tym rozdziale:

Klasy CPropertySheet oraz CPropertyPage
Modalne i niemodalne arkusze właściwości
Usuwanie standardowych przycisków klasy CPropertySheet
Zmiana pozycji standardowych przycisków klasy CPropertySheet
Włączanie i wyłączanie zakładek CPropertyPage
Zmiana tytułu i czcionki zakładki
Wykorzystanie mnemonik na zakładkach CPropertyPage


Okna dialogowe z zakładkami z pewnością nie są niczym dziwnym dla kogokolwiek, kto korzystał z któregoś z 32-bitowych systemów operacyjnych Windows. Używając Windows raczej trudno jest nie natknąć się na któryś z tych dialogów. Okna dialogowe tego typu pojawiają się nie tylko w aplikacjach systemowych, ale także w wielu popularnych aplikacjach takich jak Word for Windows, Excel czy choćby Visual Studio. Dialogi z zakładkami składają się z okien dialogowych zawierających jedną lub kilka zakładek poszczególnych kart. Zwykle są one stosowane w sytuacjach, gdy aplikacja musi wyświetlić tyle informacji, że użycie pojedynczego dialogu byłoby niepraktyczne. Korzystając z osobnych kart, aplikacja może pogrupować różne opcje według funkcji i umieścić każdą grupę na osobnej karcie. Rysunek 14.1 przedstawia przykład okna dialogowego z zakładkami, występującego w pakiecie Visual Studio.
Ten dialog jest doskonałym wytłumaczeniem sensu istnienia okien dialogowych z zakładkami. Zamiast przedzierać się przez serie różnych dialogów w celu dostosowania różnych elementów Visual Studia, programista ma do czynienia z jednym oknem zawierającym wszystkie dostosowywalne elementy pakietu. Każda zakładka oznacza grupę logicznie powiązanych ze sobą opcji.
Gdy pojawiło się MFC 3.0 (Visual C++ 2.0), wsparcie w tworzeniu okien dialogowych z zakładkami było jedną z najważniejszych zmian w bibliotece. Jednak od tego czasu nazwa okna dialogowe z zakładkami została zamieniona na arkusze właściwości (ang. property sheet) oraz karty właściwości (ang. property page). Arkusze właściwości reprezentują sam dialog, zaś karty właściwości reprezentują poszczególne zakładki. Arkusze i karty właściwości są zaimplementowane w postaci klas CPropertySheet oraz Cproper-tyPage, i właśnie na tym skupimy się w tym rozdziale.
W tym rozdziale poznasz informacje na temat tych dwóch klas MFC oraz nauczysz się sposobu ich implementacji zarówno w modalnych, jak i niemodalnych arkuszach właściwości. Gdy już poznasz różne opcje i sposoby implementacji tych klas, przedstawimy przykładowy program demonstrujący różne odmiany zakładek. Stopniowo poznając przykładowy program, nauczysz się wykonywania wielu czynności związanych z oprogramowaniem arkuszy i kart właściwości. Do omawianych zagadnień będą należeć funkcje arkusza właściwości, takie jak usuwanie, przemianowywanie oraz przemieszczanie standardowych przycisków arkusza. Oprócz tego zajmiemy się zagadnieniami kart właściwości, takimi jak zmiana tytułu i czcionki zakładki oraz implementacja mnemonik na zakładkach.
Klasy CPropertySheet oraz CPropertyPage
Jak już wspominaliśmy, obiekty CPropertySheet reprezentują arkusze właściwości, czyli okna dialogowe z zakładkami. Jednak co ciekawe, klasa CPropertySheet nie została wyprowadzona z klasy CDialog, lecz bezpośrednio z klasy cwnd. Jednak mimo to funkcje używane do zarządzania obiektami klasy CPropertySheet są bardzo podobne do funkcji używanych do zarządzania obiektami CDialog. Na przykład, w obu przypadkach konstrukcja obiektu przebiega w dwóch krokach. Modalne arkusze właściwości i modalne okna dialogowe są wyświetlane przez skonstruowanie obiektu, a następnie wywołanie jego funkcji składowej DoModal (). Niemodalne arkusze właściwości i niemodalne okna dialogowe są wyświetlane przez skonstruowanie obiektu, a następnie wywołanie jego funkcji Create () .
RozdziaÅ‚ 14. • Karty i arkusze wÅ‚aÅ›ciwoÅ›ci
353
Jednak mimo podobieństw w wyglądzie i funkcjach używanych do wyświetlania, z punktu widzenia MFC arkusze właściwości i okna dialogowe są czymś zupełnie różnym. Na przykład, obiekt dialogu zarządza danymi przeglądanymi w tym dialogu. Dla odróżnienia, dane przeglądane na arkuszu właściwości są zwykle zarządzane przez poszczególne karty właściwości. Ponieważ do reprezentacji każdej z zakładek są stosowane różne karty, które zarządzają danymi wyświetlanymi na karcie, to właśnie klasa CPropertyPage jest wyprowadzona z klasy CDialog.
Tworzenie zasobu karty właściwości
Tworzenie zasobu karty właściwości jest bardzo podobne do tworzenia zasobów szablonów dialogów. W obu przypadkach służy do tego edytor dialogów, umożliwiający rozmieszczenie kontrolek. Jednak ustawienia karty właściwości mają pewne ograniczenia w stosunku do ustawień szablonów dialogów:
Zawartość pola Caption pojawia się na zakładce, a nie na belce tytułowej arkusza właściwości.
Opcją Style karty właściwości musi być Child.
Opcją Border karty właściwości musi być Thin.
Musi być zaznaczona opcja Disabled karty właściwości.
Na szczęście, nie trzeba o tym pamiętać za każdym razem, gdy tworzy się karty właściwości, gdyż pamięta o tym okno dialogowe Insert Resource. Po wciśnięciu kombinacji Ctrl+R pojawia się okno dialogowe pokazane na rysunku 14.2. Jak widać, jedyna różnica w dostępnych rodzajach kart właściwości polega na innym początkowym rozmiarze karty. Jeśli karta właściwości jest tworzona za pomocą tego okna dialogowego, wymagane opcje są ustawiane automatycznie.
Tworzenie klasy CPropertyPage
Gdy za pomocą edytora zasobów zostanie utworzony szablon karty właściwości, używając ClassWizarda można stworzyć klasę wyprowadzoną z klasy CPropertyPage. Także tym razem odbywa się to podobnie jak przy tworzeniu klasy wyprowadzonej z klasy CDialog. Jedyna różnica polega na tym, że zamiast bazowej klasy CDialog musisz wybrać klasę CPropertyPage. I tu uwaga. Jeśli w celu stworzenia nowej klasy dla zasobu szablonu dialogu przywołasz ClassWizarda, domyślną klasą bazową zawsze będzie klasa CDialog. Niestety, ClassWizard nie analizuje ustawień szablonu dialogu w celu sprawdzenia, czy od czasu do czasu poprawną klasą bazową nie byłaby klasa CPropertyPage.
Tworzenie i wyświetlanie modalnego arkusza właściwości
Jak już wspominaliśmy, klasa CPropertySheet nie jest wyprowadzona z klasy CDialog, mimo że obiekty CPropertySheet są konstruowane i wyświetlane w ten sam sposób co obiekty klasy CDialog. Na przykład, aby utworzyć i wyświetlić modalny arkusz właściwości, zwykle zadeklarujesz na stosie obiekt klasy CPropertySheet po czym wywołasz jego metodę DoModa1 () (tak jak w przypadku obiektów CDialog). Jednak w przypadku arkusza właściwości przed wywołaniem funkcji DoModal () musisz dodać do niego także odpowiednie karty właściwości.
Poniżej podajemy przykład przeprowadzenia tej operacji. Jak widać, obiekty CPropertyPage są dodawane do obiektu CPropertySheet za pomocą funkcji CPropertySheet: :AddPage() :
void ShowModalPropertySheet() {
CPropertySheet sheet;
CMyPropertyPagel pageMyPage1;
CMyPropertyPage2 pageMyPage2;
sheet.AddPage(spageMyPagel); sheet.AddPage(&pageMyPage2); sheet.DoModal();
}
Tworzenie i wyświetlanie niemodalnego arkusza właściwości
Wyświetlanie niemodalnego arkusza właściwości jest prawie identyczne z wyświetlaniem arkusza modalnego. Główna różnica polega na tym, że zamiast funkcji DoModal () używamy funkcji CPropertySheet: :Create(). Oprócz tego, operując niemodalnymi arkuszami i kartami właściwości, powinieneś pamiętać o kilku dodatkowych zagadnieniach.
Modalne arkusze właściwości są wyświetlane synchronicznie, co oznacza, że gdy jest wywoływana funkcja DoModal () arkusza właściwości, sterowanie nie jest zwracane aż do momentu powrotu z tej funkcji, czyli do momentu zamknięcia arkusza właściwości przez użytkownika. W związku z tym obiekty arkusza i kart właściwości mogą być utworzone na stosie funkcji wywołującej funkcję DoModal ().
Niemodalne arkusze właściwości są wyświetlane w sposób asynchroniczny, gdyż natychmiast po wywołaniu funkcji wyświetlającej sterowanie wraca do funkcji wywołującej, podczas gdy arkusz właściwości, wciąż jest widoczny. Tak więc operując niemodalnymi arkuszami właściwości należy położyć dużą uwagę na to kiedy i gdzie mają być alokowane obiekty arkusza i kart. Z tego powodu obiekty arkusza i kart są zwykle tworzone na stercie, tak że gdy funkcja wywołująca zakończy działanie, obiekty w dalszym ciągu istnieją. Oto przykład tworzenia i wyświetlania niemodalnego arkusza właściwości wraz z zawartymi na nim kartami:
void CMyView::DisplayPropertySheet()
{
m pPropertySheet = new CMyPropertySheet();
CMyPropertyPage1*ppageMyPagel= new CMyPropertyPage1 ()
CMyPropertyPage2*ppageMyPage2= new CMyPropertyPage2 ()
m_pPropertySheet->AddPage(ppageMyPage1); m_pPropertySheet->AddPage(ppageMyPage2) ; m pPropertySheet->Create();
}
W jaki jednak sposób obiekty kart właściwości są niszczone? Zwykle odbywa się to przez wyprowadzenie klasy z klasy CPropertySheet i przesłonięcie jej wirtualnej funkcji PostNcDestroy ( ) . W ten sposób, gdy arkusz właściwości jest niszczony, można przejść przez listę kart właściwości, kolejno niszcząc każdą z nich. Poniżej podajemy przykład takiej operacji. Jak widać, funkcja GetPageCount () zwraca liczbę kart dodanych do obiektu CPropertySheet. Ponieważ obiekty CPropertyPage są przechowywane w tablicy wewnątrz obiektu CPropertySheet, funkcja GetPage () zwraca wskaźnik do obiektu CPropertyPage na podstawie indeksu do pozycji tablicy:
CMyPropertySheet : : PostNcDestroy ( ) {
for( int 1=0; i < GetPageCount ();
{
CPropertyPage *pPage = GetPage(i);
ASSERT (pPage) ; if (pPage) ; {
delete page;
}
}
CPropertySheet::PostNcDestroy();
}
Kolejna różnica pomiędzy modalnymi a niemodalnymi arkuszami właściwości polega na tym, że w odróżnieniu od arkuszy modalnych arkusze niemodalne nie posiadają automatycznie przycisków OK i Anuluj; program musi je sam stworzyć. Omówimy to w następnej sekcji, w której nieco bardziej szczegółowo zajmiemy się klasą cproperty-Sheet.
Tworzenie i wyświetlanie arkusza właściwości wewnątrz istniejącego dialogu
Jak dotąd, wiesz już, jak tworzyć zarówno modalne, jak i niemodalne arkusze właściwości. Czasem jednak zdarza się, że chcesz wyświetlić arkusz właściwości wewnątrz jakiegoś okna dialogowego. Jeśli faktycznie potrzebujesz tego raczej zaawansowanego rozwiązania interfejsu użytkownika, możesz to osiągnąć, wykonując kilka łatwych kroków.
Po pierwsze, zadeklaruj obiekty CPropertySheet i CPropertyPage jako zmienne składowe klasy dialogu.
class CVourDialog : public CDialog
{
...
protected:
CMyPropertySheet m_sheet; CMyPropertyPagel m_pageMyPagel; CMyPropertyPage2 m pageMyPage2;
...
Następnie w funkcji OnInitDialog () klasy okna dialogowego wywołaj funkcje CPropei-tySheet: :AddPage () w celu dodania obiektów CPropertyPage. Następnie stwórz arkusz właściwości (za pomocą znacznika WS_CHILD, określając go jako okno potomne dialogu). Po stworzeniu obiektu CPropertySheet musisz zmodyfikować kilka stylów odnoszących się do korzystania z zakładek. Ostatnim krokiem jest umiejscowienie arkusza w oknie dialogowym za pomocą funkcji SetwindowPos (). Oto jak mogłaby wyglądać Twoja funkcja OnlnitDialog () :
void CYourDialog::OnlnitDialog()
{
,,,
m_sheet.Create(this, WS_CHILD | WS_VISIBLE, 0); m_sheet.ModifyStyleEx(O, WS_EX_CONTROLPARENT); m_sheet.ModifyStyle(O, WS_TABSTOP); CRect rect;
GetDlgItem(IDC_PROPSHEET)->GetWindowRect(&rect); ScreenToClient(&rect);
m_sheet.SetwindowPos(NULL, rect.left-7, rect.top-7, O, O, SWP_ NOZORDER | SWP_ NOSIZE | SWP NOACTIYATE);
Program demonstrujący modalny arkusz właściwości
Ten program (ModalDemó} ilustruje wiele aspektów korzystania z modalnych arkuszy właściwości. W tym przypadku spróbujemy stworzyć okno dialogowe wyszukiwania. Jest ono podobne do stosowanego w wielu aplikacjach, z jednym wyjątkiem: po zakończeniu wyszukiwania wyniki są wyświetlane na drugiej zakładce. Dzięki temu możemy przeprowadzać wyszukiwania zwracające więcej niż jeden element.
Pełny kod źródłowy projektu ModalDemó znajduje się na dołączonej do książki płytce CD-ROM, w folderze Rozdzl4\ModalDemo.
Dzięki temu przykładowemu programowi będziesz umiał obsłużyć wiele realistycznych scenariuszy związanych z programowaniem arkuszy właściwości. Na przykład, w dialogu wyszukiwania nie potrzebujesz karty wyników, jeśli nie zostały znalezione żadne pozycje. Tak więc, gdy uruchomisz ten podstawowy arkusz właściwości, będziesz wiedział także, jak włączać i wyłączać poszczególne zakładki. Oprócz tego, użytkownikom z pewnością spodoba się pomysł wyświetlania ilości trafień na zakładce karty. Aby to osiągnąć, musisz nauczyć się dynamicznie zmieniać tytuł zakładki.
Głównym celem programu jest nauczenie cię różnych aspektów korzystania z klas CPropertysheet oraz CPropertyPage. Tak więc wiele elementów programu zostanie uproszczonych, tak aby nie zajmować się elementami nie mającymi nic wspólnego z arkuszami i kartami właściwości. A oto jak możesz stworzyć tę aplikację:
1. Za pomocą AppWizarda stwórz nową aplikację MFC SDI o nazwie ModalDemó.
2. Stwórz zasób karty właściwości o identyfikatorze IDP_FINDBOOK_FIND i dodaj niezbędne kontrolki tak, aby karta wyglądała podobnie do karty właściwości z rysunku 14.3. Możesz zmienić rozmiary karty tak, aby móc wygodnie rozmieścić kontrolki. Oprócz tego, pamiętaj, by wyłączyć pola edycji Autor i Wydawca.
3. Gdy już rozmieścisz kontrolki w szablonie dialogu IDP_FINDBOOK_FIND, użyj ClassWizarda do stworzenia klasy CFindPage będącej klasą pochodną klasy
CPropertyPage.
4. Następnie stwórz zmienne składowe DDX dla rozwijanej listy Język oraz przycisku Znajdź. Nazwij je odpowiednio m_cboLanguage i m_btnFind.
5. Po ukończeniu karty właściwości Znajdź stwórz drugą kartę o identyfikatorze IDP_FINDBOOK_RESULTS, po czym zmodyfikuj ją tak, by wyglądała jak karta z rysunku 14.4.
6. Także tym razem, za pomocą ClassWizarda stwórz klasę CResultsPage wyprowadzoną z klasy CPropertyPage.
7. Gdy ClassWizard stworzy klasę CResultsPage, stwórz dwie zmienne składowe DDX dla kontrolki listy i przycisku. Nazwij je odpowiednio m_istResuits
i m_btnGoto.
8. Dodaj do zasobów łańcuch znaków o identyfikatorze IDS_FIND. Jego wartością powinien być napis Znajdź książkę. Ten łańcuch zostanie przekazany do konstruktora klasy CPropertySheet.
9. Po stworzeniu obu kart właściwości znajdź zasób menu IDR_MAINFRAME i do menu Edit dodaj nową pozycję Znajdź książkę.
10. Za pomocą ClassWizarda stwórz w klasie CMainFrame funkcję obsługi nowej pozycji menu. Gdy skończysz, jej kod powinien wyglądać następująco:
void CMainFrame::OnEditFindbook() {
CPropertySheet sheet(IDS_FIND);
CFindPage pageFind;
CResultsPage pageResults;
sheet.AddPage(spageFind) ; sheet.AddPage(spageResults) ; sheet.DoModal();
}
W tym kodzie tworzymy na stosie obiekt klasy CPropertySheet, używając przy tym konstruktora z jednym tylko argumentem, identyfikatorem zasobu łańcucha znaków. Następnie tworzymy dwa obiekty kart właściwości, wyprowadzone z klasy CPropertyPage. Po stworzeniu kart, za pomocą funkcji CPropertySheet: : AddPage (), dodajemy je do obiektu arkusza właściwości. Na koniec wywołujemy funkcję CPropertySheet::DoModal o w celu wyświetlenia całego arkusza właściwości.
11. Ostatnią rzeczą do zrobienia przed uruchomieniem programu jest dodanie na początku pliku mainfrm.cpp dwóch dyrektyw #include dla dwóch stworzonych przez nas klas kart właściwości:
#include "FindPage.h"
#include "ResultsPage.h"
12. W tym momencie zbuduj i uruchom aplikację. Gdy w menu Edit wybierzesz polecenie Znajdź książkę, Twój modalny arkusz właściwości powinien wyglądać tak jak na rysunku 14.5.
Karty i arkusze właściwości: rady i techniki
Jak widziałeś, stworzenie i wyświetlenie zarówno modalnego, jak i niemodalnego arkusza właściwości jest bardzo łatwe. Niestety, na tym kończy się pomoc dokumentacji dostarczanej wraz z Visual C++ oraz większości książek. Jednak w końcu nie czytasz tego rozdziału tylko po to, by poznać zagadnienia mieszczące się na kilku stronach. Przysiądźmy więc fałd i zacznijmy robić coś, co rzeczywiście odnosi się do kart i arkuszy właściwości.
Pamiętaj, że ta sekcja wiąże się z dokonywaniem zmian w naszym demonstracyjnym programie ModalDemo. Dopóki wyraźnie tego nie zaznaczymy, wszystkie rady i techniki zaprezentowane w tej sekcji będą odnosić się zarówno do modalnych, jak i niemodalnych arkuszy właściwości.
Usuwanie standardowych przycisków
Jak już widziałeś, gdy jest wyświetlany modalny arkusz CPropertySheet, automatycznie tworzonych jest kilka przycisków. Mogą jednak wystąpić sytuacje, w których aplikacja nie potrzebuje ich wszystkich. Na przykład, przycisk Zastosuj z pewnością nie nadaje się dla wszystkich aplikacji. Tak więc pytanie brzmi: jeśli przyciski są wyświetlane automatycznie, w jaki sposób programista może je usunąć? Jak się za chwilę przekonasz, usunięcie któregoś ze standardowych przycisków arkusza właściwości nie jest trudne.
Załóżmy, że chcesz usunąć przyciski Anuluj i Zastosuj. Użytkownicy używają przycisku Anuluj wtedy, gdy zmieniają zdanie i chcą wyjść z okna dialogowego bez dokonywania zmian. Przycisk Zastosuj daje użytkownikom możliwość zastosowania zmian i pozostania w dalszym ciągu na arkuszu właściwości. Innymi słowy, używając zwykłego okna dialogowego zakładasz, że wprowadzone w nim zmiany wejdą w życie po zamknięciu okna. Jednak w przypadku arkuszy właściwości przycisk Zastosuj daje użytkownikowi możliwość sprawdzenia efektu zmian, pozostając wciąż na arkuszu. Dzięki temu użytkownik może dalej wprowadzać zmiany i, zależnie od aplikacji, sprawdzać ich efekt, przechodząc przez kolejne karty arkusza.
Jednak nasz program dotyczy arkusza właściwości wyszukiwania i ponieważ nie są zmieniane żadne dane, żaden z tych przycisków nie jest nam potrzebny. Pytanie brzmi: jak usunąć te przyciski, gdy nie mamy zasobu szablonu dialogu dla samego arkusza właściwości?
Ponieważ MFC tworzy standardowe przyciski automatycznie, są one tworzone ze statycznymi, niezmiennymi identyfikatorami zasobów. Te identyfikatory zostały zebrane w tabeli 14.1.
Tabela 14.1. Identyfikatory zasobów dla przycisków
Przycisk
Identyfikator zasobu
OK
IDOK

Anuluj (Cancel)
IDCANCEL
Pomoc (Help)
IDHELP
Zastosuj (Apply)
ID_APPLY_NOW

Aby usunąć któryś ze standardowych przycisków, musisz zastosować poniższy kod (gdzie id jest jednym z identyfikatorów z tabeli 14.1):
CWnd *pWnd = GetDlgltem(id);
if(pWnd)
{
pWnd->ShowWindow(FALSE);
}
Tak więc, aby usunąć przyciski Anuluj i Zastosuj z naszego przykładowego arkusza właściwości, musisz dokonać w kodzie następujących zmian:
1. Używając ClassWizarda, stwórz klasę CFindSheet wyprowadzoną z klasy CPropertySheet.
2. W pliku mainfrm.cpp wpisz dyrektywÄ™ #include dla pliku FindSheet.h.
3. Zmodyfikuj funkcję CMainFrame: :OnEditFindbook ( ) tak, aby zamiast obiektu CPropertySheet tworzyć obiekt CFindSheet.
4. Używając ClassWizarda, przesłoń funkcję CFindSheet :: OnlnitDialog () tak, aby ukryć niechciane przyciski (IDCANCEL i ID_APPLY_NOW).
BOOL CFindSheet: : OnlnitDialog () {
BOOL bResult = CPropertySheet: : OnlnitDialog () ;
int ids[] = {ID_APPLY_NOW, IDCANCEL};
for (int i = 0; i < sizeof ids / sizeof ids[0]; i++)
{
CWnd* pWnd = GetDlgItem(ids [i] ) ;
ASSERT (pWnd) ;
if (pWnd) pWnd->ShowWindow(FALSE) ;
}
return bResult;
}
Gdy uruchomisz aplikację, zauważysz jeszcze jeden problem związany ze standardowymi przyciskami. Jedyny pozostały przycisk (IDOK) wciąż pozostaje osamotniony na środku okna. W następnej sekcji spróbujemy przesunąć go w inne miejsce.
Zmiana położenia standardowych przycisków
W tej chwili nasz arkusz właściwości wygląda nieco dziwnie, gdyż posiada jeden przycisk, ale za to występujący w połowie szerokości okna. Oprócz tego, obie karty właściwości posiadaj ą przyciski ułożone po prawej stronie. Tak więc, nasz arkusz z pewnością wyglądałby lepiej, gdyby przycisk OK także znalazł się po prawej stronie arkusza. Niestety, nie istnieje pojedyncza funkcja składowa umożliwiająca rozmieszczanie przycisków, musimy więc posłużyć się tradycyjną metodą korzystającą z funkcji MoveWindow (). W tym celu do funkcji OnInitDialog () dodamy kod z listingu 14.1.
Listing 14.1. Przemieszczanie przycisków na arkuszu właściwości__________________
CWnd* pbtnOk = GetDlgltem(IDOK) ; ASSERT(pbtnOk);

CRect rectSheet; GetWindowRect(rectSheet);

// Pobieramy rozmiary przycisku OK CRect rectOkBtn; pbtnOk->GetWindowRect(rectOkBtn);

// Pobieramy odstęp pomiędzy dolną krawędzią przycisku a
// dolną krawędzią arkusza
int iBorder = rectSheet.bottom - rectOkBtn.bottom;

// Zmieniamy rozmiar arkusza
rectSheet.right += rectOkBtn.Width() + iBorder;
rectSheet.bottom = rectOkBtn.top;
MoveWindow(rectSheet);

// Znajdujemy pierwszÄ… kartÄ™ CPropertyPage* pPage = GetPage(O); ASSERT(pPage); CRect rectPage; pPage->GetWindowRect(rectPage);

// Zapamiętujemy szerokość i wysokość int cxOk = rectOkBtn.Width(); int cyOk = rectOkBtn.Height();
// Przenosimy przycisk OK rectOkBtn.top = rectPage.top; rectOkBtn.bottom = rectOkBtn.top +cyOk;
rectOkBtn.left = rectSheet.right -
(cxOk + iBorder);
rectOkBtn.right = rectOkBtn.left +cxOk;
ScreenToClient(rectOkBtn); pbtnOk->MoveWindow(rectOkBtn);
Oto kilka wyjaśnień dotyczących tego kodu. Najpierw tworzymy obiekt klasy cwnd (pbtnOK) reprezentujący przycisk OK. Następnie zmniejszamy wysokość arkusza o wysokość przycisku i poszerzamy arkusz o szerokość przycisku. Po zmianie rozmiarów arkusza wykorzystujemy położenie pierwszej karty właściwości do prawidłowego wyrównania przycisku OK z tą kartą.
Po wstawieniu kodu z listingu 14.1 do funkcji CFindSheer : :0ninitoiaiog () zbuduj projekt i uruchom program. Powinieneś ujrzeć okno podobne do okna z rysunku 14.6.
Zmiana tytułów standardowych przycisków
Tytuł przycisku powinien opisywać akcję, która nastąpi po kliknięciu przycisku. Gdy ktoś użyje naszego dialogu wyszukiwania i ujrzy przycisk OK, z pewnością nie będzie pewien, co się stanie, gdy go kliknie. Czy kliknięcie tego przycisku spowoduje zamknięcie dialogu, czy też raczej rozpoczęcie wyszukiwania? Tak więc, naszym następnym zadaniem będzie zmiana napisu na standardowym przycisku arkusza właściwości.
Ponieważ obiekt CPropertySheet i jego standardowe przyciski są tworzone dynamicznie, zmiana tytułu przycisków musi odbywać się w kodzie aplikacji. Poniższe dwie linie kodu to wszystko czego potrzeba do zmiany tytułu przycisku OK (łącznie z ustawieniem mnemoniki). Także w tym przypadku możemy użyć po prostu identyfikatora zasobu przycisku, który chcemy zmienić.
CWnd* pWnd = GetDlgItem(IDOK); pWnd->SetWindowText(T("Z&amknij"));
Wyłączanie zakładek
Czasem zdarza się, że konieczne jest wyłączenie poszczególnych zakładek, na przykład wtedy, gdy przed przejściem do dalszych kart konieczne jest wypełnienie pewnych pól dialogu. Niestety, w momencie gdy piszę te słowa, nie istnieje elegancki sposób wyłączenia zakładki. Tak więc podamy jedynie przepis zawierający kroki wymagane do wyłączenia karty właściwości. Po ogólnym przepisie przejdziemy do szczegółowego omówienia każdego z kroków.
1. Stwórz zmienną składową w celu przechowania indeksu bieżącej zakładki.
2. Jako zmienną składową stwórz tablicę do przechowania wszystkich indeksów aktualnie wyłączonych zakładek.
3. Obsłuż komunikat powiadamiania TCN_SELCHANGING w celu ustawienia indeksu bieżącej zakładki.
4. Obsłuż komunikat powiadamiania TCN_SELCHANGED w celu zdecydowania, czy chcesz umożliwić uaktywnienie zakładki. Jeśli nie, musisz przesłać komunikat PSM_SETCURSEL. W tym komunikacie jest przekazywany indeks ostatniej aktywnej zakładki.
Tworzenie zmiennej składowej
w celu przechowania indeksu bieżącej zakładki
Dodaj do klasy CFindSheet zmienną składową, w której znajdzie się indeks bieżącej zakładki. Gdy użytkownik spróbuje skorzystać z wyłączonej zakładki, przechowywana wartość będzie stanowiła indeks zakładki, którą należy uaktywnić. Dzieje się to tak szybko, że użytkownik zauważy jedynie, że nie może się przełączyć do nieużywanej zakładki.
protected:
int m_iLastActivePage;
Tworzenie zmiennej składowej
do przechowania indeksów aktualnie wyłączonych zakładek
Następnym krokiem jest dodanie do klasy CFindSheet zmiennej składowej typu cuintAr-ray. Ta tablica będzie zawierać indeksy wszystkich aktualnie wyłączonych kart arkusza:
CUIntArray m_arrDisabledPages;
Dodanie funkcji składowych w celu wyłączenia określonych zakładek
Następnie do klasy CFindSheet dodaj deklarację funkcji, która umożliwi wyłączenie zakładki (listing 14.2). Karty właściwości, które mają zostać wyłączone, są przekazywane na zmiennej liście argumentów zakończonej standardową wartością -1. Ta funkcja umieszcza otrzymane indeksy w tablicy m_arrDisabiedPages. Następnie jest wywoływana funkcja SetDisabledtext () dopisująca do tytułu zakładki końcówkę wyłączona.
Listing 14.2. Implementacja funkcji wyłączającej określone zakładki________ ____
void CFindSheet::DisablePage(int iFirstPage, ...) {
int iPage = iFirstPage;
va_list marker;
va_start (marker, iFirstPage);
int nArgs = 0;
while (iPage != -1) {
// dodajemy kartę do tablicy indeksów wyłączonych kart
m_arrDisabledPages.Add(iPage);
SetDisabledText(iPage);
// Pobieramy indeks następnej karty iPage = va_arg(marker, UINT);
// Lista MUSI kończyć się wartością -l !! ASSERT(nArgs++ < 100);
}
}
Oprócz powyższej pracy związanej ze zwykłym wyłączeniem zakładki nie ma także sposobu zmiany czcionki zakładki wybranej karty. Innymi słowy, nie możesz "wyszarzyć" tekstu zakładki tak, aby użytkownik od razu wiedział, że jest ona wyłączona. W związku z tym Microsoft zaleca, by po prostu dodawać do tytułu zakładki napis - Wyłączona. Dziwne? Z pewnością. Niestety, w tym momencie to wszystko co można zrobić.
Dodanie funkcji składowej ustawiającej tytuły wyłączonych zakładek
Gdy dodasz funkcję DisablePage (}, dopisz również poniższą dyrektywę #def ine oraz funkcję SetDisabledText {). Ta funkcja dołącza wartość DISABLED_TEXT na koniec tytułu wskazanej zakładki, dzięki czemu użytkownik wie, że zakładka nie jest dostępna.
ttdefine DISABLED TEXT
Wyłączona"
void CFindSheet::SetDisabledText(int iPage) {
CTabCtrł* pTab = GetTabControl();
ASSERT(pTab);
TC_ITEM t i;
char szText[100];
ti.mask = TCIF_TEXT;
ti.pszText = szText;
ti.cchTextMax = 100;
VERIFY(pTab->GetItem(iPage, &ti));
strcat (szText, DISABLED_TEXT);
VERIFY(pTab->SetItem(iPage, &ti));
}
Obsługa komunikatów powiadamiania dla wyłączonych zakładek
Gdy już masz wszystko potrzebne, aby wiedzieć, czy zakładka ma być wyłączona, musisz jeszcze dodać funkcję, która anulowałaby uaktywnianie przez użytkownika wyłączonej zakładki. Użyj ClassWizarda do zaimplementowania wirtualnej funkcji OnNotifyO i wpisz do niej kod z listingu 14.3.
Listing 14.3. Implementacja wirtualnej funkcji OnNotifyO______________________
BOOL CFindSheet: -.OnNotif y (WPARAM wParam, LPARAM IParam,
LRESULT* pResult) {
NMHDR* pnmh = (NMHDR*)IParam;
ASSERT(pnmh);
if (TCN_SELCHANGING == pnmh->code) {
m_iLastActivePage = GetActiveIndex();
}
else if (TCN_SELCHANGE == pnmh->code) { int iCurrPage = GetActiveIndex(); if (IsPageDisabled(iCurrPage)) {
PostMessage(PSM_SETCURSEL, m_iLastActivePage)
}
}
return CPropertySheet::OnNotify(wParam, iParam, pResult);
}
Jak już wspominaliśmy, za każdym razem gdy użytkownik spróbuje uaktywnić kartę właściwości, ta funkcja sprawdzi, czy dana karta jest wyłączona. Jeśli tak, przesyłając komunikat PSM_SETCURSEL do obiektu reprezentującego arkusz właściwości, funkcja OnNotify () zamieni aktualną kartę właściwości na kartę ostatnio aktywną.
Dodanie funkcji sprawdzającej, czy zakładka jest wyłączona
Dodaj funkcję z listingu 14.4 sprawdzającą, czy dana karta właściwości jest wyłączona. Funkcja po prostu przechodzi przez tablicę m_arrDisabledPages i zwraca wartość BOOL określającą, czy karta jest wyłączona.
Listing 14.4. Sprawdzanie, czy dana karta właściwości jest wyłączona
BOOL CFindSheet::IsPageDisabled(int iPage) {
BOOL bFoundEntry = FALSE;
int iSize = m_arrDisabledPages.GetSize();
int i = 0;
while (i < iSize && !bFoundEntry)
{
if (m_arrDisabledPages.GetAt(i) == (UINT)iPage) { bFoundEntry = TRUE;
}
else {
i++
}
}
return bFoundEntry;
}
Testowanie możliwości wyłączenia zakładki
Arkusz właściwości wyszukiwania posiada kartę dla wprowadzania kryteriów wyszukiwania oraz kartę właściwości wyświetlającą wyniki wyszukiwania. Logiczne więc jest, że karta wyników powinna być wyłączona do momentu zainicjowania przez użytkownika wyszukiwania, które z powodzeniem znajdzie książki spełniające kryteria podane na karcie wyszukiwania.
Tak więc znajdź funkcję CFindSheet: : oninitDialog () i umieść poniższą linię kodu tuż przed instrukcją return. Zwróć uwagę, że stosując funkcję ze zmienną liczbą argumentów,
musisz przekazać wartość stanowiącą znacznik końca listy argumentów. W tym przypadku zastosujemy standardową wartość -1. Przetestowanie i uruchomienie aplikacji powinno dać arkusz właściwości, w którym karta wyników jest niedostępna. Ponieważ chcesz ponownie uaktywnić zakładkę po rozpoczęciu wyszukiwania, następną sekcję poświęcimy właśnie temu zagadnieniu.
DisablePage (l, -1);
Ponowne włączanie kart właściwości
Gdy umiesz już wyłączyć karty właściwości, musisz także umieć je ponownie włączyć. W tym celu po prostu dodaj przedstawione dalej funkcje włączające daną kartę oraz usuwające z tytułu napis - Wyłączona.
Funkcja EnablePage
Ponieważ mamy już kod dodający indeks wyłączonej karty do tablicy (m_arrDisabied-Pages), funkcja EnablePage () po prostu musi sprawdzić, czy w tej tablicy znajduje się indeks danej karty (listing 14.5). Jeśli karta zostanie znaleziona, jest po prostu usuwana z tablicy i następuje wywołanie funkcji setEnabiedText f).
Listing 14.5. Wyszukiwanie karty w tablicy m_arrDisabledPages__________________
void CFindSheet::EnablePage(int iPage)
{
BOOL bFoundEntry = FALSE;
int iSize = m_arrDisabledPages.GetSize();

int i = 0;
while (i < iSize && !bFoundEntry)
{
if (m_arrDisabledPages.GetAt(i) == (UINT)iPage)
{ bFoundEntry = TRUE;
}
else {
i++
}
}
if (bFoundEntry)
{
m_arrDisabledPages.RemoveAt(i);
SetEnabledText(iPage);
}
}
Funkcja SetEnabledText
Gdy karta zostanie ponownie włączona, konieczne jest wywołanie tej funkcji w celu usunięcia napisu - Wyłączona z tytułu zakładki karty. Ta funkcja jest wywoływana przez funkcję EnableTab () w momencie usuwania indeksu karty z tablicy m_arrDisabledPages:
void CFindSheet::SetEnabledText(int iPage) {
CTabCtri* pTab = GetTabControl ();
ASSERT(pTab);
TC_ITEM ti;
char szText[100];
ti.mask = TCIF_TEXT;
ti.pszText = szText;
ti.cchTextMax = 100;
VERIFY(pTab->GetItem(iPage, &ti) ) ;
char* pFound = strstr(szText, DISABLED_TEXT);
if (pFound)
{
*pFound = '\Q';
VERIFY(pTab->Set!tem(iPage, &ti) );
}
}
Testowanie funkcji EnableTab
Aby sprawdzić możliwość ponownego włączania kart, użyj ClassWizarda do dodania do klasy CFindPage funkcji obsługi komunikatu pochodzącego od przycisku Znajdź. Funkcja pobiera wskaźnik do okna nadrzędnego karty (okna arkusza właściwości) i wywołuje jego funkcję CFindSheet: :EnablePage (). Aby skompilować funkcję, na początku pliku Find-Page.cpp musisz dodać dyrektywę #include włączającą plik nagłówkowy FindSheet.h. Oprócz tego, musisz zapewnić, że funkcja CFindSheet -.: EnablePage () będzie publiczną funkcją składową.
void CFindPage : : OnBtnFmd ( ) {
CFindSheet* pParentSheet = (CFindSheet*)GetParent();
ASSERT(pParentSheet->IsKindOf(RUNTIME_CLASS(CFindSheet)));
pParentSheet->EnablePage (1); }
Po zbudowaniu i uruchomieniu aplikacji otrzymasz arkusz, w którym zakładka wyszukiwania jest włączona, zaś zakładka wyników jest wyłączona. Jednak po kliknięciu przycisku Znajdź zakładka wyników także powinna stać się dostępna.
Dynamiczna zmiana tytułów kart właściwości
Gdy tworzyłeś zasoby szablonów dialogów dla obu kart właściwości, określałeś ich tytuły. Co się jednak stanie, gdy spróbujesz zmienić tytuł karty już w czasie działania programu? Jeśli chcesz spróbować, także i to pokażemy w naszym przykładowym programie. Ponieważ karta wyników zawiera listę znalezionych pozycji, użytkownicy z pewnością się ucieszą, jeśli na jej zakładce podamy ilość tych pozycji.
Użycie funkcji Setltem do ustawienia tytułu zakładki
Aby ustawić tytuł zakładki, powinieneś pobrać wskaźnik do kontrolki zakładki (obiektu klasy CTabCtrl), odczytać bieżący tekst (CTabCtrl: :Getitem()) a następnie ustawić go wywołaniem funkcji CTabCtrl: :Setitem(). W najprostszej postaci można to uczynić następująco:
CTabCtrl* pTab = GetTabControl();
ASSERT(pTab);
TC_ITEM t i;
char szText[100];
ti.mask = TCIF_TEXT;
ti.pszText = szText;
ti.cchTextMax = 100;
VERIFY(pTab->GetItem(INDEKS_DANEJ_ZAKLADKI, &ti));

stropy(szText, "Nowy tytuł zakładki"); VERIFY(pTab->SetItem(INDEKS_DANEJ_ZAKLADKI, &ti));
Testowanie działania funkcji Setltem
Poprzedni przykład był prosty. Co zrobić, jeśli jednak chcesz sformatować napis? Na przykład, w jaki sposób wyświetlić tekst "Wyniki: Znaleziono 5 książek"? Przyjrzyjmy się zmianom, jakich musimy w tym celu dokonać w naszej przykładowej aplikacji.
Dodaj poniższe dyrektywy #def ine na początek pliku FindSheet.cpp:
ttdefine RESULTS_TAB_INDEX l
ttdefine RESULTS_TAB_CAPTION "Wyniki: Znaleziono %ld książek"
Gdy dyrektywy znajdą się na miejscu, do klasy CFindSheet dodaj poniższe funkcje. Jak widać, funkcja SetResults () po prostu wywołuje funkcję zmieniającą tytuł zakładki wyników, a następnie na podstawie ilości znalezionych pozycji (zmienna nHits) włącza lub wyłącza tę zakładkę. W końcu dlaczego mielibyśmy włączać zakładkę wyników, jeśli nic nie zostało znalezione? ponieważ funkcja SetResults () będzie wywoływana z klasy CFindPage, zadeklaruj tę funkcję jako publiczną:
void CFindSheet::SetResults(int nHits)
{ SetResultsTabCaption(nHits); if (O == nHits)
{
DisablePage (l, -1) ;
}
else
{
EnablePage (1) ;
}
}
Po odczytaniu tytułu zakładki funkcją CTabCtrl: : Getitem () do sformatowania nowego tytułu zakładki używamy stałej RESULTS_TAB_CAPTION:
void CFindSheet::SetResultsTabCaption(int nHits) {
CTabCtrl* pTab = GetTabControl ();
ASSERT(pTab);
TC_ITEM ti;
char szText[100];
ti.mask = TCIF_TEXT;
ti.pszText = szText;
ti.cchTextMax = 100;
VERIFY(pTab->GetItem(RESULTS_TAB_INDEX, &ti))

sprintf(szText, RESULTS_TAB_CAPTION, nHits); VERIFY(pTab->SetItem(RESULTS TAB_INDEX, &ti))
}
Po dopisaniu obu powyższych funkcji musisz jeszcze tylko zaktualizować funkcję
CFindSheet: : OninitDialog (), usuwając wywołanie funkcji DisablePage () i zastępując je wywołaniem SetResults (). Nie musimy już sami wyłączać zakładki, gdyż funkcja setResuits () uczyni to automatycznie, gdy zostanie jej przekazana wartość O (oznaczająca brak znalezionych pozycji):
SetResults (0);
Aby sprawdzić działanie wyszukiwania, wpiszmy "na sztywno" kilka pozycji do listy Język na karcie właściwości wyszukiwania. Użyj ClassWizarda w celu dodania do klasy CFindPage funkcji OninitDialog () i uzupełnij j ą następująco:
BOOL CFindPage::OninitDialog() {
CPropertyPage::OninitDialog();
m_cboLanguages.AddString(_T("Yisual C++")); m_cboLanguages.AddString(_T("Yisual J++")); m_cboLanguages.SetCurSel(0) ;

return TRUE;
Na koniec, zaktualizuj funkcję CFindPage: :OnBtnFind() tak, aby "znajdowała" dwie książki po wybraniu z listy pozycji Visual C++ i zero książek po wybraniu pozycji Visual J++. Oczywiście, stosujemy to uproszczenie tylko po to, aby móc skoncentrować się wyłącznie na kodzie związanym z dynamiczną zmianą tytułu zakładki.
void CFindPage::OnBtnFind()
{
CFindSheet* pParentSheet = (CFindSheet*)GetParent(); ASSERT(pParentSheet->IsKindOf( RUNTIME CLASS(CFindSheet)));
int ilndex;
if (O == (ilndex = m_cboLanguages.GetCurSel()))
{
pParentSheet->SetResults(2);
}
else {
pParentSheet->SetResults(0);
}
}
Gdy w tym momencie zbudujesz i uruchomisz testową aplikację, zobaczysz, że po wybraniu pozycji visuai C++ i kliknięciu przycisku Znajdź zmieni się nie tylko tytuł zakładki wyników, ale także sama zakładka stanie się dostępna i będziesz mógł do niej przejść. Gdy z listy wybierzesz pozycję visual J++ i klikniesz przycisku Znajdź, tytuł zakładki wyników poinformuje cię że nie znaleziono żadnych pozycji i że zakładka jest wyłączona.
Zmiana czcionki zakładek
Być może przypominasz sobie, że nie jest możliwa zmiana czcionki tytułów poszczególnych zakładek. Jednak istnieje możliwość zmiany czcionki dla wszystkich zakładek arkusza naraz. Jeśli przyjrzysz się bliżej różnym zakładkom dodanym do naszej przykładowej aplikacji, zauważysz, że ich tytuły w żaden sposób się nie wyróżniają i, innymi słowy, giną w oknie dialogowym. Jeśli zechcesz zmienić czcionkę tytułów zakładek, możesz to uczynić następująco:
1. Stwórz żądaną czcionkę.
2. Pobierz wskaźnik do obiektu CTabctrl.
3. Wywołaj funkcję CTabCtrl: : Set Font ( ) .
W demonstracyjnym programie mieliśmy zamiar zmienić czcionkę jedynie dla aktywnej zakładki, jednak niestety okazało się to niemożliwe. Innymi słowy, w momencie zmiany czcionki wywołaniem CTabctrl: :SetFont () następuje zmiana czcionki tytułów wszystkich zakładek. Aby zmienić czcionkę w przykładowej aplikacji, zadeklaruj w klasie CFindSheet zmienną składową klasy CFont i nadaj jej nazwę m_f ontTab. Następnie na koniec funkcji CFindSheet: : OninitDialog () dopisz poniższe linie kodu:
m_fontTab.CreateFont(-8, O, O, O, FW_BOLD, O, O,
O, l, O, O, O, O, _T("MS Sans Serif")); CTabCtrl* pTab = GetTabControl (); ASSERT(pTab); if (pTab)
{
pTab->SetFont(&m fontTab);
}
Użycie mnemonik z obiektami CPropertyPage
Jedną z zalet interfejsu Windows wyróżniającą go od innych systemów operacyjnych jest spójność interfejsu użytkownika. Weźmy na przykład mnemoniki. Gdy widzisz na ekranie statyczny tekst z podkreśloną jedną z liter (tzw. mnemoniką), wiesz, że przytrzymanie klawisza Alt i wciśnięcie podkreślonej litery zwykle spowoduje przejście do danej kontrolki. Oprócz tego, jak wiemy z rozdziału 13., jeśli kontrolka jest przeznaczona tylko do odczytu, przechodzimy do następnej kontrolki, która może przyjmować dane.
Jednak gdy już zdążysz się przyzwyczaić do umieszczania mnemonik we wszystkich swoich oknach dialogowych, pojawia się klasa CPropertyPage, która całkowicie je ignoruje. Jeśli, na przykład, w tytule obiektu CPropertyPage wpiszesz &Wyniki i wciśniesz Alt+W, zupełnie nic się nie stanie. Aby obejść tę uderzającą "niedoróbkę" klasy CPropertyPage, musisz przesłonić funkcję CPropertySheet: : PreTranslateMessage () . Przez tą funkcję jest filtrowany każdy wciśnięty klawisz w każdej z kart właściwości posiadanych przez arkusz.
W tym momencie użyj ClassWizarda do stworzenia funkcji PreTranslateMessage (). Jej przykładowa implementacja została przedstawiona na listingu 14.6. Jak widać, interesują nas wyłącznie komunikaty WM_SYSKEYDOWN, gdyż właśnie taki komunikat jest wysyłany, gdy użytkownik trzyma wciśnięty systemowy klawisz Alt. Następnie sprawdzamy wartość pMsg->wParam, czy wciśnięty klawisz to litera lub cyfra. Następnie funkcja po prostu przechodzi przez kolejne karty arkusza właściwości i porównuje ewentualnie wciśniętą mnemonikę z ewentualnymi mnemonikami w tytułach kart. Jeśli zostanie znaleziona pasująca mnemonika, to jeśli zakładka nie jest wyłączona, następuje wywołanie funkcji setActivePage (). Rysunek 14.7 przedstawia program demonstracyjny po zastosowaniu zmian opisanych w tej sekcji.
Listing 14.6. Implementacja funkcji CFindSheet: :PreTranslateMessage()
BOOL CFindSheet::PreTranslateMessage(MSG* pMsg)
{ BOOL bHandledMsg = FALSE;
switch(pMsg->message)
{
case WM_SYSKEYDOWN:
{
// chcemy jedynie cyfr i liter
if ((Ox2f < pMsg->wParam)
&& (Ox5b > pMsg->wParam))
{
CTabCtrl *pTab = GetTabControl(); ASSERT(pTab);
TC_ITEM ti; char szText [100]; ti.mask = TCIF_TEXT;
ti.pszText = szText;
char szMnemonic[3];

sprintf(szMnemonic, "&%c", pMsg->wParam);
BOOL bFoundMatchingPage = FALSE;
int iCurrPage = 0;
while ((iCurrPage < pTab->Get!temCount())
&& (!bFoundMatchingPage))
{
ti.cchTextMax = 99; pTab->Get!tem(iCurrPage, &ti);
CString strText = szText;
strText.MakeUpper();
if (-1 != strText.Find(szMnemonic))
{
bFoundMatchingPage = TRUE;
if (!IsPageDisabled(iCurrPage))
{
SetActivePage(iCurrPage); bHandledMsg = TRUE;
}
}
else
{
iCurrPage++;
}
}
}
}
break;
default: break;
}
return (TRUE == bHandledMsg ?
TRUE : CPropertySheet::PreTranslateMessage(pMsg));
}
Podsumowanie
W tym rozdziale nauczyłeś się korzystać z klas CPropertySheet oraz cpropertypage zarówno w modalnych, jak i niemodalnych arkuszach właściwości. Wiesz także, że choć klasy te dobrze nadają się do większości przeznaczonych dla nich zastosowań, jednak brakuje im pewnych podstawowych możliwości. Dowiedziałeś się jednak, jak przezwyciężyć te trudności i jak samodzielnie włączać i wyłączać zakładki, poruszać się między nimi przy użyciu mnemonik, a także jak zmieniać czcionki w tytułach zakładek. Mamy nadzieję, że kod przedstawiony w tym rozdziale sprawi, że podejmowanie się w przyszłości podobnych zadań będzie znacznie prostsze niż mogłoby się wydawać.

Wyszukiwarka

Podobne podstrony:
radwanski wiedermann wlasciwosci mechaniczne 2 14
Arkusz 2 14
Arkusz WSiP Właściwości pierwiastków bloków s i p
Kopia pliku Medycyna ratunkowa 12 02 14 r Arkusz1
Arkusz CKU Chemia 14
14 Wlasciwosci materialow dielektrycznychid304
14 W otwarte karty
operon 2013 14 listopad PR próbna arkusz
Arkusz3 14
arkusz FUNKCJE 14
Arkusz Egzaminacyjny Asystentka Stomatologiczna 14 Czerwiec 2013 Z 15
Arkusz WSiP Właściwości pierwiastków bloku d
PEG2014 Jezyk polski 14 arkusz

więcej podobnych podstron