Entwickler-Ecke

Sonstiges (Delphi) - Menü aus INI ??


matze - Mi 07.08.02 09:28
Titel: Menü aus INI ??
Hallo !!!
Ich habe ein Programm entwickelt, das eine Linkliste besitzt. Wenn ich jetzt aber dieser Linkliste eine Link hinzufügt, muss ich den Leuten jedesmal eine neue Version meines Proggis geben, damit die auch die aktuellen Links haben !!! Das ist umständlich !!!

Ich habe mir daher gedacht, die Links in eine INI auszulagern, die dann nurnoch übers Internet geupdatet wird.

Aber wie mach ich sowas ??

wenn meine INI z.B. so ausschaut:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
 
[1] 
name = Web.de
url = www.web.de

[2]
name = Auq.de
url = www.auq.de


Wie kann ich aus einer InI die so ausschaut ein Menü erzeugen, das mir bei klick auf einen Menüeintrag die URL in dem Browserfenter läd oder per ShellExecute in dem Standardbrowser des systems ausgibt ???

Ich denke mal, dass sich das mit einer for- schleife machen liese, aber ich weiss nicht, wie ich die anzahl der sections rausbekomme und wie ich dann bei klick die URL aufrufe !!!


wwerner - Mi 07.08.02 09:36

Mit ReadSections bekommst du schon mal die Sectionen:

Auszug asu Hilfe:

Zitat:
Reads the names of all sections in an INI file into a string list.

procedure ReadSections(Strings: TStrings); override;


cbs - Mi 07.08.02 10:23

Tag auch

speicher doch einfach vorher die anzahl der sektoren ab

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
[main]
anzahl=3

[1]
name=web.de
url=www.web.de

[2]
name=auq.de
url=www.auq.de

[3]
name=google.de
url=www.google.de

dann weißt du ja wieviele einträge die ini hat und ließt sie entsprechend aus

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
var
  ini: TIniFile;
  cnt, anz: LongInt;
  name, url: string;
begin
  ini:= TIniFile.Create(FileName);
  anz:= ini.ReadInteger('main', 'anzahl', 0);
  for cnt:= 1 to anz do begin
    name:= ini.ReadString(IntToStr(cnt), 'name', '');
    url:= ini.ReadString(IntToStr(cnt), 'url', '');
    // erstelle jetzt den menü eintrag mit name und url
  end;
  ini.Free;
end;


PS: ich glaube zwischen bezeichner dem '=' und dem wert darf kein leerzeichen sein


matze - Mi 07.08.02 10:53

jo das wäre ne Idee, mit dem Anzahl abspeichern !!!

Aber wie bekommt ich den Befehl zum URL aufrufen in den Menü eintrag rein ???
Geht das vielleicht, dass man abfragt, welcher Menüeintrag geklickt wuirde und die nummer des menü eintrages der geklickt wurde irgendwie mit der nummer der INI section vergelicht :?


wwerner - Mi 07.08.02 11:03

Schau dir das mal an: http://www.swissdelphicenter.ch/de/showcode.php?id=812


cbs - Mi 07.08.02 11:09

@matze: könnte so gehen ja. jeder menüeintrag hat doch die eigenschaft tag vom typ integer. diese eigenschaft wird nicht gebraucht, kannst sie also für dich nutzen. darin speicherst du dann zb. den index des eintrags in der ini.

wenn der tag=1 ist dann ließt du die url des ini eintrags 1 aus wenn tag=5 dann den ini eintrag 5 usw.

du kannst natürlich auch die ganzen urls auch gleich in ein TStringList object speichern. so das der index (tag) des menüeintrags dem des index in der StringListe eintspricht.

klickt der user auf ein menüeintag ließt du den tag aus und öffnest dann die url die dem index in der TStringListe entspricht


matze - Mi 07.08.02 11:12

hmmm..... das könnt klappen:

