Dynamically build a menu from an XML file

Author: Benjamin Heil 

 

 

{

  The following procedure allows you to build a menu from an XML file.

  Special feature: You only need to specify the Name of the procedure which then

  will be attached to a OnClick handler.

  Note that the procedure must be declared as public.

}

 

{

  Mit folgender Prozedur kann man aus einem XML-File ein Menu

  erstellen lassen (einfach im OnCreate aufrufen).

  Besonderes Feature: Im XML-File gebt ihr nur den Namen der Prozedur an,

  die dem OnClick-Ereignis zugewiesen werden soll.

  Die einzige Einschränkung besteht darin, dass diese Prozedur

  published sein muss.

  Bindet einfach diese Prozedur in euer Hauptformular ein:

}

 

 

procedure TMainForm.CreateMenuFromXMLFile;

 

  function Get_Int(S: string): Integer;

  begin

    Result := 0;

    try

      Result := StrToInt(S);

    except

    end;

  end;

 

  procedure AddRecursive(Parent: TMenuItem; Item: IXMLNode);

  var

    I: Integer;

    Node: TMenuItem;

    Child: IXMLNode;

    Address: TMethod;

  begin

    Node := TMenuItem.Create(Parent);

    if (Uppercase(Item.Attributes['CAPTION']) <> 'SEPERATOR') then

    begin

      Node.Caption := Item.Attributes['CAPTION'];

      if (Uppercase(Item.Attributes['ID']) <> 'NONE') then

      begin

        Address.Code := MethodAddress(Item.Attributes['ID']);

        Address.Data := Self;

        if (Item.ChildNodes.Count - 1 < 0) then

          Node.OnClick := TNotifyEvent(Address);

      end;

      if (Uppercase(Item.Attributes['SHORTCUT']) <> 'NONE') then

        Node.ShortCut := TextToShortCut(Item.Attributes['SHORTCUT']);

      Node.Checked := (Item.Attributes['CHECKED'] = '1');

    end

    else

      Node.Caption := '-';

    Node.Visible := (Item.Attributes['VISIBLE'] = '1');

 

    if Parent <> nil then

      Parent.Add(Node)

    else

      MainMenu.Items.Add(Node);

 

    for I := 0 to Item.ChildNodes.Count - 1 do

    begin

      Child := item.ChildNodes[i];

      if (Child.NodeName = 'ENTRY') then

        AddRecursive(Node, Child);

    end;

  end;

var

  Root: IXMLMENUType;

  Parent: TMenuItem;

  I: Integer;

  Child: IXMLNode;

begin

  XMLDocument.FileName := ExtractFilePath(Application.ExeName) + XMLFile;

  if not FileExists(XMLDocument.FileName) then

  begin

    MessageDlg('Menu-XML-Document nicht gefunden!', mtError, [mbOK], 0);

    Halt;

  end;

  XMLDocument.Active := True;

 

  Screen.Cursor := crHourglass;

  try

    Root := GetXMLMenu(XMLDocument);

    Parent := nil;

 

    for I := 0 to Root.ChildNodes.Count - 1 do

    begin

      Child := Root.ChildNodes[i];

      if (Child.NodeName = 'ENTRY') then

        AddRecursive(Parent, Child);

    end;

  finally

    Screen.Cursor := crDefault;

  end;

end;

 

{----------------------------------------------------------

  You also need the encapsulation of the XML-File.

  ( Save it as unit and add it to your program.

   Created with Delphi6 -> New -> XML Data Binding Wizard )

-----------------------------------------------------------}

 

{----------------------------------------------------------

  Natürlich braucht man auch die Kapselung des XML-Files

  (Als Unit speichern und ins Programm einbinden.

  Die Datei wurde mit Delphi 6 -> Neu -> XML-Datenbindung erstellt):

-----------------------------------------------------------}

 

{***************************************************}

{                                                   }

{              Delphi XML-Datenbindung              }

{                                                   }

{         Erzeugt am: 27.06.2002 13:25:01           }

{                                                   }

{***************************************************}

 

unit XMLMenuTranslation;

 

interface

 

uses xmldom, XMLDoc, XMLIntf;

 

