Autor Beitrag
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 30.12.08 01:37 
Hallo!

Es gibt ja immer wieder die Frage, wo und wie ich am besten die Einstellungen zu einer Anwendung speichere. Manche möchten eher eine portable Anwendung, andere lieber eine feste Installation. Ideal wäre es doch, wenn man beide Seiten gleichzeitig zufrieden stellen könnte, ohne dafür verschiedene Versionen des Programms erstellen zu müssen.

Genau das ist möglich, wenn man es richtig angeht. Wie das geht, werde ich hier ausführen und an Hand einer Demo zeigen.
  1. Die Überlegung dahinter - wie geht es am besten
  2. Der Quelltext dazu konkret
  3. SJConfigUtils - eine Unit, die die Arbeit teilweise abnimmt
  4. Das Demoprojekt
Zusätzlich gibt es in einem weiteren Beitrag noch eine Erklärung dazu, warum es überhaupt notwendig ist, das Anwendungsdatenverzeichnis zu benutzen und man nicht einfach immer das Verzeichnis für Einstellungen nutzen kann, in dem das Programm selbst liegt:
www.delphi-library.d....php?p=541636#541636


Zuletzt bearbeitet von jaenicke am Mi 01.07.09 20:43, insgesamt 6-mal bearbeitet
jaenicke Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 30.12.08 01:38 
1. Die Überlegung dahinter - wie geht es am besten

Es gibt mehrere Orte, an denen Einstellungen abgelegt werden können. Diese gelten teilweise für alle Benutzer des PCs oder werden bei einer Netzwerkanmeldung im Netzwerkprofil gespeichert, so dass auf allen PCs, auf denen sich der Benutzer anmeldet, diese Einstellungen vorhanden sind, und so weiter.

Ideal wäre also eine Lösung, die alle diese Möglichkeiten unter einen Hut bringt und dem Benutzer die Wahl lässt wo die Einstellungen liegen sollen.

Die Lösung ist, einfach in einer bestimmten Reihenfolge an den verschiedenen Orten nach den Einstellungen zu suchen (ich gebe hier die Standardpfade an, diese können auch anders lauten :!: ):
  • Im eigenen Verzeichnis, dort wo die Exe liegt

  • C:\Dokumente und Einstellungen\[user]\Lokale Einstellungen\Anwendungsdaten (2000, XP)
    C:\Users\Sebastian\AppData\Local (Vista, 7)
    Diese Einstellungen gelten nur für diesen Benutzer und diesen PC und werden im Falle einer Netzwerkanmeldung nicht auf andere PCs kopiert.

  • C:\Dokumente und Einstellungen\[user]\Anwendungsdaten (2000, XP)
    C:\Users\Sebastian\AppData\Roaming (Vista, 7)
    Diese Einstellungen gelten nur für diesen Benutzer, werden aber im Falle einer Netzwerkanmeldung kopiert, gelten also überall wo sich der Benutzer anmeldet für diesen Benutzer.

  • ausblenden Quelltext
    1:
    C:\Dokumente und Einstellungen\All Users\Anwendungsdaten					

    C:\ProgramData (Vista, 7)
    Diese Einstellungen gelten für alle Benutzer des PCs, und werden bei einer Netzwerkanmeldung nicht kopiert.

  • In der Registry unter HKEY_CURRENT_USER\Software\Hersteller\Softwarename\Version
    Diese Einstellungen gelten nur für diesen Benutzer, werden aber im Falle einer Netzwerkanmeldung kopiert, gelten also überall wo sich der Benutzer anmeldet für diesen Benutzer.

  • In der Registry unter HKEY_LOCAL_MACHINE\Software\Hersteller\Softwarename\Version
    Diese Einstellungen gelten für alle Benutzer des PCs, und werden bei einer Netzwerkanmeldung nicht kopiert.

Mit dieser Reihenfolge ist gewährleistet, dass die Einstellungen des einzelnen Benutzers vor denen aller Benutzer berücksichtigt werden, und die lokalen für den PC vor denen des Benutzers auf allen PCs.

Zudem kann man eine portable Version vom USB-Stick mit deren Einstellungen starten ohne dass die Einstellungen einer installierten Version auf dem PC berücksichtigt oder verändert werden.


