Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - Ereignisbehandlung bei Wertzuweisung (eigene Komponente)


Kay E. - Fr 10.12.10 11:58
Titel: Ereignisbehandlung bei Wertzuweisung (eigene Komponente)
Hallo!

Ich habe eine kleine Komponente von TLabel abgeleitet, die die eingegebenen Zeichen beim verlassen des Feldes formatiert. Es werden dann nur positive Zahlen mit einer festgelegten Anzahl an Nachkommastellen dargestellt. Das behandle ich sinnigerweise in einem OnExit.
Jetzt hab ich das Problem, dass wenn ich dem Feld einen Wert programmseitig zuweise, dieser OnExit-Befehl nicht ausgeführt wird. Ich seh jetzt zwei offensichtliche Möglichkeiten. 1. ich schreib nach der Wertzuweisung ein explizites Komponente.Exit; was ich aber nicht möchte. Andere Komponenten bekommen das auch intern geregelt, daher möchte ich nicht meinen Code im Programm unnötig zumüllen. 2. ich schreib in das OnChange nen Verweis auf das OnExit, aber dann wird die Formatierung nach jedem Tastendruck sofort ausgeführt, was auch nicht gewünscht ist.

Wie kann ich das Problem lösen?

Danke schonmal für Anregungen!


Moderiert von user profile iconGausi: Topic aus Delphi Language (Object-Pascal) / CLX verschoben am Fr 10.12.2010 um 11:15


Gausi - Fr 10.12.10 12:14

So als Idee: Teste, ob die Komponente grade den Fokus hat. Wenn ja, dann gibt der User da grade was ein und eine Fortmatierung ist noch unerwünscht. Wenn nein, dann erfolgte die Änderung automatisch, und eine Formatierung ist erwünscht/notwendig.


jaenicke - Fr 10.12.10 12:21

Du musst ja nicht bei der Zuweisung Exit aufrufen, sondern die Funktion, die die Behandlung des neuen Werts durchführt. (Also die, die du in Exit auch aufrufst.)

Da Exit nur für die Fokusänderung zuständig ist, hat es mit einer Änderung auch nichts zu tun. Du musst die Reaktion also auch bei der Zuweisung manuell einbauen.


Kay E. - So 12.12.10 23:58

So, bin leider erst jetzt dazu gekommen, das auszuprobieren.
Der Vorschlag von Gausi funktioniert wunderbar. Auf die Idee bin ich nicht gekommen. Vielen Dank!


Kay E. - Mo 13.12.10 16:28

Jetzt hat sich das nächste Problem ergeben. Ich hab das mit der idee von gausi implementiert, aber jetzt hab ich das problem, dass wenn ich Programmseitig eine OnChange-Prozedur benutz, dass dann das Komponenten-interne OnChange überschrieben wird. Und wenn ich ein inherited reinsetz, bekomm ich nen StackOverflow.
Wär schön, wenn mir da jemand Hilfestellung geben könnte, wie ich das behandeln kann.

Hier der Quellcode der Komponente:

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:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
procedure TFloatEdit.ChangeMethod(Sender: TObject);
begin
  if not TFloatEdit(Sender).Focused then
  begin
    FormatFloatEdit(Sender);
  end;
end;

constructor TFloatEdit.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  OnExit := FormatFloatEdit;
  OnChange := ChangeMethod;

  Alignment := taCenter;
  Width := 65;

  Text:='1,00';
  SetDecNumber(2);
end;

procedure TFloatEdit.FormatFloatEdit(Sender: TObject);
var
  vText, vText2: string;
  i: Integer;
  IsDecNumberZero: Boolean;
begin
  FWrittenDecNumbers := 0;
  FCommaSet := false;
  if GetDecNumber = 0 then
    IsDecNumberZero := true
  else
    IsDecNumberZero := false;

  vText := Text;
  vText2 := '';

  for i := 0 to length(vText) - 1 do
  begin
    if IsDecNumberZero then
    begin
      if (ord(vText[i+1]) > 48) and (ord(vText[i+1]) < 58) then
      begin
        vText2 := vText2 + vText[i+1];
      end
      else
      if (vText[i+1] = ',') or (vText[i+1]='.') then
        break;
    end else
    begin
      // If comma is alredy set and numbers follow
      if FCommaSet then
      begin
        if (ord(vText[i+1]) > 48) and (ord(vText[i+1]) < 58) and (FWrittenDecNumbers < GetDecNumber) then
        begin
          vText2 := vText2 + vText[i+1];
          inc(FWrittenDecNumbers);
        end;
      end;

      // comma is not set now
      if (ord(vText[i+1]) > 48) and (ord(vText[i+1]) < 58) and (FCommaSet = false) then
        vText2 := vText2 + vText[i+1]
      else
      if vText[i+1] = ',' then
      begin
        vText2 := vText2 + vText[i+1];
        FCommaSet := true;
      end
      else
      if vText[i+1] = '.' then
      begin
        vText2 := vText2 + ',';
        FCommaSet := true;
      end;
    end;
  end;

  if vText2 = '' then
  begin
    vText2 := '1';
  end;

  if (FCommaSet = false) and (not IsDecNumberZero) then
  begin
    vText2 := vText2 + ',';
    FCommaSet := true;
  end;

  if FWrittenDecNumbers < GetDecNumber then
  begin
    for i := FWrittenDecNumbers to GetDecNumber -1 do
      vText2 := vText2 + '0';
  end;

  Text := vText2;
end;

function TFloatEdit.GetDecNumber;
begin
  result := FDecNumber;
end;


procedure TFloatEdit.KeyPress(var Key: Char);
begin
  inherited KeyPress(Key);

  if Key = #13 then
    FormatFloatEdit(self);
end;

procedure TFloatEdit.SetDecNumber(DecNumber: Integer);
begin
  if DecNumber < 0 then
    FDecNumber := DecNumber * (-1)
  else
    FDecNumber := DecNumber;

  FormatFloatEdit(self);
end;


Und hier noch der Programmseitige Aufruf:

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:
procedure TForm1.FormCreate(Sender: TObject);
begin
  FloatEdit := TFloatEdit.Create(self);
  with FloatEdit do
  begin
    Parent := Form1;
    Left := 50;
    Top := 50;
    OnChange := FloatEditChange;
  end;
end;

procedure TForm1.FloatEditChange(Sender: TObject);
begin
  if not TFloatEdit(Sender).Focused then
    ShowMessage('Override?');
//eine ähnliche Abfrage muss ich später im finalen programm auch machen
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  FloatEdit.Text := Edit1.Text;
end;


jaenicke - Mo 13.12.10 23:45

Du änderst selbst etwas, deshalb wird OnChange vermutlich wiederum ausgelöst, darin änderst du etwas, deshalb wird OnChange wiederum ausgelöst, darin... ;-)
--> StackOverflow

Du musst also dafür sorgen, dass du nicht auf deine eigene Änderung reagierst, wenn du Change überschreibst, was bei einer eigenen Komponente natürlich am sinnvollsten ist. Zum Beispiel mit einem Flag.
Die abgeleitete Komponente hat jedenfalls nichts an den OnXXX Handlern zu suchen. Die sind dafür da, von extern benutzt zu werden.


Kay E. - Di 14.12.10 15:46

Also den ersten Teil mit dem StackOverflow versteh ich, macht auch Sinn. Aber den zweiten Teil versteh ich leider nicht ganz.
Ich hab in nem anderen Thread gefragt, wie ich auf die Exit-Methode zugreifen kann, also dass die komponente etwas macht, wenn sie verlassen wird. Da hast du dann geschrieben:
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconKay E. hat folgendes geschrieben Zum zitierten Posting springen:
Aber das stimmt doch nicht, die OnExit-Methode ist doch in der TEdit definiert?
Nein, ist sie nicht. Definiert ist die Eigenschaft OnExit, der ein Ereignishandler zugewiesen werden kann. :idea:


Das hab ich so verstanden, dass ich jetzt der Eigenschaft OnExit der Komponente den Handler auf die Prozedur FormatFloatEdit geb (was ja auch funktioniert hat, bis ich dann OnChange respektive OnExit im Programm verändert hab).
Und jetzt sagst du mir, dass die Komponente nichts an den Onxxx-Handlern zu suchen hat. Aber wenn ich nicht die Eigenschaft OnExit beschreiben darf, wie kann ich dann auf das Ereignis reagieren?
Kann mir da bitte jemand nen kleinen Codeschnippsel als Denkanstoß geben? Das wär wirklich hilfreich!

Und was du mir mit dem Flag sagen willst, ist mir auch noch nicht so ganz klar, sorry :\

Danke schonmal!


jaenicke - Di 14.12.10 15:54

user profile iconKay E. hat folgendes geschrieben Zum zitierten Posting springen:
Ich hab in nem anderen Thread gefragt, wie ich auf die Exit-Methode zugreifen kann, also dass die komponente etwas macht, wenn sie verlassen wird.
Oh, ja, in dem Code hast du ja Exit als Methode. Du hattest von OnExit als Methode geschrieben (aber das ist ja eine Eigenschaft), in den Code habe ich gar nicht geschaut. ;-)

Die Methode, die deklariert ist, ist DoExit. Diese kannst du überschreiben. Steht alles im Quelltext drin. ;-)
Alternativ kannst du auf die Message CM_EXIT reagieren.


Kay E. - Di 14.12.10 16:24

Also wenn ich in die StdCtrls.hpp reinschau, seh ich zwar das OnExit property, aber nirgends steht was zu DoExit. Ne andere Datei, die StdCtrls heisst, hab ich nicht gefunden, also geh ich mal davon aus, dass das die Definitionsdatei ist. Nur bedingt hilfreich ist, dass das in C geschrieben ist...
Wenn ich in der Hilfe nach "TEdit DoExit" such, find ich auch nichts. Sonst eine Datei, die TEdit heisst, gibts auch nicht.
Das ist jetzt vielleicht ne doofe Frage, aber: Wo find ich denn den Sourcecode von dem TEdit?!


jaenicke - Di 14.12.10 16:32

user profile iconKay E. hat folgendes geschrieben Zum zitierten Posting springen:
Also wenn ich in die StdCtrls.hpp reinschau
Das ist ja auch wie du schon sagst eine C++ Dateierweiterung. :gruebel:

Den hier relevanten Code findest du in der Controls.pas im Quelltext von TWinControl, wovon TEdit abgeleitet ist. Diese Datei solltest du im Quelltext unter source\win32\vcl finden.

Du kannst den ursprünglichen Code nehmen, nur dass du Exit durch DoExit beim Überschreiben ersetzt.