MFC 06 ArkuszeWłaściwości


Rozdział 6 Arkusze właściwości i kreatory

Choć wiele technik stosowanych przy arkuszach właściwości jest podobnych do technik wykorzystywanych przy tworzeniu okien dialogowych, arkusze właściwości posiadają kilka unikalnych aspektów. Można konstruować kreatory, niemodalne arkusze właści­wości, a nawet własne kreatory AppWizard, przeznaczone do generowania nowych programów o zadanych właściwościach.

Nie mam wielu hobby. Prawdopodobnie dlatego, że nie mam zbyt wiele wolnego czasu. Lubię łowić ryby, czasami też prowadzę treningi drużyny mojego syna. Poza tym wszystkie moje zainteresowania można zaliczyć do grupy oryginalnych. Oczywiście, obecnie przede wszystkim zajmuję się komputerami. Oprócz tego, przez ponad dwa­dzieścia lat byłem zapalonym krótkofalowcem (jeśli Cię to interesuję, moim znakiem wywoławczym był WD5GNR).

W ciągu ostatnich dwudziestu lat wiele się w krótkofalarstwie zmieniło. Teraz, większość krótkofalówek posiada wbudowane mikroprocesory. Gdy obchodziłem swoją dwudziestą rocznicę jako krótkofalowiec, zdecydowałem się na kupno nowego sprzętu z wszystkimi nowoczesnymi „bajerami" radia Kenwood TS70D. Być może nie było to szczytowe osiągnięcie techniki, ale też radio nie należało do najgorszych. Jeśli oglądałeś film pt. Park Jurajski 2, przy odrobinie uwagi mogłeś dostrzec taki sprzęt w jednym z wozów, którymi poruszali się bohaterowie.

Obecnie wszystkie dostępne na rynku nadajniki posiadają dużo mniej przełączników i pokręteł niż ich starsze odpowiedniki. Można powiedzieć, że mają lepszy interfejs użytkownika. Stary radionadajnik miał tuziny gałek i przełączników, jednak w codziennej praktyce rzadko korzystało się z więcej niż kilku z nich. Z drugiej strony, każdy przełącznik pełnił pewną funkcję, więc sprzęt po prostu musiał je posiadać.

Obecnie wszystkie funkcje są kontrolowane przez mikroprocesory. W związku z tym przełączniki i pokrętła służą jedynie do przekazywania poleceń do mikroprocesora, który wykonuj<Tca4%,,czarną robotę" i dzięki temu pojedyncze pokrętło może spełniać kilka różnych funkcji>

Niektóre bardzo małe radia przeznaczone dla samochodów, jachtów czy sportowych samolotów w ogóle nie mają pokręteł. Wszystko w nich jest obsługiwane przez menu przy pomocy kilku przycisków. Dobrym przykładem mogą też być przenośne radia, które dawniej posiadały tylko bardzo niewiele funkcji, gdyż mieściło się na nich bardzo niewiele przełączników. Teraz, dzięki systemowi menu, także takie radia mogą mieć ogromną ilość funkcji.

Jeśli się nad tym zastanowisz, dojdziesz do wniosku, że projektanci wykorzystali mi­kroprocesor do pogrupowania funkcji i pokazania tylko tych z nich, które są potrzebne w danym momencie. Podobną ewolucję przeszedł interfejs użytkownika w programach komputerowych. Przy pomocy obecnie istniejących narzędzi można bardzo łatwo stworzyć ogromne okno dialogowe z dziesiątkami możliwych kontrolek; z drugiej strony, takie okno bardzo zniechęca potencjalnych użytkowników. Nawet zawodowi programiści i zaawansowani użytkownicy nie lubią mieć do dyspozycji wszystkich możliwych opcji w jednym oknie.

Na początku programiści próbowali ukrywać część okna dialogowego. Jeśli potrzebowałeś dodatkowych kontrolek (lub informacji), klikałeś na przycisku o nazwie Szczegóły lub Zaawansowane, po czym okno się rozwijało ukazując kolejne, zwykle bardziej skom­plikowane elementy. Był to już krok we właściwym kierunku, który jednak tylko ukrywał skomplikowane okno do momentu, w którym musiałeś z niego skorzystać.

Lepszym pomysłem jest wykorzystanie okna dialogowego zawierającego zakładki, z których każda odnosi się do grupy powiązanych ze sobą elementów. Krótkofalowiec korzysta jedynie z kilku elementów naraz; także użytkownik komputera zwykle nie używa jednocześnie więcej niż kilku opcji.

Niektórzy nieustraszeni programiści próbowali (w trudzie i znoju) samodzielnie tworzyć okna dialogowe z zakładkami i doprowadzili do tego, że idea zakładek stała się bardzo popularna. Po pewnym czasie Microsoft dodał obsługę zakładek do MFC, a także do podstawowego Windows API (konkretnie, do standardowych kontrolek). Okna dialogowe zawierające zakładki w terminologii Microsoftu noszą nazwę arkuszy właściwości (ang. property sheets).

Przykładowe arkusze właściwości znajdują się w samym pakiecie Yisual C++. Aby się o tym przekonać, wybierz polecenie Tools Options lub Project | Settings. Jak inaczej wyobrażasz sobie przedstawienie takiej ilości informacji w pojedynczym oknie dialo­gowym? I piętnastu okien mogło by być za mało. Dzięki arkuszom właściwości ta ogromna liczba opcji może być sensownie obsłużona.

Przegląd arkuszy właściwości

Jeśli jeszcze nie pracowałeś z arkuszami właściwości, przekonasz się, że nie różnią się zbytnio od zwykłych okien dialogowych. Możesz po prostu stworzyć szablon dialogu dla każdej z zakładek. Tytuł szablonu okna pojawi się na odpowiedniej zakładce. Możesz korzystać przy tym z mechanizmów DDX, DDV oraz Class Wizarda. Szablon okna dialogowego musi mieć włączonych (oraz wyłączonych) kilka opcji, ale teraz nie musisz się tym przejmować. Po prostu kliknij prawym przyciskiem w panelu zasobów i z menu kontekstowego wybierz polecenie Insert (nie wybieraj polecenia Insert Dialog). Pojawi się okno dialogowe przedstawione na rysunku 6.1; kliknij na znaku plus obok pozycji Dialog rozwijając ją, i spójrz na trzy predefiniowane rodzaje arkuszy właściwości. Wybierz jeden z nich i gotowe.


Gdy tworzysz szablon okna dialogowego i wywołasz Class Wizarda, kreator zauważy, że stworzyłeś nowy dialog, i zaoferuje stworzenie dla niego nowej klasy. Jako klasę podstawową zasugeruje CDialog. Podobnie dzieje się w przypadku tworzenia arkuszy właściwości. W tym wypadku, jednak jako klasę podstawową, zamiast CDialog, wybierzesz CPropertyPage.

Gdy będziesz gotowy do stworzenia obiektu arkusza właściwości, będziesz potrzebował również obiektu klasy CPropertySheet (lub jej klasy pochodnej). Musisz przy tym stworzyć także obiekty każdej z klas wyprowadzonych z klasy CPropertyPage, których chcesz użyć. Właśnie te klasy tworzy dla Ciebie Class Wizard w momencie tworzenia szablonu dialogu.

W obiektach CPropertyPage może wykorzystać zwykłe wywołania DDX. Do włączania szablonów dialogów do arkusza właściwości służy metoda CPropertySheet: :AddPage, z kolei metoda CPropertySheet: rDoModal umożliwia wyświetlenie przygotowanego arkusza.

A oto typowy przykład:

CPropertySheet sheet(„Przykładowy arkusz właściwości");

CPropPgl propl;

// wyprowadzone z CPropertyPage

CPropPg2 prop2,-

// wyprowadzone z CPropertyPage

sheet.AddPAge(&propl);

sheet. AddPAg&-tó>rop2) •

