Autor Beitrag
MathiasSimmack
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Di 08.07.03 12:29 
die Positionen der TCoolbar-Bands speichern/laden?

Vor einigen Monaten hatte wulfskin gefragt, wie man die Position der Bänder (Bands) in einer TCoolBar speichern kann. (ToolBar speichern?.) Da er damals den Tipp bekam, bessere Komponenten zu benutzen, habe ich meinen Vorschlag, den ich vorbereitet hatte, wieder gelöscht.

Erinnert habe ich mich daran, weil ich momentan an der NonVCL-Version (Rebar genannt; s. PSDK) sitze und den Beitrag für Luckies Tutorials eigentlich fertig habe. Aber Luckies Reaktion auf meine Demo war:
Zitat:
Jetzt fehlt eigentlich nur noch, wie man das abspeichert, damit beim nächsten Start alles wieder so ist, wie man es sich zurecht gelegt hat.

:) Darum habe ich den VCL-Code rekonstruiert und würde ihn hier gern als Idee vorstellen und zeigen, wie man es mit der normalen TCoolbar machen könnte. Da ich nicht weiß, wie das in neueren Delphi-Versionen aussieht, gilt dieser Vorschlag erst mal nur bis Delphi 5, wo die TCoolbar keine eingebaute Speichermöglichkeit hat.


Als Speicherort habe ich mich für die Registry entschieden. Das kann zwar jeder von euch anders machen, aber da bei der Toolbar die Einstellungen auch in der Registry gesichert werden (s. Toolbarbeitrag in Luckies Tutorials), bietet sich das hier natürlich auch an. Daher die Grundlagen, einzufügen im Interface-Teil der Form-Unit:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
uses
  Registry;

const
  szRegKey    = 'Software\CoolbarDemo\CoolbarPos';
  szValName   = 'BandLayout';
type
  rbBandArray = packed record
    Index,                     // Index des Bandes
    ID,                        // ID des Bandes
    Width : integer;           // Breite des Bandes
  end;


Die Idee ist folgende: jedes Band besitzt eine einzigartige ID, die bei der VCL-Version auch nur lesbar ist. Diese ID entspricht typischerweise dem Index des Bandes (quasi der Reihenfolge, in der sie erzeugt werden). Aber wenn man die Bänder verschiebt, dann ändert sich nur der Index - die ID dagegen bleibt unverändert!
Daher interessieren uns eben der Index, die ID und natürlich die Breite des Bandes. Auch von Interesse dürfte die Frage sein, ob so ein Band in einer neuen Zeile steckt oder an einem anderen Band dran hängt. Aber dazu gleich.

Zum Speichern benötigen eine Schleifenvariable (i), eine Registry-Variable (reg) und ein dynamisches Array des Typs "rbBandArray":
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
var
  i   : integer;
  reg : TRegistry;
  fba : array of rbBandArray;
begin
  SetLength(fba,0);

Dann legen wir zunächst die Größe dieses Arrays fest, wobei wir die Anzahl der Bänder als Basis nehmen:
ausblenden Delphi-Quelltext
1:
2:
3:
  with Coolbar1 do 
  begin
    SetLength(fba,Bands.Count);

In einer Schleife merken wir uns dann die o.g. Werte:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
    for i := 0 to Bands.Count - 1 do 
    begin
      fba[i].Index  := Bands[i].Index;
      fba[i].ID     := Bands[i].ID;
      fba[i].Width  := Bands[i].Width;

Jetzt die Sache, die jeder von euch nach eigenem Gutdünken handhaben kann: Wenn ein Band in einer neuen Zeile ist, dann ist seine Eigenschaft "Break" üblicherweise true. Ich habe mich dafür entschieden, in so einem Fall, den Index als negativen Wert anzugeben:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
      if(Bands[i].Break) then
        fba[i].Index := 0 - fba[i].Index;
    end;
  end;

Das heißt, aus dem Bandindex 2 wird bspw. -2. Wie gesagt, das kann jeder anders machen. Ich hätte auch eine vierte Membervariable in mein rbBandArray-Record einbauen können. Aber ich wollte die Anzahl der Bytes in der Registry möglichst klein halten, und dieser Wert hier ist der einzige, den man auch mit anderen Werten verbinden kann. Index, ID und Breite dagegen sollten in separaten Variablen gespeichert werden.

Die so gesicherten Daten werden nun in der Registry abgelegt. Ich habe den Schlüssel HKEY_CURRENT_USER benutzt, was natürlich einen Sinn hat: a) es gibt keine Rechteprobleme unter NT, b) jeder User hat sein eigenes Layout:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
  if(length(fba) > 0then 
  begin
    reg := TRegistry.Create;
    with reg do 
      try
        RootKey := HKEY_CURRENT_USER;

        if(OpenKey(szRegKey,true)) then 
          try

Hervorzuheben ist die folgende Zeile:
ausblenden Delphi-Quelltext
1:
          WriteBinaryData(szValName,fba[0],length(fba) * sizeof(rbBandArray));					

Da das dynamische Array bei Null beginnt, müssen wir das auch angeben. Sonst speichern wir alles mögliche, nicht aber die uns interessierenden Werte. :) Und der Rest noch:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
      finally
        CloseKey;
      end;
    finally
      Free;
    end;
  end;

  // Array auf Null zurücksetzen
  SetLength(fba,0);