hier mal meine überlgeung (noch nicht getestet). könntet ihr die mla bite anschauen und mir sagen ob die funzen täte ??


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
procedure TForm1.FormCreate(Sender: TObject); 
var 
  NewItem: TMenuItem; 
  ini: TIniFile; 
  cnt, anz: LongInt; 
  name, url: string; 
begin 
  ini:= TIniFile.Create(FileName); 
  anz:= ini.ReadInteger('main', 'anzahl', 0); 
  for cnt:= 1 to anz do begin

    NewItem := TMenuItem.Create(Self); 
    NewItem.Caption := ini.ReadString(IntToStr(cnt), 'name', ''); 
    NewItem.OnClick := ShellExecute (ini.ReadString(IntToStr(cnt), 'URL', ''); //Ich weiss, dass der Befehl schmarrn ist, aber ich weiss momentan den genauen ShellExecute Befehl nicht.
    Fonts1.Add(NewItem); 

  end; 
end;



Ähmm aber in welches Menü schreibt der des dann eigendlich rein ??? Ich will das in ein PopUp menü reinhaben !!!


Delete - Mi 07.08.02 12:50

Das extra Abspeichern der Anzahl halte ich (mit Verlaub!) für umständlich. Mit der schon von wwerner erwähnten Funktion ReadSections bekommst du eine Stringliste mit allen gültigen Sektionen der INI.

Sollte aus irgendeinem Grund nämlich die INI mal so aussehen:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
[1] 
name = Web.de 
url = www.web.de 

[2] 
name = Auq.de 
url = www.auq.de

[4]
name = Irgendwo
url = www.irgendwo.net

weil irgendwer versehentlich oder aus Dummheit den Punkt #3 entfernt hat, dann wird deine Schleife mit großer Wahrscheinlich nicht mehr funktionieren, da du die Schleifenvariable ja als Sektionsreferenz benutzt. Sie liest also die Sektionen 1, 2 ... findet aber keine 3. :-(

Des Weiteren ließe sich die "Hint"-Variable des Menüeintrags für die URL zweckentfremden. Also, beim Einlesen die Caption auf den Namenseintrag, und den Hint auf die URL setzen, und im "OnClick" nur den Hint an ShellExecute weiterreichen. Ist einfacher und spart den ständigen Zugriff auf die INI-Datei.


wwerner - Mi 07.08.02 13:09

Hey Mathias :!: Gute Idee :D


Delete - Mi 07.08.02 13:36

Wie wäre es damit?

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
uses
  IniFiles, ShellAPI;


// als private Prozedur deklarieren!
procedure TForm1.UrlClick(Sender: TObject);
begin
  with(Sender as TMenuItem) do
    if(Hint <> '') then ShellExecute(self.Handle,'open',pchar(Hint),nil,nil,SW_SHOWNORMAL);
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  cIni : TIniFile;
  sl   : TStringList;
  i    : integer;
  mi   : TMenuItem;
begin
  cIni := TIniFile.Create(Filename);
  with cIni do
    try
      sl := TStringList.Create;
      try
        ReadSections(sl);

        if(sl.Count > 0) then
          for i := 0 to sl.Count - 1 do
            begin
              mi         := TMenuItem.Create(nil);
              mi.Caption := ReadString(sl.Strings[i],'name','');
              mi.Hint    := ReadString(sl.Strings[i],'url','');

              if(mi.Caption <> '') and (mi.Hint <> '') then
                begin
                  mi.OnClick := UrlClick;
                  PopupMenu1.Items.Add(mi);
                end;
            end;
      finally
        sl.Free;
      end;
    finally
      Free;
    end;
end;


matze - Mi 07.08.02 16:03

ähmmm. nur mal so ne frage: vergisst du nicht am ende die INI zu "free"en ?? oder ist das nicht wichtig, oder wird das mit den FREE gemacht ??

Und nur mal verständnis halber: Wieso muss man das als private Prozedur deklarieren ?

Ansonsten: DANKE MANN !!! Du bist meine Rettung !!! Das ist genau das was ich suche !!!!


Delete - Mi 07.08.02 18:06

matze hat folgendes geschrieben:
ähmmm. nur mal so ne frage: vergisst du nicht am ende die INI zu "free"en ?? oder ist das nicht wichtig, oder wird das mit den FREE gemacht ??

Dann noch mal die gekürzte Form:

Quelltext
1:
2:
3:
4:
5:
6:
7:
cIni := TIniFile.Create(Filename); 
with cIni do 
  try 
    // Mach hier irgendwas mit der INI
  finally 
    Free; 
  end;

Frage beantwortet? :wink:

Das einzige, was man noch machen könnte, wäre, die TMenuItems wieder freizugeben, wenn sie weder Caption noch Hint haben. Dann haben sie nämlich keine Bezeichnung und keine URL und werden ohnehin nicht ins Menü aufgenommen (s. if).

Zitat:
Und nur mal verständnis halber: Wieso muss man das als private Prozedur deklarieren?

Ich hoffe, du meinst jetzt nicht die "OnCreate" der Form. Mit der privaten Prozedur ist nur "UrlClick" gemeint - und die muss man nicht unbedingt so deklarieren. "public" wäre auch okay. Aber wenn du sie nur in der Form1 benötigst, dann ist gegen "private" nichts einzuwenden. Und verkehrt ist es ja auch nicht.

Gruß.


matze - Mi 07.08.02 20:23

also reicht es die INI einfach mit free; freizugeben statt mit cINI.free; ??


cbs - Mi 07.08.02 20:27

@matze: ne, wenn dann muss es schon cINI.free; sein.
in dem fall reicht aber auch Free wegen dem with

schau mal in die hilfe wenn de nich weißt was das ist und macht

@MathiasSimmack: hast völlich recht. das mit der anzahl war nich so gut :oops:


Delete - Do 08.08.02 10:43

@cbs: Normalerweise wäre das mit der Anzahl auch meine erste Idee gewesen. Allerdings hätte ich sie nicht gespeichert, sondern gleich die for-Schleife von 1 bis X hochgezählt ... und abgebrochen, wenn er die Sektion nicht finden kann. Aber das hätte genau die selben Probleme verursacht, wenn irgendjemand eine Sektion mutwillig entfernt. Deswegen finde ich den Weg über ReadSections sicherer.

@matze: Sorry, für einen Augenblick dachte ich, du willst mich verar.... Ich hatte angenommen, du kennst die Vorteile von with. Kurz gesagt: in diesem Fall sparst du dir die Angabe von

Quelltext
1:
cReg.                    

Das funktioniert eigentlich bei allen Komponenten und Objekten. Du kannst auch mehrere Eigenschaften durch Komma getrennt angeben, etwa:

Quelltext
1:
with TreeView1,Items                    

dann kannst du sowohl auf die Membervariablen von "TreeView1" zugreifen (zu denen ja "Items" auch gehört), als auch direkt auf die Membervariablen von "Items".

Eine bessere Erklärung findest du in der Hilfe, denke ich. :-)