propl.m_valuel ^\100; // DDK

propl.m_value2 = *Test"; // DDK


prop2.m_position = 25; // także

if (sheet.DoModaK) == IDOK)

// odwrotne DDX

AfxMessageBox(propl.m_value2,"Zwrócone dane");

Kod wygląda podobnie do tego, czego mógłbyś się spodziewać przy okazji zwykłego okna dialogowego. Jedyna różnica sprowadza się do tego, że posiadasz dwa lub więcej dialogów (a właściwie obiektów CPropertyPage).

Klasa CPropertyPage posiada kilka metod, które możesz przesłonić (patrz tablica 6.1). Dzięki tym metodom możesz dowiedzieć się kiedy użytkownik kliknął na przycisku OK, Zastosuj, Anuluj. Możesz także zmienić niektóre z tych przycisków. I tak, metoda CancelToCIose służy do zmiany nazwy przycisku Anuluj na Zamknij. Jest to przydatne zwłaszcza wtedy, gdy zmiany dokonane w oknie dialogowym nie mogą być anulowane, i chcesz poinformou-ać użytkownika że kliknięcie na przycisku Anuluj spowoduje je­dynie zamknięcie arkusza właściwości, bez anulowania zmian. Podobną metodą jest SetModified, która powoduje uaktywnienie przycisku Zastosuj.

Tabela 6.2. Metody klasy CPropertyPage


Funkcja Opis

OnCancel Wywoływana przez MFC w momencie kliknięcia na przycisku Anuluj.

OnKillActive Wywoływana przez MFC w momencie gdy strona przestaje być stroną aktywną. W tym momencie powinieneś zatwierdzić dane.

OnOK Wywoływana przez MFC w momencie kliknięcia na przycisku OK, Zastosuj bądź Zamknij.

OnSetActive Wywoływana przez MFC w momencie gdy strona staje się stroną aktywną.

OnApply Wywoływana przez MFC w momencie kliknięcia na przycisku Zastosuj.

OnReset Wywoływana przez MFC w momencie kliknięcia na przycisku Anuluj.

OnQueryControl Wywoływana przez MFC w momencie kliknięcia na przycisku Anuluj, ale jeszcze przed samym przystąpieniem do anulowania.


Korzystanie z pojedynczego szablonu

Czasem zdarza się, że chcesz stworzyć arkusz właściwości, który korzysta z tego samego szablonu dialogu na kilku różnych zakładkach. Jako przykład niech posłuży rysunek 6.2. Taki arkusz właściwości mógłby pojawić się w grze w warcaby. Widzimy na nim ilość czerwonych i czarnych pionków. Obie zakładki są takie same, lecz jedna odnosi się do pionków czerwonych, a druga do czarnych.

Narzucające się od razu rozwiązanie mogłoby polegać na zaprojektowaniu dwóch identycznych szablonów i nazwaniu ich „Czerwone" i „Czarne." To by oczywiście za­działało, ale nie jest to najelegantsze rozwiązanie. Za każdym razem gdy zmieniałbyś jeden szablon, musiałbyś pamiętać o zmianie także drugiego. Oprócz tego, wykorzystanie dwóch szablonów nie byłoby efektywne; wszystko mówi Ci, że powinien wystarczyć tylko jeden z nich.

Istnieje możliwość wykorzystania pojedynczego szablonu do stworzenia kilku zakładek; sztuczka polega na tym, że każdy z nich musi posiadać inny tytuł. Dobrym sposobem jest przekazanie właściwego tytułu jako drugiego argumentu konstruktora CPropertyPage. Istnieje jednak kilka pułapek. Po pierwsze, tytuł musi być przekazany jako numeryczny identyfikator łańcucha w tablicy łańcuchów; nie można po prostu przekazać odpowiedniego łańcucha.

Drugi problem polega na tym, że normalnie generowana przez Class Wizarda klasa posiada tylko domyślny konstruktor. Ten konstruktor wywołuje konstruktor klasy podstawowej z poprawnie wypełnionym tylko pierwszym argumentem (identyfikatorem szablonu dialogu). Gdy spróbujesz zmienić konstruktor tak, by jako argument móc przekazać tytuł zakładki, Twój program się nie skompiluje. Dlaczego? Ponieważ klasa CPropertyPage korzysta z makra DECLARE_DYNCREATE, które oczekuje domyślnego konstruktora. Bardzo mało prawdopodobne jest abyś naprawdę tego makra potrzebował, ale Class Wizard i tak automatycznie je umieszcza.

Istnieją trzy sposoby ominięcia tego problemu. Po pierwsze, możesz usunąć makro DECLARE_DYNCREATE (oraz powiązane z nim makro IMPLEMENT_DYNCREATE). Po drugie, możesz zostawić w spokoju domyślny konstruktor i dodać drugi, korzystający z dodatkowego argumentu. Trzecie rozwiązanie polega na zastosowaniu w konstruktorze domyślnego argumentu, tak by konstruktor akceptował zero argumentów lub jeden argument. W ten sposób zaspokoi oczekiwania makra dynamicznego tworzenia, a w razie potrzeby pozwoli na przekazanie identyfikatora tytułu.

Taki przykładowy arkusz właściwości znajdziesz na listingu 6.1. Widok, który z niego korzysta, znajduje się na listingu 6.2. Zwróć uwagę, że w odróżnieniu od poprzedniego przykładu, każda strona arkusza wymaga podania argumentu w konstruktorze (oraz od­powiedniego identyfikatora łańcucha w tablicy łańcuchów).

Czy zastosowanie dwóch szablonów dialogu nie spełniłoby zadania? Owszem, ale po co tworzyć dwa identyczne szablony. Choć zastosowanie pojedynczego szablonu wymaga więcej pracy, może znacznie ułatwić Ci (lub komuś innemu) późniejszą modyfikację lub rozbudowę programu.

Listing 6.1. Arkusz właściwości.


// chkpropView.cpp : implementation of the CChkpropView class

//

#include "stdafx.h"

#include "chkprop.h"

#include "chkpropDoc.h"

#include "chkpropView.h"

#include "statuspg.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CChkpropView

IMPLEMENT_DYNCREATE(CChkpropView, CView)

BEGIN_MESSAGE_MAP(CChkpropView, CView)

//{{AFX_MSG_MAP(CChkpropView)

ON_WM_LBUTTONDOWN()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CChkpropView construction/destruction

CChkpropView::CChkpropView()

{

// TODO: add construction code here

}

CChkpropView::~CChkpropView()

{

}

BOOL CChkpropView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

// the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

/////////////////////////////////////////////////////////////////////////////

// CChkpropView drawing

void CChkpropView::OnDraw(CDC* pDC)

{

CChkpropDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

}

/////////////////////////////////////////////////////////////////////////////

// CChkpropView diagnostics

#ifdef _DEBUG

void CChkpropView::AssertValid() const

{

CView::AssertValid();

}

void CChkpropView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CChkpropDoc* CChkpropView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CChkpropDoc)));

return (CChkpropDoc*)m_pDocument;

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CChkpropView message handlers

void CChkpropView::OnLButtonDown(UINT nFlags, CPoint point)

{

CPropertySheet sheet("Ilość pionków");

StatusPage redpage(IDS_REDSTRING), blackpage(IDS_BLKSTRING);

redpage.m_kings=0;

redpage.m_pieces=9;

blackpage.m_kings=2;

blackpage.m_pieces=10;

sheet.AddPage(&redpage);

sheet.AddPage(&blackpage);

if (sheet.DoModal()==IDOK)

{

// ten arkusz właściwości tylko przekazuje informacje, więc nie musimy się tym przejmować

}

}

Tryb kreatora


