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

Zurueck zum Inhaltsverzeichnis


ActiveX / OLE

OLE steht für Object Linking and Embedding ("Objekte verknüpfen und einbetten"). Die ursprünglich mit OLE bezeichnete Technologie befindet sich in einer ständigen Weiterentwicklung. Die weiterführenden Elemente von OLE - also alles, was über das Verknüpfen und Einbetten hinausgeht - gehört jetzt zur Active Technology.
 
  Folgende Techniken gehören inzwischen zu OLE / Active Technology:
 
  • Verknüpfen und Einfügen (ursprüngliches OLE)
  • Direkte Aktivierung (visuelles Bearbeiten)
  • Automatisierung
  • Verbunddateien
  • Uniform Data Transfer
  • Drag & Drop
  • Component Object Model
  • Die sogenannten ActiveX-Steuerelemente sind die aktuelle Form der OLE-Steuerelemente. Es handelt sich hierbei um wiederverwendbare Softwarekomponenten, die OLE-Funktionen unterstützen. In MSDN findet man folgende Definition:
     
    ActiveX:
    A set of technologies that enables software components to interact with one another in a networked environment, 
    regardless of the language in which they were created. ActiveX™ is built on the Component Object Model (COM).

    ActiveX dient also dazu, daß sich Softwarekomponenten (auch über Netzwerke) miteinander verstehen, selbst wenn sie in verschiedenen Programmiersprachen erstellt wurden. Die Grundlage von ActiveX ist COM. In MSDN findet man hierzu folgendes:
     
    COM (Component Object Model):
    An open architecture for cross-platform development of client/server applications based on object-oriented technology. Clients have access to an object through interfaces implemented on the object. COM is language neutral, so any language that produces ActiveX™ components can also produce COM applications. 

    Der Kern dieser Technik ist also die Objektorientierung / Wiederverwendbarkeit und die Unabhängigkeit von Programmiersprachen und Betriebssystemen. Die Softwarekomponenten greifen objektorientiert über definierte Schnittstellen (engl.: interface) aufeinander zu.

    Die OLE-Klassen der MFC umhüllen und vereinfachen COM für den Programmierer. Ein ActiveX-Steuerelement (ActiveX control) kann ein Server sein, der in einem OLE-Container (Client) verwendet wird. Die Interaktion der ActiveX-Komponente mit dem Container erfolgt in der MFC-Programmierung z.B. über die von CWnd abgeleitete Control-Klasse COleControl:

    CObject --> CCmdTarget --> CWnd --> COleControl

    COleControl ist eine Control-Klasse wie z.B. CButton, CEdit oder CProgressCtrl.

    Originalton MSDN:
    The COleControl class is a powerful base class for developing OLE controls. Derived from CWnd, this class inherits all the functionality of a Windows window object plus additional functionality specific to OLE, such as event firing and the ability to support methods and properties. OLE controls can be inserted into OLE container applications and communicate with the container by using a two-way system of event firing and exposing methods and properties to the container. Note that standard OLE containers only support the basic functionality of an OLE control. They are unable to support extended features of an OLE control. Event firing occurs when events are sent to the container as a result of certain actions taking place in the control. In turn, the container communicates with the control by using an exposed set of methods and properties analogous to the member functions and data members of a C++ class. This approach allows the developer to control the appearance of the control and notify the container when certain actions occur.

    Der Client, hier eine mit MFC erstellte Anwendung, greift also z.B. über COleControl auf die umhüllende Klasse der integrierten ActiveX-Komponente zu. Vereinfachend kann man sagen, daß ActiveX-Komponenten COM-basierte Objekte sind, die sich selbst in ein eigenes Fenster zeichnen können, auf Ereignisse (z.B. Mausklick, Tastatur) reagieren und an ihrer Schnittstelle über Eigenschaften und Methoden verfügen, die eine Interaktion zulassen.

    Solange wir noch keine eigenen ActiveX-Komponenten erstellen wollen, reicht dieses grundlegende Verständnis aus.

    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    Wie verwendet man ActiveX-Komponenten nun praktisch in der Windows-Programmierung mit MFC?

    Für den praktischen Einstieg hilft ein Beispiel unter Verwendung einer vorhandenen ActiveX-Komponente:

    Erstellen Sie bitte mit dem Assistenten wie gewohnt eine MFC-Dialog-Anwendung. Wir wollen nun eine vorhandene ActiveX-Komponente in unsere Anwendung einfügen. Wie macht man dies?

    Sie befinden sich in der Ressourcen-Ansicht und wählen im Menü:
    Projekt - Dem Projekt hinzufügen - Komponenten und Steuerelemente

    Dort öffnen Sie den Ordner "Registered ActiveX Controls".

    Suchen Sie dort bitte "Microsoft MonthView Control, version 6.0".
    Die Statuszeile zeigt an, daß die Komponente sich im File ...\system32\MSCOMCT2.OCX befindet.

    Unter "Weitere Informationen" finden Sie z.B. folgendes:
    Das MonthView-Steuerelement gehört zu einer Gruppe von ActiveX-Steuerelementen in der Datei MSCOMCT2.OCX.
    Wenn also dieses Steuerelement verwendet werden soll, muß dem Projekt die Datei MSCOMCT2.OCX hinzugefügt werden.
    Beim Vertrieb Ihrer Anwendung muß die Datei auf dem Computer des Benutzers im Verzeichnis System oder System32 von Microsoft Windows installiert werden.

    Das heißt, ohne MSCOMCT2.OCX wird unsere noch zu erstellende Anwendung nicht lauffähig sein.

    Nun wagen wir es und drücken auf "Einfügen". Folgende Sicherheitsabfrage werden wir natürlich mit "OK" beantworten:

    Jetzt klappt die nächste Information auf, die wir ebenfalls mit "OK" akzeptieren:

    Lassen Sie alle drei MFC-Klassen ausgewählt. Es wird jeweils ein header- und ein cpp-file angelegt. Das sind also die MFC-Klassen, die gegenüber dem OLE-Container die Schnittstelle des noch zu modellierenden ActiveX-Objektes bilden. Die Oberfläche unseres Objektes bildet vor allem die Klasse CMonthView.

    Jetzt schließen wir unsere Komponenten- und Steuerelemente-Sammlung und finden im Menü "Steuerelemente" ein neues Icon. Das ist unsere hinzugefügte ActiveX-Komponente (Nicht mit dem bereits vorhandenen Steuerelement "Monatskalender" verwechseln).

    Im nächsten Schritt fügen wir diese Komponente in unsere Dialogfeld-Ressource ein. Das statische Feld "ZU ERLEDIGEN: Dialogfeld-Steuerelemente hier plazieren." entfernen wir sofort. Das sieht dann ungefähr wie folgt aus:

    Wenn wir nun speichern und kompilieren, erhalten wir folgende Meldung:

    Ressourcen werden kompiliert...
    Kompilierung läuft...
    StdAfx.cpp
    Kompilierung läuft...
    ActiveX_Kalender.cpp
    ActiveX_KalenderDlg.cpp
    monthview.cpp
    font.cpp
    picture.cpp
    Generieren von Code...
    Linker-Vorgang läuft...

    ActiveX_Kalender.exe - 0 Fehler, 0 Warnung(en)

    und die Anwendung stellt sich folgendermaßen dar:

    Sie können nun durch die Jahrhunderte scrollen (01.01.1753 - 31.12.9999), einzelne Tage anklicken oder in den leeren roten Kreis klicken, um auf das heutige Datum zu springen. Funktionalitäten in Bezug auf unsere Anwendung sind nicht erkennbar. Die muß der Programmierer der Client-Server-Anwendung Dialogfeld + ActiveX erst noch verpassen.

    Bevor wir tiefer eindringen und eigenen Code hinzufügen, sollten wir uns das bisher "Zusammengeklickte" näher anschauen.
    Beginnen wir mit der Klassenansicht. Dort findet man die durch die ActiveX-Komponente offenbar notwendig gewordenen Klassen CMonthView, COleFont und CPicture. Innerhalb dieser Klassen findet man insbesondere Get- und Set-Memberfunktionen, die für den Informationstransport über die Schnittstellen der ActiveX-Komponente hinweg zuständig sind.

    CMonthView ist direkt von CWnd abgeleitet, während COleFont und CPicture von COleDispatchDriver abgeleitet sind. Die MFC-Klasse COleDispatchDriver ist eine reine Support-Klasse ohne weitere Basisklasse. Diese Klasse sorgt für die OLE Automatisierung des Clients. Mittels der OLE Dispatch-Schnittstelle greift man auf die Methoden und Eigenschaften der ActiveX-Komponente zu. Die Member-Funktionen von COleDispatchDriver sorgen für eine "dispatch connection" vom Typ IDispatch. Other member functions use variable argument lists to simplify calling IDispatch::Invoke.

    Greifen wir als Beispiel die beiden Funktionen GetDay() und SetDay(...) der Klasse CMonthView heraus:



    // Mit Microsoft Visual C++ automatisch erstellte IDispatch-Kapselungsklasse(n).
    ...

    #include "stdafx.h"
    #include "monthview.h"
    ...
    /////////////////////////////////////////////////////////////////////////////
    // Operationen CMonthView
    ...

    short CMonthView::GetDay()
    {
     short result;
     InvokeHelper(0x1, DISPATCH_PROPERTYGET, VT_I2, (void*)&result, NULL);
     return result;
    }

    void CMonthView::SetDay(short nNewValue)
    {
     static BYTE parms[] =  VTS_I2;
     InvokeHelper(0x1, DISPATCH_PROPERTYPUT, VT_EMPTY, NULL, parms, nNewValue);
    }


    Wenn Sie den inneren Aufbau der Funktionen GetDay() und SetDay(...) schon jetzt verstehen wollen, sollten Sie sich die Syntax von CWnd::InvokeHelper(...) in MSDN ansehen:



    CWnd::InvokeHelper

    Call this member function to invoke the ActiveX control method or property specified by dwDispID, in the context specified by wFlags. The pbParamInfo parameter specifies the types of the parameters passed to the method or property. The variable list of arguments is represented by ... in the syntax declaration.

    void InvokeHelper( DISPID dwDispID, WORD wFlags, VARTYPE vtRet, void* pvRet, const BYTE* pbParamInfo, ... );
    throw( COleException );
    throw( COleDispatchException );

    dwDispID
    Identifies the method or property to be invoked. This value is usually supplied by Component Gallery.

    wFlags
    Flags describing the context of the call to IDispatch::Invoke. For possible wFlags values, seeIDispatch::Invoke in the Win32 SDK OLE Programmer's Reference.

    vtRet
    Specifies the type of the return value. For possible values, see the Remarks section for COleDispatchDriver::InvokeHelper.

    pvRet
    Address of the variable that will that will receive the property value or return value. It must match the type specified by vtRet.

    pbParamInfo
    Pointer to a null-terminated string of bytes specifying the types of the parameters following pbParamInfo. For possible values, see the Remarks section for COleDispatchDriver::InvokeHelper.

    ...
    Variable List of parameters, of types specified in pbParamInfo.



     

    Includiert ist die Datei monthview.h, in der wir die Deklarationen aller Memberfunktionen (Operationen)
    der Klasse CMonthView finden (Getday() und SetDay(...) sind rot markiert):



    /////////////////////////////////////////////////////////////////////////////
    // Wrapper-Klasse CMonthView

    class CMonthView : public CWnd
    {
    ...
    public:
    // Operationen
    ...
     long GetAppearance();
     void SetAppearance(long nNewValue);
     unsigned long GetBackColor();
     void SetBackColor(unsigned long newValue);
     long GetBorderStyle();
     void SetBorderStyle(long nNewValue);
     short GetDay();
     void SetDay(short nNewValue);
     BOOL GetDayBold(DATE dateIndex);
     void SetDayBold(DATE dateIndex, BOOL bNewValue);
     long GetDayOfWeek();
     void SetDayOfWeek(long nNewValue);
     BOOL GetEnabled();
     void SetEnabled(BOOL bNewValue);
     COleFont GetFont();
     void SetFont(LPDISPATCH newValue);
     void SetRefFont(LPDISPATCH newValue);
     unsigned long GetForeColor();
     void SetForeColor(unsigned long newValue);
     long GetHWnd();
     void SetHWnd(long nNewValue);
     DATE GetMaxDate();
     void SetMaxDate(DATE newValue);
     short GetMaxSelCount();
     void SetMaxSelCount(short nNewValue);
     DATE GetMinDate();
     void SetMinDate(DATE newValue);
     long GetMonth();
     void SetMonth(long nNewValue);
     unsigned long GetMonthBackColor();
     void SetMonthBackColor(unsigned long newValue);
     short GetMonthColumns();
     void SetMonthColumns(short nNewValue);
     short GetMonthRows();
     void SetMonthRows(short nNewValue);
     CPicture GetMouseIcon();
     void SetMouseIcon(LPDISPATCH newValue);
     void SetRefMouseIcon(LPDISPATCH newValue);
     long GetMousePointer();
     void SetMousePointer(long nNewValue);
     BOOL GetMultiSelect();
     void SetMultiSelect(BOOL bNewValue);
     long GetOLEDropMode();
     void SetOLEDropMode(long nNewValue);
     short GetScrollRate();
     void SetScrollRate(short nNewValue);
     DATE GetSelEnd();
     void SetSelEnd(DATE newValue);
     DATE GetSelStart();
     void SetSelStart(DATE newValue);
     BOOL GetShowToday();
     void SetShowToday(BOOL bNewValue);
     BOOL GetShowWeekNumbers();
     void SetShowWeekNumbers(BOOL bNewValue);
     long GetStartOfWeek();
     void SetStartOfWeek(long nNewValue);
     unsigned long GetTitleBackColor();
     void SetTitleBackColor(unsigned long newValue);
     unsigned long GetTitleForeColor();
     void SetTitleForeColor(unsigned long newValue);
     unsigned long GetTrailingForeColor();
     void SetTrailingForeColor(unsigned long newValue);
     DATE GetValue();
     void SetValue(DATE newValue);
     DATE GetVisibleDays(short sIndex);
     void SetVisibleDays(short sIndex, DATE newValue);
     short GetWeek();
     void SetWeek(short nNewValue);
     short GetYear();
     void SetYear(short nNewValue);
     void ComputeControlSize(short Rows, short Columns, float* Width, float* Height);
     long HitTest(long x, long y, DATE* Date);
     void OLEDrag();
     void Refresh();
    };


    Sie sehen, daß unsere ActiveX-Komponente geschmückt mit seinen "Wrapper-Klassen / IDispatch-Kapselungsklassen" eine reichhaltig ausgestattete Schnittstelle bietet. Betrachten Sie das Ganze, wie einen Getränkeautomaten. Im Moment sind Sie nur Aufsteller/Nutzer des Automaten. Die Innereien müssen Sie erst interessieren, wenn Sie solche Automaten bauen wollen. SetDay(...) ist also wie Geld einwerfen, und GetDay() ist wie Getränk entnehmen.

    Das probieren wir nun in der Praxis an unserer Anwendung aus. Erstellen Sie bitte sechs Buttons mit folgenden Eigenschaften:
     
    ID Titel
    IDC_SONNTAG Sonntag 
    IDC_MONTAG Montag
    IDC_TAG1 1. Tag
    IDC_TAG31 31. Tag
    IDC_YEAR1800 Jahr 1800
    IDC_YEAR3000 Jahr 3000

    Nun greifen wir als nächstes mit den sechs Buttons auf verschiedene Set-Funktionen der ActiveX-Komponente zu.
    Zunächst müssen wir für die ActiveX-Komponente eine Member-Variable erstellen (Sie ordnen IDC_MONTHVIEW1 die Variable m_ctrlMonthView zu:

    Nun werden wir unserem Button "1.Tag" (IDC_TAG1) bezüglich der Nachricht "BN_CLICKED" die Funktion SetDay(...) zuordnen:


     

    Bitte geben Sie folgenden Code ein:



    void CActiveX_KalenderDlg::OnTag1()
    {
     m_ctrlMonthView.SetDay(1);
    }

    Das war das erste Mal, das wir eine Zeile C++-Code eingegeben haben!
    Das Ergebnis ist eine Verbindung zwischen einem Button und einer Schnittstellen-Funktion unserer ActiveX-Komponente.

    Das gleiche Prozedere wiederholen wir mit dem Button "31.Tag":



    void CActiveX_KalenderDlg::OnTag31()
    {
     m_ctrlMonthView.SetDay(31);
    }

    Da der Februar keinen 31. Tag hat, sind wir auf das Ergebnis gespannt. Eine "intelligente" ActiveX-Komponente hätte hier vielleicht den 28.02. gewählt. Aber unsere Komponente ist einfach gestrickt (Sie kennen ja die Funktion von innen) und gibt uns die Fehlermeldung "Ungültiger Eigenschaftswert" aus:


     

    Standardisiert beginnt die Woche in Deutschland seit 1976 am Montag. Damit Sie den Wochenanfang auch auf Sonntag (wie vor 1976 üblich) setzen können, haben wir die Buttons "Sonntag" und "Montag" aufgenommen. Folgende Funktionen werden zugeordnet:



    void CActiveX_KalenderDlg::OnSonntag()
    {
     m_ctrlMonthView.SetStartOfWeek(1);
    }

    void CActiveX_KalenderDlg::OnMontag()
    {
     m_ctrlMonthView.SetStartOfWeek(2);
    }


    Die Programmierung unter Verwendung einer ActiveX-Komponente ist, wie Sie sehen, nicht schwierig. Das know-how liegt im Kennenlernen der Programmierschnittstelle einer Komponente.

    Jetzt werden wir unseren letzten beiden Knöpfen auch noch ihre Set-Funktionen verpassen:



    void CActiveX_KalenderDlg::OnYear1800()
    {
     m_ctrlMonthView.SetYear(1800);
    }

    void CActiveX_KalenderDlg::OnYear3000()
    {
     m_ctrlMonthView.SetYear(3000);
    }


    Bisher haben wir sozusagen nur Geld in den Automaten eingeworfen (Set-Funktionen). Jetzt werden wir auch noch ein Getränk auswerfen lassen (Get-Funktionen). Hierzu benötigen wir ein Ausgabefeld. Am einfachsten ist ein Static-Feld (ID: IDC_STATIC1, Titel:""). Wir ordnen die Member-Variable m_strOutput als CString zu.

    Das im Kalender angezeigte/ausgewählte Datum basteln wir uns über die Funktionen GetDay(), GetMonth() und GetYear() zusammen.
    Als Auslöser zur Anzeige wählen wir das Ereignis SelChange unserer Kalender-ActiveX-Komponente IDC_MONTHVIEW1:


     

    Innerhalb dieser neu hinzugefügten Funktion geben Sie bitte folgenden Code ein:



    void CActiveX_KalenderDlg::OnSelChangeMonthview1(DATE StartDate, DATE EndDate, BOOL FAR* Cancel)
    {
     int iDay   = m_ctrlMonthView.GetDay();
     int iMonth = m_ctrlMonthView.GetMonth();
     int iYear  = m_ctrlMonthView.GetYear();

     m_strOutput.Format("%d.%d.%d",iDay,iMonth,iYear);
     UpdateData(FALSE);
    }


    Wenn man nun ein Datum auswählt, wird das entsprechende Datum im Static-Feld ausgegeben.


     
     

    An diesem Beispiel haben Sie nun gesehen, wie man eine vorhandene ActiveX-Komponente objektorientiert in ein Projekt einbindet.
    Die Schnittstelle für den Programmierer ist die Wrapper-Klasse CMonthView mit all ihren Set- und Get-Funktionen.
    MFC übernimmt hier den Kleinkram, sodaß der  Programmierer sich auf sein eigentliches Ziel, nämlich die Erstellung einer individuellen Anwendung, konzentrieren kann.
     

    Zurueck zum Inhaltsverzeichnis