Zwei ToggleBoxen

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
Tim
Beiträge: 21
Registriert: Fr 2. Dez 2016, 16:47

Zwei ToggleBoxen

Beitrag von Tim »

Hey, ich habe ein höchstwahrscheinlich sehr einfaches Problem, ich komme aber einfach nicht weiter, da es nicht das macht, was ich will. (Ich bin Programmieranfänger, also verzeiht die Frage :)
Ich habe zwei ToggleBoxen. Wenn eine angeklickt wird (checked=true) dann soll die andere nicht angeklickt sein (checked=false). Es soll also immer nur eine angeklickt sein.
Mein Quellcode bis jetzt:

Code: Alles auswählen

procedure TForm1.ToBoxchangeClick(Sender: TObject);
begin
  IF ToBoxchange.Checked=false then
     begin
        ToBoxchange.Checked:=true;
        ToBoxdelete.Checked:=false;
     end;
end;
 
procedure TForm1.ToBoxdeleteClick(Sender: TObject);
begin
  IF ToBoxdelete.Checked=false then
     begin
        ToBoxdelete.Checked:=true;
        ToBoxchange.Checked:=false;
     end;
 
end
 


Mein Problem ist jetzt (zu Anfang sind beide nicht gecheckt), ich klicke zuerst auf die erste ToggleBox (da ist alles ok) und danach auf die zweite Box. Dabei wird die erste nicht "unchecked" sondern beide Boxen sind auf einmal "checked". Klicke ich nun nochmal auf eine der Boxen rauf kommt dieser Error:
24-07-_2017_11-56-03.png
24-07-_2017_11-56-03.png (5.73 KiB) 1884 mal betrachtet

24-07-_2017_11-56-20.png


Kann mir jemand helfen?

Liebe Grüße
Tim

Benutzeravatar
theo
Beiträge: 10467
Registriert: Mo 11. Sep 2006, 19:01

Re: Zwei ToggleBoxen

Beitrag von theo »

Ich verstehe nicht ganz, welche Zustandskombinationen erlaubt sein sollen, aber ich würde auf jeden Fall nicht den Zustand des aktuell geklickten Buttons in der Ereignisbehandlung ändern.

Code: Alles auswählen

procedure TForm1.ToggleBox1Change(Sender: TObject);
begin
  if ToggleBox1.Checked = False then
    ToggleBox2.Checked := True
  else
    ToggleBox2.Checked := False;
end;
 
procedure TForm1.ToggleBox2Change(Sender: TObject);
begin
  if ToggleBox2.Checked = False then
    ToggleBox1.Checked := True
  else
    ToggleBox1.Checked := False;
end;
 

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

Re: Zwei ToggleBoxen

Beitrag von wp_xyz »

Ich muss gestehen: Ich habe noch nie TTogglebox verwendet, und was ich eben beim Testen deiner Frage gesehen habe, kommt es mir auch so vor, als ob die Buttons auch nicht in Gruppen verwendet werden können, die sich gegenseitig verriegeln.

Nimm besser TSpeedButton. Hier findest du die Eigenschaft "GroupIndex". Alle Speedbuttons, die eine Gruppe bilden sollen, erhalten denselben Wert <> 0. Damit wird beim Klicken eines Buttons der vorher gedrückte zurückgesetzt. Wenn du erlauben willst, dass beim erneuten Anklicken eines gedrückten Buttons dieser in den Unchecked-Zustand zurückwechselt, musst du bei allen Buttons der Gruppe AllowAllUp auf true setzen.

SpeedButtons haben einen Nachteil, der zwar normalerweise keine Rolle spielt, aber wer weiß? Sie können mit dem Tabulator nicht erreicht werden. Man kann sie nur durch Anklicker mit der Maus oder mit Hilfe eines Tastatur-Hotkeys umschalten (also z.B. Alt+S, wenn als Caption "&Speichern" eingetragen ist).

Falls du den Tabulator unbedingt benötigst, musst du doch zu TToggleBox zurückgehen und die gegenseitige Auslösung selbst einprogrammieren, indem du im OnClick das Checked der anderen Boxes zurücksetzt.

Tim
Beiträge: 21
Registriert: Fr 2. Dez 2016, 16:47

Re: Zwei ToggleBoxen

Beitrag von Tim »

Also die Zustände sollen sein: (es dürfen nie beide ToggleBoxen gleichzeitig "checked" oder gleichzeitig "unchecked" sein)

(1) ToBox1.Checked:=true während ToBox2.Checked:=false
(2) ToBox1.Checked:=false während ToBox2.Checked:=true

Danke erst mal für die schnelle Hilfe, zumindest der Error kommt jetzt nicht mehr. Aber es funktioniert nicht so, wie ich es haben will, nämlich dass immer eine der ToggleBoxen checked ist, während die andere unchecked ist.

Grüße
Tim