Innym przykładem użycia arkuszy właściwości jest korzystanie z kreatorów. Kreatory przewijają się cały czas w różnych produktach Microsoftu; poza tym nie spotykamy ich zbyt często. Nie wiadomo dlaczego nie cieszą się popularnością, choć dzięki MFC można je bardzo łatwo tworzyć.

Tworzenie kreatora przypomina tworzenie arkusza właściwości, z dwoma niewielkimi różnicami. Po pierwsze, zanim wywołasz metodę DoModal, musisz wywołać metodę CPropertySheet: :SetWizardMode. Po drugie, dla każdego z obiektów zakładek musisz przesłonić metodę OnSetActłve. Wewnątrz tej metody odwołaj się do nadrzędnego okna (czyli do samego arkusza właściwości) i wywołaj metodę CPropertySheet: :SetWizardButtons w celu przygotowania przycisków, które mają się pojawić (patrz tabela 6.2). Zwróć uwagę na to, że przyciski Dalej i Zakończ to w rzeczywistości ten sam przycisk - nie da się ich włączyć obu jednocześnie.

Tabela 6.2. Przyciski kreatora

Przycisk Stała

Wstecz PSWIZB_BACK

Dalej PSWIZB_NEXT

Zakończ PSWIZB_FINISH

Wyląe/ony Zakońc/ PSWIZB_DISABLEDFINISH

Przykładowy dialog kreatora i wywołujący go program znajdziesz na listingach 6.3 oraz 6.4. Jeśli nie przesłonisz metody OnSetActive, pojawią się przyciski Wstecz i Dalej. Możesz pomyśleć, że tylko ostatni krok kreatora (ostatnia strona) wymaga zmiany nazwy przy­cisku z Dalej na Zakończ, jednak nie zawsze tak jest. Wyobraź sobie, że użytkownik doszedł do ostatniej strony, i przycisk Dalej zmienił się na przycisk Zakończ. Następnie użytkownik kliknął na przycisku Wstecz. Przycisk Dalej w dalszym ciągu, opisany jako Zakończ, dopóki nie zostanie jawnie zmieniony. Właśnie dlatego strony kreatora wy­magają użycia metody OnSetActive. Poza tym, pierwsza strona nie potrzebuje przycisku Wstecz.

Listing 6.3. Jedna ze stron arkusza właściwości stworzonego w stylu kreatora

// Page1.cpp : implementation file

//

#include "stdafx.h"

#include "wiz.h"

#include "Page1.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// Page1 property page

IMPLEMENT_DYNCREATE(Page1, CPropertyPage)

Page1::Page1() : CPropertyPage(Page1::IDD)

{

//{{AFX_DATA_INIT(Page1)

m_name = _T("");

//}}AFX_DATA_INIT

}

Page1::~Page1()

{

}

void Page1::DoDataExchange(CDataExchange* pDX)

{

CPropertyPage::DoDataExchange(pDX);

//{{AFX_DATA_MAP(Page1)

DDX_Text(pDX, IDC_NAME, m_name);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(Page1, CPropertyPage)

//{{AFX_MSG_MAP(Page1)

// NOTE: the ClassWizard will add message map macros here

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// Page1 message handlers

BOOL Page1::OnSetActive()

{

CPropertySheet *sheet=(CPropertySheet *)GetParent();

sheet->SetWizardButtons(PSWIZB_NEXT);

return CPropertyPage::OnSetActive();

}

Listing 6.4. Wykorzystanie kreatora.

// wizView.cpp : implementation of the CWizView class

//

#include "stdafx.h"

#include "wiz.h"

#include "wizDoc.h"

#include "wizView.h"

#include "page1.h"

#include "page2.h"

#include "page3.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CWizView

IMPLEMENT_DYNCREATE(CWizView, CView)

BEGIN_MESSAGE_MAP(CWizView, CView)

//{{AFX_MSG_MAP(CWizView)

ON_WM_LBUTTONDOWN()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CWizView construction/destruction

CWizView::CWizView()

{

// TODO: add construction code here

}

CWizView::~CWizView()

{

}

BOOL CWizView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

// the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

/////////////////////////////////////////////////////////////////////////////

// CWizView drawing

void CWizView::OnDraw(CDC* pDC)

{

CWizDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

}

/////////////////////////////////////////////////////////////////////////////

// CWizView diagnostics

#ifdef _DEBUG

void CWizView::AssertValid() const

{

CView::AssertValid();

}

void CWizView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CWizDoc* CWizView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CWizDoc)));

return (CWizDoc*)m_pDocument;

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CWizView message handlers

void CWizView::OnLButtonDown(UINT nFlags, CPoint point)

{

CPropertySheet sheet;

Page1 pg1;

Page2 pg2;

Page3 pg3;

sheet.AddPage(&pg1);

sheet.AddPage(&pg2);

sheet.AddPage(&pg3);

sheet.SetWizardMode();

if (sheet.DoModal()==ID_WIZFINISH)

{

CString greet="Dziękuję ";

greet+=pg1.m_name;

MessageBox("Możesz przejść",greet);

}

else

{

MessageBox("Spadasz w przepaść");

}

}

Niemodalne arkusze właściwości

Tworzenie niemodalnych arkuszy właściwości także niewiele się różni od tworzenia niemodalnych okien dialogowych. Zamiast metody DoModal wywołuje się metodę Create. Oczywiście tak jak w przypadku okna dialogowego dane z arkusza nie zostaną automatycznie przesłane, musisz więc je samodzielnie przekazać przy pomocy metody UpdateData (patrz rozdział 5).

Jednym z problemów związanych z niemodalnymi arkuszami właściwości jest czas ży­cia obiektów. Modalne okna dialogowe i arkusze właściwości zwykle korzystają ze zmiennych tworzonych na stosie, gdyż czas życia takich okien nigdy nie wykracza poza czas życia zmiennych. Z drugiej strony, niemodalne okna dialogowe i arkusze właści­wości zwykle żyją dużo dłużej niż funkcja, w której zostały otwarte. W związku z tym trzeba tworzyć je (i wszystko co jest z nimi związane) na stercie lub jako część obiektu, który będzie istniał przez cały okres życia okna.

Jedną z poręcznych technik (przedstawioną na listingach 6.5 do 6.8) jest wyprowadze­nie klasy z CPropertySheet. Nowa klasa może zawierać zmienne składowe dla każdej strony w arkuszu właściwości. W konstruktorze klasy pochodnej wywołujemy metodę AddPage dla każdej ze stron. Następnie tworzymy obiekt klasy arkusza właściwości, tak jak w przypadku „normalnych" obiektów klasy CPropertyPage.

Listing 6.5. Niemodalny arkusz właściwości

// MouseSheet.cpp : implementation file

//

#include "stdafx.h"

#include "mdlsprop.h"

#include "MouseSheet.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CMouseSheet

IMPLEMENT_DYNAMIC(CMouseSheet, CPropertySheet)

CMouseSheet::CMouseSheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)

:CPropertySheet(nIDCaption, pParentWnd, iSelectPage)

{

}

CMouseSheet::CMouseSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)

:CPropertySheet(pszCaption, pParentWnd, iSelectPage)

{

leftct=rightct=midct=0;

AddPage(&m_clickpage);

AddPage(&m_pospage);

}

CMouseSheet::~CMouseSheet()

{

}

BEGIN_MESSAGE_MAP(CMouseSheet, CPropertySheet)

//{{AFX_MSG_MAP(CMouseSheet)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CMouseSheet message handlers

BOOL CMouseSheet::OnInitDialog()

{

return CPropertySheet::OnInitDialog();

}

void CMouseSheet::SetXY(int x, int y)

{

m_pospage.m_x=x;

m_pospage.m_y=y;

if (::IsWindow(m_pospage.m_hWnd)) m_pospage.UpdateData(FALSE);

}

void CMouseSheet::MouseDown(int cmd)