Zuletzt bearbeitet von jaenicke am Mi 01.07.09 20:43, insgesamt 5-mal bearbeitet
jaenicke Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 30.12.08 01:38 
2. Der Quelltext dazu konkret

Hiermit suche ich in FindSettingsFile im Verzeichnis der Exe und in den drei Anwendungsdatenverzeichnissen. In dem FormCreate rufe ich das zuerst auf und suche, wenn keine Datei gefunden wurde, in der Registry.
ausblenden volle Höhe Delphi-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:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
// Es ist sinnvoll den Pfad an einer Stelle möglichst zusammenzusetzen,
// damit man nicht beim Lesen und Schreiben plötzlich einen Tippfehler
// drin hat und sich wundert warum es nicht geht.
function GetSettingsFileName(ARootDir: String): String;
begin
  Result := ARootDir + AppDataRootDir + AppDataProjectDir + '\MySettings.txt';
end;

// Sucht in den 4 Ordnern der Reihe nach und legt den gefundenen
// Dateinamen in AFileName. Der Rückgabewert gibt an, ob eine Datei
// gefunden wurde.
function FindSettingsFile(var AFileName: String): Boolean;
begin
  AFileName := ExtractFilePath(ParamStr(0)) + 'MySettings.txt';
  Result := FileExists(AFileName);
  if Result then
    Exit;
  AFileName := GetSettingsFileName(GetSpecialFolder(CSIDL_LOCAL_APPDATA));
  Result := FileExists(AFileName);
  if Result then
    Exit;
  AFileName := GetSettingsFileName(GetSpecialFolder(CSIDL_APPDATA));
  Result := FileExists(AFileName);
  if Result then
    Exit;
  AFileName := GetSettingsFileName(GetSpecialFolder(CSIDL_COMMON_APPDATA));
  Result := FileExists(AFileName);
  if Result then
    Exit;
  AFileName := ''// nix gefunden, also leeren String zurückgeben
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  Reg: TRegistry;
  Filename: String;
begin
  if FindSettingsFile(Filename) then
    // wenn eine Einstellungsdatei gefunden wurde....
    ...
  else
  begin
    // sonst in der Registry suchen...
    Reg := TRegistry.Create;
    try
      Reg.RootKey := HKEY_CURRENT_USER;
      if Reg.OpenKeyReadOnly('Software' + AppDataRootDir + AppDataProjectDir) then
        ...
      else
      begin
        Reg.RootKey := HKEY_LOCAL_MACHINE;
        if Reg.OpenKeyReadOnly('Software' + AppDataRootDir + AppDataProjectDir) then
          ...
      end;
    finally
      Reg.Free;
    end;
  end;
end;
Das lässt sich natürlich auch anders machen, aber dies sollte nur ein kleines Beispiel dazu sein.


Zuletzt bearbeitet von jaenicke am Mi 01.07.09 20:40, insgesamt 3-mal bearbeitet

Für diesen Beitrag haben gedankt: Xion
jaenicke Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 30.12.08 01:39 
3. SJConfigUtils - eine Unit, die die Arbeit teilweise abnimmt

Diese Unit habe ich hier auch separat als Open Source vorgestellt:
www.delphi-forum.de/viewtopic.php?p=562996

Die Unit übernimmt die Verwaltung der Einstellungen, wo sie gespeichert werden und das Suchen nach Einstellungen beim Start. Aber natürlich kann die Unit die speziellen Einstellungen der Anwendung nicht kennen. Ich habe das so gelöst, dass es eine Klasse gibt, die die Verwaltung übernimmt und dem Programmierer dann praktisch sagt was er machen muss.

Dafür enthält die Klasse mehrere abstrakte Methoden, die implementiert werden müssen:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
    procedure LoadFromStream(Data: TStream); override;
    procedure SaveToXml(Ini: IXMLDocument); override;
    procedure LoadFromINI(Ini: TCustomIniFile); override;
    procedure LoadFromRegistry(Reg: TRegistry; ParentPath: String); override;

    procedure SaveToStream(Data: TStream); override;
    procedure LoadFromXml(Ini: IXMLDocument); override;
    procedure SaveToINI(Ini: TCustomIniFile); override;
    procedure SaveToRegistry(Reg: TRegistry; ParentPath: String); override;

    procedure RunConfigWizard; override;
    procedure ApplyStandardSettings; override;
    procedure GetProgramInfo(var Author, ProductName, ProductVersion: string); override;
