Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - Edit-Feld nur Zahlen und EIN Komma


GKar - Di 07.06.05 09:02
Titel: Edit-Feld nur Zahlen und EIN Komma
Hallo, Leute,

bin neu in der Gegend...

Ich versuche gerade mir selber Delphi beizubringen... Ich möchte ein Feld, in welches ich via Tastatur nur Zahlen und EIN ',' eingeben kann. Das klappt alles auch schon, bis auf nur EIN ','. Könnt Ihr mir Bitte helfen, warum meine Kommaschleife nicht funzt??


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
unit Eingabe;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Edit1: TEdit;
    procedure Edit1Change(Sender: TObject);
    function Zahlen(a:LongInt):real;
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;
implementation

{$R *.dfm}

procedure TForm1.Edit1Change(Sender: TObject);
var a:Real;
begin
  a := Zahlen(LENGTH(Edit1.Text));
  Label2.Caption:=FloatToStr(a);
end;

function TForm1.Zahlen(a:LongInt):Real;
var b,Pos1:LongInt;
    Text:string;
begin
  b :=  1;
  Pos1  :=  0;
  Text  :=  Edit1.Text;

  while(b<=a)  do
  begin
    if(Text[b]  = ','then
    begin
      Pos1 :=  b;
      inc(b);
      continue;
    end;
    if((Text[b] = ','and (b <>  Pos1))  then
    begin
      DELETE(Text,b,2);
    end;
  inc(b);
  end;

  b :=  1;
(*Hier ist wohl noch ein Wurm drin...*)
  while(b<=a) do
    begin
      if((a>0and (Text[b]<='9'and (Text[b]>='0')  or (Text[b]=',')) then
      begin
        Text  :=  Edit1.Text;
      end
      else
      begin
        Text  :=  Label1.Caption;
        Edit1.Text  :=  Label1.Caption;
      end;
    inc(b);
  end;

  if(a>25then
    begin
      Text  :=  Label1.Caption;
      Edit1.Text  :=  Text;
  end;

  Label1.Caption  :=  Text;

  if(Text=''then result:=0.0
  else  result  :=  StrToFloat(Text);

end;

end.


Vielen Dank, Leute...
Gruß

Moderiert von user profile iconTino: Code- durch Delphi-Tags ersetzt.
Moderiert von user profile iconTino: Titel geändert.


mehmeh - Di 07.06.05 09:15

na ja so auf den ersten Blick würde ich sagen:
Probiers mal die funktion vor der procedure zu schreiben.


Heiko - Di 07.06.05 09:20

Warum einfach, wenn es auch schwer geht :wink: .


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
 if (pos(',', Edit1.Text)) or (not (Key in ['0'..'9'','#8])) then
  begin
   Key:=#0
  end
end;


GKar - Di 07.06.05 11:03

Danke, Heiko!

Kannst Du mir nur kurz erklären, was diese pos() und Key #8 bzw. #0 bedeuten? Bin doch noch anfänger...

Ansonsten probiere ich das später (sitze gerade nicht daheim) noch aus. (Und schlage mich durch die Hilfethemen...).

Gruß


jasocul - Di 07.06.05 11:09

Für die Pos-Funktion liest du hier [http://www.jasocul.de/html/stringverarbeitung.html#PosFunktion].
#8 entspricht der "Tab"-Taste. Die sollte auch zugelassen sein, damit du mit der Tastatur im Programm arbeiten kannst.
#0 "neutralisiert" die eingegebene Taste.


Heiko - Di 07.06.05 11:17

user profile iconjasocul hat folgendes geschrieben:
#8 entspricht der "Tab"-Taste. Die sollte auch zugelassen sein, damit du mit der Tastatur im Programm arbeiten kannst.

FALSCH!!! #8 ist die Backspace-Taste. Die Tab-Taste ist #9 :tongue: und in OnKeyPress-Ereignis kann man die Tab-Taste nicht abfangen, wodurch du ohne Probleme im Programm weiterarbeiten kannst, wenn #9 theroretisch blockiert werden müsste. Die Tab-Taste-Abzufangen ist nicht das leichteste.


jasocul - Di 07.06.05 11:19

Ich schäme mich jetzt ganz doll :oops:
Stimmt natürlich mit BackSpace. Sorry.


Heiko - Di 07.06.05 11:24

Wieder einer der fast :flehan: vor Scham vor mir :mrgreen: :tongue: .


alzaimar - Di 07.06.05 11:45

Die einfachste Art, einen String darauf zu testen, ob er eine gültige Real-Zahl enthält ist immernoch StrToFloat. Das kann ich doch im OnChange-Event des Edit-Controls verwenden:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
procedure TForm1.Edit1Change(Sender: TObject);
Var
  d,s: String;
  p : Integer;
  cEdit : TEdit;

begin
  cEdit := Sender as TEdit;
  cEdit.OnChange := Nil// Endlosschleifen vermeiden
  p := cEdit.SelStart; // Cursorposition merken
  Try
    If DecimalSeparator =',' Then d := '.' else d := ',';
    s := StrReplace (cEdit.Text+'0',d,DecimalSeparator); // ',' <--> '.' und "1," -> "1,0"
    Try
      StrToFloat (s);     // steht dort eine gültige Zahl ? 
      SetLength (s, Length (s) - 1); // die letzte '0' wieder rausnehmen
      fLastValidEditText := s; // Das ist korrekt, also merken
    Except
// Irgendwas ist schiefgelaufen, wir ignorieren das...
      End;
  Finally
    cEdit.Text := fLastValidEditText; // Der korrekt diagnostizierte Eingabewert
    cEdit.SelStart := p;  // Cursorposition wieder setzen
    cEdit.OnChange := Edit1Change; // Event wieder setzen (kann überflüssig sein, aber sicher ist sicher)
    End;
end;

fLastValidEditText ist ein privateds TForm1 Feld. Dann noch im Edit1.OnEnter das hier, und schon läuft es

Delphi-Quelltext
1:
2:
3:
4:
procedure TForm1.Edit1Enter(Sender: TObject);
begin
  fLastValidEditText := Edit1.Text;
end;


Heiko - Di 07.06.05 11:55

Ich finde Tastenüberwachung wesentlich besser, auch für den User de3s Programms. Außerdem wie willst du mit StrToFloat überprüfen ob eine gültige Zahl eingegeben ist? An der Fehlrmeldung etwa?

Wenn du es nicht mit Tastenüberwachung machen willst nimm lieber
function TryStrToFloat(const S: string, out Value: Extended): Boolean;anstatt StrToFloat :wink: .


Delete - Di 07.06.05 12:04

Ich würde val nehmen und den Rückgabecode auswerten, da erspart man sich die Exception, da eine Exception immer einen ziemlichen Overhead im Code produziert. Desweiteren könnte man beim OnChange prüfen, ob schon ein Komma eingegeben wurde und dann jedes weitere irgnorieren.


alzaimar - Di 07.06.05 12:07

Zitat:
Außerdem wie willst du mit StrToFloat überprüfen ob eine gültige Zahl eingegeben ist? An der Fehlrmeldung etwa?

Deine Frage verstehe ich nicht. Wenn StrToFloat keine Exception liefert, war der übergebene String wohl oder übel ein Float, oder nicht? Natürlich erkenne ich an der Fehlermeldung, das der String wohl kein Float war.

TryStrToFloat ginge auch. Oder StrToIntDef. Ich arbeite aber mit Exceptions als Mittel der Programmflusskontrolle. Das erhöht die Übersichtlichkeit, ist aber Geschmacksache.

Die Tastenüberwachung geht nicht so einfach, weil du ja dann den Editor simulieren musst:
Sei Edit.Text = '123' und Key = '-'. Erlauben wir das, oder nicht?
Hmm, hängt doch von der Cursorposition und dem Einfügemodus (insert/overwrite) ab.

Bevor Du sagst, das es mit OnKeyPress besser geht, poste doch mal die Alternative.


MrFox - Di 07.06.05 12:51

TMaskEdit ???


alzaimar - Di 07.06.05 13:01

Geht auch einigermassen, aber man bekommt syntaktisch korrekte Realzahlen mit der Maskenbeschreibung nicht hin.


hansa - Di 07.06.05 14:30

Warum denn überhaupt mit Try/except, wenn solche Fehler von vorneherein ausgeschlossen werden könen ?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
  ZulZeichen := ['0'..'9'];
...
  if (Key in ZulZeichen) then begin
    if Pos(DecimalSeparator, Text) = 0 then  // einen . zulassen
      ZulZeichen := ZulZeichen + [DecimalSeparator]
    else
      ZulZeichen := ZulZeichen - [DecimalSeparator]; // zweiten .  verhindern
    if not (Key in ZulZeichen) then
      Key := #0;
  end;


Der User kann dann unmöglich ein zweites Trennzeichen eingeben !! Vorbeugen ist nun mal besser als heilen. :mrgreen:


alzaimar - Di 07.06.05 14:54

@hansa: Das mit Try..Except bzw. gleich Val oder TryStrToFloat ist schon klar.

Zu deiner Idee: Was ist mit Vorzeichen? Und Exponenten? Deine Version erlaubt auch diese Eingaben:

Ich denke, es ging dem Fragesteller um reele Zahlen, dazu gehören auch Vorzeichenbehaftete und Zahlen mit Exponenten, aber nicht die eben Erwähnten. Das sind ja keine Zahlen.

Zitat:

Der User kann dann unmöglich ein zweites Trennzeichen eingeben !!

Ein erstes Trennzeichen kann er auch unmöglich eingeben :mrgreen:, weil ZulZeichen falsch initialisiert wurde. Damit überhaupt etwas bezüglich des '.' (oder ',') geprüft werden kann, muss der Dezimaltrenner zunächst in den ZulZeichen enthalten sein.

Und die Frage, ob nun heute mal '.' oder ',' das Dezimaltrennzeichen ist, sollte man auch nicht dem Anwender überlassen. Also, kürzen wir nochmal, damit alle berechtigten Einwände aus der Welt sind und wir erhalten:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
procedure TForm1.Edit1Change(Sender: TObject);
Var
  d,s: String;
  p : Integer;
  cEdit : TEdit;
  fDummy : Extended;

begin
  cEdit := Sender as TEdit;
  p := cEdit.SelStart; // Cursorposition merken
  If DecimalSeparator =',' Then d := '.' else d := ',';
  s := StrReplace (cEdit.Text+'0',d,DecimalSeparator); // ',' <--> '.' und "1," -> "1,0"
  If TryStrToFloat (s, fDummy) Then Begin  // steht dort eine gültige Zahl ? 
    SetLength (s, Length (s) - 1);         // die letzte '0' wieder rausnehmen
    fLastValidEditText := s;               // Das ist korrekt, also merken
    End;
  cEdit.Text := fLastValidEditText;        // Der korrekt diagnostizierte Eingabewert
  cEdit.SelStart := p;                     // Cursorposition wieder setzen
End;

Dieser Code akzeptiert einfach alle gültigen reellen Zahlen. Ich weiss nich... find ich einfacher.


jasocul - Di 07.06.05 15:19

Vorschlag:
Nehmt doch einfach eine existierende Komponente aus der Jedi-Bibliothek. Man muss das Rad doch nicht immer wieder neu erfinden.

@Hansa:
Ich weiß, dass du Fremd-Komponenten nicht magst. Das müssen wir nicht neu diskutieren. :wink:


alzaimar - Di 07.06.05 15:38

Werden da nicht gleich 170.000 andere Komponenten mit installiert? Ansonsten hast du vollkommen Recht. Ich persönlich mag nämlich 90% der Jedi-Teile nicht. Aber der Rest wäre für mich absolut brauchbar.


jasocul - Di 07.06.05 16:19

Ist zwar etwas OT, aber trotzdem meine Meinung dazu:
Stimmt leider.
Es ist eine Komponentensammlung, in der viele andere Komponentensammlungen aufgenommen wurden (z.B. rxLib). Dummerweise weiß man erst hinterher, welche Komponenten man nicht hätte mitinstallieren sollen. :wink:
Man kann aber bei der Installation durchaus entscheiden, was man haben möchte und was nicht.


hansa - Di 07.06.05 20:28

user profile iconalzaimar hat folgendes geschrieben:
Zu deiner Idee: Was ist mit Vorzeichen? Und Exponenten? Deine Version erlaubt auch diese Eingaben:
    ","
    "0,"


Ein erstes Trennzeichen kann er auch unmöglich eingeben :mrgreen:, weil ZulZeichen falsch initialisiert wurde. Damit überhaupt etwas bezüglich des '.' (oder ',') geprüft werden kann, muss der Dezimaltrenner zunächst in den ZulZeichen enthalten sein.

Und die Frage, ob nun heute mal '.' oder ',' das Dezimaltrennzeichen ist, sollte man auch nicht dem Anwender überlassen.


Von dem hier zitierten stimmt aber nicht viel. 8) Gut Du kannst auch nicht ales wissen. Eingaben wie "0," werden mit Nullen aufgefüllt. Die Anzahl der Nachkommastellen stelle ich hierbei im OI ein. Es würde also nach verlassen des Feldes z.B. "0,00" drin stehen. Führende "," "-" usw. sind nicht erwünscht. Wäre aber wohl kein Problem, bei einem "," am Anfang daraus "0," zu machen. Sobald ein gültiges Zeichen vorhanden ist, kann doch der Decimalseparator eingegeben werden. Wieso denn nicht :?: Aber nur der, wie er auch Windows bekannt ist. Das überlasse ich doch nicht dem Anwender. :shock:

Für mich bleibt da als Rechtfertigung von Try/Except nur noch die wissenschaftliche Schreibweise übrig. Aber wer braucht die schon, um Eingaben zu machen ?

Und @Jasocul : ja, das ist in der Tat ein Teil einer eigenen Komponente. Aber wieso mag ich keine Fremdkomponenten ? Die sind mir nur lediglich egal, sofern ich in der Lage bin mit vertretbarem Aufwand selber eine zu machen. Allerdings gibt es momentan tatsächlich handfeste Argumente gegen den Einsatz derselben. Ich sage nur .NET. Wielange dauert die Portierung einer Fremdkomponente ? Wird das überhaupt in Angriff genommen ? Wie weit ist z.B. Jedi.NET ? Wer weiß. Zur Zeit ist alles sehr ungewiss. Siehe Schicksal von Turbopower. Für den hier angesprochenen Zweck sind die Jedi-Ritter allerdings eher wie Atombomben auf Spatzen zu werfen. :lol:


jasocul - Di 07.06.05 20:47

user profile iconhansa hat folgendes geschrieben:
Und @Jasocul : ja, das ist in der Tat ein Teil einer eigenen Komponente. Aber wieso mag ich keine Fremdkomponenten ? Die sind mir nur lediglich egal, sofern ich in der Lage bin mit vertretbarem Aufwand selber eine zu machen. Allerdings gibt es momentan tatsächlich handfeste Argumente gegen den Einsatz derselben. Ich sage nur .NET. Wielange dauert die Portierung einer Fremdkomponente ? Wird das überhaupt in Angriff genommen ? Wie weit ist z.B. Jedi.NET ? Wer weiß. Zur Zeit ist alles sehr ungewiss. Siehe Schicksal von Turbopower. Für den hier angesprochenen Zweck sind die Jedi-Ritter allerdings eher wie Atombomben auf Spatzen zu werfen. :lol:

Ich hatte nur den Eindruck gewonnen, dass du lieber alles selbst machst, bevor du etwas "fremdes" anfasst. :D
Bei den Portierungen hast du das selbe Problem mit eigenen Komponenten. Und wenn etwas sofort mit .NET rauskommt, würde ich mirt über die Stabilität echt Gedanken machen.
Ich benutze übrigens für numerische Eingaben die Komponente TjvCalcEdit.


hansa - Di 07.06.05 22:19

@Mods : das ist zwar jetzt auch ziemlich OT, aber es ist wichtig ! Bitte jetzt nicht aus dem Zusammenhang reißen und verschieben !

user profile iconjasocul hat folgendes geschrieben:
...Ich hatte nur den Eindruck gewonnen, dass du lieber alles selbst machst, bevor du etwas "fremdes" anfasst. :D
Bei den Portierungen hast du das selbe Problem mit eigenen Komponenten...


Der Eindruck stimmt ja auch, sofern es denn ohne zuviel Aufwand geht. Das ist ein entscheidender Unterschied ! Meine Erfahrungen sind die folgenden : den Schwierigkeits-grad für Komponentenentwicklung stufe ich als sehr hoch ein. Gute und brauchbare Infos sind sehr, sehr selten zu finden. Soviel vorab für diejenigen, die denken, ohne Vorkenntnisse in 5 Min. eine eigene Komponente entwickeln zu können.

Was ist nun zu tun ? Dazukaufen oder sich das Ganze selber beibringen ? Lassen wir mal das Geld außen vor. Der Aufwand das selber zu lernen ist, wie gesagt, hoch. Aber der Aufwand eine Fremdkomponente richtig zu beherschen ist auch nicht zu unterschätzen. Bei letzterem kommt noch folgendes dazu : die Vorgaben sind bekannt und man muß was suchen, das paßt. Einfach ist das nicht und zeitraubend noch dazu. Ich hatte irgendwann für solche Sachen wie hier gefragt, keinen Nerv mehr. Irgendwie waren die fertigen Komponenten nie das richtige und zudem total überfrachtet. Wenn überhaupt dabei, so ist es äußerst schwierig sich in fremden Source einzuarbeiten. Oder zu umfangreich, wie die Jedis, um schnell mal was passendes zu finden. Allerdings ist es bei jedem anders. Mir war von Anfang an klar, daß ich fast das ganze von Delphi abgedeckte Spektrum beherrschen mußte. Da gehören auch eigene Komponenten dazu. Irgendwann wäre das sowieso gekommen. Ja und deshalb habe ich so eine "leichte" Sache wie die hier genommen um das mal bis zum bitteren Ende durchzuziehen.

Insgesamt hat das jetzt alles (trotz dem Zeitaufwand) eine Reihe von Vorteilen : ich weiß was ein package ist und wo was hin muß. Natürlich habe ich auch Fremdkomponenten. Bei den eigenen weiß ich genau wo was im Fall der Fälle im Source zu ändern ist. Wie ich das in die Komponentenpalette packe und dort auch ein eigenes Icon hinkriege. Die Umstellung auf .NET wird IMHO nicht viel werden, ich brauche aber nicht auf andere Leute zu warten und mache das so wie ich will. Vor Longhorn wird in der Richtung allerdings kaum was passieren.

Und jetzt noch das führende "," mit automatischer Null. Ich habe das ganze natürlich OOP-mäßig gemacht. Deshalb die Variable ZulZeichen. Das wird für string, integer und real eben immer so besetzt, wie es sinnvoll ist. Ab integer wurde z.B. auch rechtsbündige Eingabe eingeführt. Insofern bräuchte ich nur das OnKeyPress im RealEdit daraufhin anzupassen und schon hätten alle Real-Felder diese Eigenschaft. Man versuche so was mal bei einer Fremdkomponente zu machen. 8)


GKar - Mi 08.06.05 07:28

Also,

jetzt melde ich mich wieder zurück...

Habe hier ja ganzschön was losgetreten.

Inzwischen habe ich das doch anders lösen können, ich poste den Code später mal hier, dann bitte ich Euch, mir als Neuling vielleicht ein ppar kritiken (gutgemeinte) dazuzugeben, oke?!

Jetzt kam da der Begriff "Jedi-Bibliothek" auf. Was ´n des? :shock:

Gruß


alzaimar - Mi 08.06.05 07:53

user profile iconhansa hat folgendes geschrieben:

Eingaben wie "0," werden mit Nullen aufgefüllt. Die Anzahl der Nachkommastellen stelle ich hierbei im OI ein.

Wo denn? In Delphi? Bei TEdit? Gibt's nicht.

user profile iconhansa hat folgendes geschrieben:

Führende "," "-" usw. sind nicht erwünscht. Wäre aber wohl kein Problem, bei einem "," am Anfang daraus "0," zu machen.

Ich glaube, es geht hier um ein TEdit, das die Eingabe reeller Zahlen ermöglichen soll. Und wenn nicht, habe ich eine Lösung präsentiert, die das kann. Zu den reellen Zahlen gehören auch nun mal auch Vorzeichenbehaftete und Angaben der 10er Potenz (1,23E+34).
Das man Deine Lösung noch abändern kann, steht außer Frage. Aber so, wie sie dasteht, funktioniert sie erstens nicht und ist zweitens eben unvollständig. Und mit allen (selbstverständlich einfach zu implementierenden) Änderungen und Erweiterungen erscheint Deine Lösung ziemlich kompliziert (an Codegröße gemessen).

Zu Der Frage, ob der Anwender wissen sollte, ob nun der Punkt oder das Komma der Dezimalseparator sein soll, verweise ich Dich auf den Dezimalpunkt auf der numerischen Tastatur. Jeder Datentypist wird Dir mit deiner Einstellung eins Husten, wenn Du ihm sagst, das in der deutschen Version der Dezimalpunkt eben ein Dezimalkomma ist und er deshalb bitte auf die komfortable Eingabe via numerischer Tastatur verzichten muss.

user profile iconhansa hat folgendes geschrieben:

Das überlasse ich doch nicht dem Anwender. :shock:

Eben, dafür sorge ich im Code.

Obwohl Du mit der Prämisse, Probleme zu vermeiden, anstatt Sie nachträglich zu lösen, Recht hast, passt das hier (leider) nicht. Wenn ich eine Art TFloatEdit haben will, lande ich IMHO mit deinem Ansatz (OnKeyPress) in einer Sackgasse (Siehe Oben). Mein Vorschlag, das mit TryFloatToStr zu machen, ist auch nicht perfekt, hier wäre ein DEA angebracht. Mir ging es um eine Möglichkeit, die vorhandenen Mittel zu nutzen, um zum Ziel zu kommen. Eure Einwände bezügl. des Try...Except sind einfach korrekt. Aber dazu ist so ein Forum ja da: Dazulernen und Erfahrungen austauschen.

Ich meine, eine Kombination aus OnKeyPress und OnChange (bzw. über einen Validierer) wäre stilistisch ideal: OnKeyPress lässt nur legale Zeichen zu, während der Validierer die Syntax überprüft.

@GKar: Die Jedi-Bibliotheken gibt es hier http://www.delphi-jedi.org/