![]() |
TNumberBox Min/Max-Eingabeproblem
Moin,
ich steh gerade auf dem Schlauch: Ich würde gern eine TNumberbox zur Zahleneingabe verwenden und diese gern auf einen Zahlenraum von Min=5..Max=90 beschränken (Mal so als Beispiel). Funktioniert im Prinzip wunderbar, solange ich nur die Up/Down-Buttons benutze. Wenn ich nun aber direkt z.B. 45 eingeben möchte, führt schon die Eingabe der 4 dazu, das die untere Grenze verletzt wird und somit durch 5 ersetzt wird. Mit anderen Worten: Zahlen zwischen 10 und 49 kann ich so nicht direkt eingeben. Mein Wunschverhalten wäre: Die Up/Down-Buttons sind begrenzt, es kann jedoch jede Zahl manuell eingegeben werden, hierbei werden die Grenzen z.B. erst bei OnExit geprüft und ggf. die Eingabe begrenzt. Hat jemand eine Idee, wie ich das mit überschaubarem Aufwand realisieren kann? Schon mal vielen Dank. |
AW: TNumberBox Min/Max-Eingabeproblem
Hallo, ich kenne diese Komponente noch nicht aber aus dem Bauch heraus würde ich eine eigene Prüfung einbinden.
Also min max so einstellen das alles angenommen werden kann. Da ich nicht weiß wie deine GUI mit der Komponente aussieht und wann du dir erwartet hast das etwas passieren soll gebe ich hier ein mini beispiel. Einen Knopf "Go." platzieren und dem OnClick davon dann deine Edit felder prüfen lassen, wenn alles gut ist "machWas()" wenn was aus der reihe hüpft "rotEinfärbenUndFocus()" Ein automatische prüfung ohne Knopf, da würde ich mal gucken ob Komponente ein "OnLeave" oder "OnExit" hat um mich da reinzuklinken. |
AW: TNumberBox Min/Max-Eingabeproblem
Ich habe so was ähnliches für ein Datumsfeld umgesetzt. Die Prüfung erfolgt aber nicht bei der Eingabe, sondern über eine Funktion der Eingabekomponente.
Die wird erst aufgerufen, wenn z.B. Speichern angeklickt wird oder auf eine andere Eingabezeile gewechselt wird. Man kann es auch im OnExit machen, sollte aber dann bei Modalresult = mrCancel die Prüfung übergehen. Konkret habe ich eine Prüfung eingebaut, die beim MouseEnter auf den Speichernschalter im Hint das Ergebnis der Prüfung anzeigt und beim Klicken des Schalters eine Messagebox zeigt und die fehlerhafte Eingabezeile fokussiert. |
AW: TNumberBox Min/Max-Eingabeproblem
Die Wertbegrenzung durch die Up/Down-Buttons würde ich ja gern erhalten.
Mir scheint die ganze Min/Max-Implementation sinnlos zu sein. Um meine eigene Wert-Begrenzung einzubauen, müsste ich wissen, ob der Benutzer auf einen Up/Down-Button gedrückt hat. Leider liefert die Komponente aber auch kein OnUpDownButtonPressed-Ereignis... Da hilft wohl nur, eine eigene Komponente von TNumberBox abzuleiten und lange rumzufummeln, um diese Basisfunktionalität zu erreichen? |
AW: TNumberBox Min/Max-Eingabeproblem
Das ist definitiv ein nicht zu erwartendes und auch nicht sinnvolles Verhalten. Bitte mach dafür doch einen QP-Report auf, damit das behoben werden kann. Die Min/Max-Überprüfung darf einfach nicht nach jedem Tastendruck erfolgen, sondern erst bei Enter oder Verlassen des Controls.
Als Workaround kannst du das AcceptExpressions einschalten und deine Eingabe mit einem + beginnen. Für die Praxis taugt das allerdings nicht. |
AW: TNumberBox Min/Max-Eingabeproblem
Ich würde da die Eingabeprüfung generell erstmal ausschalten und sie verzögert durchführen lassen. Entweder erst, wenn der User OK (oder Save oder was auch immer) klickt, oder durch einen Timer gesteuert. Letzteres ist dabei meine bevorzugte Methode:
Es gibt einen Timer mit z.B. 200 ms Laufzeit. Der wird bei jedem OnChange-Event zuerst gestoppt und dann wieder gestartet, d.h. jeder Tastendruck startet die Zeit wieder von vorne. Im OnTimer-Event wird dann die Eingabeprüfung durchgeführt und falls sie fehlschägt dem User "irgendwie" mitgeteilt. Das "irgendwie" könnte z.B ein Label mit einer Fehlermeldung einblenden, das wieder verschwindet, wenn die Eingabe OK ist. Wenn ich so drüber nachdenke: Eigentlich braucht man den Timer gar nicht. Man kann die Prüfung sofort im OnChange Event durchführen, solange diese Prüfung die Eingabe nicht stört. Der Timer hätte nur den Vorteil, dass der User nicht unnötig eine Fehlermeldung angezeigt bekommt, wenn er mit der Eingabe noch gar nicht fertig ist. |
AW: TNumberBox Min/Max-Eingabeproblem
Zitat:
|
AW: TNumberBox Min/Max-Eingabeproblem
Das Problem ist doch nicht, dass eine "falsche" Eingabe erkannt wird, sondern dass diese dann automatisch "korrigiert" wird. Ein optisches Feedback, dass der aktuelle Wert nicht plausibel ist, halte ich auch für sinnvoll und praktiziere das auch öfter so, indem ein rotes Label eingeblendet und bei gültigem Wert wieder ausgeblendet wird.
|
AW: TNumberBox Min/Max-Eingabeproblem
Zitat:
|
AW: TNumberBox Min/Max-Eingabeproblem
Ich hab eine Helper-Klasse geschrieben, mit der das ganze nun für meine Anwendung gut funktioniert.
Falls jemand dasselbe Problem hat, einfach die Unit einbinden und im FormCreate eine Zeile hinzufügen:
Delphi-Quellcode:
procedure TForm1.FormCreate(Sender: TObject);
begin TNumberBoxHelper.InitializeNumberBoxes(Self); // rekursiv alle TNumberBox'en auf den Class Helper einschwören end;
Delphi-Quellcode:
unit lib_NumberBoxHelper;
// Eine kleine Helper-Klasse für TNumberBox. // 25.11.2021, idontknow // // TNumberBox lässt normalerweise keine Eingabe von Ziffern zu, sobald diese einzeln betrachtet bereits die Min/Max-Grenzen // verletzen. Eine NumberBox mit Min=5 und Max=90 wird den Wert 45 nicht akzeptieren wenn dieser eingetippt wird, weil bereits // die Ziffer 4 die untere Grenze verletzt. // // Diese Helper-Klasse legt bei Aufruf von TNumberBox.Init ein TNumberBoxHelper-Objekt an. // TNumberBox.Min und Max werden daraufhin auf 0 gesetzt, TNumberBox nimmt somit jeden Wert an. // Bei OnExit oder nach Ablauf eines Timers, der gestartet wird, sobald die bisherige Eingabe die Min/Max-Bedingung nicht erfüllt, // wird TNumberBox.Value korrigiert. // // Beispiel für Benutzung, die Numberboxen seien mit OnChange=NumberBoxValueChanged auf dem Form festgelegt: // // procedure TForm1.FormCreate(Sender: TObject); // begin // TNumberBoxHelper.InitializeNumberBoxes(Self); // rekursiv alle TNumberBox'en auf den Class Helper einschwören // end; // // procedure TForm1.NumberBoxValueChanged(Sender: TObject); // var // NumberBox: TNumberBox; // Text: String; // begin // NumberBox := TNumberBox(Sender); // // if NumberBox.Mode = nbmInteger then // Text := Format('NumberBox: %s, LastValue: %d, Value: %d', [NumberBox.Name, NumberBox.PreviousValueInt, NumberBox.ValueInt]); // // if NumberBox.Mode = nbmFloat then // Text := Format('NumberBox: %s, LastValue: %.2f, Value: %.2f', [NumberBox.Name, NumberBox.PreviousValueFloat, NumberBox.ValueFloat]); // // Memo1.Lines.Add(Text); // end; // // Achtung: TNumberBox.Tag (von TControl geerbt) brauche ich blöderweise in meiner Application auch. // Hier wird Tag verwendet (weil ein class helper keine neuen Felder haben kann), um einen Zeiger auf das TNumberBoxHelper-Objekt // zu haben. // Lösung: Ich verwende überall TControl(Self).Tag zum Zugriff auf den "TNumberBoxHelper-Objekt-Zeiger" // und ein neu eingeführtes Property TNumberBoxHelperClass.Tag zum Zugriff auf TNumberBoxHelper.Tag. // Nach aussen gibt es somit weiterhin ein frei verwendbares Tag-Property. interface uses System.Classes, Vcl.Controls, Vcl.NumberBox, Vcl.ExtCtrls, System.Math; const DetermineUpDownIntervalms = 100; type TNumberBoxHelper = class(TControl) // damit die Objekte automatisch aufgeräumt werden... private MinValue, MaxValue, Value, PreviousValue: Extended; // Value und PreviousValue sind gültige Werte Input: Extended; // Input kann eine ungültige Eingabe ausserhalb des MinMaxRange sein Tag: NativeUint; Changed: Boolean; LastKeyPressedAt: Int64; onChange: TNotifyEvent; onEnter: TNotifyEvent; onExit: TNotifyEvent; public class procedure InitializeNumberBoxes(WinControl: TWinControl); end; TNumberBoxHelperClass = class helper for TNumberBox private // NumberBoxHelper: TNumberBoxHelper; leider keine Felder in class helper, daher verwende ich TNumberBox.Tag class var ValidationTimer: TTimer; procedure ValidationTimerElapsed(Sender: TObject); procedure CreateValidationTimer; function GetMaxValue: Extended; function GetMinValue: Extended; function GetTag: NativeUInt; // Beschafft Tag aus TNumberBoxHelper, darin steht das ursprüngliche TNumberBox.Tag procedure SetMaxValue(const Value: Extended); procedure SetMinValue(const Value: Extended); procedure SetTag(const Value: NativeUInt); procedure DoValidateChar(AChar: Char; var AValidated: Boolean); public procedure Init; procedure DoEnterOrChange(Sender: TObject); procedure DoChange(Sender: TObject); procedure DoEnter(Sender: TObject); procedure DoExit(Sender: TObject); function Correct: Boolean; function PreviousValueInt: Integer; function PreviousValueFloat: Extended; class procedure ForceCorrection; published property Tag: NativeUInt read GetTag write SetTag; property MinValue: Extended read GetMinValue write SetMinValue; property MaxValue: Extended read GetMaxValue write SetMaxValue; end; implementation class procedure TNumberBoxHelper.InitializeNumberBoxes(WinControl: TWinControl); var i: Integer; Control: TControl; begin for i := 0 to WinControl.ControlCount-1 do begin Control := WinControl.Controls[i]; if Control.InheritsFrom(TNumberBox) then TNumberBox(Control).Init else if Control.InheritsFrom(TWinControl) then InitializeNumberBoxes(TWinControl(Control)); end; end; { TNumberBoxHelperClass } procedure TNumberBoxHelperClass.DoValidateChar(AChar: Char; var AValidated: Boolean); var NumberBoxHelper: TNumberBoxHelper; begin // Das ist hier der Weg rauszufinden, ober der User eine Zahl eintippt oder die Up/Down-Buttons verwendet... AValidated := TRUE; NumberBoxHelper := TNumberBoxHelper(TControl(Self).Tag); NumberBoxHelper.LastKeyPressedAt := Int64(TThread.GetTickCount64); end; procedure TNumberBoxHelperClass.DoExit(Sender: TObject); var NumberBox: TNumberBox; begin if not Assigned(ValidationTimer) then CreateValidationTimer; ValidationTimer.Tag := 0; ValidationTimer.Enabled := FALSE; NumberBox := TNumberBox(Sender); NumberBox.Correct; end; procedure TNumberBoxHelperClass.CreateValidationTimer; begin ValidationTimer := TTimer.Create(Self); ValidationTimer.OnTimer := ValidationTimerElapsed; ValidationTimer.Tag := 0; ValidationTimer.Enabled := FALSE; end; procedure TNumberBoxHelperClass.DoChange(Sender: TObject); begin DoEnterOrChange(Sender); end; procedure TNumberBoxHelperClass.DoEnter(Sender: TObject); var NumberBox: TNumberBox; NumberBoxHelper: TNumberBoxHelper; begin NumberBox := TNumberBox(Sender); NumberBoxHelper := TNumberBoxHelper(TControl(NumberBox).Tag); if Assigned(NumberBoxHelper.onEnter) then NumberBoxHelper.onEnter(Sender); DoEnterOrChange(Sender); end; procedure TNumberBoxHelperClass.DoEnterOrChange(Sender: TObject); var NumberBox: TNumberBox; NumberBoxHelper: TNumberBoxHelper; begin if not Assigned(ValidationTimer) then CreateValidationTimer; ValidationTimer.Enabled := FALSE; ValidationTimer.Tag := NativeUInt(Sender); NumberBox := TNumberBox(Sender); NumberBoxHelper := TNumberBoxHelper(TControl(NumberBox).Tag); if SameValue(Abs(NumberBox.Value - NumberBoxHelper.Value), NumberBox.SmallStep) or SameValue(Abs(NumberBox.Value - NumberBoxHelper.Input), NumberBox.SmallStep) then begin // wenn der Unterschied +- SmallStep beträgt UND die letzte Tastatureingabe in das Eingabefeld länger als ~100ms her ist, // dann wurde mit hoher Wahrscheinlichkeit eine Up/Down-Taste gedrückt -> Wert sofort korrigieren if ((Int64(TThread.GetTickCount64) - NumberBoxHelper.LastKeyPressedAt) > DetermineUpDownIntervalms) then begin NumberBox.Correct; NumberBoxHelper.Input := NumberBox.Value; end; end else begin NumberBoxHelper.Input := NumberBox.Value; // speichert jede (auch ungültige) Eingabe, die vielleicht gleich durch Up/Down inkrementiert/dekrementiert wird. ValidationTimer.Enabled := TRUE; end; end; function TNumberBoxHelperClass.Correct: Boolean; var NumberBoxHelper: TNumberBoxHelper; begin NumberBoxHelper := TNumberBoxHelper(TControl(Self).Tag); if Value < NumberBoxHelper.MinValue then Value := NumberBoxHelper.MinValue else if Value > NumberBoxHelper.MaxValue then Value := NumberBoxHelper.MaxValue; Result := NumberBoxHelper.Value <> Value; NumberBoxHelper.Changed := NumberBoxHelper.Changed or Result; NumberBoxHelper.Value := Value; if NumberBoxHelper.Changed then begin if Assigned(NumberBoxHelper.onChange) then NumberBoxHelper.onChange(Self); NumberBoxHelper.PreviousValue := NumberBoxHelper.Value; NumberBoxHelper.Changed := FALSE; end; end; procedure TNumberBoxHelperClass.ValidationTimerElapsed(Sender: TObject); begin ForceCorrection; end; class procedure TNumberBoxHelperClass.ForceCorrection; var NumberBox: TNumberBox; begin // wird von ValidationTimerElapsed aufgerufen oder kann vom Benutzer aufgerufen werden. // Vom Benutzer normalerweise nicht nötig, weil onExit ebenfalls ForceCorrection aufruft. if ValidationTimer.Enabled then begin ValidationTimer.Enabled := FALSE; NumberBox := TNumberbox(ValidationTimer.Tag); NumberBox.Correct; end; end; procedure TNumberBoxHelperClass.Init; var NumberBoxHelper: TNumberBoxHelper; begin NumberBoxHelper := TNumberBoxHelper.Create(Self); // Die Min/Max-Werte im NumberBoxHelper ablegen... NumberBoxHelper.MinValue := TCustomNumberBox(Self).MinValue; NumberBoxHelper.MaxValue := TCustomNumberBox(Self).MaxValue; NumberBoxHelper.Value := Value; NumberBoxHelper.PreviousValue := Value; NumberBoxHelper.Input := Value; NumberBoxHelper.Changed := FALSE; NumberBoxHelper.onEnter := OnEnter; NumberBoxHelper.onExit := OnExit; NumberBoxHelper.onChange := OnChange; Self.onValidateChar := DoValidateChar; NumberBoxHelper.Tag := TControl(Self).Tag; // Tag in Sicherheit bringen... TControl(Self).Tag := NativeUint(NumberBoxHelper); // und ab jetzt intern verwenden... Das Ursprungs-Tag mit TNumberBox.getTag ermitteln. OnEnter := nil; OnChange := nil; OnExit := nil; // ... und hier auf 0 setzen, damit künftig jeder Wert bei der Eingabe akzeptiert wird TCustomNumberBox(Self).MinValue := 0; TCustomNumberBox(Self).MaxValue := 0; NumberBoxHelper.LastKeyPressedAt := Int64(TThread.GetTickCount64) - 1000; Correct; // Den Wert ggf. schon mal in die Begrenzung fahren, jedoch ohne OnChange. OnEnter := DoEnter; OnChange := DoChange; OnExit := DoExit; end; function TNumberBoxHelperClass.PreviousValueInt: Integer; var NumberBoxHelper: TNumberBoxHelper; begin NumberBoxHelper := TNumberBoxHelper(TControl(Self).Tag); Result := Round(NumberBoxHelper.PreviousValue); end; function TNumberBoxHelperClass.PreviousValueFloat: Extended; var NumberBoxHelper: TNumberBoxHelper; begin NumberBoxHelper := TNumberBoxHelper(TControl(Self).Tag); Result := NumberBoxHelper.PreviousValue; end; procedure TNumberBoxHelperClass.SetMaxValue(const Value: Extended); var NumberBoxHelper: TNumberBoxHelper; begin NumberBoxHelper := TNumberBoxHelper(TControl(Self).Tag); NumberBoxHelper.MaxValue := Value; end; procedure TNumberBoxHelperClass.SetMinValue(const Value: Extended); var NumberBoxHelper: TNumberBoxHelper; begin NumberBoxHelper := TNumberBoxHelper(TControl(Self).Tag); NumberBoxHelper.MinValue := Value; end; procedure TNumberBoxHelperClass.SetTag(const Value: NativeUInt); var NumberBoxHelper: TNumberBoxHelper; begin NumberBoxHelper := TNumberBoxHelper(TControl(Self).Tag); NumberBoxHelper.Tag := Value; end; function TNumberBoxHelperClass.GetMaxValue: Extended; var NumberBoxHelper: TNumberBoxHelper; begin NumberBoxHelper := TNumberBoxHelper(TControl(Self).Tag); Result := NumberBoxHelper.MaxValue; end; function TNumberBoxHelperClass.GetMinValue: Extended; var NumberBoxHelper: TNumberBoxHelper; begin NumberBoxHelper := TNumberBoxHelper(TControl(Self).Tag); Result := NumberBoxHelper.MinValue; end; function TNumberBoxHelperClass.GetTag: NativeUInt; var NumberBoxHelper: TNumberBoxHelper; begin // weil wir TNumberBox.Tag zum Speichern des NumberBoxHelper verwenden... NumberBoxHelper := TNumberBoxHelper(TControl(Self).Tag); // gibt es stattdessen ein Tag-Feld im NumberBoxHelper... Result := NumberBoxHelper.Tag; end; end. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:56 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