Benutzeravatar
theo
Beiträge: 10467
Registriert: Mo 11. Sep 2006, 19:01

Re: Zwei ToggleBoxen

Beitrag von theo »

Tim hat geschrieben:Danke erst mal für die schnelle Hilfe, zumindest der Error kommt jetzt nicht mehr. Aber es funktioniert nicht so, wie ich es haben will, nämlich dass immer eine der ToggleBoxen checked ist, während die andere unchecked ist.



Bei mir schon. (Lazarus 1.9.0 r55559M FPC 3.0.2 x86_64-linux-gtk2)

Betriebssystem? Lazarus Version? Widget Set?

Tim
Beiträge: 21
Registriert: Fr 2. Dez 2016, 16:47

Re: Zwei ToggleBoxen

Beitrag von Tim »

Ich probiere das gleich mal mit den SpeedButtons, melde mich nachher wieder.
Benutze Lazarus 1.4.4 und Windows 10. Sollte vielleicht mal updaten :)

Benutzeravatar
theo
Beiträge: 10467
Registriert: Mo 11. Sep 2006, 19:01

Re: Zwei ToggleBoxen

Beitrag von theo »

Tim hat geschrieben:Benutze Lazarus 1.4.4 und Windows 10. Sollte vielleicht mal updaten :)


Ja, unbedingt. Hat ja keinen Sinn, dass man sich mit alten Bugs aufhält.

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

Re: Zwei ToggleBoxen

Beitrag von wp_xyz »

Unter Windows bleiben die Toggleboxes individuell anwählbar. Wenn das unter anderen Widgetsets anders ist, sollte ein Fehler gemeldet werden.

Der beigefügte Code zeigt, wie sich die Toggleboxes auch unter Windows gegenseitig verriegeln lassen, so dass nur ein einziger Button gedrückt sein kann. Dazu muss die Allzweck-Eigenschaft "Tag" der Boxes, die eine Gruppe bilden sollen, auf denselben Wert (<> 0) gesetzt werden und in OnClick-Handler der betreffenden Boxes die Methode CheckToggleBox aufgerufen werden. Dort wird auch dafür Sorge getragen, dass sich die Prozedur nicht selbst rekursiv aufruft und das Programm damit ins Jenseits schickt (das programmgesteuerte Setzen von Checked bewirkt selbst wieder ein OnClick-Event).
Dateianhänge
ToggleBox_Group.zip
(2.07 KiB) 69-mal heruntergeladen

Tim
Beiträge: 21
Registriert: Fr 2. Dez 2016, 16:47

Re: Zwei ToggleBoxen

Beitrag von Tim »

Vielen Dank wp_xyz! Der Code hat funktioniert! Obwohl ich ihn nicht zu 100% verstanden habe (ich bin Schüler, der versucht mit dem Basic-Wissen, welches uns unsere total inkompetente Info-Lehrerin beibringt, etwas anzufangen und auch darüber hinaus zu arbeiten), hab ich doch was neues gelernt, aber ich sag ganz ehrlich, darauf wäre ich nie gekommen!!! Ich denke, selbst mein fertiges Programm hätte noch tausende Verbesserungsmöglichkeiten, aber naja...Hauptsache es funktioniert :)

Danke nochmal!
Grüße
Tim

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

Re: Zwei ToggleBoxen

Beitrag von wp_xyz »

Naja, gerade wenn du Schüler bist, wäre es wichtig, dass du ALLES verstehst. Was sind denn die restlichen Prozent, die du nicht verstanden hast?

