Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Delphi TabOrder dynamisch... (https://www.delphipraxis.net/210767-taborder-dynamisch.html)

BigAl 6. Jun 2022 23:26

TabOrder dynamisch...
 
Hallo zusammen,

ich habe hier sehr umfangreiche Formulare die dynamisch generiert werden. Dabei vergebe ich im Moment auch die TabOrder dynamisch bzw. ermittle diese über diverse Gruppierungen und Sortierungen. Das funktioniert soweit auch ganz gut, ich würde aber gerne einen Schritt weiter gehen.

z.B. folgende Eingabefelder:

E0.. E2.. E4..
E1.. E3.. E5..
E6............
E7.. E8.. E9..

Erreichen möchte ich, dass die aktuelle Spalte priorisiert wird (solange mit <Tab> gearbeitet wird). Befindet sich der Fokus z.B. auf E0, dann soll folgende Reihenfolge mit Tab durchlaufen werden:
E0 > E1 > E6 > E7

Befindet sich der Fokus auf E2, dann soll Tab folgende Reihenfolge verwenden:
E2 > E3 > E6 > E8

Und schließlich beim Fokus in E4:
E4 > E5 > E6 > E9

Das Eingabefeld E6 wird also immer aufgerufen, und es soll die Spalte > 0 gemerkt werden und beim Verlassen von E6 wieder auf die vorher aktive Spalte gegangen werden. Ich müsste also irgendwie die Tab-Sequenz dynamisch beeinflussen. Man müsste sich also z.B. in "FindNextControl" von TWinControl einklinken um sowas zu erreichen. Leider kann ich das ja nicht zentral überschreiben. Man muss vielleicht noch dazu sagen, dass es sich bei den Eingabefeldern um Edits, CheckBoxen, ComboBoxen usw. handelt. Also alle möglichen Arten von Eingabefeldern...

Ich hoffe ich habe das irgendwie so erklärt, dass man es versteht.

Hat irgendwer schonmal sowas gemacht oder eine Idee wie man das erreichen könnte?

Alex

himitsu 6. Jun 2022 23:52

AW: TabOrder dynamisch...
 
Im OnKeyDown von E1, E3 und E5 beim TAB ein SetFocus auf F6 und merken, von wem es kam.
Und im OnKeyDown auf TAB reagieren und SetFocus entsprechend dem gespeichertem Vorgänger.


Natürlich könnte man auch im OnEnter von E1/E3/E5 schauen die Taborder umschreiben.
(E6 an die entsprechende Stelle verschieben, also zwischen E1-E7, E3-E8, E5-E9 usw, .... bzw.
Delphi-Quellcode:
E6.TabOrder:=TEdit(Sender).TabOrder+1
)

Uwe Raabe 7. Jun 2022 00:51

AW: TabOrder dynamisch...
 
Nur so ne Idee: Du könntest im Form das GetTabOrderList überschreiben. Erst rufst du inherited auf und erhältst die aktuelle Reihenfolge der WinControls, die du aber dann noch beliebig verändern kannst. Du wirst dir irgendwie das letzte Control merken müssen, um das nächste korrekt in der Liste zu platzieren. Womöglich reicht nicht mal nur das letzte wenn es auch Fälle geben kann, wo z.B. zwei dieser E6-Kandidaten untereinander stehen.

Denke daran, dass man mit Shift-TAB auch den korrekten Rückweg nimmt.

BigAl 7. Jun 2022 06:49

AW: TabOrder dynamisch...
 
Beides gute Ideen. Das mit GetTabOrderList überschreiben muss ich mir mal genauer anschauen. Mir schwebt da grad so 'ne Idee im Kopf rum mit mehreren Tab-Listen (sind maximal drei). Je nach Zustand könnte ich dann die entsprechende aktivieren.

Ich denke das könnte die Lösung sein :-).

Danke!

BerndS 7. Jun 2022 08:50

AW: TabOrder dynamisch...
 
Würde es nicht auch reichen, zur Laufzeit im OnEnter Event Tabstop entsprechend zu setzen und TabOrder zu lassen. Dabei könnte ein Event für alle Edits gesetzt werden und anhand der Taborder des gerade akuellen Edits bei den anderen TabStop gesetzt werden.
hier mal ein Beispiel:
Delphi-Quellcode:
procedure TForm1.e1Enter(Sender: TObject);
  function EditColumn(AEdit: TEdit): Integer;
  begin
    case AEdit.TabOrder of
      0, 1, 7:
       Result := 0;
      2, 3, 8:
       Result := 1;
      4, 5, 9:
       Result := 2;
      else
        Result := -1;
    end;
  end;
var
  I, Col: Integer;
  WC: TControl;
  E: TEdit;
begin
  if Sender is TEdit then
  begin
    Col := EditColumn(TEdit(Sender));
    for I := 0 to TEdit(Sender).Parent.ControlCount - 1 do
    begin
      WC := TEdit(Sender).Parent.Controls[I];
      if WC is TEdit then
      begin
        E := TEdit(WC);
        E.TabStop := (E = E6) or (EditColumn(E) = Col);
        if E.TabStop then
          E.Color := clInfoBk
        else
          E.ParentColor := True;
      end;
    end;
  end;
