21
Das Erstellen eines ActiveX-Steuerelements mit den MFC ist recht einfach, wie Sie bereits in den Kapiteln 17, »Ein ActiveX-Steuerelement erstellen«, und 20, »Ein Internet-ActiveX-Steuerelement erstellen«, feststellen konnten. Sie kommen über die Runden, ohne wirklich zu wissen, was eine COM-Schnittstelle ist oder wie man mit einer Typbibliothek arbeitet. Ihr Steuerelement kann alle möglichen praktischen MFC-Klassen, wie etwa CString oder CWnd, nutzen und CDC-Mitgliedsfunktionen sorgen für seine Anzeige und mehr. Der einzige Nachteil ist, daß die Anwender Ihres Steuerelements die MFC-DLLs benötigen, und wenn sich diese DLLs noch nicht auf dem Anwender-System befinden, so ist der Download einer etwa 700 Kbyte großen CAB-Datei und die dadurch bedingte Verzögerung nicht unbeträchtlich.
Die Alternative ist, die ActiveX-Funktionalität aus der Active Template Library (ATL) zu beziehen und Win32-SDK-Funktionen direkt zu verwenden, wie dies C-Programmierer in den Tagen vor Visual C++ und den MFC gemacht haben. Das Win32-SDK ist sehr umfangreich und kann in diesem Kapitel nicht vollständig abgedeckt werden. Die gute Neuigkeit ist: Wenn Sie mit den wichtigsten MFC-Klassen wie CWnd und CDC bereits vertraut sind, werden Sie einige der SDK-Funktionen wiedererkennen, selbst wenn Sie sie nie zuvor gesehen haben. Viele der MFC-Mitgliedsfunktionen sind einfache Wrapper für SDK-Funktionen.
Wieviel Download-Zeit können Sie sich dadurch sparen? Das MFC-Steuerelement aus Kapitel 20, »Ein Internet-ActiveX-Steuerelement erstellen«, ist etwa 30 Kbyte groß plus die Größe der MFC-DLLs. Das in diesem Kapitel zu erstellende ATL-Steuerelement wird höchstens 100 Kbyte einnehmen, und damit sind sämtliche Funktionalitäten in das Element integriert. Mit einigen Tricks könnten Sie es auf 50 Kbyte für das Steuerelement und 20 Kbyte für die ATL-DLL reduzieren û also ein Zehntel der Größe des gesamten Steuerelements und der DLL aus Kapitel 20!
Es gibt einen speziellen Anwendungs-Assistenten, die Ihnen das Erstellen von ATL-Steuerelementen durchaus etwas erleichtert. Wie immer wählen Sie im Developer Studio Datei/Neu und öffnen das Register Projekt. Markieren Sie in der Liste links den Eintrag ATL-COM-Anwendungs-Assistent, wählen Sie das geeignete Verzeichnis aus, weisen Sie dem Projekt den Namen DieRollControl zu (vgl. Abbildung 21.1), und klicken Sie dann auf OK.
Abbildung 21.1: Der ATL-Anwendungs-Assistent macht das Erstellen von ATL-Steuerelementen einfach.
Abbildung 21.2: Der Anwendungs-Assistent läßt nur wenige Einstellungen zu.
Dre ATL-COM-Anwendungs-Assistent besteht aus nur einem Schritt, der in Abbildung 21.2 zu sehen ist. Die vorgegebenen Einstellungen û Dynamic Link Library (DLL), keine Vereinigung von Proxy-/Stub-Code zuslassen, keine MFC unterstützen û sind auch für dieses Projekt die richtigen. Die Dateierweiterung wird nicht wie bei MFC-Steuerelementen OCX sein, sondern DLL, doch ist dies kein gewichtiger Unterschied. Klicken Sie auf Fertigstellen.
Das Dialogfeld Neue Projektinformationen, das Sie in Abbildung 21.3 sehen, faßt noch einmal die vorgenommenen Einstellungen für das Projekt zusammen. Bestätigen Sie mit OK, damit das Projekt angelegt wird.
Abbildung 21.3: Die ATL-Einstellungen werden zusammengefaßt, bevor Sie das Projekt anlegen.
Der ATL-COM-Anwendungs-Assistent hat sieben Dateien angelegt, doch wurde noch kein Gerüst für das Element erstellt. Zunächst müssen Sie die Anweisung aus Schritt 1 des Assistenten noch befolgen und ein ATL-Objekt in das Projekt einfügen.
Wählen Sie Einfügen/Neues ATL-Objekt. in ATL-Projekte einfuegenSteuerelemente in Projekte einfuegenATL- (in Projekte einfuegen)Der ATL-Objekt-Assistent wird geöffnet (siehe Abbildung 21.4).
Abbildung 21.4: Fügen Sie ein ATL-Steuerelement in das Projekt ein.
Es stehen verschiedene ATL-Objekte für die Aufnahme in ein Projekt zur Verfügung, doch für das Beispiel ist nur ein Steuerelement interessant, markieren Sie daher im Listenfeld links den Eintrag Controls. Rechts haben Sie nun die Auswahl zwischen einem vollständigen Steuerelement (Full Control), einem Steuerelement für den Internet Explorer (Internet Explorer Control) und einer Eigenschaftsseite (Property Page). Wenn Sie sicher wissen, daß ein Steuerelement nur im Internet Explorer eingesetzt werden soll, etwa als Teil eines Intranet-Projekts, können Sie Internet Explorer Control auswählen und dadurch etwas Platz sparen. Da das Dieroll-Steuerelement vielleicht auch in einem anderen Browser, in einer VB-Anwendung oder an ganz anderer Stelle eingesetzt wird, sollten Sie sich hier für die Option Full Control entscheiden. Eine Eigenschaftsseite wird weiter unten in diesem Kapitel eingefügt. Wählen Sie also Full Control, und klicken Sie auf Weiter.
Jetzt erscheint das Dialogfeld Eigenschaften von ATL Object Wizard. Das erste Register ist das Register Namen. Hier können Sie alle für dieses Steuerelement verwendeten Namen selbst festlegen. Tragen Sie als Kurzbezeichnung den Namen DieRoll ein, und die restlichen Namen werden auf der Grundlage dieser Kurzbezeichnung automatisch eingetragen (vgl. Abbildung 21.5). Wenn Sie wollen, können Sie diese Namen auch ändern, doch gibt es keinen Grund dafür. Der hier eingetragene Name für den Typ, hier DieRoll Class, später im Dialogfeld Objekt einfügen der meisten Container erscheint. Da die MFC-Version von DieRoll vermutlich schon unter Windows registriert wurde, ist es ganz gut, hier einen anderen Namen für diese Version zu haben. Bei anderen Projekten können Sie sich überlegen, ob Sie den Namen für den Typ ändern.
Abbildung 21.5: Legen Sie die Namen der Dateien und des Steuerelements fest.
Öffnen Sie nun das Register Attribute desselben Dialogfelds. Übernehmen Sie die Vorgaben: Threading-Modell Apartment, Schnittstelle Dual und Ja für Aggregation. Aktivieren Sie die Optionen ISupportErrorInfo unterstützen und Verbindungspunkte unterstützen. Lassen Sie Abruffunktion mit freien Threads deaktiviert. Um Ihre Einstellungen zu überprüfen, können Sie sie mit Abbildung 21.6 vergleichen. Jede dieser Optionen wird nachfolgend in einem eigenen Abschnitt erörtert.
Abbildung 21.6: Im Register Attribute stellen Sie die COM-Eigenschaften Ihres Steuerelements ein.
Threading-Modell: Vermeiden Sie die Auswahl des Einzel-Threading-Modells, selbst wenn Ihre Steuerelemente nicht mit mehreren Programmfäden arbeiten. Um sicherzustellen, daß keine zwei Funktionen eines solchen Steuerelements gleichzeitig ausgeführt werden, müssen alle Methodenaufrufe eines Einzel-Threading-Steuerelements durch einen Proxy koordiniert werden, wodurch die Ausführungsgeschwindigkeit beträchtlich herabgesetzt wird. Die Einstellung Apartment ist für neue Steuerelemente die bessere Wahl.
Das Apartment-Modell verweist auf das STA-Modell (kurz für engl. Single-Threaded Apartment). Dies bedeutet, daß Zugang zu Ressourcen, die von Instanzen des Steuerelements (globale und statische) geteilt werden, durch Serialisierung erfolgt. Für Instanzdaten û lokale automatische Variablen und Objekte, die auf dem Heap dynamisch zugeteilt werden û ist dieser Schutz nicht erforderlich. Dies macht STA-Steuerelemente schneller als Einzel-Threading-Steuerelemente. In den Internet Explorer eingebundene Steuerelemente nutzen STA.
Das freie Threading-Modell (auch Multithreaded Apartment oder MTA) bezieht sich auf Steuerelemente, die mit mehreren Programmfäden arbeiten und bereits einen Schutz gegen Kollisionen mitbringen. Es scheint zwar eine großartige Idee zu sein, Steuerelemente mit mehrfachen Programmfäden zu schreiben, doch bedarf der Einsatz eines solchen Steuerelements in einem Container, der nur aus einem Programmfaden besteht und auch kein STA-Container ist, wieder der Koordination (Marshalling) û diesmal um den Container dagegen zu schützen, daß zwei Funktionen gleichzeitig aufgerufen werden. Auch dies führt zu einer ineffizienten Programmausführung. Zudem bedeutet diese Methode für den Entwickler einen nicht unbeträchtlichen Mehraufwand, da ein Kollisionsschutz für die verschiedenen Programmfäden implementiert werden muß.
Die Option Beides erstellt ein Steuerelement, das STA (Apartment) und MTA (Frei) unterstützt. Dies gewährleistet eine effiziente Verarbeitung in einem Einzel-Threading- oder einem STA-Container und nutzt dennoch die Leistungsstärke des MTA-Modells, wenn verfügbar. Sie kommen jedoch nicht umhin, wie auch beim Schreiben von MTA- Steuerelementen, den Threading-Schutz zu implementieren.
Momentan sollten für den Internet Explorer STA-Steuerelemente erstellt werden. DCOM-Steuerelemente hingegen, auf die ein Zugriff über mehrere Verbindungen gleichzeitig möglich ist, können von dem MTA-Modell profitieren.
Duale und Benutzer-Schnittstellen: COM-Objekte kommunizieren über Schnittstellen, d.h. Sammlungen von Funktionen, die das mögliche Verhalten eines COM-Objekts beschreiben. Um eine Schnittstelle nutzen zu können, erhalten Sie einen Zeiger auf diese Schnittstelle. Alle Automations-Server und ActiveX-Steuerelemente besitzen zusätzlich zu den funktionalitätsspezifischen Schnittstellen die Schnittstelle IDispatch. Um eine Methode eines Steuerelements aufzurufen, können Sie die Methode Invoke der Schnittstelle IDispatch verwenden, wobei Sie die dispid der aufzurufenden Methode angeben. (Diese Technik wurde entwickelt, damit Methoden auch von Visual Basic und anderen zeigerlosen Sprachen aufgerufen werden konnten.)
Bei einem Steuerelement mit einer Dual-Schnittstelle können Sie Methoden nach beiden Verfahren aufrufen: also entweder über die Mitgliedsfunktion einer selbstdefinierten Schnittstelle oder durch den Einsatz von IDispatch. MFC-Steuerelemente verwenden nur das IDispatch-Verfahren, doch ist dies langsamer als die Verwendung einer benutzerdefinierten Schnittstelle. Wählen Sie die Option Benutzerdefiniert, wenn Sie IDispatch überhaupt nicht verwenden wollen, und die Option Dual, wenn das Steuerelement bei Bedarf auch in einem Visual Basic verwendbar sein soll.
Aggregation: In diesem Gruppenfeld legen Sie fest, ob eine COM-Klasse diese COM-Klasse nutzen kann, indem darin eine Referenz auf eine Instanz dieser Klasse eingebunden wird. Wenn Sie Ja wählen, können andere COM-Objekte diese Klasse verwenden; wenn Sie Nein wählen, ist dies nicht möglich. Immer bedeutet, das Objekt kann nicht für sich allein ausgeführt und muß somit immer eingebunden werden.
Weitere Attribute: Bei Auswahl der Option ISupportErrorInfo unterstützen werden umfangreichere Fehlerinformationen an den Container geliefert. Die Option Verbindungspunkte unterstützen ist notwendig für ein Steuerelement wie das im Beispiel, das Ereignisse auslöst. Die Auswahl der Option Abruffunktion mit freien Threads ist für ein STA-Steuerelement nicht erforderlich.
Öffnen Sie das Register Sonstige, in dem alle Vorgabewerte übernommen werden können (siehe Abbildung 21.7). Das Steuerelement sollte Deckend sein, mit einem Einheitlichen Hintergrund und ein DC normieren, auch wenn dies die Effizienz nur wenig verbessert, da Ihr Zeichencode viel leichter zu schreiben sein wird.
Abbildung 21.7: Übernehmen Sie im Register Sonstige die vorgegebenen Werte.
Öffnen Sie das Register Grundeigenschaften, um festzulegen, welche Standardeigenschaften das Steuerelement unterstützen soll. Damit eine Standardeigenschaft unterstützt wird, markieren Sie sie links im Listenfeld Nicht unterstützt und klicken auf die Schaltfläche >, damit sie nach rechts in die Liste Unterstützt verlagert wird. Legen Sie für das Beispielsteuerelement eine Unterstützung der Eigenschaften Background Color und Foreground Color fest (vgl. Abbildung 21.8). Beabsichtigen Sie für ein Steuerelement die Unterstützung der Mehrzahl der Eigenschaften, so klicken Sie auf die Schaltfläche >>, damit alle nach rechts verlagert werden, und verschieben dann die Eigenschaften wieder in die linke Liste, deren Unterstützung Sie nicht benötigen.
Abbildung 21.8: Die Standard-Vorder- und -Hintergrundfarbe sollte unterstützt werden.
Klicken Sie nun im Objekt-Assistenten auf OK, damit das Steuerelement angelegt wird. Sie können jetzt das Projekt versuchsweise einmal erstellen, wenn auch das Steuerelement noch keine Aktionen ausführt.
Die MFC-Versionen -Steuerelemente (Eigenschaften hinzufuegen)ATL- (Eigenschaften hinzufuegen)hinzufuegen (ATL-Steuerelemente)Eigenschaften (ATL-Steuerelemente)von Dieroll besaßen drei Standardeigenschaften: BackColor, ForeColor und ReadyState. Die ersten beiden wurden bereits in die ATL-Version implementiert, doch die Standardeigenschaft ReadyState muß manuell eingefügt werden. Zudem sind zwei benutzerdefinierte Eigenschaften, Number und Dots, sowie eine asynchrone Eigenschaft, Image, zu definieren.
Eine ATL-COM-Klasse implementiert oder verwendet eine Schnittstelle, indem sie von einer Klasse abgeleitet wird, die diese Schnittstelle repräsentiert. In Listing 21.1 sind alle Klassen aufgelistet, von denen CDieRoll abgeleitet ist.
Listing 21.1: Die ATL-Klassen für ein ActiveX-Steuerelement |
class ATL_NO_VTABLE CDieRoll :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CDieRoll, &CLSID_DieRoll>,
public CComControl<CDieRoll>,
public CStockPropImpl<CDieRoll, IDieRoll, &IID_IDieRoll,
&LIBID_DIEROLLCONTROLLib>,
public IProvideClassInfo2Impl<&CLSID_DieRoll, NULL,
&LIBID_DIEROLLCONTROLLib>,
public IPersistStreamInitImpl<CDieRoll>,
public IPersistStorageImpl<CDieRoll>,
public IQuickActivateImpl<CDieRoll>,
public IOleControlImpl<CDieRoll>,
public IOleObjectImpl<CDieRoll>,
public IOleInPlaceActiveObjectImpl<CDieRoll>,
public IViewObjectExImpl<CDieRoll>,
public IOleInPlaceObjectWindowlessImpl<CDieRoll>,
public IDataObjectImpl<CDieRoll>,
public ISupportErrorInfo,
public IConnectionPointContainerImpl<CDieRoll>,
public ISpecifyPropertyPagesImpl<CDieRoll>
Jetzt sehen Sie auch, woher das T in ATL kommt: Alle diese Klassen sind Templates. (Wenn Sie mit dem Einsatz von Templates nocht nicht vertraut sind, sollten Sie Kapitel 26, »Ausnahmen, Templates und die neuesten Ergänzungen zu C++«, lesen. Sie definieren eine Schnittstellenunterstützung für ein Steuerelement, indem Sie diese Liste der Schnittstellenklassen um einen weiteren Eintrag ergänzen, von dem es dann ebenfalls erbt.
Weiter unten in der Header-Datei finden Sie die in Listing 21.2 abgebildete COM-Tabelle.
Listing 21.2: Eine ATL-COM-Tabelle |
BEGIN_COM_MAP(CDieRoll)
COM_INTERFACE_ENTRY(IDieRoll)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY_IMPL(IViewObjectEx)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IViewObject2, IViewObjectEx)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IViewObject, IViewObjectEx)
COM_INTERFACE_ENTRY_IMPL(IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IOleInPlaceObject,
IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IOleWindow, IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY_IMPL(IOleInPlaceActiveObject)
COM_INTERFACE_ENTRY_IMPL(IOleControl)
COM_INTERFACE_ENTRY_IMPL(IOleObject)
COM_INTERFACE_ENTRY_IMPL(IQuickActivate)
COM_INTERFACE_ENTRY_IMPL(IPersistStorage)
COM_INTERFACE_ENTRY_IMPL(IPersistStreamInit)
COM_INTERFACE_ENTRY_IMPL(ISpecifyPropertyPages)
COM_INTERFACE_ENTRY_IMPL(IDataObject)
COM_INTERFACE_ENTRY(IProvideClassInfo)
COM_INTERFACE_ENTRY(IProvideClassInfo2)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
END_COM_MAP()
Diese COM-Tabelle ist die Verbindung zwischen IUnknown::QueryInterface und allen von dem Steuerelement unterstützten Schnittstellen. Alle COM-Objekte müssen IUnknown implementieren. Mit Hilfe der Funktion QueryInterface kann ermittelt werden, ob das Steuerelement eine bestimmt Schnittstelle unterstützt, und ein Zeiger auf diese Schnittstelle bezogen werden. Die Makros verbinden die Ixxx-Schnittstellen mit den IxxxImpl-Klassen, von denen CDieRoll abgeleitet ist.
Wenn Sie sich noch einmal die Erbschaftsliste für CDieRoll betrachten, werden Sie keinen Hinweis darauf finden, welche Eigenschaften unterstützt werden. Weiter unten in der Header-Datei wurden CDieRoll zwei Mitgliedsvariablen hinzugefügt:
OLE_COLOR m_clrBackColor;
OLE_COLOR m_clrForeColor;
Der Objekt-Assistent hat aber die IDL-Datei DieRollControl.idl aktualisiert, um die zwei Standardeigenschaften bekanntzumachen (siehe Listing 21.3).
Listing 21.3: Auszug aus DieRollControl.idl û Standardeigenschaften |
[
object,
uuid(2DE15F32-8A71-11D0-9B10-0080C81A397C),
dual,
helpstring("IDieRoll Interface"),
pointer_default(unique)
]
interface IDieRoll : IDispatch
{
[propput, id(DISPID_BACKCOLOR)]
HRESULT BackColor([in]OLE_COLOR clr);
[propget, id(DISPID_BACKCOLOR)]
HRESULT BackColor([out,retval]OLE_COLOR* pclr);
[propput, id(DISPID_FORECOLOR)]
HRESULT ForeColor([in]OLE_COLOR clr);
[propget, id(DISPID_FORECOLOR)]
HRESULT ForeColor([out,retval]OLE_COLOR* pclr);
};
Diese Klasse wird die gesamte Untersützung für die get- und put-Funktionen bereitstellen, und den Container davon benachrichten, wenn sich eine dieser Eigenschaften ändert.
ReadyState wurde zwar vom ATL-Objekt-Assistenten nicht in die Liste der Standardeigenschaften aufgenommen, wird jedoch von CStockPropImpl unterstützt. Sie können eine weitere Standardeigenschaft hinzufügen, indem Sie die Header- und die IDL-Datei bearbeiten. Deklarieren Sie dazu in der Header-Datei, unmittelbar nach der Deklaration von m_clrBackColor und m_clrForeColor, eine weitere Mitgliedsvariable:
long m_nReadyState;
Diese Eigenschaft wird in derselben Weise verwendet wie die ReadyState-Eigenschaft in der MFC-Version von Dieroll: um Image als asynchrone Eigenschaft zu implementieren. Fügen Sie in die Datei DieRollControl.idl die folgenden Zeilen in den IDispatch-Block ein, und zwar hinter die Zeilen für BackColor und ForeColor:
[propget, id(DISPID_READYSTATE)]
HRESULT ReadyState([out,retval]long* pclr);
Sie brauchen kein Zeilenpaar hinzufügen, um put für diese Eigenschaft zu implementieren, da externe Objekte ReadyState nicht aktualisieren können. Speichern Sie die Header- und IDL-Dateien, damit die ClassView aktualisiert wird û andernfalls können Sie in der ClassView keine neuen Eigenschaften hinzufügen. Öffnen Sie das im Register ClassView, und erweitern Sie CDieRoll und IDieRoll û wie Sie sehen, wurden die Variable in CDieRoll und die Funktion ReadyState in IDieRoll eingefügt.
Um selbst neue Eigenschaften zu definieren, verwenden Sie ein ATL-Tool, das dem MFC-Klassen-Assistenten ähnelt. Klicken Sie im Register ClassView mit der rechten Maustaste auf IDieRoll (in der obersten Ebene, nicht die unter CDieRoll angeordnete Klasse), um das Kontextmenü in Abbildung 21.9 einzublenden, und wählen Sie Eigenschaft hinzufügen.
Abbildung 21.9: ATL-Projekte haben im Register ClassView ein anderes Kontextmenü als MFC-Projekte.
Das Dialogfeld Eigenschaft zu Schittstelle hinzufügen wird eingeblendet (siehe Abbildung 21.10). Wählen Sie den Typ short aus, und tragen Sie den Namen Number ein. Deaktivieren Sie die Option Put-Funktion, da die auf dem Würfel angezeigte Zahl nicht vom Container geändert zu werden braucht. Übernehmen Sie den Rest der Einstellungen, und klicken Sie auf OK. Die Eigenschaft wird hinzugefügt.
Abbildung 21.10: Fügen Sie Number als schreibgeschützte Eigenschaft ein.
Wiederholen Sie denselben Vorgang für die Eigenschaft Dots des Typs BOOL, die sowohl eine get- als auch eine put-Funktion benötigt. (Lassen Sie auch die Option PropPut aktiviert.) Das Register ClassView weist nun neue Einträge bezüglich dieser Eigenschaften sowohl unter CDieRoll als auch unter IDieRoll auf. Versuchen Sie, zweimal auf die neuen Einträge zu klicken. Wenn Sie etwa CDieRoll erweitern und dort unter IDieRoll zweimal auf get_Dots klicken, wird die CPP-Datei an der Stelle der Funktion get_Dots geöffnet. Klicken Sie hingegen in IDieRoll der obersten Ebene zweimal auf Dots, wird die IDL-Datei geöffnet und der propget-Eintrag für Dots angezeigt.
Es wurden zwar eine Reihe von Einträgen in CDieRoll aufgenommen, jedoch keine Mitgliedsvariablen. Nur Sie können Mitgliedsvariablen hinzufügen, die den neuen Eigenschaften entsprechen. Man kann zwar oft davon ausgehen, daß die neuen Eigenschaften einfach Mitgliedsvariablen der Steuerelementklasse sind, doch ist dies nicht immer so. Zum Beispiel hätte Number anstelle einer Variablen auch die Größe eines innerhalb der Klasse gespeicherten Arrays sein können.
Fügen Sie in die Header-Datei, nach den Deklarationen von m_clrBackColor, m_clr-ForeColor und m_nReadyState, die folgende Zeile ein:
short m_sNumber;
BOOL m_bDots;
In der IDL-Datei verwenden die neuen propget- und propput-Einträge die folgenden fest programmierten dispids von 1 und 2:
[propget, id(1), helpstring("property Number")]
HRESULT Number([out, retval] short *pVal);
[propget, id(2), helpstring("property Dots")]
HRESULT Dots([out, retval] BOOL *pVal);
[propput, id(2), helpstring("property Dots")]
HRESULT Dots([in] BOOL newVal);
Um den Code etwas lesbarer zu gestalten, verwenden Sie eine Aufzählung (enum) von dispids. Nach der Deklaration der Aufzählung in der IDL-Datei kann sie sowohl in der IDL- als auch in der Header-Datei verwendet werden. Fügen Sie am Anfang von DieRollControl.idl die folgenden Zeilen ein:
typedef enum propertydispids
{
dispidNumber = 1,
dispidDots = 2,
}PROPERTYDISPIDS;
Jetzt können Sie die propget-und propput-Einträge ändern:
[propget, id(dispidNumber), helpstring("property Number")]
HRESULT Number([out, retval] short *pVal);
[propget, id(dispidDots), helpstring("property Dots")]
HRESULT Dots([out, retval] BOOL *pVal);
[propput, id(dispidDots), helpstring("property Dots")]
HRESULT Dots([in] BOOL newVal);
Der nächste Schritt besteht darin, den Code für die get- und set-Funktion zum Verwenden der Mitgliedsvariablen zu schreiben. Listing 21.4 zeigt die vollständigen Funktionen. (Wenn Sie diese im Register ClassView nicht entdecken können, erweitern Sie CDieRoll und dann IDieRoll.)
Listing 21.4: get- und set-Funktionen für die Eigenschaften |
STDMETHODIMP CDieRoll::get_Number(short * pVal)
{
*pVal = m_sNumber;
return S_OK;
}
STDMETHODIMP CDieRoll::get_Dots(BOOL * pVal)
{
*pVal = m_bDots;
return S_OK;
}
STDMETHODIMP CDieRoll::put_Dots(BOOL newVal)
{
if (FireOnRequestEdit(dispidDots) == S_FALSE)
{
return S_FALSE;
}
m_bDots = newVal;
SetDirty(TRUE);
FireOnChanged(dispidDots);
FireViewChange();
return S_OK;
}
Der Code in den beiden get-Funktionen ist einfach und direkt. Der Code für put_dots ist etwas komplexer, da er keine Benachrichtigungen auslöst. FireOnRequestEdit benachrichtigt alle IPropertyNotifySink-Schnittstellen, die diese Eigenschaft ändern wird. Jede dieser Schnittstellen kann die Anfrage verweigern. In diesem Fall wird diese Funktion den Wert S_FALSE liefern, um die Änderung zu verbieten.
Vorausgesetzt, die Änderung ist zulässig, wird die Mitgliedsvariable geändert und das Steuerelement als geändert (»dirty«) gekennzeichnet, damit es gespeichert wird. Ein Aufruf von FireOnChange setzt die IPropertyNotifySink-Schnittstellen davon in Kenntnis, daß diese Eigenschaft geändert wurde, und der Aufruf von FireViewChange teilt dem Container mit, daß das Steuerelement neu gezeichnet werden muß.
Nachdem der Code zum Abrufen und Einstellen (get und set) dieser Eigenschaften hinzugefügt wurde, sollten Sie jetzt den Konstruktor von CDieRoll ändern, damit er alle Standardeigenschaften und alle selbstdefinierten Eigenschaften initialisiert (vgl. Listing 21.5). In die Header-Datei wird ein Konstruktorgerüst eingefügt, das Sie bearbeiten können.
Listing 21.5: Eigenschaften initialisieren |
CDieRoll()
{
srand( (unsigned)time( NULL ) );
m_nReadyState = READYSTATE_INTERACTIVE;
m_clrBackColor = 0x80000000 | COLOR_WINDOW;
m_clrForeColor = 0x80000000 | COLOR_WINDOWTEXT;
m_sNumber = Roll();
m_bDots = TRUE;
}
Fügen Sie am Anfang des Headers die folgende Zeile ein (die Deklaration der Funktion time):
#include "time.h"
Wie bereits bei der MFC-Version dieses Steuerelements initialisieren Sie m_sNumber auf eine Zufallszahl zwischen 1 und 6, die von der Funktion Roll geliefert wird. Fügen Sie diese Funktion in CDieRoll ein, indem Sie im Register ClassView mit der rechten Maustaste auf den Klassennamen klicken und aus dem Kontextmenü den Befehl Member-Funktion hinzufügen wählen. Roll hat den Zugriffsstatus protected, akzeptiert keine Parameter und liefert einen Wert des Typs short. Den Code für Roll finden Sie in Listing 21.6; er wurde bereits in Kapitel 17, »Ein ActiveX-Steuerelement erstellen«, erläutert.
Listing 21.6: CDieRoll::Roll() |
short CDieRoll::Roll()
{
double number = rand();
number /= RAND_MAX + 1;
number *= 6;
return (short)number + 1;
}
Wie auch in Kapitel 20, »Ein Internet-ActiveX-Steuerelement erstellen«, repräsentiert die Eigenschaft Image eine Bitmap-Grafik, die asynchron geladen und als Hintergrundbild verwendet werden soll. Fügen Sie die Eigenschaft ebenso wie die Eigenschaften Number und Dots der Schnittstelle hinzu. Verwenden Sie den Typ BSTR und den Namen Image. Aktualisieren Sie Aufzählung in der IDL-Datei, so daß dispidImage 3 ist, und ändern Sie die propget- und propput-Zeilen in der IDL-Datei so, daß Sie den enum-Wert verwenden:
[propget, id(dispidImage), helpstring("property Image")]
HRESULT Image([out, retval] BSTR *pVal);
[propput, id(dispidImage), helpstring("property Image")]
HRESULT Image([in] BSTR newVal);
Fügen Sie nun die Mitgliedsvariable m_bstrImage hinzu:
CComBSTR m_bstrImage;
CComBSTR ist eine ATL-Wrapper-Klasse mit nützlichen Mitgliedsfunktionen zum Manipulieren von Variablen des Typs BSTR.
Eine Reihe anderer Mitgliedsvariablen für die Verarbeitung der Bitmap-Grafik und des asynchronen Ladens müssen noch hinzugefügt werden. Fügen Sie die folgenden Zeilen in DieRoll.h ein:
HBITMAP hBitmap;
BITMAPINFOHEADER bmih;
char *lpvBits;
BITMAPINFO *lpbmi;
HGLOBAL hmem1;
HGLOBAL hmem2;
BOOL BitmapDataLoaded;
char *m_Data;
unsigned long m_DataLength;
Die ersten sechs dieser neuen Variablen werden zum Zeichnen der Bitmap-Grafik verwenden und sollen hier nicht erörtert werden. Die letzten drei erreichen zusammen dasselbe Verhalten wie die Datenpfad-Eigenschaft der MFC-Version dieses Steuerelements.
Fügen Sie die folgenden drei Zeilen in den Konstruktor ein:
m_Data = NULL;
m_DataLength = 0;
BitmapDataLoaded = FALSE;
Fügen Sie einen Destruktor für CDieRoll (in die Header-Datei) und den Code in Listing 21.7 ein.
Listing 21.7: CDieRoll::~CDieRoll() |
~CDieRoll()
{
if (BitmapDataLoaded)
{
GlobalUnlock(hmem1);
GlobalFree(hmem1);
GlobalUnlock(hmem2);
GlobalFree(hmem2);
BitmapDataLoaded = FALSE;
}
if (m_Data != NULL)
{
delete m_Data;
}
}
Die Eigenschaft Image wird über get- und put-Funktionen realisiert. Fügen Sie für diese denselben Code wie in Listing 21.8 ein.
Listing 21.8: get_Image() und put_Image() |
STDMETHODIMP CDieRoll::get_Image(BSTR * pVal)
{
*pVal = m_bstrImage.Copy();
return S_OK;
}
STDMETHODIMP CDieRoll::put_Image(BSTR newVal)
{
USES_CONVERSION;
if (FireOnRequestEdit(dispidImage) == S_FALSE)
{
return S_FALSE;
}
// if there was an old bitmap or data, delete them
if (BitmapDataLoaded)
{
GlobalUnlock(hmem1);
GlobalFree(hmem1);
GlobalUnlock(hmem2);
GlobalFree(hmem2);
BitmapDataLoaded = FALSE;
}
if (m_Data != NULL)
{
delete m_Data;
}
m_Data = NULL;
m_DataLength = 0;
m_bstrImage = newVal;
LPSTR string = W2A(m_bstrImage);
if (string != NULL && strlen(string) > 0)
{
// not a null string so try to load it
BOOL relativeURL = FALSE;
if (strchr(string, ':') == NULL)
{
relativeURL = TRUE;
}
m_nReadyState = READYSTATE_LOADING;
HRESULT ret = CBindStatusCallback<CDieRoll>
::Download(this, OnData, m_bstrImage,
m_spClientSite, relativeURL);
}
else
{
// was a null string so don't try to load it
m_nReadyState = READYSTATE_INTERACTIVE;
}
SetDirty(TRUE);
FireOnChanged(dispidImage);
return S_OK;
}
Wie bei den Eigenschaften Numbers und Dots ist die get-Funktion wieder sehr einfach und die put-Funktion etwas komplizierter. Der Anfang und das Ende der Funktion sind wie bei put_Dots; es werden Benachrichtigungen ausgelöst, um festzustellen, ob die Variable geändert werden kann, und anschließend andere Benachrichtigungen, die von der Änderung der Variablen berichten. Der Code dazwischen ist ein für asynchrone Eigenschaften spezifischer Code.
Um den Download der asynchronen Eigenschaft zu starten, wird die Funktion CBindStatusCallback<CDieRoll>::Download aufgerufen; doch zunächst muß bestimmt werden, ob es sich bei der URL in m_bstrImage um eine relative oder eine absolute URL-Adresse handelt. Verwenden Sie das ATL-Makro W2A, um die auf 2-Byte-Zeichen beruhende BSTR-Variable in eine CString-Variable zu konvertieren, so daß die C-Funktion strchr zur Suche nach dem Zeichen : in der URL verwendet werden kann. Ist das Zeichen : nicht zu finden, so wird angenommen, daß es sich um eine relative URL-Adresse handelt.
In der MFC-Version des Steuerelements Dieroll mit der asynchronen Eigenschaft Image wurde die Funktion OnDataAvailable aufgerufen, wenn ein Datenblock durchkam. Der Aufruf von Download sorgt dafür, daß eine Funktion mit dem Namen OnData aufgerufen wird, wenn Daten ankommen. Sie werden die Funktion OnData selbst schreiben. Fügen Sie sie als public-Funktion der Klasse hinzu, die Implementierung sehen Sie in Listing 21.9.
Listing 21.9: DieRoll.cpp û CDieRoll::OnData() |
void CDieRoll::OnData(CBindStatusCallback<CDieRoll>* pbsc,
BYTE * pBytes, DWORD dwSize)
{
char *newData = new char[m_DataLength + dwSize];
memcpy(newData, m_Data, m_DataLength);
memcpy(newData+m_DataLength, pBytes, dwSize);
m_DataLength += dwSize;
delete m_Data;
m_Data = newData;
if (ReadBitmap())
{
m_nReadyState = READYSTATE_COMPLETE;
}
}
Da es beim Verwenden von new kein realloc gibt, verwendet diese Funktion new, um genügend chars zuzuteilen, um die bereits gelesenen Daten (m_DataLength) sowie die neuen eingehenden Daten (dwSize) zu speichern, und kopiert dann m_Data in diesen Block und die neuen Daten (pBytes) nach m_Data. Anschließend versucht sie, die bisher empfangenen Daten in eine Bitmap-Grafik zu konvertieren. Gelingt dies, muß der Download vollständig sein, und der Aufruf von FireViewChange sendet eine Benachrichtigung an den Container, um das Ssteuerelement neu zu zeichnen. Die Funktion ReadBitmap finden Sie auf der CD û kopieren Sie sie, und fügen Sie sie in Ihr Projekt ein: Sie ist der MFC-Version sehr ähnlich, doch verwendet sie keine MFC-Klassen wie CFile.
Die Struktur von OnDraw ist wie folgt:
HRESULT CDieRoll::OnDraw(ATL_DRAWINFO& di)
// Zeichnen der Bitmap, wenn sie fertig ist
// andernfalls Zeichnen eines Hintergrunds mit BackColor
// wenn !Dots, Zeichnen einer Ziffer in ForeColor
// andernfalls Zeichnen der Punkte
Zunächst muß überprüft werden, ob die Bitmap-Grafik fertig ist. Dann muß sie, wenn möglich, gezeichnet werden. Der Code hierfür ist in Listing 21.10 angeführt: Fügen Sie diesen anstelle des vom Anwendungs-Assistenten generierten Code in die vorhandene OnDraw ein. Beachten Sie folgendes: Liefert ReadyState den Wert READYSTATE_COMPLETE, doch der Aufruf von CreateDIBitmap resultiert nicht in einem gültigen Bitmap-Handle, so wird der Inhalt der Bitmap-Mitgliedsvariablen gelöscht, damit die nachfolgenden Aufrufe dieser Funktion etwas schneller ausgeführt werden. Es ist jedoch nicht Gegenstand dieses Kapitels, das Zeichnen von Bitmaps zu erörtern.
Listing 21.10: CDieRoll::OnDraw() û Bitmap verwenden |
int width = (di.prcBounds->right û di.prcBounds->left + 1);
int height = (di.prcBounds->bottom û di.prcBounds->top + 1);
BOOL drawn = FALSE;
if (m_nReadyState == READYSTATE_COMPLETE)
{
if (BitmapDataLoaded)
{
hBitmap = ::CreateDIBitmap(
di.hdcDraw, &bmih, CBM_INIT, lpvBits,
lpbmi, DIB_RGB_COLORS);
if (hBitmap)
{
HDC hmemdc;
hmemdc = ::CreateCompatibleDC(di.hdcDraw);
::SelectObject(hmemdc, hBitmap);
DIBSECTION ds;
::GetObject(hBitmap,sizeof(DIBSECTION),
(LPSTR)&ds);
::StretchBlt(di.hdcDraw,
di.prcBounds->left, // links
di.prcBounds->top, // oben
width, // Zielbreite
height, // Zielhöhe
hmemdc, // das Bild
0, // Abstand zum Bild -x
0, // Abstand zum Bild -y
ds.dsBm.bmWidth, // Breite
ds.dsBm.bmHeight, // Höhe
SRCCOPY); // kopieren
drawn = TRUE;
::DeleteObject(hBitmap);
hBitmap = NULL;
::DeleteDC(hmemdc);
}
else
{
GlobalUnlock(hmem1);
GlobalFree(hmem1);
GlobalUnlock(hmem2);
GlobalFree(hmem2);
BitmapDataLoaded = FALSE;
}
}
}
Wenn die Bitmap-Grafik nicht gezeichnet wurde, da entweder ReadyState noch nicht den Wert READYSTATE_COMPLETE angenommen hatte oder es ein Problem mit der Bitmap-Grafik gab, so zeichnet OnDraw einen einfarbigen Hintergrund entsprechend der Eigenschaft BackColor (vgl. Listing 21.11). Fügen Sie diesen Code in OnDraw ein. Die SDK-Aufrufe sind den MFC-Aufrufen aus der MFC-Version von DieRoll sehr ähnlich: So entspricht ::OleTranslateColor beispielsweise der Funktion TranslateColor.
Listing 21.11: CDieRoll::OnDraw() û Zeichnen eines einfarbigen Hintergrunds |
if (!drawn)
{
COLORREF back;
::OleTranslateColor(m_clrBackColor,
NULL, &back);
HBRUSH backbrush = ::CreateSolidBrush(back);
::FillRect(di.hdcDraw, (RECT *)di.prcBounds,
backbrush);
::DeleteObject(backbrush);
}
Sobald der Hintergrund gezeichnet ist, entweder als Grafik oder als einheitliche Farbe, muß OnDraw für die Anzeige des Vordergrunds sorgen. Das Abrufen der Vordergrundfarbe ist recht einfach: Fügen Sie die folgenden zwei Zeilen in OnDraw ein:
COLORREF fore;
::OleTranslateColor(m_clrForeColor, NULL, &fore);
Wenn Dots den Wert FALSE annimmt, sollte statt der Punkte eine Zahl gezeichnet werden. Fügen Sie den Code in Listing 21.12 in OnDraw ein. Wieder erledigen die SDK-Funktionen dieselbe Aufgabe wie die ähnlich benannten MFC-Funktionen, die in der MFC-Version von DieRoll verwendet werden.
Listing 21.12: CDieRoll::OnDraw() û eine Ziffer zeichnen |
if (!m_bDots)
{
_TCHAR val[20]; // Zeichenentsprechung
// des short-Werts
_itot(m_sNumber, val, 10);
::SetTextColor(di.hdcDraw, fore);
::ExtTextOut(di.hdcDraw, 0, 0, ETO_OPAQUE,
(RECT *)di.prcBounds, val,
_tcslen(val), NULL );
}
Den Code zum Zeichnen der Punkte finden Sie in Listing 21.13. Fügen Sie ihn in OnDraw ein, um die Funktion fertigzustellen. Dieser Code ist lang, wurde jedoch in Kapitel 17, »Ein ActiveX-Steuerelement erstellen«, erläutert. Wie beim Rest von OnDraw wurden die MFC-Funktionsaufrufe durch SDK-Aufrufe ersetzt.
Listing 21.13: CDieRoll::OnDraw() û Punkte zeichnen |
else
{
// Punkte sind 4 Einheiten breit und hoch
// und um eine Einheit vom Rand abgesetzt
int Xunit = width/16;
int Yunit = height/16;
int Xleft = width%16;
int Yleft = height%16;
// Die Hälfte des verbleibenden Abstands oben
// und links als Abstand einfügen
int Top = di.prcBounds->top + Yleft/2;
int Left = di.prcBounds->left + Xleft/2;
HBRUSH forebrush;
forebrush = ::CreateSolidBrush(fore);
HBRUSH savebrush = (HBRUSH)::SelectObject(
di.hdcDraw, forebrush);
switch(m_sNumber)
{
case 1:
::Ellipse(di.hdcDraw, Left+6*Xunit,
Top+6*Yunit, Left+10*Xunit,
Top + 10*Yunit); //Mitte
break;
case 2:
::Ellipse(di.hdcDraw, Left+Xunit,
Top+Yunit, Left+5*Xunit,
Top + 5*Yunit); //links oben
::Ellipse(di.hdcDraw, Left+11*Xunit, Top+11*Yunit,
Left+15*Xunit, Top + 15*Yunit); //rechts unten
break;
case 3:
::Ellipse(di.hdcDraw, Left+Xunit, Top+Yunit,
Left+5*Xunit, Top + 5*Yunit); //links oben
::Ellipse(di.hdcDraw, Left+6*Xunit, Top+6*Yunit,
Left+10*Xunit, Top + 10*Yunit); //Mitte
::Ellipse(di.hdcDraw, Left+11*Xunit, Top+11*Yunit,
Left+15*Xunit, Top + 15*Yunit); //rechts unten
break;
case 4:
::Ellipse(di.hdcDraw, Left+Xunit, Top+Yunit,
Left+5*Xunit, Top + 5*Yunit); //links oben
::Ellipse(di.hdcDraw, Left+11*Xunit, Top+Yunit,
Left+15*Xunit, Top + 5*Yunit); //rechts oben
::Ellipse(di.hdcDraw, Left+Xunit, Top+11*Yunit,
Left+5*Xunit, Top + 15*Yunit); //links unten
::Ellipse(di.hdcDraw, Left+11*Xunit, Top+11*Yunit,
Left+15*Xunit, Top + 15*Yunit); //rechts unten
break;
case 5:
::Ellipse(di.hdcDraw, Left+Xunit, Top+Yunit,
Left+5*Xunit, Top + 5*Yunit); //links oben
::Ellipse(di.hdcDraw, Left+11*Xunit, Top+Yunit,
Left+15*Xunit, Top + 5*Yunit); //rechts oben
::Ellipse(di.hdcDraw, Left+6*Xunit, Top+6*Yunit,
Left+10*Xunit, Top + 10*Yunit); //Mitte
::Ellipse(di.hdcDraw, Left+Xunit, Top+11*Yunit,
Left+5*Xunit, Top + 15*Yunit); //links unten
::Ellipse(di.hdcDraw, Left+11*Xunit, Top+11*Yunit,
Left+15*Xunit, Top + 15*Yunit); //rechts unten
break;
case 6:
::Ellipse(di.hdcDraw, Left+Xunit, Top+Yunit,
Left+5*Xunit, Top + 5*Yunit); //links oben
::Ellipse(di.hdcDraw, Left+11*Xunit, Top+Yunit,
Left+15*Xunit, Top + 5*Yunit); //rechts oben
::Ellipse(di.hdcDraw, Left+Xunit, Top+6*Yunit,
Left+5*Xunit, Top + 10*Yunit); //Mitte links
::Ellipse(di.hdcDraw, Left+11*Xunit, Top+6*Yunit,
Left+15*Xunit, Top + 10*Yunit); //Mitte rechts
::Ellipse(di.hdcDraw, Left+Xunit, Top+11*Yunit,
Left+5*Xunit, Top + 15*Yunit); //links unten
::Ellipse(di.hdcDraw, Left+11*Xunit, Top+11*Yunit,
Left+15*Xunit, Top + 15*Yunit); //rechts unten
break;
}
::SelectObject(di.hdcDraw, savebrush);
::DeleteObject(forebrush);
}
return S_OK;
}
Die Eigenschaften wurden dem Steuerelement hinzugefügt und werden beim Zeichnen des Steuerelements verwendet. Bleibt nur noch, die Eigenschaften persistent zu machen und eine Eigenschaftsseite einzuzufügen.
Wählen Sie Einfügen/Neues ATL-Objekt. Der ATL-Objekt-Assistent wird geöffnet. Markieren Sie daher im Listenfeld links den Eintrag Controls und rechts das Symbol Property Page, und wählen Sie Weiter. Öffnen Sie das Register Namen, tragen Sie als Kurzname DieRollPPG ein, und öffnen Sie dann das Register Zeichenfolgen (die Einstellungen im Register Attribute brauchen nicht genändert zu werden). Geben Sie als Titel General ein und DieRoll Property Page als Dok.-Zeichenfolge. Löschen Sie den Eintrag im Feld Hilfedatei, und lassen Sie das Feld leer. Jetzt können Sie OK wählen, damit die Eigenschaftsseite in das Projekt aufgenommen wird.
Öffnen Sie im Arbeitsbereich das Register ResourceView, und öffnen Sie das Dialogfeld IDD_DIEROLLPPG. Löschen Sie das Textfeld Fügen Sie Ihre Steuerelemente hier ein. Fügen Sie dann ein Kontrollkästchen mit der Ressourcen-ID IDC_DOTS und der Beschriftung Display Dot Pattern in das Dialogfeld ein sowie ein Eingabefeld mit der Ressourcen-ID IDC_IMAGE und darüber ein statisches Textfeld mit der Beschriftung Image URL (vgl. Abbildung 21.11).
Abbildung 21.11: Fügen Sie die Steuerelemente in das Dialogfeld ein.
Fügen Sie am Anfang der Datei DieRollPPG.h die folgende Zeile ein:
#include "DieRollControl.h"
Nun müssen die Steuerelemente auf dieser Eigenschaftsseite mit den Eigenschaften des Programms Dieroll verbunden werden. Der erste Schritt dafür ist, die folgenden drei Zeilen in die Nachrichtentabelle in DieRollPPG.h einzufügen, so daß der Inhalt dem Listing 21.14 entspricht.
Listing 21.14: Deklaration der Nachrichtentabelle für die Eigenschaftsseite |
BEGIN_MSG_MAP(CDieRollPPG)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_HANDLER(IDC_DOTS, BN_CLICKED, OnDotsChanged)
COMMAND_HANDLER(IDC_IMAGE, EN_CHANGE, OnImageChanged)
CHAIN_MSG_MAP(IPropertyPageImpl<CDieRollPPG>)
END_MSG_MAP()
Diese neuen Zeilen gewährleisten, daß OnInitDialog beim Initialisieren des Dialogfelds und OnDotsChanged bwz. OnImageChanged aufgerufen werden, wenn Dots bzw. Image geändert werden (die anderen Eigenschaften besitzen keine put-Methoden und lassen sich daher nicht ändern.)
Fügen Sie den Code in Listing 21.15 in die Header-Datei ein, um OnInitDialog zu deklarieren und zu implementieren.
Listing 21.15: OnInitDialog() |
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL & bHandled)
{
USES_CONVERSION;
CComQIPtr<IDieRoll, &IID_IDieRoll> pDieRoll(m_ppUnk[0]);
BOOL dots;
pDieRoll->get_Dots(&dots);
::SendDlgItemMessage(m_hWnd, IDC_DOTS,
BM_SETCHECK, dots, 0L);
BSTR image;
pDieRoll->get_Image(&image);
LPTSTR image_URL = W2T(image);
SetDlgItemText(IDC_IMAGE, image_URL);
return TRUE;
}
Dieser Code beginnt damit, mit Hilfe der Template-Klasse CComQIPtr einen Zeiger auf eine IDieRoll-Schnittstelle zu deklarieren und ihn auf das erste Element des Arrays m_ppUnk zu initialisieren. (Es ist auch möglich, eine Eigenschaftsseite mit mehreren Steuerelementen zu verbinden.) Der Konstruktor für die Template-Klasse CComQIPtr verwendet die Methode QueryInterface des Zeigers IUnknown, der an den Konstruktor übergeben wurde, um einen Zeiger auf eine IDieRoll-Schnittstelle zu finden. Jetzt können Sie Mitgliedsfunktionen dieser Schnittstelle aufrufen, um auf die Eigenschaften des Steuerelements DieRoll zugreifen zu können.
Das Abrufen des Werts der Eigenschaft Dots des Objekts CDieRoll ist recht einfach: Sie rufen einfach get_Dots auf. Um diesen Wert zum Initialisieren des Kontrollkästchens auf der Eigenschaftsseite zu verwenden, senden Sie mit Hilfe der SDK-Funktion SendDlgItemMessage eine Nachricht an das Steuerelement. Der Parameter BM_SETCHECK zeigt an, daß Sie einstellen, ob das Kontrollkästchen angekreuzt (aktiviert) ist oder nicht. Die Weiterleitung von dots als vierten Parameter stellt sicher, daß IDC_DOTS aktiviert wird, wenn dots TRUE ist, und deaktiviert wird, wenn dots FALSE ist. In ähnlicher Weise rufen Sie die URL für das Bild mit get_Image ab, konvertieren sie von Zwei-Byte- in Ein-Byte-Zeichen und verwenden dann SetDlgItemText, um den Inhalt des Eingabefelds auf diese URL zu setzen.
OnDotsChanged und OnImageChanged sind recht einfach: Fügen Sie den Code aus Listing 21.16 in die Header-Datei ein, und zwar nach OnInitDialog.
Listing 21.16: Die OnChanged-Funktionen |
LRESULT OnDotsChanged(WORD wNotify, WORD wID,
HWND hWnd, BOOL& bHandled)
{
SetDirty(TRUE);
return FALSE;
}
LRESULT OnImageChanged(WORD wNotify, WORD wID,
HWND hWnd, BOOL& bHandled)
{
SetDirty(TRUE);
return FALSE;
}
Die Aufrufe von SetDirty in diesen Funktionen stellen sicher, daß die Funktion Apply aufgerufen wird, wenn der Anwender auf der Eigenschaftsseite auf OK klickt.
Vom Objekt-Assistenten wurde eine einfache Apply-Funktion generiert, doch hat diese keinen Einfluß auf die Eigenschaften Dots oder Number. Ändern Sie die Funktion Apply so, daß sie dem Inhalt von Listing 21.17 entspricht.
Listing 21.17: Apply() |
STDMETHOD(Apply)(void)
{
USES_CONVERSION;
BSTR image = NULL;
GetDlgItemText(IDC_IMAGE, image);
BOOL dots = (BOOL)::SendDlgItemMessage(
m_hWnd, IDC_DOTS,
BM_GETCHECK, 0, 0L);
ATLTRACE(_T("CDieRollPPG::Apply\n"));
for (UINT i = 0; i < m_nObjects; i++)
{
CComQIPtr<IDieRoll, &IID_IDieRoll> pDieRoll(m_ppUnk[i]);
if FAILED(pDieRoll->put_Dots(dots))
{
CComPtr<IErrorInfo> pError;
CComBSTR strError;
GetErrorInfo(0, &pError);
pError->GetDescription(&strError);
MessageBox(OLE2T(strError), _T("Error"),
MB_ICONEXCLAMATION);
return E_FAIL;
}
if FAILED(pDieRoll->put_Image(image))
{
CComPtr<IErrorInfo> pError;
CComBSTR strError;
GetErrorInfo(0, &pError);
pError->GetDescription(&strError);
MessageBox(OLE2T(strError), _T("Error"),
MB_ICONEXCLAMATION);
return E_FAIL;
}
}
m_bDirty = FALSE;
return S_OK;
}
Apply beginnt damit, die Werte der Eigenschaften dots und image aus dem Dialogfeld abzurufen. Beachten Sie in dem Aufruf von SendDlgItemMessage, daß der dritte Parameter BM_GETCHECK ist, so daß dieser Aufruf den Zustand des Kontrollkästchens (TRUE oder FALSE; aktiviert oder deaktiviert) erhält. Anschließend wird durch den Aufruf von ATLTRACE eine Nachricht für die Ablaufverfolgung ausgegeben, um die Fehlersuche zu erleichtern. Wie auch die in Kapitel 24 erörterten Anweisungen zur Verfolgung des Programmverlaufs verschwindet eine solche Anweisung in der Auslieferversion des Programms.
Den größten Teil von Apply nimmt die Schleife ein, die einmal für jedes mit dieser Eigenschaftsseite verbundene Steuerelement ausgeführt wird. Wie in OnInitDialog wird ein IDieRoll-Schnittstellenzeiger abgerufen, und versuchsweise werden die Mitgliedsfunktionen put_Dots und put_Image dieser Schnittstelle aufgerufen. Ist einer dieser Aufrufe nicht erfolgreich, wird der Anwender über ein Meldungsfenster davon unterrichtet. Nach der Schleife kann die Mitgliedsvariable m_bDirty auf den Wert FALSE gesetzt werden.
Die Änderungen in CDieRollPPG sind jetzt vollständig. Einige Änderungen müssen noch an der Klasse CDieRoll vorgenommen werden, um sie mit der Eigenschaftsseitenklasse zu verbinden. Insbesondere muß die Eigenschaftentabelle noch um einige Einträge ergänzt werden. Bearbeiten Sie sie, bis ihr Inhalt dem Listing 21.18 entspricht.
Listing 21.18: Apply, die zweite |
BEGIN_PROPERTY_MAP(CDieRoll)
PROP_ENTRY( "Dots", dispidDots, CLSID_DieRollPPG)
PROP_ENTRY( "Image", dispidImage, CLSID_DieRollPPG)
PROP_ENTRY( "Fore Color", DISPID_FORECOLOR, CLSID_StockColorPage )
PROP_ENTRY( "Back Color", DISPID_BACKCOLOR, CLSID_StockColorPage )
END_PROPERTY_MAP()
Die Einträge für Dots und Image sind neu. Für Fore Color und Back Color wird das vom Objekt-Assistenten generierten Makro PROP_PAGE durch das Makro PROP_ENTRY ersetzt. Dadurch ist gewährleistet, daß die Eigenschaften bestehen bleiben, d.h. mit dem Steuerelement gespeichert werden.
Es gibt eine Reihe unterschiedlicher Methoden, wie der Internet Explorer Eigenschaftswerte aus einem HTML-Code abrufen und einem Steuerelement zuweisen kann, das vom <OBJECT>-Tag eingeschlossen ist. Mit der standardmäßig bereitgestellten Stream-Persistenz verwenden Sie im <OBJECT>-Tag das Attribut DATA. Wenn Sie die viel besser lesbaren <PARAM>-Tags verwenden würden, muß das Steuerelement Persistenz über die Schnittstelle IPersistPropertyBag unterstützen.
Ergänzen Sie die Liste der Basisklassen am Anfang der Klasse CDieRoll um eine weitere Klasse:
public IPersistPropertyBagImpl<CDieRoll>,
Fügen Sie die folgende Zeile in die COM-Tabelle ein:
COM_INTERFACE_ENTRY_IMPL(IPersistPropertyBag)
Jetzt können Sie mit Hilfe der <PARAM>-Tags die Eigenschaften des Steuerelements einstellen.
Sie haben jede Menge Code in CDieRoll und CDieRollPPG eingefügt, und es ist an der Zeit, das Steuerelement zu erstellen. Nachdem Sie Schreibfehler und kleinere sonstige Fehler korrigiert haben, können Sie mit dem Steuerelement arbeiten.
Sie werden den HTML-Code erstellen, der das Steuerelement im Microsoft Control Pad anzeigt. Wenn Sie kein Control Pad besitzen, können Sie es per Download kostenlos von der Webseite http://www.microsoft.com/workshop/author/cpad/download.htm beziehen. Wenn Ihre Kopie des Control Pad vor dem Januar 1997 datiert ist, sollten Sie sich die neue Version besorgen. Für die alte Version scheinen die Arbeiten, die Sie weiter unten hinsichtlich der Sicherheitsregistrierung für die Initialisierung und Skript-Ausführung vornehmen, nicht richtig zu funktionieren.
Wenn Sie das Control Pad starten, erstellt es ein leeres HTML-Dokument. Setzen Sie den Cursor zwischen <BODY> und </BODY>, und wählen Sie Edit/Insert ActiveX Control. Das Dialogfeld Insert ActiveX Control wird eingeblendet. Wählen Sie DieRoll Class aus der Liste (Sie mögen sich aus Abbildung 21.5 daran erinnern, daß der Typenname für dieses Steuerelement DieRoll Class ist), und klicken Sie auf OK. Das Steuerelement und ein Eigenschaftsdialog erscheinen. Klicken Sie auf die Eigenschaft Image, und geben Sie in das Eingabefeld am Anfang des Eigenschaftsdialogs den vollständigen Pfad zu der Datei beans.bmp (die auf der CD verfügbar ist) ein. Klicken Sie auf Apply, und das Steuerelement wird mit dem Bild als Hintergrund neu gezeichnet (siehe Abbildung 21.12). Schließen Sie den Eigenschaftsdialog und das Dialogfeld Edit ActiveX Control, und Sie sehen den HTML-Code, der für Sie generiert wurde, einschließlich der <PARAM>-Tags, die eingefügt wurden, da das Control Pad festgestellt hat, daß DieRoll die IPersistPropertyBag-Schnittstelle unterstützt.
Abbildung 21.12: Wenn Sie das Steuerelement in das Control Pad einfügen, wird es angezeigt.
Das Steuerelement besitzt jetzt noch nicht seine vollständige Funktionalität. Es wird noch nicht gewürfelt, wenn Sie auf das Element klicken. Im nächsten Abschnitt werden die Ereignisse hinzugefügt.
Zwei Ergeignisse müssen hinzugefügt werden: eines, wenn der Anwender auf das Steuerelement klickt, und ein zweites, wenn sich der Bereitschaftszustand ändert. Das Klick-Ereignis wurde in Kapitel 17, »Ein ActiveX-Steuerelement erstellen«, erörtert und das Ereignis ReadyStateChanged in Kapitel 20, »Ein Internet-ActiveX-Steuerelement erstellen«.
Es gibt kein ATL-Dialogfeld zum Hinzufügen von Ereignissen. Daher werden Sie die Ereignisse gänzlich selbst programmieren. Zunächst benötigen Sie eine globale, eindeutige ID (GUID für engl. Global Unique ID) für die Ereignis-Schnittstelle. ATL- (GUIDs FUeR Ereignis-Schnittstellen)-Steuerelemente (GUIDs fuer Ereignis-Schnittstellen) Dieser Bezeichner sollte mit Hilfe des Dienstprogramms guidgen auf Ihrem eigenen System generiert werden. Wählen Sie unter Windows Start/Ausführen, tragen Sie den Pfad zur Datei guidgen.exe ein, und klicken Sie auf OK. Das Dialogfeld Create GUID erscheint (vgl. Abbildung 21.13). Aktivieren Sie die Option Registry Format, das dem in IDL-Dateien verwendeten Format am nächsten kommt, und klicken Sie dann auf die Schaltfläche Copy, damit die neue GUID in die Zwischenablage kopiert wird. Klicken Sie auf die Schaltfläche Exit, und aktivieren Sie wieder das Developer Studio.
Abbildung 21.13: Generieren Sie eine GUID für Ihre Ereignis-Schnittstelle.
Fügen Sie die Zeilen aus Listing 21.19 in den library-Abschnitt der IDL-Datei ein, und zwar nach den beiden importlib-Anweisungen. Fügen Sie dann anstelle der in Listing 21.19 vorgegebenen GUID die neue ein.
Listing 21.19: DieRoll.idl û Zeilen, die in den Library-Abschnitt einzufügen sind |
[
uuid(6E46C460-8C00-11d0-9B12-0080C81A397C),
helpstring("Event interface for DieRoll")
]
dispinterface _DieRollEvents
{
properties:
methods:
[id(DISPID_CLICK)] void Click();
[id(DISPID_READYSTATECHANGE)]
void ReadyStateChange();
};
Fügen Sie einige Zeilen weiter unten, im coclass-Block für DieRoll, die folgende Zeile ein:
[default, source] dispinterface _DieRollEvents;
Damit Sie Ereignisse auslösen können, ATL- (Ereignisse ausloesen)-Steuerelemente (Ereignisse ausloesen)ATL-Steuerelementewerden Sie die Schnittstelle IConnectionPoint implementieren. Die ATL-Proxy-Komponenten unterstützen Sie dabei. Zunächst speichern Sie die IDL-Datei und erstellen das Projekt, damit die mit dem Projekt verbundene Typbibliothek aktualisiert wird.
Wählen Sie Projekt/Dem Projekt hinzufügen/Komponenten und Steuerelemente. Das Dialogfeld Sammlung der Komponenten und Steuerelemente wird eingeblendet. Klicken Sie zweimal auf Developer Studio Components. Markieren Sie ATL Proxy Generator (siehe Abbildung 21.14), und klicken Sie auf die Schaltfläche Einfügen. Klicken Sie im Meldungsfenster auf OK, damit die Komponente eingefügt wird.
Klicken Sie auf die Schaltfläche ..., um nach der Datei typelib zu suchen. Markieren Sie DieRollControl.tlb, und klicken Sie auf Öffnen. Klicken Sie im Listenfeld Nicht gewählt auf _DieRollEvents und anschließend auf die Schaltfläche >, damit der Eintrag in das Listenfeld Gewählt verlagert wird. Aktivieren Sie den Proxy-Typ Verbindungspunkt (siehe Abbildung 21.15, und klicken Sie auf die Schaltfläche Einfügen.
Das Dialogfeld Save wird eingeblendet, damit Sie die Komponente in der Datei CPDieRollControl.h speichern können. Klicken Sie auf die Schaltfläche Speichern und im Meldungsfenster auf die Schaltfläche OK. Schließen Sie das Dialogfeld ATL-Proxy-Generator und dann das Dialogfeld Sammlung der Komponenten und Steuerelemente.
Abbildung 21.14: Fügen Sie eine ATL-Proxy-Generator-Komponente ein.
Abbildung 21.15: Wählen Sie die neue Schnittstellenkomponente aus, und klicken Sie auf Einfügen.
Der Proxy-Generator hat in CPDieRollControl.h die Wrapper-Klasse CProxy_DieRoll-Events angelegt. Fügen Sie die folgende include-Anweisung in DieRoll.h ein:
#include "CPDieRollControl.h"
Um die folgende Schnittstelle in CDieRoll einzufügen, fügen Sie sie in die Erbschaftsliste am Anfang der Klasse ein:
public CProxy_DieRollEvents<CDieRoll>,
Der neue Verbindungspunkt muß in die leere Verbindungspunkttabelle eingefügt werden:
BEGIN_CONNECTION_POINT_MAP(CDieRoll)
CONNECTION_POINT_ENTRY(DIID__DieRollEvents)
END_CONNECTION_POINT_MAP()
Die allgemeine Vorbereitung ist jetzt vollständig.
Wenn der Anwender auf das Steuerelement klickt, sollte dadurch ein fuer das Steuerelement DieRoll ausloesenKlick-Ereignis ausloesenATL-Steuerelemente (DieRoll)Klick-Ereignis ausgelöst werden. Fügen Sie einen Eintrag in die Nachrichtentabelle ein:
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
Fügen Sie die Mitgliedsfunktion OnLButtonDown in CDieRoll ein und den Code in Listing 21.20 hinzu.
Listing 21.20: OnLButtonDown() |
LRESULT CDieRoll::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL & bHandled)
{
m_sNumber = Roll();
FireOnChanged(dispidNumber);
Fire_Click();
FireViewChange();
return 0;
}
Dieser Code sorgt dafür, daß der Würfel geworfen wird, löst eine Benachrichtigung aus, daß sich die Zahl geändert hat, löst ein Klick-Ereignis aus und benachrichtigt den Container davon, daß das Steuerelement neu aufgebaut werden muß.
put_Image und OnData können jetzt Ereignisse auslösen, wenn sich der Bereitschaftszustand ändert. Suchen Sie in der Funktion put_Image nach der folgenden Zeile:
m_nReadyState = READYSTATE_LOADING;
Und fügen Sie unmittelbar danach die folgende Zeile ein:
Fire_ReadyStateChange();
Suchen Sie dann nach dieser Zeile:
m_nReadyState = READYSTATE_INTERACTIVE;
Fügen Sie ebenfalls nach dieser Zeile den Aufruf der Funktion Fire_ReadyStateChange hinzu. Suchen Sie in OnData nach der folgenden Zeile:
m_nReadyState = READYSTATE_COMPLETE;
Erstellen Sie das Steuerelement, und fügen Sie es in eine neue Seite im Control Pad ein. Klicken Sie im Fenster Edit ActiveX Control auf den Würfel, und er wird bei jedem Klick eine neue Zahl würfeln. Als weiteren Test öffnen Sie den Test-Container für ActiveX-Steuerelemente (über das Menü Extras im Developer Studio), fügen ein Steuerelement ein und verwenden dann das Ereignisprotokoll, um sicherzustellen, daß die Ereignisse Click und ReadyStateChange ausgelöst werden.
Das nächste Stadium bei der Entwicklung des Steuerelements besteht darin, eine Funktion bereitzustellen, die es dem Container erlaubt, den Würfel zu werfen. Eine Einsatzmöglichkeit für diese Fähigkeit wäre, daß immer, wenn ein Würfel angeklickt wird, ein zweiter ebenfalls geworfen wird. Klicken Sie im Register ClassView mit der rechten Maustaste auf die Schnittstelle IDieRoll, und wählen Sie Methode hinzufügen. Tragen Sie DoRoll als Methodennamen ein, und lassen Sie das Feld Parameter leer. Klicken Sie auf OK.
Funktionen besitzen wie Eigenschaften eine Dispatch_ID (dispid). Fügen Sie zu der Aaufzählung (enum) der dispid-Werte in der IDL-Datei einen Eintrag hinzu, so daß dispidDoRoll 4 ist. Dadurch ist gewährleistet, daß später, beim Hinzufügen einer anderen Eigenschaft, deren dispid nicht mit der von DoRoll kollidiert. Beim Einfügen der Funktion in die Schnittstelle wurde nach den get- und put-Einträgen ein Zeile für die Eigenschaften hinzugefügt. Ändern Sie diese so, daß sie die neue dispid verwendet und damit folgenden Inhalt hat:
[id(dispidDoRoll), helpstring("method DoRoll")] HRESULT DoRoll();
Den Code für DoRoll finden Sie in Listing 21.21. Fügen Sie diesen in die Datei DieRoll.cpp ein.
Listing 21.21: CDieRoll::DoRoll() |
STDMETHODIMP CDieRoll::DoRoll()
{
m_sNumber = Roll();
FireOnChanged(dispidNumber);
FireViewChange();
return S_OK;
}
Dieser Code ist derselbe wie der für OnLButtonDown, löst jedoch kein Click-Ereignis aus.
In Kapitel 20, »Ein Internet-ActiveX-Steuerelement erstellen«, haben Sie Registrierungseinträge hinzugefügt, die anzeigen, daß es sicher ist, wenn das Steuerelement Parameter in einer Web-Seite akzeptiert oder wenn es mit einem Skript interagiert. Bei einem ATL-Steuerelement erreichen Sie denselben Zweck, wenn Sie für die Unterstützung der fuer ATL-Steuerelementefuer ATL-Steuerelemente Schnittstelle IObjectSafety sorgen. Ein Container wird diese Schnittstelle abfragen, um festzustellen, ob ein Steuerelement sicher ist.
Fügen Sie die folgende Zeile in die Erbschaftsliste für CDieRoll ein:
public IObjectSafetyImpl<CDieRoll>,
Fügen Sie diese Zeile in die COM-Tabelle ein:
COM_INTERFACE_ENTRY_IMPL(IObjectSafety)
Dadurch wird das Steuerelement automatisch sicher gemacht. Das Standardverhalten in der ATL macht Steuerelemente nicht initialisierungssicher. Dazu müssen Sie die Standarddefinition von IObjectSafetyImpl::GetInterfaceSafetyOptions und IObjectSafetyImpl::SetInterfaceSafetyOptions überschreiben.
Den Code für diese Funktionen, der gegenüber der ATL-Quelle in DevStudio\-VC\ATL\INCLUDE\ATLCTL.H geändert wurde, finden Sie in Listing 21.22 und 21.23. Fügen Sie diese Funktionen nach dem Destruktor in DieRoll.h ein.
Listing 21.22: CDieRoll::GetInterfaceSafetyOptions() |
STDMETHOD(GetInterfaceSafetyOptions)(REFIID riid,
DWORD *pdwSupportedOptions,
DWORD *pdwEnabledOptions)
{
ATLTRACE(_T("IObjectSafetyImpl
::GetInterfaceSafetyOptions\n"));
if (pdwSupportedOptions == NULL
|| pdwEnabledOptions == NULL)
return E_POINTER;
*pdwSupportedOptions =
INTERFACESAFE_FOR_UNTRUSTED_CALLER
|| INTERFACESAFE_FOR_UNTRUSTED_DATA;
*pdwEnabledOptions =
INTERFACESAFE_FOR_UNTRUSTED_CALLER
|| INTERFACESAFE_FOR_UNTRUSTED_DATA;
return S_OK;
}
Listing 21.23: SetInterfaceSafetyOptions |
STDMETHOD(SetInterfaceSafetyOptions)
(REFIID riid, DWORD dwOptionSetMask,
DWORD dwEnabledOptions)
{
ATLTRACE(_T("IObjectSafetyImpl
::SetInterfaceSafetyOptions\n"));
return S_OK;
}
Die Änderungen hier sind so, daß nicht nur die Option INTERFACESAFE_FOR_-UNTRUSTED_CALLER unterstützt und aktiviert wird, sondern auch die Optionen INTER-FACESAFE_FOR_UNTRUSTED_DATA und INTERFACESAFE_FOR_UNTRUSTED_CALLER. Anstatt diese Optionen nur auf die Schnittstelle IDispatch zu beschränken, berichtet dieser Code, daß das Steuerelement auch für alle anderen Schnittstellen sicher ist.
Wenn ein Entwickler in einer Anwendung wie Visual Basic oder Visual C++ ein Formular oder ein Dialogfeld erstellt, Steuerelement- (Schaltflaechen fuer ATL-Steuerelemente)kann er die einzufügenden Steuerelemente leicht anhand der Steuerelementpalette identifizieren. Im nächsten Schritt wird das Symbol erstellt, das in dieser Palette für den Würfel verwendet wird.
Erstellen Sie eine Bitmap-Ressource, indem Sie Einfügen/Ressource wählen und zweimal auf Bitmap klicken. Wählen Sie dann Ansicht/Eigenschaften, und stellen Sie Breite und Höhe auf 16. Ändern Sie die Ressourcen-ID auf IDB_DIEROLL, und zeichnen Sie das in Abbildung 21.16 gezeigte Symbol.
Das Registrierungs-Skript für dieses Steuerelement verweist auf dieses Symbol durch eine Ressourcennummer. Um festzustellen, welche Nummer dem Symbol IDB_DIEROLL zugewiesen wurde, wählen Sie Ansicht/Ressourcensymbole, und lesen den Wert ab, der mit IDB_DIEROLL verbunden wurde. (Auf dem System, auf dem dieses Beispiel erstellt wurde, war es der Wert 202.) Öffnen Sie im Register FileView die Datei DieRoll.rgs (die Skript-Datei), und suchen Sie nach der folgenden Zeile:
ForceRemove 'ToolboxBitmap32' = s '%MODULE%, 1'
Ändern Sie diese in:
ForceRemove 'ToolboxBitmap32' = s '%MODULE%, 202'
Verwenden Sie anstelle von 202 auf jeden Fall den bei Ihrem System angezeigten Wert. Erstellen Sie das Steuerelement noch einmal. Führen Sie das Control Pad noch einmal aus, und wählen Sie File, New HTML Layout Control. Öffnen Sie in der Steuerelement-Palette das Register Additional, und klicken Sie dann mit der rechten Maustaste auf die Seite. Wählen Sie aus dem Kontextmenü den Befehl Additional Controls. Suchen Sie in der Liste nach DieRoll Class, markieren Sie diesen Eintrag, und klicken Sie dann auf OK. Das neue Symbol erscheint nun im Register Additional (vgl. Abbildung 21.17.
Abbildung 21.16: Zeichnen Sie ein Symbol für das Steuerelement.
Abbildung 21.17: Fügen Sie die Klasse DieRoll in die HTML-Layout-Toolbox ein.
Bis hierher haben Sie immer Debug-Versionen des Steuerelements erstellt. Dieroll.dll ist ca. 300 Kbyte groß. Diese ist zwar um einiges kleiner als die 700 Kbyte der cab-Datei für die MFC-DLLs, welche die MFC-Version von Dieroll beansprucht, doch immer noch einiges größer als die ca. 30 Kbyte der Auslieferversion von dieroll.ocx. Sobald die Entwicklung beendet ist, sollten Sie eine Auslieferversion erstellen. ATL- (Groesse der ausfuehrbaren Datei reduzieren)-Steuerelemente (Groesse der ausfuehrbaren Datei reduzieren)Groesse der ausfuehrbaren Datei reduzieren
Dazu wählen Sie Erstellen/Aktive Konfiguration festlegen. Das Dialogfeld Konfiguration für aktives Projekt festlegen wird eingeblendet (siehe Abbildung 21.18). Sie werden feststellen, daß es für ein ATL-Projekt doppelt so viele Auslieferversionstypen gibt wie für ein MFC-Projekt: Sie können nicht nur auswählen, ob es Unicode unterstützen soll oder nicht, sondern auch »MinSize« oder »MinDependency«.
Abbildung 21.18: Bestimmen Sie im Dialogfeld Konfiguration für aktives Projekt festlegen den Erstellungstyp für Ihr Projekt.
Die Release MinSize macht das Steuerelement so klein wie möglich, indem eine dynamische Verknüpfung zu einer ATL-DLL und der ATL-Registrierungsdatei hergestellt wird. Die Realease MinDependency stellt eine statische Verknüpfung zu diesen Dateien her, wodurch das Steuerelement größer wird, doch für sich ausführbar ist. Wenn Sie sich für die minimale Größe entscheiden, müssen Sie cab-Dateien für das Steuerelement und die DLLs einrichten, wie in Kapitel 20, »Ein Internet-ActiveX-Steuerelement erstellen«, für die MFC-DLLs erläutert wurde. Zu diesem frühen Stadium einer ATL-Akzeptanz ist es wohl besser, die minimalen Abhängigkeiten zu wählen.
Wenn Sie die minimale Abhängigkeit wählen und dann das Projekt erstellen, erhalten Sie vom Linker die folgenden Fehlermeldungen:
Linker-Vorgang läuft...
Bibliothek ReleaseMinDependency/DieRollControl.lib und Objekt ReleaseMinDependency/DieRollControl.exp wird erstellt
***LIBCMT.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
ReleaseMinDependency/DieRollControl.dll :
***fatal error LNK1120: 1 unresolved externals
Error executing link.exe.***
DieRollControl.dll û 2 Fehler, 0 Warnung(en)
Dieser Fehler erscheint nicht, weil Sie einen Fehler gemacht haben. Standardmäßig wird für eine ATL-Version einige winzige Version der C Runtime Library (CRT) erstellt, so daß eine möglichst kleine DLL entsteht. Dieses minimale CRT umfaßt nicht die Funktionen time, rand und srand, die zum Würfeln verwendet werden. Der Linker findet diese Funktionen in der vollständigen CRT, doch diese Bibliothek erwartet, in Ihrem Steuerelement eine Hauptfunktion anzutreffen. Da keine vorhanden ist, entsteht beim Linker-Vorgang ein Fehler.
Dieses Verhalten wird durch eine Linker-Einstellung gesteuert. Wählen Sie Projekt/Einstellungen und dann aus der Drop-down-Liste oben links den Eintrag Win32 Release MinDependency. Öffnen Sie rechts das Register C/C++. Setzen Sie den Cursor in das Eingabefeld Präprozessor-Definitionen, und drücken Sie auf (Ende), damit die Einfügemarke am Ende des Texts gesetzt wird. Entfernen Sie das Flag _ATL_MIN_CRT, das in Abbildung 21.19 hervorgehoben ist, sowie das Komma unmittelbar davor. Erstellen Sie das Projekt wieder, und die Linker-Fehler verschwinden.
Abbildung 21.19: Entfernen Sie das Flag, das nur einen winzigen Teil der C-Runtime-Bibliothek verknüpft.
Wenn Sie die Aufrufe von rand, srand und time auskommentieren, damit das Steuerelement nicht mehr funktioniert wird es mit _ATL_MIN_CRT in eine 48-Kbyte-DLL verknüpft. Wenn _ATL_MIN_CRT entfernt wird, sind es 104 Kbyte û ein signifikanter Zuwachs, doch immer noch um einiges kleiner als das MFC-Steuerelement und seine DLLs. Eine Auslieferversion minimaler Größe, bei der _ATL_MIN_CRT entfernt wurde, umfaßt 98 Kbyte: Die Einsparungen sind kaum der Mühe wert, die ATL-DLLs zusammenzupacken. Sind die Funktionen rand, srand und time auskommentiert und _ATL_MIN_CRT bleibt stehen, so umfaßt die mimimale Version nur noch 45 Kbyte.
Durch ein Entfernen des _ATL_MIN_CRT-Flag nimmt die Größe des Steuerelements um über 50 Kbyte zu. Es gibt zwar keine Möglichkeit, dieses Steuerelement so umzuschreiben, daß es die Funktionen rand, srand und time nicht benötigt, doch könnten Sie Ihre eigenen Versionen dieser Funktionen schreiben und in das Projekt einbinden, so daß das Steuerelement dennoch mit dem _ATL_MIN_CRT-Flag verbunden wird. Algorithmen für Zufallszahlengeneratoren und deren Startfunktionen finden Sie in Algorithmenbüchern. Die SDK-Funktion GetSystemTime kann die Funktion time ersetzen. Wenn Sie ein Steuerelement schreiben würden, das das erste Mal von vielen Anwendern in einer zeitsensitiven Anwendung verwendet werden würde, könnte sich diese Zusatzarbeit bezahlt machen. Erinnern Sie sich, daß beim zweiten Besuch einer Web-Seite mit einem ActiveX-Steuerelement das Steuerelement nicht noch einmal heruntergeladen werden muß.
Dieses Steuerelement hat einen geringfügig anderen Namen und eine andere CLSID als die in Kapitel 20, »Ein Internet-ActiveX-Steuerelement erstellen«, entwickelte MFC-Version. Sie können beide in der gleichen Web-Seite verwenden, um sie zu vergleichen. In Listing 21.21 ist ein HTML-Code angeführt, der die beiden Steuerelement in eine Tabelle einfügt. (Verwenden Sie Ihre eigenen CLSID-Werte, wenn Sie diese Seite erstellen û Sie können auch, wie oben bereits erläutert, das Control Pad verwenden.) In Abbildung 21.20 sehen Sie diese Seite im Explorer. Wenn Sie die Seite in den Netscape Navigator laden wollen, führen Sie dieroll.htm mit Hilfe des NCompassLabs-Konverters aus, wie es in Kapitel 20 beschrieben wurde.
Listing 21.24: dieroll.htm |
</HEAD>
<BODY>
<TABLE CELLSPACING=15>
<TR>
<TD>
Here's the MFC die:<BR>
<OBJECT ID="MFCDie"
CLASSID="CLSID:46646B43-EA16-11CF-870C-00201801DDD6"
WIDTH="200" HEIGHT="200">
<PARAM NAME="ForeColor" VALUE="0">
<PARAM NAME="BackColor" VALUE="16777215">
<PARAM NAME="Image" VALUE="beans.bmp">
If you see this text, your browser does not support the OBJECT tag.
</OBJECT>
</TD>
<TD>
Here's the ATL die:<BR>
<OBJECT ID="ATLDie" WIDTH=200 HEIGHT=200
CLASSID="CLSID:2DE15F35-8A71-11D0-9B10-0080C81A397C">
<PARAM NAME="Dots" VALUE="1">
<PARAM NAME="Image" VALUE="beans.bmp">
<PARAM NAME="Fore Color" VALUE="2147483656">
<PARAM NAME="Back Color" VALUE="2147483653">
</OBJECT>
</TD>
</TR>
</TABLE>
</BODY>
</HTML>
Abbildung 21.20: Das ATL-Steuerelement läßt sich in den gleichen Situationen einsetzen wie das MFC-Steuerelement.
Wenn Sie zwei Steuerelemente so nebeneinander einsetzen, werden Sie möglicherweise einen Unterschied bemerken. Wenn nach dem Würfeln das Steuerelement neu gezeichnet wird, wird das MFC-Element den Würfelhintergrund nicht neu zeichnen. Der ATL-Würfel hingegen zeichnet einen weißen Hintergrund, bevor er die Bitmap-Grafik wieder zeichnet. Dieses kurze Flimmern ist sichtbar und stört. Wenn Sie eines der mit Visual C++ 5.0 gelieferten ATL-Steuerelemente öffnen, werden Sie dasselbe Flimmern sehen und möglicherweise zu dem Schluß kommen, daß dies ein unvermeidbarer Nachteil von ATL ist. So ist es keineswegs, und Sie können das Flimmern abstellen.
Sie werden die Funktion FireViewChange, die von OnData aufgerufen wurde, überschreiben. In Listing 21.25 sehen Sie den Coder der Basisklasse.
Listing 21.25: dieroll.htm |
HRESULT CComControlBase::FireViewChange()
{
if (m_bInPlaceActive)
{
// Activ
if (m_hWndCD != NULL)
return ::InvalidateRect(
m_hWndCD, NULL, TRUE); // Window based
if (m_spInPlaceSite != NULL)
return m_spInPlaceSite->InvalidateRect(
NULL, TRUE); // Windowless
}
// Inactiv
SendOnViewChange(DVASPECT_CONTENT);
return S_OK;
}
Fügen Sie in die Klasse CDieRoll die Funktion FireViewChange ein, und kopieren Sie den Code von CComControlBase::FireViewChange (Sie finden ihn unter DevStudio\VC\atl\-include\ATLCTL.CPP), und ändern Sie dann jedes TRUE in FALSE. Erstellen Sie Ihr Projekt neu, und laden Sie die Seite mit den beiden Steuerelementen nebeneinander û das Flimmern sollte jetzt verschwunden sein.
Das vorliegende Kapitel stellte Ihnen die Active Template Library vor, welche die C++-Template-Technologie dafür nutzt, Steuerelemente ohne die MFC zu erstellen. Es bedeutet mehr Arbeit, ein Steuerelement auf diese Weise zu erstellen, und Sie können die zugrundeliegenden COM-Konzepte nicht ignorieren, wie dies beim Einsatz von MFC durchaus der Fall sein kann. Doch werden die erstellten Steuerelemente kleiner und lassen sich somit schneller von einem Web-Server herunterladen. Und sie besitzen dennoch die gesamte Funktionalität der MFC-Steuerelemente.
Die folgenden Kapitel könnten für Sie interessant sein: