Default Button

Rund um die LCL und andere Komponenten
Antworten
braunbär
Beiträge: 363
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Default Button

Beitrag von braunbär »

Buttons haben eine property "default". Mir ist aber nicht klar, wie das funktioniert.
Ich war der Meinung, die Onclick Routine des Buttons mit der Property default=true wird automatisch ausgeführt, wenn man die Enter-Taste drückt. Anscheinend ist das aber nicht der Fall, es wird bei Enter die Onclick-Routine von dem Button ausgeführt, der gerade den Focus hat, und das ist ja eigentlich auch in Ordnung.
Bloß, was macht dann die Property "default"?

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2329
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Default Button

Beitrag von m.fuchs »

braunbär hat geschrieben:
Mi 9. Dez 2020, 02:55
Ich war der Meinung, die Onclick Routine des Buttons mit der Property default=true wird automatisch ausgeführt, wenn man die Enter-Taste drückt.
Ja.
braunbär hat geschrieben:
Mi 9. Dez 2020, 02:55
Anscheinend ist das aber nicht der Fall, es wird bei Enter die Onclick-Routine von dem Button ausgeführt, der gerade den Focus hat, und das ist ja eigentlich auch in Ordnung.
Auch ja. Wenn ein anderer Button den Fokus hat, wird dessen OnClick ausgeführt. Wenn aber beispielsweise ein TEdit den Fokus hat, dann wäre die OnClick-Methode des Buttons mit Default dran.

In der Delphi-Hilfe ist das ganz gut beschrieben:
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/StdCtrls_TButton_Default.html hat geschrieben: If Default is true, the button's OnClick event handler executes when the user presses Enter.

Although an application can have more than one Default button, the form calls the OnClick event handler only for the first visible button in the tab order. Moreover, any button that has focus becomes the Default button temporarily; hence, if the user selects another button before pressing Enter, the selected button's OnClick event handler executes instead.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

braunbär
Beiträge: 363
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Default Button

Beitrag von braunbär »

Ok, danke.
Ich habe jetzt etwas herumprobiert.
Der beste Weg, zu einer intuitiv guten Lösung zu kommen, dürfte sein, zusätzlich zu

Code: Alles auswählen

default=true 
noch

Code: Alles auswählen

TabOrder=0
zu setzen - oder jedenfalls die kleinste Taborder von allen Buttons. Dann bekommt kein anderer Button beim Öffnen des Dialogs den Focus.

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 728
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Winux (L 2.0.11 FPC 3.2)
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: Default Button

Beitrag von fliegermichl »

Das ist doch aber gar nicht der Sinn. Das erste Edit sollte den Focus bekommen und wenn der Anwender Enter drückt, bekommt er automatisch den default Button bedient. Wie schon weiter oben geschrieben wurde, hat der fokussierte Button ja sowieso Vorrang.

braunbär
Beiträge: 363
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Default Button

Beitrag von braunbär »

Das Problem kommt offenbar daher, dass bei mir die Buttons auf einem eigenen Panel liegen, auf dem Panel befinden sich nur andere Buttons und sonst keine Controls.
Nun hat ein anderes Control, das nicht am Panel liegt, den Focus. Der in der Taborder auf dem Panel liegende erste Button scheint aber irgendwie doch "auf dem Panel" den Focus zu haben, er wird auch so gezeichnet, als hätte er den Focus. Wenn man Enter drückt, wird nicht der auf dem Panel liegende Default Button ausgelöst, sondern dieser Button.

Allerdings, wenn man irgendwo auf das Panel klickt und danach wieder auf ein Control außerhalb des Panels, dann wechselt im Panel der Focus auf den Button, der als Default definiert worden ist, und danch wird bei Enter auch der richtige Button aktiviert. Es handelt sich da offensichtlich um einen Fehler, beim Öffnen eines Formulars bekommt auf einem Panel der Button mit Taborder 0 den Focus (unabhängig davon, dass das Panel den Focus nicht bekommt) und nicht der Default Button.
Zuletzt geändert von braunbär am Do 10. Dez 2020, 21:08, insgesamt 1-mal geändert.

wp_xyz
Beiträge: 3462
Registriert: Fr 8. Apr 2011, 09:01

Re: Default Button

Beitrag von wp_xyz »

Die gesamte Tab-Reihenfolge hat nicht mit der Verteilung der Controls auf Container zu tun. Rechts-Klick auf dem Formular > "Tabulatorreihenfolge" > die Checkbox "rekursiv" markieren und den Button darüber klicken, dann wird die Tab-Reihenfolge von links/oben nach rechts/unten eingestellt. Beim Öffen des Formulars hat das Control links oben den Fokus (es sei denn du hast ActiveControl des Formlars verstellt). Wenn nun ein Control fokussiert ist, das die ENTER-Taste selbst nicht verwertet (das wäre z.B. ein Memo, oder eben ein Button), dann wird mit ENTER der Default-Button ausgelöst.

