Einzelnen Beitrag anzeigen

Benutzerbild von sakura
sakura

Registriert seit: 10. Jun 2002
Ort: Unterhaching
11.412 Beiträge
 
Delphi 12 Athens
 
#4
  Alt 16. Mai 2003, 16:09
Dieser zweite Teil stellt ein etwas komplexeres Beispiel vor. Bevor Sie sich diesen Teil durchlesen, empfehle ich dringend den ersten Teil, falls Sie sich vorher nicht mit Erweiterungen, DLLs oder ähnlichem ausführlich beschäftigt haben. Gute Kenntnisse in der objekt-orientierten Programmierung sind zwingend erfordlich, da dieses Tutorial dafür keinen Platz bietet.

In diesem Teil stelle ich Ihnen eine Methode vor, welche sehr stark an die Art angelehnt ist, wie auch Delphi diese bereits seit mehreren Versionen unterstützt. Nachteil dieser Methode ist, daß auch die Plug-Ins in Borland Delphi bzw. Borland C++ erstellt werden müssen. Der Vorteil ist die relative "Einfachheit" der Strategie.

Code:
[color=#000080][i]HINWEIS:[/i]
Auch diese Version nutzt die Unit ShareMem.
Die Unit ShareMem muß als erstes in der Uses-Klausel des Projektes und aller PlugIns stehen![/color]
Registrieren der Plug-Ins

Um auch einen anderen Weg der Registrierung von Plug-Ins zu zeigen, habe ich dieses Mal einen Eintrag in die Registry gewählt. Ähnlich dem Verfahren, welches auch Borland Delphi nutzt. Alle zu ladenen Plug-Ins müssen sich unter folgendem Registry-Key eintragen.
HKCU\Software\Delphi-PRAXiS\Extendable Application\PlugIns

Für jedes Plug-In muß ein String-Wert eingetragen werden, dessen Value den Pfad und den Namen der Plug-In-DLL enthalten. Der Name des Wertes kann frei gewählt werden.

Das Design der Software

Unter Design ist nicht das Aussehen der Anwendung zu verstehen, als viel mehr der interne Aufbau des Codes! In einer öffentlichen Unit (Shared Units\uAppIntf.pas) werden die abstrakten Basisklassen (TIApplication, TIMainMenu, TIMenuItems, TIMenuItem, TIDocuments, TIDocument, TIDocument) vorgestellt, welche durch die Anwendung den Plug-Ins zur Verfügung gestellt werden. Zusätzlich wird auch die Basisklasse (TPlugInObject) vorgestellt, welche durch jedes Plug-In exportiert werden muß.

Diese Unit muß sowohl von der Applikation als auch von jedem Plug-In importiert werden. Mit einer Ausnahme sind alle Klassenmethoden als abstrakt definiert. Die Ausnahme bildet der constructor der Klasse TPlugInObject, welcher eine Referenz auf das von der Applikation exportierte Hauptobjekt speichert.

Die Aufgaben der Applikation

Damit die Applikation dem Plug-In wirkliche Möglichkeiten zur Erweiterung bietet, muß diese Schnittstellen nach aussen geben. Die Startschnittstelle ist TIApplication und diese ist in diesem Beispiel wie folgend definiert.
Delphi-Quellcode:
  TIApplication = class
  public
    function GetHandle: Integer; virtual; stdcall; abstract;
    function GetMainMenu: TIMainMenu; virtual; stdcall; abstract;
    function GetDocuments: TIDocuments; virtual; stdcall; abstract;
    function GetActiveDocument: TIDocument; virtual; stdcall; abstract;
    procedure ShowMessage(const Msg: String); virtual; stdcall; abstract;
  end;
Während der Initialisierung des Plug-Ins erhält das Plug-In von der Anwendung eine Referenz auf ein Objekt vom Typ TIApplication. Diese Objekt bietet in diesem Beispiel fünf Methoden.
  • GetHandle liefert das Handle der Anwendung zurück
  • GetMainMenu erstellt ein Objekt vom Typ TIMainMenu und liefert dieses an die Anwendung zurück, welche dieses anschließend nutzen kann, um das Hauptmenü zu erweitern
  • GetDocuments erstellt ein Objekt vom Typ TIDocuments und gibt dem Plug-In die Möglichkeit mit den einzelnen, geöffneten Dokumenten zu arbeiten.
  • [i]GetActiveDocument[I] erstellt ein Objekt vom Typ TIDocument. Die Plug-Ins haben so jederzeit Zugriff auf das aktuelle Dokument.
  • ShowMessage fordert die Anwendung auf, dem Nutzer eine MessageBox zu zeigen

Code:
[color=#000080][i]HINWEIS:[/i]
Die Anwendung erstellt sämtliche Objekte (außer TIApplication) ausschließlich für die Plug-Ins.
Wenn die Plug-Ins das Objekt nicht mehr benötigen, dann müssen diese das Objekt auch wieder freigeben,
um Resourcen zu sparen.[/color]
Das Menü dem Client anbieten

Der Trick. Wie schon mit der Applikation, möchten wir dem Plug-In keinen direkten Zugriff auf das Menü gewähren, um den dadurch entstehenden Risiken vorzubeugen. Das bringt natürlich auch etwas Arbeit mit sich, da ein Wrapper für das Menü und sämtliche Menüpunkte erstellt werden muss.

Dazu werden die folgenden abstrakten Klassen definiert, welche auch den Plug-Ins bekannt sind:
Delphi-Quellcode:
  TIMainMenu = class
  public
    function GetMenuItems: TIMenuItems; virtual; stdcall; abstract;
    function InsertMenuItem(aIndex: Integer; aCaption, aHint, aName: String;
        aShortCut: Word; aTag: Integer; OnClick: TOnMenuClick): TIMenuItem;
        virtual; stdcall; abstract;
  end;

  TIMenuItems = class
  public
    function GetCount: Integer; virtual; stdcall; abstract;
    function GetItem(Index: Integer): TIMenuItem; virtual; stdcall; abstract;
    function FindItem(aName: String): TIMenuItem; virtual; stdcall; abstract;
    function RemoveItem(aMenuItem: TIMenuItem): Boolean; virtual; stdcall;
        abstract;
  end;

  TIMenuItem = class
  public
    function GetMenuItems: TIMenuItems; virtual; stdcall; abstract;
    function InsertMenuItem(aIndex: Integer; aCaption, aHint, aName: String;
        aShortCut: Word; aTag: Integer; OnClick: TOnMenuClick): TIMenuItem;
        virtual; stdcall; abstract;
    function FindItem(aName: String): TIMenuItem; virtual; stdcall; abstract;

    function GetIndex: Integer; virtual; stdcall; abstract;
    function GetCaption: String; virtual; stdcall; abstract;
    procedure SetCaption(aValue: String); virtual; stdcall; abstract;
    function GetHint: String; virtual; stdcall; abstract;
    procedure SetHint(aValue: String); virtual; stdcall; abstract;
    function GetName: String; virtual; stdcall; abstract;
    function GetShortCut: Word; virtual; stdcall; abstract;
    procedure SetShortCut(aValue: Word); virtual; stdcall; abstract;
    function GetTag: Integer; virtual; stdcall; abstract;
    procedure SetTag(aValue: Integer); virtual; stdcall; abstract;
    function GetOnClick: TOnMenuClick; virtual; stdcall; abstract;
    procedure SetOnClick(aValue: TOnMenuClick); virtual; stdcall; abstract;
  end;
TIMainMenu wird vom Applikations-Objekt zurückgeliefert. Von dort kann dann das Plug-In auf alle Menüpunkte zugreifen, eigene Menüpunkte und ganze Menüs einbauen, etc.

In der Applikation

Die Applikation bietet Klassen, welche die einzelnen Menüpunkte verwalten und die gewünschten Schnittstellen nach außen bieten.
Delphi-Quellcode:
  TMainMenuImpl = class(TIMainMenu)
  private
    FMenuItem: TMenuItem;
    FAppIntf: TApplicationImpl;
  public
    constructor Create(aAppIntf: TApplicationImpl; aMenuItem: TMenuItem);
    // overrides
    function GetMenuItems: TIMenuItems; override; stdcall;
    function InsertMenuItem(aIndex: Integer; aCaption, aHint, aName: String;
        aShortCut: Word; aTag: Integer; OnClick: TOnMenuClick): TIMenuItem;
        override; stdcall;
  end;

...

constructor TMainMenuImpl.Create(aAppIntf: TApplicationImpl; aMenuItem:
    TMenuItem);
begin
  inherited Create;
  FAppIntf := aAppIntf;
  FMenuItem := aMenuItem;
end;

function TMainMenuImpl.GetMenuItems: TIMenuItems;
begin
  Result := TMenuItemsImpl.Create(FAppIntf, FMenuItem);
end;

function TMainMenuImpl.InsertMenuItem(aIndex: Integer; aCaption, aHint,
  aName: String; aShortCut: Word; aTag: Integer;
  OnClick: TOnMenuClick): TIMenuItem;
var
  MI: TMenuItem;
  PMI: TPluginMenuItem;
begin
  MI := TMenuItem.Create(frmMain);
  MI.Caption := aCaption;
  MI.Hint := aHint;
  MI.Name := aName;
  MI.ShortCut := aShortCut;
  MI.Tag := aTag;
  MI.OnClick := nil;
  FMenuItem.Insert(aIndex, MI);

  PMI := TPluginMenuItem.Create(FAppIntf, MI);
  PMI.OnMenuClick := OnClick;
  FAppIntf.FPlugInMenus.Add(PMI);

  Result := TMenuItemImpl.Create(FAppIntf, MI);
end;
Dazu speichert sich der Menü-Wrapper eine Referenz auf das Applikationsobjekt (um diese an die einzelnen Wrapper der Menüpunkt durchzureichen) und eine Referenz auf das Menüitem, welches die Referenz auf den zu bearbeitenden Menüpunkt hält. Desweiteren fallen die folgenden Zeilen auf
Delphi-Quellcode:
  PMI := TPluginMenuItem.Create(FAppIntf, MI);
  PMI.OnMenuClick := OnClick;
  FAppIntf.FPlugInMenus.Add(PMI);
Da der Klick auf ein dynamisches Menüitem nicht automatisch durchgereicht werden soll (Sicherheitsaspekte) wird der Klick auf ein separat erstelltes Objekt gelenkt. Dieses Objekt stellt die interne Klick-Methode zur Verfügung
Delphi-Quellcode:
procedure TPluginMenuItem.OnClick(Sender: TObject);
var
  TMII: TMenuItemImpl;
begin
  TMII := TMenuItemImpl.Create(FAppIntf, FLinkedMenuItem);
  try
    if Assigned(FOnMenuClick) then
      FOnMenuClick(TMII);
  finally
    TMII.Free;
  end;
end;
Wenn der Menüpunkt angeklickt wird, erstellt diese Methode einen Wrapper auf den Menüpunkt, ruft die OnClick Methode des Plug-Ins auf und zerstört anschließend das Wrapper-Objekt wieder. Damit wird dem Plug-In die Möglichkeit geboten auf Klick im Menü zu reagieren.

Die Plug-Ins (I)

Das erste Plug-In ist wieder ein Non-Sense Plug-In, dafür aber sehr einfach. Es erstellt im Hauptmenü einen neuen Menüpunkt und erstellt darunter drei Einträge. Wenn einer dieser Einträge ausgewählt wird, wird eine MessageBox angezeigt, welche den Namen des angeklickten Menüpunkt nennt.
Delphi-Quellcode:
procedure TPlugInObject01.OnMenuClick(Sender: TIMenuItem);
begin
  Parent.ShowMessage(Format('"%s" was clicked', [Sender.GetCaption]));
end;
Die Plug-Ins (II)

Das zweite Plug-In ist etwas sinnvoller. Es stellt zwei neue Menüpunkt im File Menü zur Verfügung. "Alle Dokumente speichern" und "Alle Dokumente schließen". Nach Ausführung teilt es dem User mit, ob die Anfrage erfolgreich durchgeführt wurde.

Die Plug-Ins (III)

Das dritte Plug-In ist ein wenig komplexer. Es ermöglicht dem User die ShortCuts sämtlicher Menüpunkte neu zu definieren! Eigentlich eine echt geile Funktion. Nur das Programm brauch die eigentlich nicht

Die Einstellungen werden in einer INI Datei im Verzeichnis, in welchem auch die DLL liegt, gespeichert. Dadurch kann das Plug-In die Einstellungen beim nächsten Programmstart auch wiederherstellen Dieses Beispiel zeigt auch, wie man ein VCL-Form in eine DLL einbinden kann - und gleich ist die 350 KB größer als die anderen

Viel Spaß

Und nicht vergessen Schaut in die About-Box, dort wird noch der Umgang mit der THeaderControl gezeigt

......
Angehängte Dateien
Dateityp: zip extandable_application__ii___exkl._bins_.zip (35,5 KB, 224x aufgerufen)
Dateityp: zip extandable_application__ii___inkl._bins_.zip (535,0 KB, 481x aufgerufen)
Daniel Lizbeth
Ich bin nicht zurück, ich tue nur so
  Mit Zitat antworten Zitat