C++ und MFC
erstellt von: Dr. Erhard Henkes (e.henkes@gmx.net)    (Stand: 18.02.2001)

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:



/////////////////////////////////////////////////////////////////////////////
// CAmpelCtrl::OnDraw - Zeichenfunktion

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(...):



void CAmpelCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
 CRect rect(0,0,40,120);
 pdc->FillRect(&rect, CBrush::FromHandle((HBRUSH)GetStockObject(BLACK_BRUSH)));

 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):



class CAmpelCtrl : public COleControl
{
 DECLARE_DYNCREATE(CAmpelCtrl)

// 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:



void CAmpelCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
 CRect rect(0,0,40,120);
 pdc->FillRect(&rect, CBrush::FromHandle((HBRUSH)GetStockObject(BLACK_BRUSH)));

 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:



int CAmpelCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
 if (COleControl::OnCreate(lpCreateStruct) == -1)return -1;

 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::SetGruen()
{
 m_Rot=FALSE;
 m_Gruen=TRUE;
 m_RotGelb=FALSE;
 m_Gelb=FALSE;
 SetModifiedFlag();
 InvalidateControl();
}

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:

Das werden wir nun in einer einfachen Dialoganwendung (incl. ActiveX + Automatisierung) testen. Entwerfen Sie bitte eine Dialogfeld-Ressource (ohne OK, Abbrechen und Static-Feld), mit Ampel-OCX und fünf Buttons:

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:



class CAmpelTest1Dlg : public CDialog
{
 DECLARE_DYNAMIC(CAmpelTest1Dlg);
 friend class CAmpelTest1DlgAutoProxy;

// 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::OnButtongruen()
{
     m_Ampel.SetGruen();
}

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:



void CAmpelCtrl::OnDestroy()
{
 COleControl::OnDestroy();

 // 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.
 

Zurueck zum Inhaltsverzeichnis