matze - Mi 21.08.02 08:00

So, nach langer Zeit (urlaub) hab ich mal das getesten und ich habe folgendes Probelm:

Immer wenn ich den Code

Quelltext
1:
2:
3:
4:
5:
procedure TForm1.UrlClick(Sender: TObject); 
begin 
 with(Sender as TMenuItem) do 
  if(Hint <> '') then ShellExecute(self.Handle,'open',pchar(Hint),nil,nil,SW_SHOWNORMAL); 
end;

unter private in meinem Quelltext schreibe kommen hein Haufen Fehlermeldungen.

END erwartet aber BEGIN gefunden.
Undefinierter Bezeichner URLklick.
Undefinierter Bezeichner TForm1.
Undefinierter Bezeichner TObject.

usw...

Was mache ich da falsch :cry:


Tino - Mi 21.08.02 09:15

Du musst natürlich in der Privatesection nur den Procedure-Kopf definieren:

Quelltext
1:
2:
Private
  Procedure UrlClick (Sender: tObject);

Und dann kannst Du die Proceduer im Implementations-Teil Deiner Unit hinzufügen.

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
Implementation

{...}

procedure TForm1.UrlClick(Sender: TObject); 
begin 
  with Sender as TMenuItem do 
    if Hint <> '' then 
      ShellExecute (
          Handle,
          'open',
          PChar (Hint), 
          nil, nil, 
          SW_SHOWNORMAL
        ); 
End;

Du musst tForm1 dann wahrscheinlich noch umbennen. So wie halt das Objekt heißt in dem es definiert wurde.

Gruß
TINO


matze - Mi 21.08.02 13:36

jo ich hab alles so gemacht, wie du es gesagt hast, aber wie muss ich das denn umbennen ?? ich hab folgendes probiert:

Quelltext
1:
2:
3:
Tpopupmenu1.UrlClick;
Tpopupmenu.UrlClick;
Tform1.UrlClick;

das alles klappt nicht


Tino - Mi 21.08.02 14:14

Es muss so heißen wie das Objekt in dem die Procedure definert ist. Ansonsten poste mal den Code.

Gruß
TINO


matze - Mi 21.08.02 15:00

ich hba den code von MatthiasSimmack (oben) unverändert übernommen !!!

Nur dass ich das procedure TForm1.UrlClick(Sender: TObject); (das ja nicht funzt) in procedure TPopUpMenu1.UrlClick(Sender: TObject); umgeändert habe. Das funzt allerdings auch nicht :cry:


Tino - Mi 21.08.02 16:10

matze hat folgendes geschrieben:
Das funzt allerdings auch nicht :cry:

Was kommt denn für eine Fehlermeldung?


matze - Mi 21.08.02 16:45

wenn ich TpopUpMenu1. benutze, kommen viele Fehlermeldungen: Z.B.
Fehler: . gefunden aber ; erwartet
Undefinierter Bezeicher: sender
Undefinierter Bezeicher: self


Wenn ich TForm1. beutze kommt nur
URLklick ist ein undefinierter Bezeichner und er markiert folgende stelle rot: mi.OnClick := UrlClick;


Tino - Mi 21.08.02 22:59

Wie heißt denn jetzt die Form in der Du diese Procedure implementieren willst/hast? tPopUpMenu1 oder tForm1?


matze - Do 22.08.02 08:42

halt stopp !!! ich hab den fehler gefunden !!!!

Ich hatte nicht, wie in dem Wuellext von MatthiasSimmack die Funktioenen für das laden des Menüs in Form1.Create, sondern ich hab das komplette Zeugs in eine eigene Procedure reingeschrieben. Ich hab jetzt das aus der Procedure ausgenmmen und in Form1. Create reingeschrieben und alles funzt bestens !!!

Aber: wie muss ich das ändern, wenn ich das in eine eigene Procedure reinschreiben will: also z.B:


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
procedure Menuladen; 
var 
  cIni : TIniFile; 
  sl   : TStringList; 
  i    : integer; 
  mi   : TMenuItem; 
begin 
  cIni := TIniFile.Create(Filename); 
  with cIni do 
    try 
      sl := TStringList.Create; 
      try 
        ReadSections(sl); 

        if(sl.Count > 0) then 
          for i := 0 to sl.Count - 1 do 
            begin 
              mi         := TMenuItem.Create(nil); 
              mi.Caption := ReadString(sl.Strings[i],'name',''); 
              mi.Hint    := ReadString(sl.Strings[i],'url',''); 

              if(mi.Caption <> '') and (mi.Hint <> '') then 
                begin 
                  mi.OnClick := UrlClick; 
                  PopupMenu1.Items.Add(mi); 
                end; 
            end; 
      finally 
        sl.Free; 
      end; 
    finally 
      Free; 
    end; 