Mal ins blaue hinein erklärt:
  • "Tag" (=englisch für "Aufkleber") ist eine für sonst nichts benutze Eigenschaft jeder Komponente. Indem ich dort einen Wert eintrage, werden alle Buttons, die zur selben Gruppe gehören, gekennzeichnet.
  • Alle Buttons verwenden denselben OnClick-Handler. Man könnte natürlich auf jeden doppelt klicken und in dem OnClick-Prozedur-Rahmen den Aufruf von CheckToggleBox eintragen. Aber es geht einfacher, wenn man das nur bei einem Button macht und bei den anderen im Objektinspektor bei OnClick auf den Dropdown-Pfeil geht und die schon erzeugte Event-Prozedur auswählt.
  • Jeder OnClick-Handler hat von Lazarus als Parameter "Sender" einen Zeiger auf das Objekt erhalten, das angeklickt worden ist. Da das ganz allgemein gilt, ist Sender nur als TObject deklariert. Die Methode CheckToggleBox will als Parameter aber eine TTogglebox. Damit der Compiler diesen Typ-Wechsel akzeptiert, muss man das Objekt "Sender" in eine ToggleBox umwandeln. Deshalb das "Sender as TTogglebox". Damit das funktioniert, muss das Objekt, das diesen OnClick-Handler verwendet, aber auch als ToggleBox erzeugt worden sein. Das ist hier der Fall. Aber genauso gut könntest du einem TEdit diese OnClick-Code zuweisen, nur würde dann das Programm zur Laufzeit abstürzen, weil die Umwandlung "Sender as TTogglebox" nicht möglich ist.
  • In "CheckToggleBox" werden in einer for-Schleife alle Komponenten durchlaufen, die sich auf dem Formular befinden. Die Komponenten heißen Components[index], ihre Anzahl ComponentCount. Das erste Element von Components hat den Index 0, daher geht die Schleife von 0 bis ComponentCount-1.
  • Da wir nur TToggleBox-Objekte benötigen, wird mit "if components[i] is TToggleBox" geprüft, ob die aktuell in der for-Schleife behandelte Komponente eine TTogglebox ist. Wenn ja wird mit "tb := TToggleBox(Components[i])" die Komponente für den Compiler als TTogglebox erkennbar gemacht. Man könnte auch "tb := Components[i] as TTogglebox" verwenden, aber das prüft nochmals den typ des Objekt, und das wissen wir ja schon. Ganz allgemein muss man aber vor solchen Typumwandlungen warnen. Man sagt damit dem Compiler: Ich weiß es besser, nur muss das halt auch stimmen. Sonst gibt es einen Absturz.
  • Dann wird geprüft, ob das Tag der Togglebox in der Schleife (tb) mit dem Tag der angeklickten Togglebox (ATogglebox) übereinstimmt. Damit bleiben andere Toggleboxes auf dem Formular, die nicht zu der Gruppe gehören, unberücksichtigt.
  • Wenn diese Togglebox nicht diejenige ist, die als Parameter übergeben wurde, muss deren Eigenschaft "Checked" gelöscht werden (Du willst ja, dass nur die geklickte Togglebox gedrückt bleibt). Andernfalls muss die gefundene Togglebox auf Checked = true gesetzt werden. Wenn die gefundene Togglebox vorher Checked = false hatte, ändert sich ihr Zustand, und es wird das Ereignis OnClick erneut ausgelöst - das ist etwas seltsam, denn man halt ja hier gar nicht geklickt, aber es ist halt so gemacht... Auf jeden Fall wird nun die Routine immer wieder aufgerufen und der Zustand der togglebox ändert sich jedes Mal. Dieser rekursive Aufruf endet schließlich in einem Stacküberlauf = Absturz. Das ist auch der Fehler, den du in deinem 1.Beitrag beschreibst. Um das zuverhindern, habe ich mir zuerst gemerkt, welchen OnClick-Handler die Togglebox verwendet (savedOnClick := tb.OnClick), dann den OnClick-Handler ausgehängt (tb.OnClick := nil), so dass die Togglebox nicht mehr auf OnClick reagiert. Und erst, wenn der Zustand der Togglebox umgeschaltet wurde, wird der OnClick-Handler wieder restauriert (tb.OnClick := savedOnClick).

Eine andere Möglichkeit, den rekursiven Prozeduraufruf zu verhindern, wäre, eine Steuervariable zu verwenden, die immer dann gesetzt wird, wenn man im Code den Zustand von Checked verändert und anschließend wieder zurücksetzt. Bei gesetzter Zustandsvariable wird der Eventhandler gleich wieder verlassen.

Code: Alles auswählen

type
  TForm1 = class(TForm)
  private
    FLockCount: Integer;
  ...
  end;
 
procedure TForm1.CheckToggleBox(AToggleBox: TTogglebox);
var
  i: Integer;
  tb: TTogglebox;
begin
  if FLockCount > 0 then
    exit;
 
  for i:=0 to ComponentCount-1 do
    if (Components[i] is TToggleBox) then begin
      tb := TToggleBox(Components[i]);
      if (tb.Tag <> 0) and (tb.Tag = AToggleBox.Tag) then
      begin
        inc(FLockCount);
        try
          tb.Checked := tb = AToggleBox;
        finally
          dec(FLockCount);
        end;
      end;
    end;
end;
Zuletzt geändert von wp_xyz am Mo 24. Jul 2017, 15:40, insgesamt 1-mal geändert.

Tim
Beiträge: 21
Registriert: Fr 2. Dez 2016, 16:47

Re: Zwei ToggleBoxen

Beitrag von Tim »

Hallo nochmal,
Vielen Dank für die ausführliche Erklärung! Du hast eigentlich genau die Punkte besprochen, die mir noch nicht ganz klar waren! Wenn unsere Info-Lehrerin solche Dinge auch so gut erklären könnte würden wahrscheinlich alle Schüler schon viel viel besser programmieren können.

Das mit dem aufrufen des OnClick-Handlers der einen ToggleBox hab ich auch dazu gelernt, jedoch musste ich die kleine Prozedur mit dem Aufruf der Prozedur "CheckToggleBox" für jede ToBox schreiben, da bei der einen ToBox noch etwas zusätzlich passieren soll.

Danke nochmal! :D

Flügel hoch
Tim

Antworten