Um diese zu implementieren, muss man nichts weiter wissen, alles nötige bekommt man geliefert.
Natürlich kann man per Compilerschalter auch einzelne dieser Möglichkeiten deaktivieren. Man muss also nicht z.B. INIs und die Registry unterstützen, nur die Unit tut es prinzipiell.

Durch die Unterstützung eines Streams als Ziel kann man beliebige Formate verwenden, von XML bis zu eigenen Speichermethoden.

Die Unit einzeln sowie eine genauere Erläuterung befinden sich in dem Vorstellungsthread in der Open Source Unit Sparte.
www.delphi-forum.de/viewtopic.php?p=562996

Ein Anwendungsbeispiel folgt in der Vorstellung des Demoprojektes direkt anschließend.


Zuletzt bearbeitet von jaenicke am Mi 01.07.09 11:20, insgesamt 5-mal bearbeitet
jaenicke Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 30.12.08 01:39 
4. Das Demoprojekt

Beim Start muss lediglich die abgeleitete Klasse instantiiert werden und schon kann man die Einstellungen lesen:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
  TfrmMain = class(TForm)
  ...
  public
    { Public declarations }
    Config: TAppConfig;
  end;

...

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  Config := TAppConfig.Create;
  edtUserName.Text := Config.UserName;
  ...
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  Config.Free;
end;
Die Klasse TAppConfig wiederum sieht z.B. so aus, hier einmal nur für XML-Dateien:
ausblenden volle Höhe Delphi-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:
  TAppConfig = class(TAppConfigManager)
  private
  ...
  protected
  ...

implementation

procedure TAppConfig.RunConfigWizard;
begin
  frmConfigWizard := TfrmConfigWizard.Create(nil); // Wizard für die Einstellungen anzeigen
  frmConfigWizard.ShowModal(Self);
end;

procedure TAppConfig.ApplyStandardSettings;
begin
  fUserName := 'Standardname'
end;

procedure TAppConfig.GetProgramInfo(var Author, ProductName,
  ProductVersion: string);
begin
  Author := 'Sebastian Jänicke';
  ProductName := 'SJ Config Utils Demo';
  ProductVersion := '1.0';
end;

procedure TAppConfig.LoadFromXml(Ini: IXMLDocument);
begin
  if AccessManager.InitReadLocation('Userinfo', sIniSectionOpenError) then
  begin
    fUserName := AccessManager.ReadString('Username', sIniValueNotFound);
  end;
end;

procedure TAppConfig.SaveToXml(Ini: IXMLDocument);
begin
  if AccessManager.InitWriteLocation('Userinfo', sIniSectionOpenError) then
  begin
    AccessManager.WriteString('Username', fUserName);
  end;
end;
Das komplette Demoprojekt gibt es im Vorstellungsthread der Open Source Unit:
www.delphi-forum.de/viewtopic.php?p=562996
jaenicke Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mi 01.07.09 20:51 
Abschließend bleibt zu sagen, dass die SJ Config Utils sicher nicht besonders umfangreich sind, die wirklich gleichen Aufgaben aber nach Möglichkeit abnehmen. Manche Features fehlen bewusst, weil es nicht so sinnvoll war, die einzubauen.

Der Sinn von diesem Thread ist auch vor allem zu zeigen wie man die verschiedenen Möglichkeiten der Speicherung (portabel, nicht portabel) sinnvoll kombinieren kann. Denn da gibt es eben viele, die portabel gut finden, und viele, die das nicht gut finden. Deshalb ist für viele Programme eine solche Kombilösung eine gute Alternative zu einer Festlegung auf eine bestimmte Speicherung.

Wenn jemand Änderungswünsche oder Ergänzungsvorschläge zu den SJ Config Utils hat, dann ist der Vorstellungsthread der Unit der richtige Ort, unter anderem dafür habe ich die Threads überhaupt so getrennt.