end;


Tino - Do 22.08.02 09:17

Du musst Deine Procedure Menuladen so umbauen das diese das PopupMenu und den Dateinamen als Parameter bekommt.

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
procedure Menuladen (aPopupMenu: tPopupMenu; aFilename: String); 
var 
 cIni : TIniFile; 
 sl  : TStringList; 
 i  : integer; 
 mi  : TMenuItem; 
begin 
 cIni := TIniFile.Create(aFilename); 
 with cIni do 
  try 
   sl := TStringList.Create; 
   try 
    ReadSections(sl); 

    if(sl.Count > 0) then 
     for i := 0 to sl.Count - 1 do 
      begin 
       mi     := TMenuItem.Create(nil); 
       mi.Caption := ReadString(sl.Strings[i],'name',''); 
       mi.Hint  := ReadString(sl.Strings[i],'url',''); 

       if(mi.Caption <> '') and (mi.Hint <> '') then 
        begin 
         mi.OnClick := UrlClick; 
         aPopupMenu.Items.Add(mi); 
        end; 
      end; 
   finally 
    sl.Free; 
   end; 
  finally 
   Free; 
  end; 
end;

Im Event FormCreate kannst Du dann diese Procedrue aufrufen:

Quelltext
1:
2:
3:
4:
Procedure tForm1.FormCreate (aSender: tObject);
Begin
  MenuLaden (PopupMenu1, 'c:\test.ini');
End;

Gruß
TINO

PS: Ich hoffe das das funktioniert... hab es nicht getestet!


matze - Do 22.08.02 09:23

und wie muss ich dann die Funktion URLklick umbauen ??


Tino - Do 22.08.02 11:07

Stimmt das OnClick-Event habe ich vergessen. Das OnClick-Event ist vom Type TNotifyEvent. Das heißt das Du der Menuladen Procedure einen weiteren Parameter übergeben musst: nämlich das OnClickEvent und diesen Parameter kannst Du dann den OnClickEvents der MenuItems zuweisen.

Wenn Du die Procedure dann aufrufst müsste das ungefähr so aussehen:

Quelltext
1:
2:
3:
4:
Procedure tForm1.FormCreate (aSender: tObject);
Begin
 MenuLaden (PopupMenu1, UrlClick 'c:\test.ini');
End;


matze - Do 22.08.02 13:13

also die URLKlick routine kann ich unverändert lassen, richtig ???

Köntest du mal den neuen Quellcode der Load Routine und der URLKlick Routine posten !!! Das würe mir sehr helfen !!!

Danke !!!


Delete - Do 22.08.02 17:08

Boah, mattttttttttttze :wink: Was sind das denn für Fragen?

Die "UrlClick"-Routine kann unverändert bleiben, weil du sie ja ganz offensichtlich nicht verändert sondern einfach nur aufgerufen hast, s.:

Quelltext
1:
mi.OnClick := UrlClick;                    

Durch die Benutzung des "OnClick" ist die Syntax für die Funktion (oder besser: Prozedur) ja bereits vorgegeben.

Wenn du deine "Menuladen"-Routine auch noch als private Form-Prozedur deklarierst:

Quelltext
1:
procedure TDEINEFORM.Menuladen(aFilename: string);                    

kannst du a) die Angabe des Popupmenüs direkt im Code unterbringen, weil die Prozedur nun ja auf die Form-Komponenten zugreifen kann (oder du lässt es so wie Tino vorschlägt, was die Funktion universell für andere Programme einsetzbar machen würde), und b) dürfte es keine Probleme geben, wenn du die "OnClick"-Funktion festlegst (s. Codeauszug oben)


matze - Fr 23.08.02 08:23

danke !!!

Ich werds mal testn !!! :D