braunbär
Beiträge: 363
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Default Button

Beitrag von braunbär »

Ich habe jetzt ein ganz simples Testprogramm mit Butttons in einem Panel und einem TEdit gemacht. Zu meiner Überraschung funktioniert es Bei diesem Testprogramm richtig.
Jetzt muss ich nur noch herausfinden, warum in meinem Programm beim Öffnen des Formulars der falsche Button den Focus bekommt.
wp_xyz hat geschrieben:
Do 10. Dez 2020, 19:28
Rechts-Klick auf dem Formular > "Tabulatorreihenfolge" > die Checkbox "rekursiv" markieren und den Button darüber klicken, dann wird die Tab-Reihenfolge von links/oben nach rechts/unten eingestellt.
Danke für den Hinweis, das ist ein sehr praktisches Feature, das ich bis jetzt noch nicht bemerkt hatte.

braunbär
Beiträge: 363
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Default Button

Beitrag von braunbär »

Es ist mir jetzt gelungen, ein Minimalprogramm zu schreiben, in dem der Fehler auftritt.Es sind zwei Units, das Formular des Hauptprogramms ist leer und ruft nur im FormShow die Dialogroutine auf.
Die Form der Dialogroutine enthält ein leeres Panel für die Buttons und einen Label für die Frage.
Zwei Buttons "Ja" und "Nein" werden im Panel dynamisch erzeugt, der Button "Ja" bekommt die Property Default=true.
Wenn man nach der Dialoganzeige Enter drückt, wird aber der Nein-Button aktiviert.

Windows 10 64bit
Lazarus 2.0.10
FPC 3.2.0
Dateianhänge
Test.zip
(2.98 KiB) 11-mal heruntergeladen

wp_xyz
Beiträge: 3462
Registriert: Fr 8. Apr 2011, 09:01

Re: Default Button

Beitrag von wp_xyz »

Es ist offenbar so, dass das erste Control, das in ein Formular eingefügt wird, standardmäßig als ActiveControl genommen wird. Damit ist bei deinem Testprogramm der Nein-Button der aktive und ist fokussiert (d.h. hat auf Windows den blauen Rand). Füge einmal des Labels ein TEdit ein. Damit ist das Edit fokussiert und die Default-Eigenschaft des Ja-Buttons kommt zum Tragen.

Sind nur Buttons und Labels auf dem Formular, musst du also zusätzlich zur Default-Eigenschaft des Ja-Buttons auch dlg.ActiveControl auf den Ja-Button setzen.

braunbär
Beiträge: 363
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Default Button

Beitrag von braunbär »

@wp_xyz
Danke. Nach deinen Erklärungen und einigem weiteren Herumprobieren habe ich jetzt verstanden, wie es funktioniert.

Es ist für meine Aufgabenstellung trotzdem recht lästig, weil die Dialogunit nicht weiß, was der Programmierer als Zusatzcontrol an den Dialog anhängen wird. Wenn nämlich das Zusatzcontrol keine fokussierbaren Komponenten enthält (z.B. nur ein Panel mit Labels mit Erläuterungen), dann hilft es nicht, dem Zusatzcontrol die Taborder 0 zu geben - der erste Button bekommt dann trotzdem den Focus, weil in dem Fall außer den anderen Buttons, die alle in der Tabfolge später kommen, keine fokussierbaren Controls in der Form sind. Und wie schon gesagt, das weiß die Dialogunit im vorhinein nicht.

Wahrscheinlich gibt es keine Möglichkeit, im Formshow vorab herauszufinden, was nach dem FormShow das aktive Control der Form sein wird? Wenn nicht, dann fällt mir als Lösung nur ein, beim ersten OnPaint Ereignis der Form den activecontrol wenn nötig zu korrigieren, zu dem Zeitpunkt sollte der activecontrol ja hoffentlich feststehen.

wp_xyz
Beiträge: 3462
Registriert: Fr 8. Apr 2011, 09:01

Re: Default Button

Beitrag von wp_xyz »

Du könntest vor dem Einfügen der Buttons alle Controls des Dialogs durchlaufen und prüfen, ob es mindestens ein fokussierbares Control gibt. Falls nein, kannst du unbesorgt den Default-Button zum ActiveControl machen. Etwa so:

Code: Alles auswählen