type

 

  { Forward-Deklarationen }

 

  IXMLMENUType  = interface;

  IXMLENTRYType = interface;

 

  { IXMLMENUType }

 

  IXMLMENUType = interface(IXMLNode)

    ['{8F36F5E2-834F-41D9-918F-9B1A441C9074}']

    { Zugriff auf Eigenschaften }

    function Get_ENTRY: IXMLENTRYType;

    { Methoden & Eigenschaften }

    property ENTRY: IXMLENTRYType read Get_ENTRY;

  end;

 

  { IXMLENTRYType }

 

  IXMLENTRYType = interface(IXMLNode)

    ['{AD85CD05-725E-40F8-A8D7-D6EC05FD4360}']

    { Zugriff auf Eigenschaften }

    function Get_CAPTION: WideString;

    function Get_VISIBLE: Integer;

    function Get_ID: Integer;

    function Get_ENTRY: IXMLENTRYType;

    procedure Set_CAPTION(Value: WideString);

    procedure Set_VISIBLE(Value: Integer);

    procedure Set_ID(Value: Integer);

    { Methoden & Eigenschaften }

    property Caption: WideString read Get_CAPTION write Set_CAPTION;

    property Visible: Integer read Get_VISIBLE write Set_VISIBLE;

    property ID: Integer read Get_ID write Set_ID;

    property ENTRY: IXMLENTRYType read Get_ENTRY;

  end;

 

  { Forward-Deklarationen }

 

  TXMLMENUType  = class;

  TXMLENTRYType = class;

 

  { TXMLMENUType }

 

  TXMLMENUType = class(TXMLNode, IXMLMENUType)

  protected

    { IXMLMENUType }

    function Get_ENTRY: IXMLENTRYType;

  public

    procedure AfterConstruction; override;

  end;

 

  { TXMLENTRYType }

 

  TXMLENTRYType = class(TXMLNode, IXMLENTRYType)

  protected

    { IXMLENTRYType }

    function Get_CAPTION: WideString;

    function Get_VISIBLE: Integer;

    function Get_ID: Integer;

    function Get_ENTRY: IXMLENTRYType;

    procedure Set_CAPTION(Value: WideString);

    procedure Set_VISIBLE(Value: Integer);

    procedure Set_ID(Value: Integer);

  public

    procedure AfterConstruction; override;

  end;

 

  { Globale Funktionen }

 

function GetXMLMENU(Doc: IXMLDocument): IXMLMENUType;

function LoadMENU(const FileName: WideString): IXMLMENUType;

function NewMENU: IXMLMENUType;

 

implementation

 

{ Globale Funktionen }

 

function GetXMLMENU(Doc: IXMLDocument): IXMLMENUType;

begin

  Result := Doc.GetDocBinding('MENU', TXMLMENUType) as IXMLMENUType;

end;

 

function LoadMENU(const FileName: WideString): IXMLMENUType;

begin

  Result := LoadXMLDocument(FileName).GetDocBinding('MENU', TXMLMENUType) as IXMLMENUType;

end;

 

function NewMENU: IXMLMENUType;

begin

  Result := NewXMLDocument.GetDocBinding('MENU', TXMLMENUType) as IXMLMENUType;

end;

 

{ TXMLMENUType }

 

procedure TXMLMENUType.AfterConstruction;

begin

  RegisterChildNode('ENTRY', TXMLENTRYType);

  inherited;

end;

 

function TXMLMENUType.Get_ENTRY: IXMLENTRYType;

begin

  Result := ChildNodes['ENTRY'] as IXMLENTRYType;

end;

 

{ TXMLENTRYType }

 

procedure TXMLENTRYType.AfterConstruction;

begin

  RegisterChildNode('ENTRY', TXMLENTRYType);

  inherited;

end;

 

function TXMLENTRYType.Get_CAPTION: WideString;

begin

  Result := ChildNodes['CAPTION'].Text;

end;

 

procedure TXMLENTRYType.Set_CAPTION(Value: WideString);

begin

  ChildNodes['CAPTION'].NodeValue := Value;

end;

 

function TXMLENTRYType.Get_VISIBLE: Integer;

begin

  Result := ChildNodes['VISIBLE'].NodeValue;

end;

 

procedure TXMLENTRYType.Set_VISIBLE(Value: Integer);

begin

  ChildNodes['VISIBLE'].NodeValue := Value;

end;

 

function TXMLENTRYType.Get_ID: Integer;