end;

Voilà. Idealerweise ist dieser Code im "OnClose"-Ereignis einzufügen, bzw. über dieses aufzurufen, damit beim Beenden des Programms die aktuellen Band-Positionen gesichert werden.
Moderiert von user profile iconjasocul: Sourcen an Style-Guide angepasst
Moderiert von user profile iconjasocul: Beitrag geprüft am 16.05.2006
MathiasSimmack
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Di 08.07.03 12:32 
Titel: Und die Positionen wieder auslesen
Und selbstverständlich die umgekehrte Richtung: wir holen zuerst die Daten aus der Registry. Da wir diesmal nichts schreiben, reicht es auch aus, die Registry nur lesend zu öffnen (auch wenn wir auf HKCU zugreifen):
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
SetLength(fba,0);

reg := TRegistry.Create(KEY_READ);
with reg do 
  try
    RootKey := HKEY_CURRENT_USER;

    if(OpenKey(szRegKey,false)) then 
      try
        if(ValueExists(szValName)) then 
        begin
          len := GetDataSize(szValName);

An der Stelle ein kurzer Stop um eine Sache zu klären: Mit "GetDataSize" kann man ermitteln, wie viele Bytes ein Eintrag belegt. Abhängig davon müssen wir die Größe unseres Arrays setzen. Nun könnte man aber die Daten in der Registry manuell ändern. Ich könnte durchaus ein paar Bytes löschen, was unerwartete Folgen für das Programm hätte. Also sollten wir sicherstellen, dass a) überhaupt Daten vorhanden sind:
ausblenden Delphi-Quelltext
1:
      if(len > 0and					

und b) dass diese ohne Rest durch die Größe des Arrays teilbar sind. Das dient dazu, besagte Byte-Löschungen zu entdecken:
ausblenden Delphi-Quelltext
1:
        (len mod sizeof(rbBandArray) = 0and					

Und c) sollte die Division der Bytes durch die Arraygröße die Anzahl der TCoolBar-Bands ergeben:
ausblenden Delphi-Quelltext
1:
2:
        (len div sizeof(rbBandArray) = Coolbar1.Bands.Count) then
      begin

Das ist zwar auch nicht 100% sicher (ich könnte die Werte an sich ändern, ohne die Bytegröße zu manipulieren), aber es schützt zumindest ein bisschen. Wenn ihr ein bspw. eine neue Programmversion veröffentlicht, in der ein TCoolbar-Band entfernt wurde oder auch hinzugekommen ist, dann werden beim Start dieser neuen Version die alten Einstellungen ignoriert, und das Programm präsentiert sich für den User erst mal wieder mit dem Grundaussehen.
Wie dem auch sei, wenn die o.g. Bedingungen erfüllt sind, dann setzen wir die Arraygröße und lesen die Werte aus der Registry:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
        SetLength(fba,len div sizeof(rbBandArray));
        ReadBinaryData(szValName,fba[0],len);
      end;
    end;
  finally
    CloseKey;
  end;
finally
  Free;
end;

Fehlt nur noch das "Umbauen" der TCoolbar. Dazu blockieren wir erst mal die Bildschirmausgabe. Das halte ich für nützlich, denn wenn jemand sehr viele Bänder hat, bzw. einen schwachen PC, dann kann man u.U. miterleben, wie sich die Bänder neu anordnen. Und das sieht nicht professionell aus. :)
ausblenden Delphi-Quelltext
1:
2:
3:
4:
if(length(fba) > 0then
  with Coolbar1.Bands do 
  begin
    BeginUpdate;

In einer for-Schleife prüfen wir zunächst ob der Index innerhalb der Anzahl der Bänder liegt, und ob die Methode "FindItemId" auch etwas zurückliefert:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
    for i := 0 to length(fba) - 1 do
      if(abs(fba[i].Index) < Count) and
        (FindItemId(fba[i].ID) <> nilthen
      begin

Erst dann ändern wir den Index des jeweiligen Bandes, wodurch es verschoben wird. Da wir ja auch negative Werte haben können (an das "Break" denken!), benutzen wir aber die ABS-Funktion, um den Wert ohne Vorzeichen zu übergeben:
ausblenden Delphi-Quelltext
1:
        FindItemID(fba[i].ID).Index := abs(fba[i].Index);					

Wenn es in eine neue Zeile muss, dann ist der Indexwert -wie gesagt!- kleiner als Null, was wir ohne umständliches if direkt an die entsprechende Eigenschaft "Break" des Bandes weitergeben:
ausblenden Delphi-Quelltext
1:
        (FindItemID(fba[i].ID) as TCoolBand).Break := (fba[i].Index < 0);					

Und die gespeicherte Breite muss auch noch eingestellt werden:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
        (FindItemID(fba[i].ID) as TCoolBand).Width := fba[i].Width;
      end;

    EndUpdate;
  end;

Fertig. Dieser Teil gehört natürlich am besten ins "OnCreate" eurer Form.


Wie gesagt: es ist eine mögliche Idee von vielen.
Und wenn das jemandem als Anregung weiterhilft, dann freut mich das.
Moderiert von user profile iconjasocul: Sourcen an Style-Guide angepasst
Moderiert von user profile iconjasocul: Beitrag geprüft am 16.05.2006