end;
Zur Verdeutlichung habe ich auch die Farbe gesetzt. Das ist aber nur gemacht, um im Beispiel zu sehen, ob es auch klappt.

KodeZwerg 7. Jun 2022 10:45

AW: TabOrder dynamisch...
 
Wenn es total dynamisch sein soll, würde ich mir das Tags property zu nutze machen.
Tags entsprechend Priorität fortlaufend vergeben, angenommen es wird ein "Hauptsprungziel" erschaffen, dann ab in eine 1000er Zählung, untergeordnete in eine 2000er usw.
Nun beim KeyDown event das aktuelle Tag auslesen und entsprechend dessen Inhalt das vorige/nächste Tag finden und fokussieren.

Frickler 7. Jun 2022 12:43

AW: TabOrder dynamisch...
 
Wie "priorisiert" sind denn die Felder? Ist es erlaubt, die aktuelle Reihe jederzeit etwa mit der Maus zu verlassen, um eine andere Reihe abzuarbeiten?

Ich habe das mal gemacht für ein Formular, wo die jeweils gewählte Reihe auf gar keinen Fall verlassen werden durfte (außer Programmende), bis man zum letzten Feld kam oder auf dem ersten Feld stand. Da wurden dann einfach sämtliche Felder außerhalb der Reihe auf enabled=false gesetzt...

BigAl 8. Jun 2022 06:45

AW: TabOrder dynamisch...
 
Liste der Anhänge anzeigen (Anzahl: 2)
Zitat:

Zitat von Frickler (Beitrag 1506919)
Wie "priorisiert" sind denn die Felder? Ist es erlaubt, die aktuelle Reihe jederzeit etwa mit der Maus zu verlassen, um eine andere Reihe abzuarbeiten?

Ich habe das mal gemacht für ein Formular, wo die jeweils gewählte Reihe auf gar keinen Fall verlassen werden durfte (außer Programmende), bis man zum letzten Feld kam oder auf dem ersten Feld stand. Da wurden dann einfach sämtliche Felder außerhalb der Reihe auf enabled=false gesetzt...

Das ist eben der Punkt. Die Spalte kann z.B. mit der Maus verlassen werden. Dann ist halt eine andere Spalte aktiv.

Nochmal zum Verständnis: Es geht z.T. um mehrere hundert Eingabe-Controls pro Form. Spalte 1 enthält z.B. die Daten im imperialen Format, Spalte 2 die entsprechenden metrischen Pendants. Oder Spalte 1 enthält die Daten in Fremdwährung pro imperialer Einheit, Spalte 2 die Daten in Fremdwährung pro metrischer Einheit und Spalte 3 die Daten in Euro pro metrischer Einheit. Das sieht dann am Bildschirm so aus:

Anhang 55142

Oder so:

Anhang 55143

Wird ein Wert in einer beliebigen Spalte eingegeben, dann werden die Werte in den anderen Spalten automatisch berechnet. Die Blöcke werden halt immer wieder durch Eingaben unterbrochen, welche z.B. einspaltig sind. Grundsätzlich soll die Eingabe von oben nach unten erfolgen. Es kann aber z.B. sein, dass die Werte imperial vorliegen, aber einzelne Ausnahmen (z.B. ein Block) dann doch metrisch sind. Der Benutzer muss also die Möglichkeit haben zwischendurch gezielt (z.B. mit der Maus) die Spalte zu wechseln...

Fritzew 8. Jun 2022 10:46

AW: TabOrder dynamisch...
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo,

ich würde den Ansatz von Uwe verfolgen.

im Anhang ein Testprojekt

Gruss Fritz

BigAl 8. Jun 2022 23:12

AW: TabOrder dynamisch...
 
Zitat:

Zitat von Fritzew (Beitrag 1507007)
Hallo,

ich würde den Ansatz von Uwe verfolgen.

im Anhang ein Testprojekt

Gruss Fritz

Hallo Fritz,

habe eben mal das Beispiel von Dir angeschaut. Wie ich oben schon erwähnt habe werde ich den Ansatz mit "GetTabOrderList" verfolgen. Ich kann zwar Dein Beispiel so nicht übernehmen, da der Aufbau meiner Formulare etwas komplexer ist (die Formulare basieren auf Datenbanktabellen und verschiedenen JSON-Dateien), aber man kann sehen, dass es grundsätzlich funktioniert.

Vielen Dank!

Alex

BigAl 9. Jun 2022 12:04

AW: TabOrder dynamisch...
 
Hallo zusammen,

hat perfekt funktioniert. Der Lösungsansatz über "GetTabOrderList" war perfekt.

Ich habe das nun folgendermaßen implementiert (falls mal jemand was ähnliches machen will):

Zum Verwalten der Tabulatorreihenfolgen habe ich folgende Member angelegt:

Delphi-Quellcode:
   
    FCols: Integer;                         // Number of input columns.
    FActiveCol: Integer;                    // The active column to tab through.
    FTabOrders: TArray<TArray<TWinControl>>; // The TabOrderLists for the columns.
In "TabOrders" speichere ich die Reihenfolge für die verschiedenen möglichen Spalten. Diese werden dann einfach in "GetTabOrderList" aktiviert, je nach Wert in "FActiveCol":

Delphi-Quellcode:
procedure TFrameQnr.GetTabOrderList(List: TList);
// Get the actual (prepared) tab order.
begin
  for var P in FTabOrders[FActiveCol] do
    List.Add(P);
end;
Jedem der Inout-Controls habe ich noch ein "OnMouseDown" und ein "OnKeyDown" verpasst. Klickt der Benutzer in eine bestimmte Spalte, so wird diese in den Listen gesucht und als aktiv gesetzt:

Delphi-Quellcode:
procedure TFrameQnr.OnDBEditMouseDown(ASender: TObject; AButton: TMouseButton; AShift: TShiftState; AX, AY: Integer);
// Switch the active column if a DBEdit is selected by mouse click.
begin
  // Find the first column that contains the control.
  for var ColIndex := 0 to High(FTabOrders) do
    for var Control in FTabOrders[ColIndex] do
      if ASender = Control then
      begin
        FActiveCol := ColIndex;
        Exit;
      end;
end;
Hatte das Anfang über die "Tag"-Eigenschaft gemacht, diese verwende ich aber nur sehr ungerne.

Da ich immer auch eine tastaturbasierte Lösung bereitstelle habe ich in "OnKeyDown" die Möglichkeit geschaffen die Spalte über Hotkeys zu wechseln:

Delphi-Quellcode:
procedure TFrameQnr.OnDBEditKeyDown(ASender: TObject; var AKey: Word; AShift: TShiftState);
// Enable <Ctrl><Tab> and <Ctrl><Shift><Tab> to change the active column.

  function GetActiveControlIndex: Integer;
  begin
    for var I := 0 to High(FTabOrders[FActiveCol]) - 1 do
      if FTabOrders[FActiveCol][I] = TControl(ASender) then
        Exit(I);
    Result := -1;
  end;

  procedure SetActiveControlIndex(AIndex: Integer);
  begin
    if (AIndex >= 0) then
      FTabOrders[FActiveCol][AIndex].SetFocus;
  end;

var
  Index: Integer;
begin
  if AKey = VK_TAB then
  begin
    if AShift = [ssCtrl] then // next column
    begin
      Index := GetActiveControlIndex;
      if (FActiveCol < FCols - 1) and (FTabOrders[FActiveCol][Index] <> FTabOrders[FActiveCol + 1][Index]) then
      begin
        Inc(FActiveCol);
        SetActiveControlIndex(Index);
      end;
      AKey := 0;
    end
    else if AShift = [ssCtrl, ssShift] then // previous column
    begin
      Index := GetActiveControlIndex;
      if (FActiveCol > 0) and (FTabOrders[FActiveCol][Index] <> FTabOrders[FActiveCol - 1][Index]) then
      begin
        Dec(FActiveCol);
        SetActiveControlIndex(Index);
      end;
      AKey := 0;
    end;
  end;
end;
Das war es im Wesentlichen schon. Am aufwendigsten war die ursprüngliche Erstellung der Arrays mit den Reihenfolgen. Diese ist aber sehr programmspezifisch und es macht keinen Sinn, dass ich diese hier poste.

In diesem Sinne. Nochmal vielen Dank für eure tolle Unterstützung.

Alex

himitsu 9. Jun 2022 14:46

AW: TabOrder dynamisch...
 
Zitat:

Hatte das Anfang über die "Tag"-Eigenschaft gemacht, diese verwende ich aber nur sehr ungerne.
Dabei ist Delphi-Referenz durchsuchenTComponent.Tag sogar extra nur für "dich" vorhanden.
Zitat:

Zitat von OH
Tag hat keine vordefinierte Bedeutung. Die Eigenschaft Tag kann zusätzliche Integerwerte speichern, um die Arbeit des Programmierers zu erleichtern.


Im FMX gibt es zusätzlich auch noch TagFloat, TagObject und TagString .... nur schade, dass Diese nicht im Formdesigner verfügbar sind (abgesehn vom Objekt hätte der OI damit kein Problem).

BigAl 9. Jun 2022 14:51

AW: TabOrder dynamisch...
 
[QUOTE=himitsu;1507084]
Zitat:

Dabei ist Delphi-Referenz durchsuchenTComponent.Tag sogar extra nur für "dich" vorhanden.
Außer ein anderer hat da schonmal was "Quick and Dirty" mit angestellt :-).

Ich nutze die Tag-Eigenschaft eigentlich nur um mal schnell was zu testen oder so. Ich habe da einfach zu viel Angst, dass ich das irgendwann mal versehentlich mehrfach verwende und es dann knallt. Wäre zwar auch keine große Sache das zu fixen aber jeder hat halt so seine eigenen Regeln bezüglich "sauberem Code" :-).

Aber grundsätzlich hast Du natürlich recht.


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:22 Uhr.

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz