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/)
-   -   verschränke ToggleBoxes (https://www.delphipraxis.net/183535-verschraenke-toggleboxes.html)

TForm1 17. Jan 2015 23:24

verschränke ToggleBoxes
 
Hallo zusammen,
folgender Code:
Delphi-Quellcode:
procedure TForm1.GehevorChange(Sender: TObject);
begin
  if Gehevor.Checked = false then
  begin
    Gehevor.Checked:= true;
    exit;
  end;
  Gehezurueck.Checked:= false;
end;

procedure TForm1.GehezurueckChange(Sender: TObject);
begin
  if Gehezurueck.Checked = false then
  begin
    Gehezurueck.Checked:= true;
    exit;
  end;
  Gehevor.Checked:= false;
end;
Ich habe zwei ToggleBoxes welche jeweils abwechselnd Checked bzw. nicht Checked sein sollen. Damit nicht der Fall Auftritt, dass beide Checked oder nicht Checked sind, gibt es die if abfragen welche eine solche Kombination verhüten sollen. Ich hatte die Prozeduren ursprünglich im Change- Event, allerdings nicht beachtet, dass OnChange auch bei Veränderungen durch das Programm ausgelöst wird, sodass es mir gelungen ist, eine "doppelt gewundene Endlosschleife" zu programmieren :lol:, welche irgendwann (nach ca. 1/2 sekunde) in einem Zugriffsfehler endet (Warum?). Jetzt wollte ich die Methoden über den Objektinspektor neu zuweisen, sodass jetzt das Klick- Ereignis diese auslöst. Allerdings besteht nach wie vor der Fehler. Kann es sein, dass Lazarus die alten Verbindungen irgendwie nicht gelöscht hat, obwohl ich sie im Objektinspektor entfernt habe?

Sir Rufo 17. Jan 2015 23:36

AW: verschränke ToggleBoxes
 
Es ist eher simpel zu lösen:

Erstelle dir eine Eigenschaft vom Typ
Delphi-Quellcode:
Boolean
mit einer Setter-Methode:
Delphi-Quellcode:
procedure TForm1.SetStatus( const Value : Boolean );
begin
  if FStatus <> Value then
  begin
    FStatus := Value;
    GeheVor.Checked := FStatus;
    GeheZurueck.Checked := not FStatus;
  end;
end;

procedure TForm1.GehevorChange(Sender: TObject);
begin
  SetStatus( GeheVor.Checked );
end;

procedure TForm1.GehezurueckChange(Sender: TObject);
begin
  SetStatus( not GeheZurueck.Checked );
end;

Perlsau 18. Jan 2015 07:17

AW: verschränke ToggleBoxes
 
Liste der Anhänge anzeigen (Anzahl: 2)
Zitat:

Zitat von TForm1 (Beitrag 1286871)
Ich habe zwei ToggleBoxes welche jeweils abwechselnd Checked bzw. nicht Checked sein sollen. Damit nicht der Fall Auftritt, dass beide Checked oder nicht Checked sind, gibt es die if abfragen welche eine solche Kombination verhüten sollen.

Du hast dann vermutlich in etwa das, was dort im oberen Bildteil angezeigt wird. Das sind CheckBoxen. Das riecht doch irgendwie stark nach TRadioGroup :stupid:

Den Begriff ToggleBox scheint es im Zusammenhang mit Delphi nicht wirklich zu geben. Es gibt aber ToggleButtons, z.B. bei den Jedis (siehe Bild 2).

Dejan Vu 18. Jan 2015 09:16

AW: verschränke ToggleBoxes
 
@Sir Rufo: Rufst Du innerhalb der Klasse immer explizit den Setter der Eigenschaft auf, oder ist das ein Versehen? Ich würde das nie machen. Der Setter ist eigentlich privater als privat und sollte noch nicht einmal innerhalb der Klasse aufgerufen werden.

mkinzler 18. Jan 2015 09:28

AW: verschränke ToggleBoxes
 
Warum ist der Setter privater als privat? Es gibt Sprachen, in denen ist der sogar der einzige Weg auf private Felder zuzugreifen.

Warum sollte man interne Prüfungen auf Wertebereiche, welche im Setter stehen an anderer Stelle wiederholen sollen?

TForm1 18. Jan 2015 09:32

AW: verschränke ToggleBoxes
 
Zitat:

Zitat von Perlsau (Beitrag 1286874)
Den Begriff ToggleBox scheint es im Zusammenhang mit Delphi nicht wirklich zu geben. Es gibt aber ToggleButtons, z.B. bei den Jedis (siehe Bild 2).

Ich weiß nicht, inwiefern sich die VCL von Delphi da von Lazarus unterscheidet, aber bei mir gibt es sehr wohl einen Button vom Typ TToggleBox (warum der nun Box heißt und nicht Button weiß ich nicht). Einfach 2 Radiobuttons wären wohl einfacher gewesen, allerdings gefällt mir das Design nicht, d.h. ich brauche das Ganze in Form von zwei Buttons, oder gibt es eine Möglichkeit die Radiobuttons auch so darstellen zu können?

@SirRufo: Ich möchte nach Möglichkeit sofort die Änderungsinformation an eine weitere Klasse weiterreichen, deswegen wollte ich kein zusätzliches Feld deklarieren und die gesamte Abfrage innerhalb des OnChange bzw. OnClick durchführen.

Was mich immer noch wundert, ist, dass er das Ereignis immer noch als OnChange verwendet, obwohl ich es im Objektinspektor auf OnClick umgestellt habe.

mkinzler 18. Jan 2015 09:35

AW: verschränke ToggleBoxes
 
Zitat:

Was mich immer noch wundert, ist, dass er das Ereignis immer noch als OnChange verwendet, obwohl ich es im Objektinspektor auf OnClick umgestellt habe.
Und hast u die onChange-Property auch geleert?

TForm1 18. Jan 2015 10:08

AW: verschränke ToggleBoxes
 
Ja, alles ist ordnungsgemäß so eingestellt, dass es funktionieren sollte. :roll: Anscheinend gibt es da einen Bug in der Zuweisung der Events im Objektinspektor oder ich mache irgendwas anderes falsch.
Der Fehler tritt auch in der Form auf, dass er die "Schleife" ein paar mal durchrattert und danach den Fehler eines falschen Zugriffs schmeißt. Wisst ihr, warum er das tut?

Sir Rufo 18. Jan 2015 10:17

AW: verschränke ToggleBoxes
 
Zitat:

Zitat von TForm1 (Beitrag 1286892)
@SirRufo: Ich möchte nach Möglichkeit sofort die Änderungsinformation an eine weitere Klasse weiterreichen, deswegen wollte ich kein zusätzliches Feld deklarieren und die gesamte Abfrage innerhalb des OnChange bzw. OnClick durchführen.

Ja und?

Dann packe die Logik doch einfach in diese Klasse oder schalte da einfach eine Klasse davor, die diese Logik innehat. Was man nicht macht, ist die Logik in der Form/View so an direkt mit den Controls zu verkoppeln, wie du das gemacht hast.

@Dejan Vu

Es ging mir nur um das Prinzip, wie man das zusammenfassen kann.

Richtig, richtig, wird das z.B. mit einem ViewModel, wo jeder Button seinen Status auslesen und setzen kann und zwar unabhängig voneinander. Das ViewModel kümmert sich nun intern um die entsprechenden logischen Zusammenhänge und übergibt den resultierenden Wert an die Datenklasse oder wohin auch immer.

ViewModel:
Delphi-Quellcode:
type
  TFooViewModel = class( TViewModelBase )
  private
    FStatus: Boolean;
    FButton2Checked: Boolean;
    FButton1Checked: Boolean;
    procedure SetStatus( const Value: Boolean );
    procedure SetButton1Checked( const Value: Boolean );
    procedure SetButton2Checked( const Value: Boolean );
    function GetButton1Checked: Boolean;
    function GetButton2Checked: Boolean;
  public

    property Button1Checked: Boolean read GetButton1Checked write SetButton1Checked;
    property Button2Checked: Boolean read GetButton2Checked write SetButton2Checked;
  end;

implementation

{ TFooViewModel }

function TFooViewModel.GetButton1Checked: Boolean;
begin
  Result := FStatus;
end;

function TFooViewModel.GetButton2Checked: Boolean;
begin
  Result := not FStatus;
end;

procedure TFooViewModel.SetButton1Checked( const Value: Boolean );
begin
  SetStatus( Value );
end;

procedure TFooViewModel.SetButton2Checked( const Value: Boolean );
begin
  SetStatus( not Value );
end;

procedure TFooViewModel.SetStatus( const Value: Boolean );
begin
  if FStatus <> Value
  then
    begin
      FStatus := Value;
      OnPropertyChanged( ['Button1Checked', 'Button2Checked'] );
    end;
end;
View:
Delphi-Quellcode:
type
  TFooViewForm = class( TBaseForm )
    Switch1: TSwitch;
    Switch2: TSwitch;
    procedure Switch1Switch( Sender: TObject );
    procedure Switch2Switch(Sender: TObject);
  private
    FViewModel: Ref<TFooViewModel>;
  protected
    procedure ViewModelPropertyChanged( Sender: TObject; const e: TPropertyChangedArgs ); override;
  public
    procedure SetViewModel( AViewModel: TViewModelBase ); override;
  end;

var
  FooViewForm: TFooViewForm;

implementation

{$R *.fmx}
{ TFooViewForm }

procedure TFooViewForm.SetViewModel( AViewModel: TViewModelBase );
begin
  FViewModel := AViewModel as TFooViewModel;
  inherited;

end;

procedure TFooViewForm.Switch1Switch( Sender: TObject );
begin
  inherited;
  if FViewModel.IsAssigned
  then
    FViewModel.Reference.Button1Checked := Switch1.IsChecked;
end;

procedure TFooViewForm.Switch2Switch(Sender: TObject);
begin
  inherited;
  if FViewModel.IsAssigned
  then
    FViewModel.Reference.Button2Checked := Switch2.IsChecked;
end;

procedure TFooViewForm.ViewModelPropertyChanged( Sender: TObject; const e: TPropertyChangedArgs );
begin
  inherited;
  if FViewModel.IsAssigned
  then
    begin
      if e.Match( 'Button1Checked' )
      then
        Switch1.IsChecked := FViewModel.Reference.Button1Checked;
      if e.Match( 'Button2Checked' )
      then
        Switch2.IsChecked := FViewModel.Reference.Button2Checked;
    end;
end;

Dejan Vu 18. Jan 2015 10:33

AW: verschränke ToggleBoxes
 
Ich sehe gerade, das im Code die Methode 'SetStatus' als 'Setter' bezeichnet wird, was er ja gar nicht ist, denn wo keine Property, da auch kein Setter. Insofern führt das dann auch mal zu Misverständnissen. Richtig(er) sollte es also sein, eine private Property 'Status' einzuführen. Denn sonst sieht man ja nicht, das 'FStatus' und 'SetStatus' etwas miteinander zu zun haben.
Delphi-Quellcode:
Type
  TFoo = class
    property Status : boolean Read FStatus Write SetStatus;
  end;
Zitat:

Zitat von mkinzler (Beitrag 1286890)
Warum ist der Setter privater als privat? Es gibt Sprachen, in denen ist der sogar der einzige Weg auf private Felder zuzugreifen.

Das glaube ich nicht. Setze einfach die Eigenschaft. Also anstatt 'SetStatus(Foo)' schreibst Du einfach 'Status := Foo'.
Ich kenne keine Programmiersprache, bei der direkte Aufruf des Setters "sogar der einzige Weg" ist, um "auf private Felder zuzugreifen".
Zitat:

Warum sollte man interne Prüfungen auf Wertebereiche, welche im Setter stehen an anderer Stelle wiederholen sollen?
Wot? Was für eine überflüssige Frage. Davon redet keiner. Es geht darum, den Setter nicht explizit aufzurufen, sondern die Eigenschaftszuweisung zu verwenden.

mkinzler 18. Jan 2015 10:39

AW: verschränke ToggleBoxes
 
Zitat:

Ich kenne keine Programmiersprache, bei der direkte Aufruf des Setters "sogar der einzige Weg" ist, um "auf private Felder zuzugreifen".
Von außen schon, in Sprachen ohne Properties wie z.B. Java.

Im Setter kann auch etwas mehr als "nur" die Zuweisung erfolgen, deshalb kann es auch intern sinnvoll sein den Setter aufzurufen.

Will ich sicherstellen, das weitere Contraints erfüllt werden, welche im Setter überprüft werden -> Aufruf Setter

Will ich den Wert auf jedenfall setzen -> direkter Zugriff auf Feld.

Sir Rufo 18. Jan 2015 10:47

AW: verschränke ToggleBoxes
 
Zitat:

Zitat von Dejan Vu (Beitrag 1286904)
Ich sehe gerade, das im Code die Methode 'SetStatus' als 'Setter' bezeichnet wird, was er ja gar nicht ist, denn wo keine Property, da auch kein Setter. Insofern führt das dann auch mal zu Misverständnissen. Richtig(er) sollte es also sein, eine private Property 'Status' einzuführen. Denn sonst sieht man ja nicht, das 'FStatus' und 'SetStatus' etwas miteinander zu zun haben.
Delphi-Quellcode:
Type
  TFoo = class
    property Status : boolean Read FStatus Write SetStatus;
  end;

Ah sehr schön, dass du meinen Text aufmerksam durchgelesen hast:
Zitat:

Zitat von Sir Rufo (Beitrag 1286872)
Erstelle dir eine Eigenschaft vom Typ
Delphi-Quellcode:
Boolean
mit einer Setter-Methode:

Soll ich es größer und bunter gestalten? Soll ich explizit noch dazu schreiben
Zitat:

Ich gehe mal davon aus, dass jeder weiß was eine Eigenschaft ist und wie man die erstellt, so dass diese eine Setter-Methode hat. Darum zeige ich hier jetzt nur die Setter-Methode. Die IDE erstellt zu einer Eigenschaft auch automatisch ein Feld, das gleichlautend wie die Eigenschaft ist, mit einem vorangestellten F.
Also eine Eigenschaft
Delphi-Quellcode:
property Status : Boolean;
bekommt durch die Code-Vervollständigung automatisch ein privates Feld
Delphi-Quellcode:
FStatus: Boolean;
und eine private Setter-Methode
Delphi-Quellcode:
procedure SetStatus( const Value : Boolean );

Sollte ich vorsichtshalber auch noch vermerken "Einatmen - Ausatmen! Und hübsch in dieser Reihenfolge und kontinuierlich!" :wall:

Jetzt mal wieder an die normaldenkende Allgemeinheit (jeder der sich dazu zählt, darf sich angesprochen fühlen)

Um aus diesen Toggle-buttons jetzt einen TriState zu machen, ändert man einfach das ViewModel ab (macht natürlich nur Sinn, wenn diese Information im Daten-Modell auch verwertet werden kann, logisch - aber genau das sind ja die Änderungen, die immer wieder auf einen zukommen) und schwups, ohne eine Änderung an der View, passiert alles wie gewünscht:
Delphi-Quellcode:
type
  TStatus = ( None, Prev, Next );

  TFooViewModel = class( TViewModelBase )
  private
    FStatus: TStatus;
    FButton2Checked: Boolean;
    FButton1Checked: Boolean;
    procedure SetStatus( const Value: TStatus );
    procedure SetButton1Checked( const Value: Boolean );
    procedure SetButton2Checked( const Value: Boolean );
    function GetButton1Checked: Boolean;
    function GetButton2Checked: Boolean;
  public

    property Button1Checked: Boolean read GetButton1Checked write SetButton1Checked;
    property Button2Checked: Boolean read GetButton2Checked write SetButton2Checked;
  end;

implementation

{ TFooViewModel }

function TFooViewModel.GetButton1Checked: Boolean;
begin
  Result := FStatus = TStatus.Prev;
end;

function TFooViewModel.GetButton2Checked: Boolean;
begin
  Result := FStatus = TStatus.Next;
end;

procedure TFooViewModel.SetButton1Checked( const Value: Boolean );
begin
  if GetButton1Checked <> Value
  then
    if Value
    then
      SetStatus( TStatus.Prev )
    else
      SetStatus( TStatus.None );
end;

procedure TFooViewModel.SetButton2Checked( const Value: Boolean );
begin
  if GetButton2Checked <> Value
  then
    if Value
    then
      SetStatus( TStatus.Next )
    else
      SetStatus( TStatus.None );
end;

procedure TFooViewModel.SetStatus( const Value: TStatus );
begin
  if FStatus <> Value
  then
    begin
      FStatus := Value;
      OnPropertyChanged( ['Button1Checked', 'Button2Checked'] );
    end;
end;

TForm1 18. Jan 2015 19:07

AW: verschränke ToggleBoxes
 
Zitat:

Zitat von Sir Rufo (Beitrag 1286901)
Dann packe die Logik doch einfach in diese Klasse oder schalte da einfach eine Klasse davor, die diese Logik innehat. Was man nicht macht, ist die Logik in der Form/View so an direkt mit den Controls zu verkoppeln, wie du das gemacht hast.

Nagut, überzeugt :wink:. Dennoch ist damit mein Problem nicht gelöst, denn es wird immer noch der OnChange aufgerufen, was ja eigentlich bei deinem Code kein Problem darstellen sollte, denn durch die Abfrage
Delphi-Quellcode:
if FStatus <> Value
, sollte bei dem ungewünschten Aufruf der folgende Code einfach übergangen werden. Dennoch tritt bei mir der Fall auf, wenn ich zurück wechseln möchte von FStatus:= false zu true, dass dann aus irgend einem Grund (ich habe keine Ahnung, aus Welchem) trotzdem in den if-Block gegangen wird und somit eine weitere Verkettung sozusagen "rekursiver" Aufrufe erfolgt. :gruebel: Hat jemand eine Idee, warum das passiert?

Im Prinzip könnte ich ja einfach komplett neue Methoden direkt als OnClick anlegen und den alten Code kopieren, aber mich interessiert es zu wissen, warum trotz der neuen Verknüpfung immer noch OnChange aufgerufen wird.

Sir Rufo 18. Jan 2015 19:55

AW: verschränke ToggleBoxes
 
Dann zeig doch mal deinen Code denn nur anhand deiner Beschreibung kann man da nichts finden

TForm1 18. Jan 2015 22:25

AW: verschränke ToggleBoxes
 
...Beim Schreiben viel mir gerade der Fehler auf, wo der Fehler lag...war ein einfacher Tippfehler :roll::wall: Der Code funktioniert jetzt prima:thumb:

Allerdings bleibt jetzt dennoch das Grundübel, nämlich, dass beim OnClick gleichzeitig das OnChange mit aufgerufen wird, obwohl ich das im Objektinspektor geändert habe. Das war eigentlich auch meine Ausgangsfrage. Weiß jemand über einen Bug in Lazarus bescheid, bei dem die Verknüpfungen der Events bei Löschen trotzdem bestehen bleiben?

Sir Rufo 18. Jan 2015 23:03

AW: verschränke ToggleBoxes
 
Ok, letzte Gelegenheit, dann frag einfach nicht mehr, bzw. ich bin solange raus bis:
Zitat:

Zitat von Sir Rufo (Beitrag 1286979)
Dann zeig doch mal deinen Code denn nur anhand deiner Beschreibung kann man da nichts finden


TForm1 19. Jan 2015 17:39

AW: verschränke ToggleBoxes
 
Oh.:shock: Dann habe ich mich wohl nicht verständlich ausgedrückt. Der Code funktioniert jetzt wunderbar, alles kein Problem. Ich kann ihn totzdem noch mal posten, auch wenn es nichts anderes ist, als bereits oben schon:
Delphi-Quellcode:
procedure TForm1.GehevorClick(Sender: TObject); //Diese Methoden sind beide OnClick, ich habe sie zuvor von OnChange auf OnClick geändert. Allerdings werden sie trotzdem immer noch als OnChange aufgerufen, wenn die Eigenschaft Checked der ToggleBox unten* neu zugewiesen wird.
begin
  SetRichtung(Gehevor.Checked);
end;

procedure TForm1.GehezurueckClick(Sender: TObject);
begin
 SetRichtung(not Gehezurueck.Checked);
end;

procedure TForm1.SetRichtung(const aValue: boolean);
begin
  if FRichtung <> aValue then
  begin
    FRichtung:= aValue;
    Gehevor.Checked:= FRichtung; //*Hier werden wieder die Methoden oben aufgerufen, obwohl diese als OnClick ja nicht aufgerufen werden sollten.
    Gehezurueck.Checked:= not FRichtung;
  end;
end;
Der Code funktioniert ohne Fehler und tut was er soll, da die if- Verzweigung davor schützt, dass eine "Endlosschleife" entsteht. Im Prinzip ist es also jetzt nur noch ein Schönheitsfehler, bzw, wie ich vermute Bug in Lazarus, den ich aber auch gerne verstehen würde. Ich habe also im Objektinspektor die Property OnChange geleert und OnClick befüllt und schließlich noch von Hand auf Gehevor/zurückClick umbenannt, dann dürfte die Methode doch nicht bei OnChange aufgerufen werden, oder?

Sir Rufo 19. Jan 2015 19:33

AW: verschränke ToggleBoxes
 
Aha, das ist aber ein völlig normales Verhalten und sollte auch so dokumentiert sein. Eine Änderung der Eigenschaft Checked löst auch einen OnClick-Event aus. Es kann auch genauso gut sein, dass beim Setzen der Eigenschaft Checked immer OnClick gefeuert wird und OnChange nur dann kommt, wenn Changed vorher anders war. Genauso gut kann es sogar (auch wenn es falsch wäre -> Bug) sein, dass beide Events immer gefeuert werden, wenn der Eigenschaft Checked ein Wert zugewiesen wird.

Das genaue Verhalten kann man sich im Quelltest der Komponente aber sehr schön anschauen, dann weiß man was da genau passiert.

TForm1 19. Jan 2015 21:25

AW: verschränke ToggleBoxes
 
Tatsächlich! OnClick wird auch dann ausgelöst, wenn Checked gesettet wird. Irgendwie bin ich davon ausgegangen, dass OnClick logischerweise nur dann ausgelöst wird, wenn man darauf klickt :wink:. Dann hat ja alles seine Richtigkeit, vielen Dank für die Info:thumb:

Und drauf gekommen, mal im Quellcode nachzuschauen bin ich natürlich auch nicht. :wall:


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:05 Uhr.

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