{

switch (cmd)

{

case WM_LBUTTONDOWN:

leftct++;

break;

case WM_RBUTTONDOWN:

rightct++;

break;

case WM_MBUTTONDOWN:

midct++;

break;

}

m_clickpage.m_left=leftct;

m_clickpage.m_right=rightct;

m_clickpage.m_middle=midct;

if (::IsWindow(m_clickpage.m_hWnd)) m_clickpage.UpdateData(FALSE);

}

Listing 6.6. Strona pozycji myszy.

// PosPage.cpp : implementation file

//

#include "stdafx.h"

#include "mdlsprop.h"

#include "PosPage.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CPosPage property page

IMPLEMENT_DYNCREATE(CPosPage, CPropertyPage)

CPosPage::CPosPage() : CPropertyPage(CPosPage::IDD)

{

//{{AFX_DATA_INIT(CPosPage)

m_x = 0;

m_y = 0;

//}}AFX_DATA_INIT

}

CPosPage::~CPosPage()

{

}

void CPosPage::DoDataExchange(CDataExchange* pDX)

{

CPropertyPage::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CPosPage)

DDX_Text(pDX, IDC_X, m_x);

DDX_Text(pDX, IDC_Y, m_y);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CPosPage, CPropertyPage)

//{{AFX_MSG_MAP(CPosPage)

// NOTE: the ClassWizard will add message map macros here

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CPosPage message handlers

Listing 6.7. Strona kliknięć.


// ClickPage.cpp : implementation file

//

#include "stdafx.h"

#include "mdlsprop.h"

#include "ClickPage.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CClickPage property page

IMPLEMENT_DYNCREATE(CClickPage, CPropertyPage)

CClickPage::CClickPage() : CPropertyPage(CClickPage::IDD)

{

//{{AFX_DATA_INIT(CClickPage)

m_left = 0;

m_middle = 0;

m_right = 0;

//}}AFX_DATA_INIT

}

CClickPage::~CClickPage()

{

}

void CClickPage::DoDataExchange(CDataExchange* pDX)

{

CPropertyPage::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CClickPage)

DDX_Text(pDX, IDC_LEFT, m_left);

DDX_Text(pDX, IDC_MIDDLE, m_middle);

DDX_Text(pDX, IDC_RIGHT, m_right);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CClickPage, CPropertyPage)

//{{AFX_MSG_MAP(CClickPage)

// NOTE: the ClassWizard will add message map macros here

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CClickPage message handlers

Listing 6.8. Wykorzystanie niemodalnego arkusza właściwości.

// mdlspropView.cpp : implementation of the CMdlspropView class

//

#include "stdafx.h"

#include "mdlsprop.h"

#include "mdlspropDoc.h"

#include "mdlspropView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CMdlspropView

IMPLEMENT_DYNCREATE(CMdlspropView, CView)

BEGIN_MESSAGE_MAP(CMdlspropView, CView)

//{{AFX_MSG_MAP(CMdlspropView)

ON_WM_LBUTTONDOWN()

ON_WM_MOUSEMOVE()

ON_WM_RBUTTONDOWN()

ON_WM_MBUTTONDOWN()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CMdlspropView construction/destruction

CMdlspropView::CMdlspropView() : psheet("Właściwości myszy")

{

// TODO: add construction code here

}

CMdlspropView::~CMdlspropView()

{

}

BOOL CMdlspropView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

// the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

/////////////////////////////////////////////////////////////////////////////

// CMdlspropView drawing

void CMdlspropView::OnDraw(CDC* pDC)

{

CMdlspropDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

}

/////////////////////////////////////////////////////////////////////////////

// CMdlspropView diagnostics

#ifdef _DEBUG

void CMdlspropView::AssertValid() const

{

CView::AssertValid();

}

void CMdlspropView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CMdlspropDoc* CMdlspropView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMdlspropDoc)));

return (CMdlspropDoc*)m_pDocument;

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CMdlspropView message handlers

void CMdlspropView::OnLButtonDown(UINT nFlags, CPoint point)

{

if (::IsWindow(psheet.m_hWnd)) psheet.MouseDown(WM_LBUTTONDOWN);

CView::OnLButtonDown(nFlags, point);

}

void CMdlspropView::OnMouseMove(UINT nFlags, CPoint point)

{

if (::IsWindow(psheet.m_hWnd)) psheet.SetXY(point.x,point.y);

CView::OnMouseMove(nFlags, point);

}

void CMdlspropView::OnRButtonDown(UINT nFlags, CPoint point)

{

if (::IsWindow(psheet.m_hWnd)) psheet.MouseDown(WM_RBUTTONDOWN);

CView::OnRButtonDown(nFlags, point);

}

void CMdlspropView::OnMButtonDown(UINT nFlags, CPoint point)

{

if (::IsWindow(psheet.m_hWnd)) psheet.MouseDown(WM_MBUTTONDOWN);

CView::OnMButtonDown(nFlags, point);

}

void CMdlspropView::OnInitialUpdate()

{

CView::OnInitialUpdate();

psheet.Create(this, DS_MODALFRAME | DS_3DLOOK | DS_CONTEXTHELP |

DS_SETFONT | WS_CHILD | WS_VISIBLE | WS_CAPTION);

}

I tak, arkusz właściwości myszy z rysunku 6.3 należy do zawierającego go widoku. Ten widok posiada zmienną typu CMouseSheet; z kolei ta klasa zawiera dwie strony arku­sza właściwości (CClickPage oraz CPosPage). CMouseSheet zawiera także funkcje obsługujące przekazywanie danych pomiędzy stronami arkusza a światem zewnętrznym. Zwróć uwagę, że te funkcje muszą uważać, by nie wywoływać metody UpdateData jeśli dana strona akurat nie istnieje. Właśnie z tego powodu każde wywołanie UpdateData jest zabezpieczone wcześniejszym testem ::IsWindow. Jak widać na rysunku, niemo-dalne arkusze właściwości nie zawierają żadnych domyślnych przycisków. Wszystko, co chces^umieścić na stronach, musisz dodać samodzielnie.

Własne kreatory App Wizard

Nie jestem dobrym handlowcem. To może nieco dziwić, zwłaszcza jeśli wziąć pod uwagę fakt, że mój ojciec był doskonałym handlowcem. Gdy byłem dzieckiem, ojciec zajmował się sprzedażą dokładnie wszystkiego - mam tylko nadzieję, że z wyjątkiem odkurzaczy i encyklopedii. Ojciec uwielbiał sprzedawać do tego stopnia, że gdy odszedł na emeryturę, całkowicie oddał się temu zajęciu.

Gdy ojciec wraz z matką zajęli się sprzedażą zaproszeń na wesela (czym moja matka zresztą zajmuje się do dzisiaj), rozpoczynali od niewielkiego interesu - bardzo niewiel­kiego. Polegało to na przeglądaniu rubryki towarzyskiej w lokalnej gazecie, dzwonieniu do przyszłej panny młodej i umawianiu się na wizytę z katalogiem w jej domu. W tym czasie byłem bardzo młody i to, czym zajmowali się dorośli, było dość mgliste, więc ten sposób zarabiania wcale mnie nie obchodził. Gdy trochę podrosłem, czasem czułem za­kłopotanie w związku z tym, że moi rodzice chodzą do obcych ludzi prezentować swoje towary. Oczywiście każde dziecko przechodzi okres, w którym poczynania rodziców wydają się nieco dziwne (moje dzieci nie różnią się pod tym względem). Wtedy, prawie nastolatkowi, dzwonienie do nieznajomych i nawiedzanie ich w domach wydawało się czymś nad wyraz niestosownym.

Później, rodzice rozbudowali interes na tyle, że ludzie sami zaczęli przychodzić do nas do domu. W tym czasie ich domowa firma miała już całkiem niezłą reputację. Prak­tycznie wszystkie zaproszenia weselne w mieście wychodziły z pod ręki mojej matki. Później, gdy ojciec odszedł na emeryturę, matka w dalszym ciągu prowadziła interes.