begin

  Result := ChildNodes['ID'].NodeValue;

end;

 

procedure TXMLENTRYType.Set_ID(Value: Integer);

begin

  ChildNodes['ID'].NodeValue := Value;

end;

 

function TXMLENTRYType.Get_ENTRY: IXMLENTRYType;

begin

  Result := ChildNodes['ENTRY'] as IXMLENTRYType;

end;

 

end.

 

{---------------------------------------------------------------------

 

  Finally, I'll show you an example for the XML-File.

  The Procedure Name is assigned to the ID which then will be called.

 

---------------------------------------------------------------------}

 

{---------------------------------------------------------------------

 

  Als Beispiel für das XML-File hier noch eines aus

  einem meiner Programme.

 

  In ID steht der Name der Prozedur, die man als OnClick aufrufen will

  - denkt auch daran, dass diese Prozedur unbedingt als published

  deklariert sein muss, sonst liefert MethodAddress() Nil zurück.

 

----------------------------------------------------------------------}

 

{

<?xml version="1.0" encoding="ISO-8859-1"?>

<MENU>

    <ENTRY CAPTION="Datei" VISIBLE="1" ID="None" SHORTCUT="None" CHECKED="0">

    <ENTRY CAPTION="Beenden" VISIBLE="1" ID="CloseProgram" SHORTCUT="Strg+X" CHECKED="0"></ENTRY>

    </ENTRY>

 

    <ENTRY CAPTION="Anzeige" VISIBLE="1" ID="None" SHORTCUT="None" CHECKED="0">

    <ENTRY CAPTION="Toolbar" VISIBLE="1" ID="ShowToolbar"  SHORTCUT="None" CHECKED="1"></ENTRY>

    <ENTRY CAPTION="Seperator" VISIBLE="1"></ENTRY>

    <ENTRY CAPTION="Optionen" VISIBLE="1" ID="ShowOptionen"  SHORTCUT="Strg+O" CHECKED="0"></ENTRY>

    </ENTRY>

 

    <ENTRY CAPTION="News" VISIBLE="1" ID="None" SHORTCUT="None" CHECKED="0">

    <ENTRY CAPTION="Refresh" VISIBLE="1" ID="RefreshAll"  SHORTCUT="F5" CHECKED="0"></ENTRY>

    <ENTRY CAPTION="Seperator" VISIBLE="1"></ENTRY>

    <ENTRY CAPTION="Administration" VISIBLE="1" ID="None" SHORTCUT="None" CHECKED="0">

    <ENTRY CAPTION="neue Nachricht hinzufügen" VISIBLE="1" ID="NewMarkedNews" SHORTCUT="Strg+N" CHECKED="0"></ENTRY>

    <ENTRY CAPTION="markierte Nachricht bearbeiten" VISIBLE="1" ID="EditMarkedNews" SHORTCUT="Strg+E" CHECKED="0"></ENTRY>

     <ENTRY CAPTION="markierte Nachricht löschen" VISIBLE="1" ID="DeleteMarkedNews" SHORTCUT="None" CHECKED="0"></ENTRY>

    <ENTRY CAPTION="Seperator" VISIBLE="1"></ENTRY>

    <ENTRY CAPTION="Film hinzufügen" VISIBLE="1" ID="AddMPG" SHORTCUT="None" CHECKED="0"></ENTRY>

     <ENTRY CAPTION="markierten Film löschen" VISIBLE="1" ID="DeleteMPG" SHORTCUT="None" CHECKED="0"></ENTRY>

    </ENTRY>

    </ENTRY>

 

    <ENTRY CAPTION="Hilfe" VISIBLE="1" ID="None" SHORTCUT="None" CHECKED="0">

    <ENTRY CAPTION="LogView" VISIBLE="1" ID="ShowLog" SHORTCUT="Strg+L" CHECKED="0"></ENTRY>

    <ENTRY CAPTION="eMail schreiben" VISIBLE="1" ID="WriteEMail" SHORTCUT="None" CHECKED="0"></ENTRY>

    <ENTRY CAPTION="Seperator" VISIBLE="1"></ENTRY>

    <ENTRY CAPTION="Über" VISIBLE="1" ID="About" SHORTCUT="None" CHECKED="0"></ENTRY>

    </ENTRY>

 

</MENU>

}