Zurueck zum Inhaltsverzeichnis
ActiveX / OLE / COM - selbst erstellen
In diesem Kapitel werden wir mit der Erstellung eigener ActiveX-Komponenten beginnen. MS Visual C++ bietet hier einen entsprechenden MFC-Assistenten an, der die Kleinarbeit für uns erledigt. Damit kann man die schöpferische Kraft auf die Gestaltung und Funktionalität konzentrieren. Zum Testen der erzeugten ActiveX-Komponente (Miniserver) steht ein OLE/COM-Testcontainer (Client) zur Verfügung.
Wir beginnen mit einem praktischen Beispiel:
Erstellen Sie mit dem MFC-Assistenten eine ActiveX-Komponente. Übernehmen Sie hierbei alle Standardeinstellungen. Wir nennen unser Projekt "Ampel". Das Ergebnis wird dann ein OCX-File namens "Ampel.ocx" sein. Betrachten Sie zunächst die Klassenansicht:
Hierbei spielt für uns die Klasse
CAmpelCtrl die zentrale Rolle. Diese Klasse ist von der Klasse COleControl
abgeleitet.
Eine ActiveX-Komponente kann sich selbst in einem Fenster darstellen.
Hierfür
ist die Zeichen-Funktion OnDraw zuständig. Daher beginnen wir an
dieser
Stelle. Ändern Sie den Standardcode (Ellipse im Rechteck)
zunächst
auf folgenden eigenen Code ab:
void CAmpelCtrl::OnDraw(CDC* pdc,
const
CRect& rcBounds, const CRect& rcInvalid)
{
RECT
rect;
rect.top = rcBounds.top;
rect.bottom = rcBounds.bottom;
rect.left = rcBounds.left;
rect.right = rcBounds.right;
pdc->FillRect(rcBounds,
CBrush::FromHandle((HBRUSH)GetStockObject(BLACK_BRUSH)));
pdc->Ellipse(rcBounds);
for(int n=30; n<50; n++)
{
pdc->DrawText("Hallo Welt!", &rect, n );
}
}
Als nächstes müssen wir unsere ActiveX-Komponente testen. Damit dies bei der Erstellung der Release-Version sofort im Testcontainer getestet wird, stellen wir diese Software (TSTCON32.EXE) in den Projekteinstellungen unter Debug als ausführbares Programm ein:
Beim Kompilieren/Starten öffnet sich der noch leere Testcontainer. Nach Bearbeiten / Neues Steuerelement eingeben können wir bereits unser ampel.ocx auswählen:
Nach "OK" und Vergrößern des ActiveX-Bereiches zeigt sich nun folgendes Bild:
So schnell und einfach kann man eine erste eigene ActiveX-Komponente erzeugen. Der Gruß "Hallo Welt!" ist hier angebracht, da man ActiveX-Komponenten auch in Web-Seiten integrieren kann. Damit erreicht man mit dem klassischen Computer-Gruß (siehe Buch von K&R) nicht nur den direkten Nutzer, sondern die Welt des Internets. Die Datei Ampel.ocx benötigt in der Release-Version in obiger Version 36 KB. Das ist eine akzeptable Größe.
Da Sie die Funktion OnDraw(...) inzwischen gut kennen, können Sie beliebig weiter experimentieren, um ein Gefühl für die Visualisierungsmöglichkeiten zu erhalten.
Nun kommen wir zurück zu der Idee einer Verkehrsampel. Wir werden ein schwarzes Rechteck darstellen mit den Farben rot, gelb und grün. Wenn eine Lampe leuchtet, soll die Farbe leuchtend sein. Die nichtleuchtenden Lampen sollen durch entsprechend dunkle Farben dargestellt werden. Nach dem Start des OCX soll die Ampel sicherheitshalber rot zeigen. Das Umschalten auf andere Farben soll objektorientiert über Set-Funktionen erfolgen können. Soweit die Grundidee.
Einen ersten Versuch unternehmen wir
mit
folgendem einfachen Code in OnDraw(...):
CBrush
brush_Hellrot,brush_Hellgelb,brush_Hellgruen,
brush_Dunkelrot,brush_Dunkelgelb,brush_Dunkelgruen;
brush_Hellrot.CreateSolidBrush(RGB(255,0,0));
brush_Hellgelb.CreateSolidBrush(RGB(255,255,0));
brush_Hellgruen.CreateSolidBrush(RGB(0,255,0));
brush_Dunkelrot.CreateSolidBrush(RGB(70,0,0));
brush_Dunkelgelb.CreateSolidBrush(RGB(70,70,0));
brush_Dunkelgruen.CreateSolidBrush(RGB(0,70,0));
pdc->SelectObject(&brush_Hellrot);
pdc->Ellipse(5,
5,35, 35);
pdc->SelectObject(&brush_Dunkelgelb);
pdc->Ellipse(5,45,35,
75);
pdc->SelectObject(&brush_Dunkelgruen);
pdc->Ellipse(5,85,35,115);
}
Na, das sieht ja schon nach einer roten
Ampel aus. Wir testen unser Ampel.ocx sofort in einer eigenen
Dialogfeldanwendung.
Wir programmieren nun einen eigenen
Client-Container
für unseren OCX-Mini-Server. Wie man das OCX in die
Steuerelemente-Leiste
integriert, wissen Sie ja bereits (in Projekt einfügen ...).
Nach dem Kompilieren zeigt sich folgendes Bild:
Das sieht doch optisch schon ganz gut aus. Als nächsten Schritt benötigen wir innerhalb der ActiveX-Komponente Set-Funktionen, um die Ampel zu verändern. Also wechseln wir wieder zurück zur OCX-Programmierung. Diese Funktionen sollen die Namen SetRot(), SetRotGelb(), SetGruen(), SetGelb() erhalten.
Bevor wir diese Set-Funktionen erzeugen, verpassen wir unserer Ampel die (protected) Member-Variablen für die entsprechenden Eigenschaften m_Rot, m_RotGelb, m_Gruen, m_Gelb. Diese können entweder TRUE oder FALSE sein.
Zunächst erzeugen wir also die
Member-Variablen
der Klasse CAmpelCtrl (Kindklasse von COleControl):
// Konstruktor
public:
CAmpelCtrl();
// Überladungen
// Vom
Klassenassistenten
generierte Überladungen virtueller Funktionen
//{{AFX_VIRTUAL(CAmpelCtrl)
public:
virtual void
OnDraw(CDC*
pdc, const CRect& rcBounds, const CRect& rcInvalid);
virtual void
DoPropExchange(CPropExchange*
pPX);
virtual void
OnResetState();
//}}AFX_VIRTUAL
// Implementierung
protected:
BOOL
m_Rot;
BOOL
m_RotGelb;
BOOL
m_Gruen;
BOOL
m_Gelb;
~CAmpelCtrl();
Danach verändern wir die Methode
OnDraw(...)
wie folgt:
CBrush
brush_Hellrot,brush_Hellgelb,brush_Hellgruen,
brush_Dunkelrot,brush_Dunkelgelb,brush_Dunkelgruen;
brush_Hellrot.CreateSolidBrush(RGB(255,0,0));
brush_Hellgelb.CreateSolidBrush(RGB(255,255,0));
brush_Hellgruen.CreateSolidBrush(RGB(0,255,0));
brush_Dunkelrot.CreateSolidBrush(RGB(70,0,0));
brush_Dunkelgelb.CreateSolidBrush(RGB(70,70,0));
brush_Dunkelgruen.CreateSolidBrush(RGB(0,70,0));
if(m_Rot==TRUE)
{
pdc->SelectObject(&brush_Hellrot);
pdc->Ellipse(5,
5,35, 35);
pdc->SelectObject(&brush_Dunkelgelb);
pdc->Ellipse(5,45,35,
75);
pdc->SelectObject(&brush_Dunkelgruen);
pdc->Ellipse(5,85,35,115);
}
if(m_Gruen==TRUE)
{
pdc->SelectObject(&brush_Dunkelrot);
pdc->Ellipse(5,
5,35, 35);
pdc->SelectObject(&brush_Dunkelgelb);
pdc->Ellipse(5,45,35,
75);
pdc->SelectObject(&brush_Hellgruen);
pdc->Ellipse(5,85,35,115);
}
if(m_Gelb==TRUE)
{
pdc->SelectObject(&brush_Dunkelrot);
pdc->Ellipse(5,
5,35, 35);
pdc->SelectObject(&brush_Hellgelb);
pdc->Ellipse(5,45,35,
75);
pdc->SelectObject(&brush_Dunkelgruen);
pdc->Ellipse(5,85,35,115);
}
if(m_RotGelb==TRUE)
{
pdc->SelectObject(&brush_Hellrot);
pdc->Ellipse(5,
5,35, 35);
pdc->SelectObject(&brush_Hellgelb);
pdc->Ellipse(5,45,35,
75);
pdc->SelectObject(&brush_Dunkelgruen);
pdc->Ellipse(5,85,35,115);
}
}
Die Initialisierung solcher Member-Variablen führt man direkt im Konstruktor oder in der Funktion OnCreate(...) durch, die wir durch Hinzufügen der Nachricht WM_CREATE erzeugen:
In die hinzugefügte Funktion
OnCreate(...)
geben wir den Code für eine rote Ampel ein:
m_Rot=TRUE;
m_Gruen=FALSE;
m_RotGelb=FALSE;
m_Gelb=FALSE;
return 0;
}
Damit ist sicher gestellt, daß unsere Ampel mit der Farbe rot startet. Wie kann man jetzt die Eigenschaften der Ampel z.B. in einer Dialoganwendung sozusagen von außen ändern? Hierzu benötigen wir unsere Set-Funktionen. Das Hinzufügen von Methoden erfolgt im Klassenassistent unter Automatisierung:
Das Innenleben dieser Funktionen ist
schnell
gefüllt:
void CAmpelCtrl::SetRot()
{
m_Rot=TRUE;
m_Gruen=FALSE;
m_RotGelb=FALSE;
m_Gelb=FALSE;
SetModifiedFlag();
InvalidateControl();
}
void CAmpelCtrl::SetGelb()
{
m_Rot=FALSE;
m_Gruen=FALSE;
m_RotGelb=FALSE;
m_Gelb=TRUE;
SetModifiedFlag();
InvalidateControl();
}
void CAmpelCtrl::SetRotGelb()
{
m_Rot=FALSE;
m_Gruen=FALSE;
m_RotGelb=TRUE;
m_Gelb=FALSE;
SetModifiedFlag();
InvalidateControl();
}
Testen Sie das veränderte OCX sofort im Testcontainer. Dort können Sie direkt die neuen Set-Funktionen prüfen:
Nachdem wir alle Methoden geprüft
haben, wechseln wir zu einer eigenen Dialoganwendung, in die wir die
ActiveX-Komponente
einbinden.
Hier gibt es einen wichtigen Punkt zu
beachten. Standardmäßig ist z.B. bei der Erstellung einer
Dialoganwendung
nur dir Option "ActiveX-Steuerelemente" aktiviert:
Wenn Sie hier vergessen, "Automatisierung" zu aktivieren, werden Sie die Set-Funktionen hinterher nicht nützen können. Das ist eine typische Fehlerquelle. Also achten Sie bitte darauf, daß sowohl "ActiveX-Steuerelemente" als auch "Automatisierung" eingebunden wird:
Den Unterschied können Sie hinterher leicht ausmachen. In der Klassenansicht findet man bei zugefügter Automatisierung die Klasse C...AutoProxy, die dafür sorgt, daß man die Set-/Get-Funktionen der ActiveX-Komponente auch wirklich bedienen kann:
Wenn Sie dies geschafft haben, besteht
kein Hindernis, Ampel.ocx einzusetzen.
Momentan verfügt die
ActiveX-Komponente
über folgende von außen zu erreichende Funktionen:
Sie benötigen eine Member-Variable
für die ActiveX-Komponente, damit diese als Objekt angesprochen
werden
kann. Dies erledigt der Klassen-Assistent für Sie:
//
Konstruktion
public:
CAmpelTest1Dlg(CWnd* pParent
= NULL); // Standard-Konstruktor
virtual ~CAmpelTest1Dlg();
//
Dialogfelddaten
//{{AFX_DATA(CAmpelTest1Dlg)
enum { IDD =
IDD_AMPELTEST1_DIALOG
};
CAmpel
m_Ampel;
//}}AFX_DATA
// Vom
Klassenassistenten generierte Überladungen virtueller Funktionen
//{{AFX_VIRTUAL(CAmpelTest1Dlg)
protected:
virtual void
DoDataExchange(CDataExchange*
pDX); // DDX/DDV-Unterstützung
//}}AFX_VIRTUAL
Den Buttons ordnen Sie mit dem Klassen-Assistenten entsprechende Funktionen mit nachfolgendem Code zu:
void CAmpelTest1Dlg::OnButtongelb()
{
m_Ampel.SetGelb();
}
void CAmpelTest1Dlg::OnButtonrot()
{
m_Ampel.SetRot();
}
void
CAmpelTest1Dlg::OnButtonrotgelb()
{
m_Ampel.SetRotGelb();
}
void
CAmpelTest1Dlg::OnButtonaboutbox()
{
m_Ampel.AboutBox();
}
Achten Sie auf die beiden Klammern
hinter
den Funktionen, damit das Ganze funktioniert. Der Klassen-Assistent ist
hier nachlässig.
Damit können Sie nun die OLE-Ampel
/ COM-Ampel / ActiveX-Ampel / OCX-Ampel (nennen Sie die Komponente, wie
Sie wollen) von außen bedienen. Stellen Sie sich das so vor wie
bei
den Knöpfen eines Getränkeautomaten. Programmieren Sie die
Ampel-Komponente
Ampel.ocx mit dem MFC-ActiveX-Assistenten, dann arbeiten Sie im Inneren
des Getränkeautomaten. Binden Sie die ActiveX-Komponente Ampel.ocx
in eine Ihrer Anwendungen ein, dann sehen Sie den
Getränkeautomaten
von außen. Ihre Anwendung ist dann der Client. Ampel.ocx dient
diesem
Client als Mini-Server. Alleine lauffähig ist Ampel.ocx nicht.
Diese
Komponente benötigt einen Client-Container.
Wenn Sie alles richtig gemacht haben, sollten Sie folgendes sehen (Button "Rot + Gelb" ist gedrückt):
Versuchen Sie zunächst das obige
Beispiel
möglichst weitgehend zu verstehen. Experimentieren Sie mit Lust
und
Laune. Sie können zusätzliche Funktionen hinzufügen, die
Darstellung der Ampel verändern. Binden Sie auch ruhig mehr als
eine
Ampel in eine Anwendung ein.
Dann benützt man einfach mehrere
Member-Variablen m_Ampel1, m_Ampel2 ...
Wenn Sie eine Kreuzung darstellen wollen,
benötigen Sie z.B. vier verschieden orientierte Ampeln. Am besten
entwickeln Sie vier verschiedene OCX. Sie können natürlich
auch
nur ein OCX verwenden, indem Sie eine Member-Variable setzen, die sich
dann in OnDraw(...) um die richtige Orientierung kümmert. Die
Grenzen
werden durch Ihre Phantasie und Programmierkunst gesetzt. Vergessen Sie
nicht die Kommentierung, damit Sie sich auch nach Monaten noch zurecht
finden!
Nur Mut!
Einen Punkt werden wir jedoch noch an
obigem
Beispiel zusammen betrachten:
Nehmen wir an, Sie wollen ein Demo-OCX
ohne von außen zu erreichende Funktionen erzeugen, die Ihr
Ampel-OCX
vorstellen soll. Hierzu wollen Sie sicher timer-gesteuert einen
automatischen
Ablauf in das OCX selbst programmieren. In diesem Fall sollten Sie wie
folgt vorgehen:
Set-Funktionen einfach durch
Kommentierung
lahmlegen. Sie können dort ja eine Message-Box "In Demo nicht
verfügbar"
ausgeben.
Für die Timer-Steuerung müssen
Sie einen Timer starten. Hierzu eignet sich am besten die Funktion
OnCreate(...),
die wir bereits oben durch Einbinden von WM_CREATE erzeugt haben. Dort
starten Sie mit SetTimer(...) einen Timer, den Sie in einer anderen
Funktion
abfragen können, um die Member-Variablen m_Rot usw. setzen zu
können.
Die Funktion KillTimer(...) setzt man in die Funktion CAmpelCtrl::OnDestroy()
ein, die man im Klassen-Assistenten analog
WM_CREATE über die Nachricht WM_DESTROY einfügen kann:
// TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen
}
Ein einfaches Beispiel findet man z.B.
in dem Buch von Schmidberger et.al. "MFC mit Visual C++ 6.0" in Kapitel
9. Dort wird eine einfache Analoguhr als ActiveX-Komponente erstellt,
die
dann die Uhrzeit abfragt und analog in OnDraw(...) darstellt.
wird
fortgesetzt.