Procedure DlgSetStdButtons;
var
  B:TBitBtn;
  focusButtons: Boolean;

    procedure DlgAddButton;
    begin
      B:=TBitBtn.Create(dlg.pnlButtons);
      B.Parent:=dlg.pnlButtons;
      B.Autosize:=true;
      B.OnClick:=@dlg.BitBtnClick;
    end;

    function CanFocusAnyControl: Boolean;
    var
      i: Integer;
      s: String;
    begin
      for i := 0 to dlg.ComponentCount-1 do
      begin
        s := TComponent(dlg.Components[i]).Name;
        if (dlg.Components[i] is TWinControl) and TWinControl(dlg.Components[i]).CanFocus and
           (dlg.Components[i] <> dlg.pnlButtons) then
        begin
          Result := true;
          exit;
        end;
      end;
      result := false;
    end;

begin
  focusButtons := not CanFocusAnyControl;
  DlgAddButton;
  B.Caption:='Nein';
  B.Tag:=ord(dlgNo);
  B.Left:=20;
  DlgAddButton;
  B.Caption:='Ja';
  B.Tag:=ord(dlgYes);
  B.Left:=80;
  B.Default:=true;
  if focusButtons then dlg.ActiveControl := B;
end;
Warnung: Dieser Code deckt nicht alle Fälle ab. Wenn zum Beispiel im Dialog ein leeres Panel ist, würde hier CanFocus anspringen und den falschen Rückgabewert von CanFocusAnyControl() bewirken. Also: noch mehr Arbeit nötig, die ich aber dir überlassen möchte.

braunbär
Beiträge: 363
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Default Button

Beitrag von braunbär »

Danke für deine Antwort. So ähnlich habe ich es befürchtet. :)
Jetzt heißt es einmal nachdenken und probieren.
Wahrscheinlich muss ich dann die Controls, wenn sie Parent von anderen Controls sind, rekursiv durchgehen, um zu sehen, ob irgendwas fokussierbar ist. Mal sehen...

wp_xyz
Beiträge: 3462
Registriert: Fr 8. Apr 2011, 09:01

Re: Default Button

Beitrag von wp_xyz »

braunbär hat geschrieben:
Fr 11. Dez 2020, 21:14
Wahrscheinlich muss ich dann die Controls, wenn sie Parent von anderen Controls sind, rekursiv durchgehen, um zu sehen, ob irgendwas fokussierbar ist. Mal sehen...
Normalerweise nicht. In meinem Beispiel werden die "Components" des Formulars verwendet, das sind die Komponenten, die das Formular als Owner haben. Da dies normalerweise der Fall ist, erreicht die Schleife alle Controls, selbst wenn sie auf mehrere Panels in Panels in Radiogroups usw verteilt sind. - siehe beigefügtes Beispiel.

Ich glaube, das Hauptproblem ist, die Komponenten zu erkennen, die überhaupt fokussierbar sind. Mein Test auf "CanFocus" ist auf jeden fall zu simpel, weil er auch ein Panel erkennt. Bei der Durchsicht der Unit Controls ist mir die Methode GetTabOrderList aufgefallen, in der jeder Container seine Kinder in Tab-Reihenfolge auflistet. Für deine Aufgabe müsste es reichen, diese Liste, die vom Formular aus erzeugt worden ist, zu durchlaufen, bis man das erste Control findet, bei dem TabStop true ist.
Dateianhänge
form_components.zip
(2.6 KiB) 10-mal heruntergeladen

braunbär
Beiträge: 363
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Default Button

Beitrag von braunbär »

Danke dir, auf die Lösung wäre ich nur schwer gekommen, und GetTabOrderList werde ich mir auf jeden Fall merken..

Ich war in der Zwischenzeit auch nicht untätig und habe für mein Problem eine wirklich einfache Lösung gefunden. Ich muss ja nur wissen, ob beim Starten der Formulars der erste von mir erzeute Button den Fokus hat, oder ob irgendein anderes (egal welches) Control das activecontrol ist. Im OnPaint eines Formulars ist activecontrol bereits richtig gesetzt, ich brauche da also gar nicht selbst suchen.
Beim Erzeugen der Buttons merke ich mir, welcher Button als erster erzeugt worden ist, in dlg.DefaultButton speichere ich einen Zeiger auf den Button, der als DefaultButton vorgesehen ist, und dann ist es ganz einfach:

Code: Alles auswählen

procedure TDialog.FormPaint(Sender: TObject);
begin
  if FirsttimePaint
  then begin
       FirstTimePaint:=false;
       if (DefaultBtn<>nil) and (ActiveControl=FirstButton)
       then ActiveControl:=DefaultBtn;
       end;
end;

Antworten