Myśląc o tym wszystkim, dochodzę do wniosku, że niewłaściwie oceniałem domową sprzedaż ojca jako swego rodzaju głupotę. Teraz widzę, że robił to, co powinien robić każdy dobry handlowiec: ułatwiał zakup produktu.

Z pewnością zastanawiasz się jak ta historyjka ma się do programowania. Zastanów się: jeśJJsam piszesz swoje programy, musisz je sam sprzedawać. Być może nie w tradycyjnym sensie tego słowa, ale w każdym razie rozprowadzasz - sprzedajesz - swoje programy i swoje pomysły.

Czy chcesz, by klient używał Twojego interfejsu użytkownika? Czy inni programiści zaadoptują twoją bibliotekę kodu, Twój styl pisania, czy nawet Twoje techniki? Czy chcesz, aby Twój szef miał Cię na uwadze przy planowaniu szczególnie interesującego projektu? Wszystko to - w pewien sposób - nawiązuje do handlu. Jedną z trudności „sprzedaży" innym programistom swoich rozwiązań jest to, że często trudno je im „kupić." Jako programista, jeśli musiałbym odcyfrowywać Twój kod, wygrzebywać go z kontekstu i modyfikować struktury danych, prawdopodobnie wolałbym napisać go samodzielnie.

Yisual C++ oferuje kilka sposobów, dzięki którym możesz łatwiej sprzedać swoje pro­gramistyczne pomysły. Jeśli kiedykolwiek próbowałeś napisać rozszerzenie powłoki

Windows 95 zajmujące się obsługą dynamicznych ikon, prawdopodobnie wiesz już, że to nie łatwe zadanie. A gdybyś tak miał kreatora AppWizard, który automatycznie wy­generowałby szkielet takiego programu, wymagając przy tym bardzo niewiele lub żadnej wiedzy z Twojej strony (na przykład kreatora z rysunku 6.4)?

To jeszcze nie wszystko! Nawet jeśli nie wiesz, jak działa kreator, możesz użyć go do stworzenia rozszerzenia powłoki. Gdy raz wykonasz żmudną pracę stworzenia szkieletu programu, możesz łatwo stworzyć z niego kreatora AppWizard. Możesz go później sam używać lub udostępnić go innym - nawet jeśli nie mają pojęcia, jak Twój program działa. Nie mam zamiaru objaśniać kodu generowanego przez kreatora. Zamiast tego skupimy się na samej pracy naszego AppWizarda. Jeśli chcesz dowiedzieć się czegoś więcej o obsłudze ikon, zajrzyj do dodatku A.



Tworzenie kreatora

Stworzenie kreatora było proste. Rozpocząłem od działającego projektu programu obsługi ikon. Następnie stworzyłem nowy projekt. Jako typ projektu wybrałem Gustom AppWizard. Potem musiałem poinformować Yisuala, że chcę oprzeć mojego nowego kreatora AppWizard na istniejącym projekcie, i wskazać, jakiego projektu chcę użyć (patrz rysunki 6.5 i 6.6).

W rezultacie otrzymałem serię plików źródłowych i zasobów. Po zbudowaniu projektu, zostanie stworzony plik AWX (automatycznie umieszczany w kartotece \Program Files\ DevStudio\SharedIDE\Teplate). Od tego momentu, przy tworzeniu nowego projektu, na liście projektów pojawi się nowy kreator AppWizard (patrz rysunek 6.7).

Dostosowywanie projektu kreatora

To działa zadziwiająco dobrze. Własny AppWizard jest na tyle bystry, że potrafi zmie­nić Twoje identyfikatory tak, by odpowiadały nazwie tworzonego projektu. Niestety, nie zawsze wykonuje wszystkie zmiany, których możesz sobie życzyć. Oprócz tego, nie zawsze daje sobie radę z tworzeniem pliku nowego projektu.

Na przykład, gdy pierwszy raz tworzyłem kreatora obsługi ikon, stworzył plik DEF, ale nie dodał go do projektu. Ponadto nie zmienił identyfikatora GUID projektu. Wynika z tego, że musisz samodzielnie zmodyfikować wygenerowany kod w celu rozwiązania tych problemów.

Gdy spojrzysz na kod własnego kreatora AppWizard, nie powinieneś mieć problemów z odcyfrowaniem go. Przede wszystkim występuje w nim obiekt wyprowadzony z klasy CCustomAppWiz, reprezentujący Twojego AppWizarda. Może być interesujący tylko wtedy, gdy chcesz dostosować konkretne kroki lub dokonać własnej inicjalizacji (na przykład, wygenerować identyfikator GUID).

Pliki szablonów tworzą główną część projektu. Są to szkieletowe pliki sterujące gene­rowaniem nowych plików. Jeśli otworzysz pliki szablonów, przekonasz się, że wyglądają podobnie do Twojego oryginalnego kodu źródłowego, z tym, że Yisual C++ zastąpił niektóre części makrami. Wszędzie tam, gdzie występowała nazwa projektu, teraz znajduje się napis $$ROOT$$. To makro jest rozwijane w nazwę nowego projektu. Listę stan­dardowych makr znajdziesz w tabeli 6.3. To nie jest zbyt skomplikowane - po rozwinięciu makr, kreator po prostu kopiuje pliki szablonów do nowego projektu. Makro $$IF działa podobnie jak polecenie #if preprocesora. Możesz je wykorzystać do wybrania lub odrzucenia części pliku szablonu. Jeśli chcesz zmodyfikować pliki szablonów, otwórz je z poziomu Dependencies panelu plików - możesz także otworzyć je jako zasoby, ale wtedy będziesz musiał modyfikować je przy pomocy edytora binarnego.

Tworzenie projektu

Dwa pliki, których możesz nie znać, to CONFIRM.INF i NEWPROJ.INF. Plik CONFIRM. INF generuje końcowe okienko tekstowe, wyświetlane tuż przed tym, zanim AppWizard zabiera się do generowania kodu. NEWPROJ.INF jest nieco bardziej skomplikowany. Steruje sposobem, w jaki AppWizard generuje pliki i konstruuje aktualny projekt. W odróżnieniu od innych plików, AppWizard nie tworzy pliku MĄKĘ z szablonu. Zamiast tego, plik MĄKĘ konstruowany jest na podstawie pliku NEWPROJ.INF. Spójrzmy na plik NEWPROJ.INF:


Tabela 6.3. Standardowe makra AppWizarda

Makro Znaczenie

$$IF Polecenie warunkowego włączenia bloku kodu

$$ELSE Polecenie warunkowego włączenia bloku kodu

$$ENDIF Polecenie warunkowego włączenia bloku kodu

$$ELIF Polecenie warunkowego włączenia bloku kodu

$$INCLUDE Włączenie innego pliku

$$BEGINLOOP Początek pętli

$$ENDLOOP Koniec pętli

$$SET_DEFAULT_LANG Ustawienie domyślnego języka

$$// Komentarz

$$$$ Wyemitowanie ciągu "$$"

$$ROOT Nazwa projektu bez rozszerzenia (wielkimi literami)

$$Root Podobnie jak $$ROOT, ale z zachowaniem wielkości liter

$$FULL_DIR_PATH Ścieżka do kartoteki zawierającej projekt



newproj.inf = template for list of template fileś

format is 'sourceResName' \t 'destFileName'

The source res name may be preceded by any combination of

'=', '+', and/or '*'

'=' => the resource is binary

'+' => the file should be added to the project '*' => bypass the custom AppWizard's resources when loading if name starts with / => create new subdir


ROOT.CLW $$root$$.clw +ROOT.CPP $$root$$.cpp ROOT.H $$root$$.h +ROOT.DEF $$root$$.def STDAFK.H StdAfx.h +STDA&X.CPP StdAfK.cpp RESOURCE. H resource. h +ROOT.RCV $$root$$.rc ROOT.REG $$root$$.reg ICONHANDLER.H IconHandler.h +ICONHANDLER.CPP IconHandler.cpp = ICON1. ICO iconl. ico ROOT.RC2 res\$$root$$.rc2

Pierwsza kolumna zawiera nazwę szablonu, zaś druga zawiera nazwę, jaką należy nadać plikowi (używając rozwinięcia makra). Gdy pierwszy raz wygenerowałem ten plik, plik DE nie znalazł się w projekcie (to właśnie przeszkadza w poprawnym działaniu DLL-a). Rozwiązanie: dodaj znak + przed nazwą szablonu ROOT.DEF.

Równie łatwo rozwiązuje się problem z identyfikatorem GUID. Obiekt AppWizarda w /miennej m_Dictionary posiada słownik makr. Aby stworzyć własne makro, po prostu dodaj je do słownika:

//W konstruktorze obiektu AppWizarda

m_Dictionary[_T("Ulubiony_Magazyn")] = _T("Visual Developer");

Teraz makro $$Ulubiony_Magazyn$$ da wynik, jakiego oczekujesz. Makro _T rzutuje łańcuch do typu LPTSTRT, wymaganego przez to i wiele innych wywołań OLE.

Uzbrojony w powyższe informacje możesz łatwo stworzyć makro dla identyfikatora GU1D. Musisz wywołać CoCreateGuid w celu wygenerowania liczby. Potem powi­nieneś przekonwertować jąna łańcuch przy pomocy StringFromCLSID (CLSID, czyli Class ID to jedna z postaci identyfikatora GUID). Łańcuch będzie w formacie UNICODE, więc jeśli operujesz zwykłymi łańcuchami, powinieneś także przekonwertować go na ANS1. Kod wymaga, by GUID był podany w postaci długiego łańcucha oraz jako seria liczb szesnastkowych oddzielonych przecinkami. W przykładowym kodzie znajdziesz makra dla obu przypadków.

Dostosowywanie szablonów jest proste do momentu, w którym zechcesz poprawić plik REG. Plik REG dostarcza pozycji Rejestru wymaganych przez powłokę dla rozpoznania programu obsługi. Idealnie byłoby, gdybyś mógł określić rozszerzenie używanego pliku w jednym z kroków kreatora. Niestety, to wymaga napisania nieco kodu, a ponieważ dotąd nie zniżyliśmy się do napisania ani linijki prawdziwego kodu kreatora, wstydem byłoby zrobienie tego teraz. Oprócz tego, Rejestr wymaga podania pełnej ścieżki dostępu do DLL-a. To nie powinno być problemem, jeśli tylko weźmiemy pod uwagę, fakt, że do oddzielenia ścieżki w Rejestrze wymagane są podwójne znaki backslash. Oczywiście możesz odczytać zmienną $$PATH$$, programowo podwoić znaki backslash i dostarczyć nowe makro dla takiej wartości. Pozostałoby jeszcze tylko podjęcie decyzji, w jaki sposób obsługiwać odmienne położenia plików programu dla debbugera (Debug) i programu w końcowej postaci (Release).

Zdecydowałem, że szkoda na to wysiłku. Zamiast tego, w podejrzane miejsca w miejsce pliku REG wstawiłem ciąg XXX i oznaczyłem je komentarzem TODO. Oprócz tego zamieściłem nazwy DLL-a bez informacji o ścieżce. Musisz umieścić DLL-a w miejscu, w którym Windows znajdzie go samo, lub ręcznie zmodyfikować plik REG.

Gdy już stworzysz kreatora programu obsługi ikon, możesz bez końca tworzyć to rozszerzenie powłoki - nawet jeśli zupełnie nie masz pojęcia jak to działa. Podobnie możesz stworzyć swojego własnego kreatora i udostępnić go innym programistom, którzy mogą nie być w stanie samodzielnie stworzyć odpowiedniego kodu.

Inne opcje

Oparcie nowego kreatora na istniejącym projekcie jest tylko jednym ze sposobów tworzenia takich kreatorów. Możesz także zacząć od zwykłych kroków kreatora (i ewentualnie je zmodyfikować) lub możesz sam stworzyć wszystkie kroki. Jeśli chcesz, możesz dodać do swojego kreatora kontekstową pomoc.

Jako twórcę kreatorów mogą zainteresować Cię trzy nowe klasy: CCustomAppWiz, CAppWizStepDlg oraz OutputStream. W Twoim kodzie znajduje się klasa wyprowadzo­na z klasy CCustomAppWiz (odpowiednik obiektu aplikacji w normalnym programie). Obiekt ten zarządza, między innymi, wspomnianym wcześniej słownikiem makr. Możesz przesłaniać jego funkcje składowe w celu dostosowania działań kreatora.

Zwykle nie będziesz zajmował się klasą OutputStream (o dziwo, nazwa tej klasy MFC nie zaczyna się od litery C). Klasa zawiera dwie proste funkcje: WriteLine oraz Write-Block. WriteLine przenosi linie tekstu z szablonu do strumienia wyjściowego, podczas gdy WriteBlock przenosi do niego zasoby binarne (na przykład bitmapy).

Jeśli chcesz stworzyć własne kroki kreatora, będziesz musiał stworzyć szablon dialogu dla każdego kroku i powiązać go z klasą wyprowadzoną z CAppWizStepDlg (która sama w sobie pochodzi z klasy CDialog). W każdym kroku, w wyprowadzonej klasie zostaje przesłonięta metoda CAppWizStepDlg::OnDismiss. Ta metoda przejmuje kontrolę w momencie, gdy użytkownik kliknie na przycisku Next, Back lub Finish. W rym momencie masz okazję do zaktualizowania słownika.

DLL kreatora AppWizard użytkownika także eksportuje kilka funkcji, których możesz użyć. Funkcja GetDialog udostępnia jeden ze standardowych kroków, który możesz wykorzystać. Główna funkcja Twojego DLL-a wywołuje SetCustomAppWizClass (AppWizard przygotowuje ją dla Ciebie automatycznie). Możesz także określić ilość kroków (SetNumberOfSteps) lub wybrać obsługiwane języki (ScanForAvailable-Languages / SetSupportedLanguages).


Dalsze modyfikacje

Nie byłbym sobą, gdybym zostawił program taki, jaki jest, więc zdecydowałem się dodać do kreatora jeden krok (patrz rysunek 6.4). Zmiany w kodzie były minimalne. Oczywiście, wywołanie SetNumberOfSteps wymagało jako argumentu l zamiast 0. Oprócz tego musiałem zaprojektować okno dialogowe. Tworzone okno dialogowe powinno mieć nadany styl Child oraz Control. Jeśli nie zastosujesz stylu Control, nie będziesz mógł przemieszczać się pomiędzy elementami kontrolnymi przy pomocy klawisza Tab. Nie dodawaj belki tytułowej ani żadnego ze standardowych przycisków (Finish, Next, Back itd.).

ClassWizard nie ma pojęcia o klasie CAppWizStepDlg, więc zamiast niej, jako klasy podstawowej, musiałem użyć klasy CDialog. Następnie ręcznie zmieniłem wszystkie wystąpienia klasy CDialog w plikach nagłówkowych i CPP. Oprócz tego musiałem zmodyfikować konstruktor stworzony przez ClassWizarda, który konstruktorowi klasy CDialog przekazywał dwa argumenty, identyfikator zasobu i uchwyt okna nadrzędnego; konstruktor CAppWizStepDlg wymaga tylko pierwszego z nich.

Przy pomocy ClassWizarda stworzyłem połączenie DDX pomiędzy trzema polami edycji a zmiennymi obiektu CSteplDlg (klasy, którą stworzyłem w ClassWizardzie). Następnie przesłoniłem metodę OnDismiss. Ta funkcja wywołuje metodę UpdateData(TRUE) w celu przekazania danych do zmiennych, po czym aktualizuje słownik. Oprócz tego zmodyfikowałem pliki REG i CONFIRM.INF, tak by odzwierciedlały nowe pozycje słownika. Właśnie tę wersję kodu znajdziesz na listingach 6.9 i 6.10.

Listing 6.9. ShlconWzAw.CPP.


// shiconwzaw.cpp : implementation file

//

#include "stdafx.h"

#include "shiconwz.h"

#include "shiconwzaw.h"

#include <objbase.h>

#include <afxpriv.h>

#ifdef _PSEUDO_DEBUG

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

// This is called immediately after the custom AppWizard is

// loaded. Initialize the state of the custom AppWizard here.

void CShiconwzAppWiz::InitCustomAppWiz()

{

// There are no steps in this custom AppWizard.

SetNumberOfSteps(1);

// Inform AppWizard that we're making a DLL.

m_Dictionary[_T("PROJTYPE_DLL")] = _T("1");

// TODO: Add any other custom AppWizard-wide initialization here.

char clsid[66],part[66];

GUID g;

LPOLESTR str;

CoCreateGuid(&g);

StringFromCLSID(g,&str);

USES_CONVERSION;

strcpy(clsid,W2A(str));

// Set up CLSID in various ways in the dictionary

m_Dictionary[_T("EXT_CLSID")]=_T(clsid);

strcpy(part,"0x");

clsid[9]='\0';

strcpy(part+2,clsid+1);

m_Dictionary[_T("CLSID_P1")]=_T(part);

clsid[14]='\0';

strcpy(part+2,clsid+10);

m_Dictionary[_T("CLSID_P2")]=_T(part);

clsid[19]='\0';

strcpy(part+2,clsid+15);

m_Dictionary[_T("CLSID_P3")]=_T(part);

part[2]=clsid[20];

part[3]=clsid[21];

strcpy(part+4,",0x");

part[7]=clsid[22];

part[8]=clsid[23];

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

{

strcpy(part+9+5*i,",0x");

part[9+5*i+3]=clsid[25+i*2];

part[9+5*i+4]=clsid[26+i*2];

}

part[39]='\0';

m_Dictionary[_T("CLSID_P4")]=_T(part);

}

// This is called just before the custom AppWizard is unloaded.

void CShiconwzAppWiz::ExitCustomAppWiz()

{

// TODO: Add code here to deallocate resources

// used by the custom AppWizard

}

// This is called when the user clicks "Create..." on the

// New Project dialog or next

CAppWizStepDlg* CShiconwzAppWiz::Next(CAppWizStepDlg* pDlg)

{

// Set template macros based on the project name entered by the user.

// Get value of $$root$$ (already set by AppWizard)

if (pDlg==NULL) // first time

{

CString strRoot;

m_Dictionary.Lookup(_T("root"), strRoot);

// Set value of $$Doc$$, $$DOC$$

CString strDoc = strRoot.Left(6);

m_Dictionary[_T("Doc")] = strDoc;

strDoc.MakeUpper();

m_Dictionary[_T("DOC")] = strDoc;

// Set value of $$MAC_TYPE$$

strRoot = strRoot.Left(4);

int nLen = strRoot.GetLength();

if (strRoot.GetLength() < 4)

{

CString strPad(_T(' '), 4 - nLen);

strRoot += strPad;

}

strRoot.MakeUpper();

m_Dictionary[_T("MAC_TYPE")] = strRoot;

return &step_1; // bring up step 1

}

// only 1 step so we are done if we get here (and we

// should never get here). The step_1 dialog updates

// the dictionary

return NULL;

}

// Here we define one instance of the CShiconwzAppWiz class.

// You can access m_Dictionary and any other public members

// of this class through the global Shiconwzaw.

CShiconwzAppWiz Shiconwzaw;

W pierwszej wersji kreatora kod korzystał z domyślnej procedury Next. W tym mo­mencie Next wymaga modyfikacji w celu wywołania dodatkowego kroku kreatora. Główna klasa (CShiconAppWiz) obecnie zawiera dodatkową zmienną składową (step_l), będącą obiektem klasy CSteplDlg. Jeśli procedura Next otrzyma wskaźnik NULL, oznacza to, że powinieneś zwrócić wskaźnik do pierwszego kroku. Kod zwraca adres zmiennej step_l. Jeśli chciałbyś użyć kilku stron, musiałbyś sprawdzać wskaźnik, aby zdecydować, który obiekt dialogu należy zwrócić. Jeśli chciałbyś jeden ze standar­dowych kroków, możesz to osiągnąć wywołując metodę GetDialog, aby dowiedzieć się, którego wskaźnika użyć.

Jeśli w oryginalnej wersji kreatora określiłbym, że chcę użyć kroków kreatora, AppWizard dostarczyłby klasę CDialogChooser do zarządzania wieloma dialogami. W przypadku jednego roku to zbyt duża sprawa; zdecydowałem się więc obsłużyć dialog samodziel­nie. Oprócz tego, jeśli oprzesz swojego kreatora na istniejącym projekcie, AppWizard nie doda od siebie żadnej obsługi pliku pomocy. Nie jest to jednak duży problem. Po prostu nadaj plikowi pomocy taką samą nazwę, jaką ma Twój kreator.' Gdy użytkownik klika na przycisku Pomoc w jednym z Twoich kroków kreatora, Yisual C++ uruchamia program WinHelp z nazwą Twojego pliku pomocy i identyfikatorem kontekstu równym 131072 plus identyfikator okna dialogowego. Jeśli tylko w pliku pomocy znajduje się temat o odpowiednim identyfikatorze kontekstu, wszystko działa poprawnie.

Debuggowanie kreatorów

Jeśli zechcesz zrobić cokolwiek skomplikowanego z kreatorem, prawdopodobnie będziesz musiał go debuggować. Yisual C++ aranżuje projekt w taki sposób, że powstaje wersja release i wersję do debuggowania, ale to jeszcze nie wszystko. Zauważysz, że IDE oznacza wersję inną niż release jako pseudo-debug. W każdym przypadku Yisual C++, używa bibliotek dla wersji release. Jednak w trybie pseudo-debug istnieje minimalne wsparcie debuggowania. Jeśli uruchomisz kreatora, uruchamia się kolejna kopia Yisual C++, i do wykonania kodu musisz użyć polecenia New Project Workspace.

Kolejne pomysły na kreatory

Spróbuj stworzyć własne kreatory na podstawie projektu dowolnego typu. To doskonały sposób na wspomożenie innych programistów i promocję własnych dzieł. Po prostu na­pisz minimalny program, przypraw go komentarzami TODO i stwórz z niego kreatora. Jeśli umieścisz w kreatorze zbyt dużo specyficznego kodu, przeszkodzisz innym w jego używaniu; staraj się więc uogólniać.

Jeśli masz żyłkę handlowca, powinieneś być w stanie sprzedać swoje kreatory. Pełny zestaw kreatorów rozszerzeń powłoki prawdopodobnie cieszyłby się powodzeniem (jakieś oferty?). A co z kreatorem do tworzenia gier przygodowych, programów baz danych, serwerów WWW lub serwerów Winsock? Tu leżą pieniądze. Szkoda, że nie jestem dobrym handlowcem.

Podsumowanie

MFC bardzo ułatwia tworzenie arkuszy właściwości i kreatorów. Jedyna trudność polega na szukaniu wymówek, aby ich nie tworzyć. Modalne arkusze właściwości są proste. Niemodalne arkusze właściwości są tylko odrobinę bardziej skomplikowane.

MFC potwierdza to, o czym przekonują reklamy (przynajmniej w tym przypadku). W oparciu o istniejące projekty można bardzo prosto tworzyć własne kreatory. Jest to jedna z najbardziej ekscytujących i prawdopodobnie najmniej wykorzystana możliwość pakietu Yisual C++.

Praktyczny przewodnik Arkusze właściwości i kreatory

Tworzenie arkusza właściwości

Tworzenie kreatora

Korzystanie z pojedynczego szablonu

Niemodalne arkusze właściwości

Tworzenie własnych kreatorów AppWizard

Praca z arkuszami właściwości nie różni się zbytnio od obsługi zwykłych okien dialogo­wych. Należy tylko przyzwyczaić się do operowania grupą dialogów, gdyż każda zakładka (strona) odpowiada pojedynczej, podobnej do dialogu klasie.

Tworzenie arkusza właściwości

Stworzenie arkusza właściwości jest kwestią wykonania kilku prostych kroków:

1. Kliknij prawym przyciskiem w panelu zasobów i w menu kontekstowym wybierz polecenie Insert.

2. Gdy pojawi się okno dialogowe, kliknij na znaku plus rozwijając gałąź Dialog.

3. Wybierz jeden ż rodzajów arkuszy właściwości (smali - mały, medium - średni lub large - dużyi

4. Zapełnij okno dialogu elementami kontrolnymi, tak jak w przypadku zwykłego okna dialogowego.

5. Wywołaj ClassWizarda i użyj go do stworzenia nowej klasy (wyprowadzonej z CPropertyPage) odpowiadającej stworzonej stronie.

6. Przy pomocy zakładki Member Yariables, w oknie ClassWizarda stwórz zmienne sterujące stworzonymi przez Ciebie elementami kontrolnymi.

7. Powtórz poprzednie kroki tworząc pozostałe zakładki.

8. Aby wyświetlić arkusz, stwórz obiekty dla klas każdej ze stron, które stworzyłeś.

9. Stwórz obiekt klasy CPropertySheet.

10. Zmodyfikuj wszystkie zmienne składowe stron, które chcesz zainicjować.

11. Dla każdej ze stron wywołaj metodę CPropertySheet: :AddPage.

12. Wywołaj metodę CPropertySheet::DoModal i obsłuż ją tak samo jak w przypadku normalnego okna dialogowego.

A oto typowy przykład:

CPropertySheet sheet(„Przykładowy arkusz właściwości");
CPropPgl propl; // wyprowadzone z CPropertyPage
CPropPg2 prop2; // wyprowadzone z CPropertyPage
sheet.AddPAge(Łpropl);
sheet.AddPAge(&prop2);
propl.m_valuel = 100; // DDK
propl.m_value2 = "Test"; // DDK

prop2.m_position = 25; // także DDK

if(sheet.DoModal() == IDOK)

// odwrotne DDK

AfxMessageBox(propl.m_value2,"Zwrócone dane");

Tworzenie kreatora

Aby stworzyć kreatora, wykonaj te same kroki co w przypadku arkusza właściwości. Pamiętaj tylko, aby przed wywołaniem metody DoModal wywołać metodę CProperty­Sheet : :SetWizardMode.

Oprócz tego, jeśli chcesz poprawnie obsłużyć przyciski, musisz przesłonić metodę OnSet-Active każdego obiektu zakładki arkusza. Wewnątrz nowej metody musisz odwołać się do nadrzędnego okna (czyli do właściwego arkusza) i wywołać metodę CPropertySheet: :SetWizardButtons w celu ustalenia przycisków, które mają się pojawić (patrz tabela 6.2).

Korzystanie z pojedynczego szablonu

Czasem możesz użyć tego samego szablonu strony dla kilku zakładek okna. Dozwolone jest użycie kilku egzemplarzy obiektu strony w arkuszu właściwości, ale ponieważ tytuł dialogu staje się nazwą zakładki, w rezultacie otrzymasz arkusz właściwości z zakładkami o tych samych nazwach.

Odpowiedź jest prosta. W konstruktorze klasy CPropertyPage musisz dostarczyć identyfikator łańcucha w zasobach. Ten identyfikator określa ciąg, który zawiera nazwę zakładki. Jeśli zdecydujesz się na przekazanie identyfikatora do konstruktora wyprowa­dzonej przez siebie klasy, okaże się, że MFC żąda użycia domyślnego konstruktora. Aby to ominąć, możesz albo przeciążyć konstruktor, albo argumentowi zawierającemu identyfikator przypisać domyślną wartość, tak by mógł służyć także jako domyślny konstruktor. Szczegóły znajdziesz w listingach 6.1 i 6.2.

Niemodalne arkusze właściwości

Poprzez wywołanie metody Create zamiast DoModal można tworzyć niemodalne arkusze właściwości. Oczywiście będziesz musiał samodzielnie użyć metody UpdateData i pamiętać o zasięgu zmiennych (podobnie jak w niemodalnych oknach dialogowych).

Jednym z możliwych sposobów radzenia sobie z problemem zasięgu jest wyprowadzenie nowej klasy z klasy CPropertySheet. Nowa klasa powinna zawierać zmienne składowe, odpowiadające obiektom każdej z klas zakładek zawartych w arkuszu właściwości. Możesz następnie stworzyć obiekt nowej klasy na stercie (przy pomocy new), a program sam zajmie się resztą (przejrzyj listingi od 6.5 do 6.8).


Tworzenie własnych kreatorów AppWizard

Do Yisual C++ możesz dodawać własne kreatory AppWizard. Dzięki temu możesz łatwo powielać projekty lub udostępniać innym programistom szkieletowe aplikacje zgodne z Twoimi specyfikacjami.

Najprostszym sposobem osiągnięcia tego celu jest rozpoczęcie od gotowej aplikacji, którą chcesz powielić. Następnie rozpocznij nowy projekt, jako jego typ wybierając Gustom AppWizard. W tym momencie będziesz mógł wskazać istniejący projekt, po czym kreator przygotuje dla Ciebie nowy kreator. To naprawdę jest takie proste.

Możesz także zacząć od standardowych kroków kreatora, a nawet stworzyć go od początku, jednak wymaga to dużo więcej wysiłku (szczegóły znajdziesz w tekście rozdziału oraz na listingach 6.9 i 6.10).

Czarna Księga MFC 1

Rozdział 6. Arkusze właściwości i kreatory



Wyszukiwarka

Podobne podstrony:
06 Arkusz analizy dokumentów, Oligofrenopedagogika, NIEPEŁNOSPRAWNOŚĆ, oligofrenopedagogika kurs, do
06 Arkusz samooceny
logistyk 06 2013 praktyczny arkusz
2012 06 Technik informatyk arkusz zadaniaid 27644
logistyk 06 2014 praktyczny arkusz
arkusz 2 opm chemia z tutorem 12 06 2014 klasy przedmaturalne
zeglugi srodlad 06 11 arkusz
logistyk 06 2010 praktyczny arkusz
2012 06 Technik informatyk arkusz zadania, E12 E13 E14, Technik Informatyk
2012 06 Technik informatyk arkusz zadania
Wypełnione, SpecyfikacjaWymagań 01 2007 06 13 MI, ARKUSZ ZLECENIA PROJEKTOWEGO
312[01] 06 122 Arkusz egzaminac Nieznany (2)
eksploatacji portow i terminali 06 2013 arkusz
eksploatacji portow i terminali 06 2011 arkusz
Wypełnione, ZlecenieProjektowe 01 2007 06 13 MI, ARKUSZ ZLECENIA PROJEKTOWEGO
zeglugi srodlad 06 13 arkusz
chemia z tutorem arkusz 12 06 2013
2008 06 teleinformatyk arkusz x
Kurs Excel`a, Lekcja 06, Lekcja 6 - zmiana nazw arkuszy i ich struktury

więcej podobnych podstron