Entwickler-Ecke

Windows API - Sender und Ereignis auswerten


Mathematiker - Di 15.07.14 23:57
Titel: Sender und Ereignis auswerten
Hallo,
ich habe eine Methode die von mehreren Stellen aufgerufen wird. Mit dem in der Hilfe beschriebenen

Delphi-Quelltext
1:
2:
3:
4:
5:
procedure TMainForm.Button1Click(Sender: TObject);
begin
  if Sender = Button1 then ...;
  if Sender = Button2 then ...;
end;

kann ich ja den Sender lokalisieren.
Mein Problem ist nun, dass bei einigen Sendern verschiedene Ereignisse, z.B. OnBottomClick und OnTopClick die Methode rufen sollen. Beiden Ereignissen ist z.B. Button1Click zugewiesen. Aus verschiedenen Gründen (Übersichtlichkeit mit weniger Methoden, kleinere Exe durch Vermeidung von "fast" doppeltem Code, ...) möchte ich nicht für jedes Ereignis eine einzelne Methode verwenden.

Seht Ihr eine Möglichkeit außer dem Sender auch das Ereignis zu identifizieren?

Danke für Ideen und beste Grüße
Mathematiker


Delete - Mi 16.07.14 00:51

Falls du mit "von mehreren Stellen aus" eigentich "von mehreren Units/Forms aus" meinst: Erstelle dir doch selber ein Ereignis, das jedesmal, wenn du deine Methode benötigst, diese aufruft. Ich hab z.B. in meinen Mainforms zumeist eine Statusbar, die von allen möglichen Forms und Units aktualisiert wird, ohne daß diese Forms und Units die Mainform-Klasse kennen. Z.B. im Datenmodul, wo diverse Querys im AfterScroll-Ereignis meine Methode zur Aktualisierung der Statusbar in der Mainform aufrufen.

Dazu erstelle ich erst einmal einen neuen Typ noch vor der Klassendeklaration im Datenmodul:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
UNIT UnitData;

INTERFACE

USES
  SysUtils, Classes, ...;

TYPE
  TUpdateStatusbarEvent = Procedure of Object;
  ...

TYPE
  TDatMod = CLASS(TDataModule)
  ...


Dann spendiere ich meinem Datenmodul eine private Variable vom Typ TUpdateStatusbarEvent:


Delphi-Quelltext
1:
2:
3:
4:
5:
  ...
    PRIVATE { Private-Deklarationen }
      Var
        fUpdateStatusbar : TUpdateStatusbarEvent;
  ...


Danach erstelle ich ein entsprechendes Property im Public-Abschnitt:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
  ...
    PUBLIC  { Public-Deklarationen  }

      Function  VerbindenDatenbank : Boolean;
      Function  VerbindenQueries   : Boolean;
  ...
      Property OnUpdateStatusbar : TUpdateStatusbarEvent Read fUpdateStatusbar Write fUpdateStatusbar;
  ...
  END;


Und zu guter Letzt (was das Datenmodul betrifft) deklariere und implementiere ich ein Ereignis AfterScroll für ein Query:


Delphi-Quelltext
1:
2:
3:
4:
5:
Procedure TDatMod.Query_MessungAfterScroll(DataSet: TDataSet);
begin
  ...
  If Assigned(fUpdateStatusbar) Then fUpdateStatusbar;
end;


Jetzt nehm ich mir die Mainform vor, wo sich die Statusbar befindet, deren Aktualisierung von diesem Ereignis ausgelöst werden soll. Dort deklariere und implementiere ich erst einmal eine private Methode, die die Aktualisierung der Statusbar ausführen soll:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
Procedure TFormMain.StatusBarAktuell;
begin
  StatBarMain.Panels[1].Text := ...;
  StatBarMain.Panels[2].Text := ...;
  ...
end;


Nun muß ich nur noch dem in UnitData (Datenmodul) deklarierten Ereignis eine Ereignisbehandlung zuweisen, wofür ich sinnigerweise die eben erstellte Aktualisierungsmethode verwende:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
Procedure TFormMain.FormActivate(Sender: TObject);
begin
  ...
  DatMod.OnUpdateStatusbar := StatusBarAktuell;
  ...
end;


Jetzt wird jedesmal, wenn die Datenmenge im Query gescrollt wird, die Statusbar aktualisiert. Diese Methode kann ich quasi von jeder anderen Methode im Datenmodul aus aufrufen. Um die Aktualisierungsmethode aus einer anderen Unit heraus aufzurufen, mach ich dort einfach dasselbe wie im Datenmodul und weise danach auch diesem Ereignis in der Hauptunit die Statusbar-Aktualisierungsmethode als Ereignisbehandlung zu. In deinem Fall rufst löst du das Ereignis einfach in deinen Buttonklicks aus.

Wenn es wirklich wichtig ist, welcher Button geklickt wurde, kannst du ja ein Read-Only-Property deklarieren, das einen entsprechenden Hinweis liefert. Ich habe z.B. eine Unit GlobalUtils, die eine Klasse Globalix enthält, in der neben häufig benötigten allgemeinen Methoden ein paar wenige globale Variablen gehalten werden. Eine davon ist z.B. ProgMode, die je nach Modus des Anwendung (die bei mir häufig aus etlichen verschachtelten PageControls besteht) z.B. andere Bereiche der Statusbar-Aktualisierung ausführt (Case GL.ProgMode Of). Genauso verwende ich dieselbe globale Variable ProgMode in einer Datenbank-Klasse, die nahezu alle Bearbeitungen in der Datenbank erledigt – mit nur einem Query: Je nach ProgMode wird dem Query das tatsächlich benötigte Query zugewiesen, womit ich für zahlreiche Tabellen immer denselben Code verwenden kann.


Mathematiker - Mi 16.07.14 07:34

Hallo user profile iconPerlsau,

Danke für die ausführliche Antwort.
Ich versuche heute Deine Ideen umzusetzen. Dass es anspruchsvoll wird, hatte ich mir schon gedacht.
Insbesondere die Idee "Query" muss ich mir genauer ansehen.

Beste Grüße
Mathematiker


Delete - Mi 16.07.14 08:40

Moin,

das Query war doch nur als Beispiel aufgeführt. Soweit ich weiß, befaßt du dich nicht mit Datenbanken – oder inzwischen doch?

Beim Verwenden von selbstdeklarierten Ereignissen handelt es sich um eine durchaus übliche Vorgehensweise. Das ist nicht wirklich kompliziert, zumindest scheint es mir nicht so komplex zu sein wie so manche mathematische Gleichungen und Funktionen (was vielleicht daran liegen könnte, daß ich kein Mathematiker bin).

Eine weitere Möglichkeit, Ereignisse auszulösen, besteht in sog. Callback-Funktionalität: Irgend eine Methode irgend einer Klasse benötigt dazu eine Funktion als Parameter, z.B. bei Bass die Sound-Stream-Erzeugung. Die Call-Back-Funktion wird dann ausgelöst, wenn gewisse Streambewegungen auftreten (z.B. wenn ein Sample abgespielt wird).


baumina - Mi 16.07.14 09:19

Ich würde OnBottomClick und OnTopClick trennen und von dort jeweils eine Procedure DoXXX(Sender, AusBottomClick) aufrufen. Also einfach selbst sagen aus welchem Ereignis raus der Aufruf stattfindet.


WasWeißDennIch - Mi 16.07.14 10:02

Ich verstehe die Anforderung nicht. Den Sender-Parameter auszuwerten ist ja in gewissen Fällen noch sinnvoll, aber wozu muss man das auslösende Ereignis wissen?


jasocul - Mi 16.07.14 10:18

@user profile iconWasWeißDennIch: Wenn der Source für beide Ereignisse nahezu gleich ist, kann man auf diese Weise alles in eine Methode packen und nur die geringfügige Abweichung vom auslösenden Ereignis abhängig machen.

Ich würde es vermutlich auch so, wie user profile iconbaumina lösen. Wobei ich die Idee von user profile iconPerlsau auch interessant finde und sicher in meinem Hinterkopf bleibt.


WasWeißDennIch - Mi 16.07.14 10:36

Tut mir leid, aber irgendwie klingt das in meinen Ohren unsauber, wahrscheinlich, weil ich noch nie in der Verlegenheit war, ein Problem auf diese Weise anzugehen. Wenn man schon ein eigenes Ereignis deklariert, kann man dem doch auch einen weiteren Parameter spendieren, den man im auslösenden Ereignis entsprechend setzt.

Delphi-Quelltext
1:
2:
3:
4:
type
  TDingens = (dTop, dLeft, dBottom, dRight);

  TDingensEvent = procedure(Sender: TObject; Dingens: TDingens) of object;


Delphi-Quelltext
1:
2:
3:
4:
5:
type
  TSomeForm = class(TForm)
  ...
  private
    procedure DoDingens(Sender: TObject; Dingens: TDingens);

Button1 würde dann z.B.

Delphi-Quelltext
1:
DoDingens(self, dTop);                    

aufrufen und Checkbox42

Delphi-Quelltext
1:
DoDingens(self, dBottom);                    


baumina - Mi 16.07.14 10:45

Das entspricht dem was ich schrieb, nur versteh ich nicht warum du TDingensEvent dazu brauchst.


WasWeißDennIch - Mi 16.07.14 10:54

OK, das hab ich vermischt. Allerdings verstehe ich meinerseits nicht, wieso Du das auslösende Event übergeben willst statt der Daten, die von Interesse wären.


baumina - Mi 16.07.14 11:01

Kann ich jetzt nicht beurteilen was dem TE an dieser Stelle wichtiger ist, wirklich zu wissen woher er kommt oder lieber zu wissen was zu machen ist.


WasWeißDennIch - Mi 16.07.14 11:08

Genau deshalb habe ich ja oben die Frage gestellt, möglicherweise wird das Konzept noch einmal durchdacht ;)


Delete - Mi 16.07.14 11:32

Ist doch eigentlich ganz einfach: Stellen wir uns vor, wir haben 2 Buttons, die beide ungefähr dasselbe auslösen sollen, mit einem kleinen Unterschied: Bei Button1 soll der Wert 1 in eine private Variable Wertspeicher geschrieben werden, bei Button2 der Wert 2. Beide Buttons rufen nun die Procedure Auswerten auf, die folgendermaßen implementiert ist:


Delphi-Quelltext
1:
2:
3:
4:
Procedure FormMain.Auswerten(Wert : Integer);
begin
  Wertspeicher := Wert;
end;


Nun muß ich lediglich bei jeder Ereignisbehandlung für jeden Button den entsprechenden Wert mitgeben und gut ist:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
Procedure FormMain.Button1Click(Sender: TObject);
begin
  Auswerten(1);
end;

Procedure FormMain.Button2Click(Sender: TObject);
begin
  Auswerten(2);
end;


Oder wenn's um Strings geht:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
Procedure FormMain.Auswerten(Wert : Integer);
begin
  Case Wert Of
   1 : Wertspeicher := 'Der obere Button wurde geklickt';
   2 : Wertspeicher := 'Der untere Button wurde geklickt';
  End;
end;


baumina - Mi 16.07.14 11:39

Und woher weiß er dann den Unterschied von welchem Event er kommt wenn er folgendes in der dfm zugewiesen hatte?


Delphi-Quelltext
1:
2:
3:
          
OnClick = Button1Click
OnEnter = Button1Click


Deswegen muss er sie trennen:


Delphi-Quelltext
1:
2:
3:
          
OnClick = Button1Click
OnEnter = Button1Enter


Mathematiker - Mi 16.07.14 11:55

Hallo,
vielen Dank für die Antworten.
Im Moment habe ich user profile iconbauminas und user profile iconPerlsaus Ideen genutzt. Insbesondere der Vorschlag

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
Procedure FormMain.Auswerten(Wert : Integer);
begin
  Wertspeicher := Wert;
end;
Procedure FormMain.Button1Click(Sender: TObject);
begin
  Auswerten(1);
end;
Procedure FormMain.Button2Click(Sender: TObject);
begin
  Auswerten(2);
end;

ist genau das Richtige und naheliegend. Irgendwie habe ich mich wohl zu blöd angestellt.

Zur Erklärung, warum ich das Ganze mache: Seit einigen Wochen habe ich mir mein Programm vorgenommen und gehe alle 580000 Zeilen Quellcode durch. Eine ziemliche Arbeit, aber notwendig.
Da ich vor 20 Jahren mit dem Programm begonnen habe, sind die frühesten Units/Algorithmen aus heutiger Sicht extrem laienhaft konstruiert, auch wenn sie fehlerfrei funktionieren. Damals konnte/wusste ich es nicht besser.
Also muss Alles kontrolliert werden. Neben gefundenen totem Code und umständlichen Lösungen gibt es einige fast gleiche Routinen, die oft ziemlich umfangreiche Berechnungen enthalten. Und die versuche ich zu vereinheitlichen. In der ganzen Zeit hat sich einiges angesammelt.
Meine konkrete Frage betraf eine Fremdkomponente (RXSpinbutton), die für die zwei Pfeilschalter die Ereignisse OnBottomClick und OnTopClick auswertet.

Mittlerweile sind schon 6000 Programmzeilen "weg". Schöner Nebeneffekt ist, dass die Exe von 9120 kbyte auf 8760 kByte reduziert ist. Ist zwar bei den heutigen Speichergrößen nicht notwendig, aber mir gefällt es.

Nochmals Danke für die vielen Hinweise
Beste Grüße
Mathematiker


WasWeißDennIch - Mi 16.07.14 11:57

Und nochmal die Frage: wieso muss man das Event kennen? Mich beschleicht das Gefühl, dass aus Gründen der Code-Ersparnis herumgemurkst werden soll. Ich kenne die genaue Anforderung ja nicht, aber möglicherweise wäre auch eine Property samt zugehörigem Event denkbar.

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:
type
  TMyType = (mtOne, mtTwo, mtThree);  
  
  TMyClass = class
  private
    FMyProp: TMyType;
    FOnMyPropChanged: TNotifyEvent;
    procedure SetMyProp(const Value: TMyType);
  public
    property MyProp: TMyType read FMyProp write SetMyProp;
    property OnMyPropChanged: TMyPropChangedEvent read FOnMyPropChanged write FOnMyPropChanged;
  end;

...

procedure TMyClass.SetMyProp(const Value: TMyType);
begin
  if Value <> FMyProp then
    begin
      FMyProp := Value;
      if Assigned(FOnMyPropChanged) then
        FOnMyPropChanged(self);
    end;
end;


Nun kann man der Eigenschaft OnMyPropChanged eine Methode zuweisen, welche immer dann aufgerufen wird, wenn sich MyProp ändert. Auf der GUI-Seite kann man verschiedenen Controls dieselbe Methode zuweisen, in der dann z.B. mtTwo zugewiesen wird, wieder anderen Controls eine andere Methode, die den Wert mtOne setzt usw.


Mathematiker - Mi 16.07.14 12:01

Hallo,
user profile iconWasWeißDennIch hat folgendes geschrieben Zum zitierten Posting springen:
Mich beschleicht das Gefühl, dass aus Gründen der Code-Ersparnis herumgemurkst werden soll.

Oh je, Du hast mich erwischt. :oops:
Das hatte ich wirklich vor, aber mit den hier genannten Hinweisen kann ich darauf verzichten.

Unabhängig davon, dass ich es nicht mehr brauche, interessiert es mich doch noch, ob man auch das auslösende Ereignis einfach(!) ermitteln kann.

Danke für die Hinweise
Beste Grüße
Mathematiker


WasWeißDennIch - Mi 16.07.14 12:04

Wenn man das nicht selbst mitgibt (z.B. als Parameter, siehe baumina), wüsste ich nicht, wie man das bewerkstelligen sollte.


Boldar - Mi 16.07.14 15:22

Naja, theoretisch könnte man möglicherweise den Callstack analysieren, aber das wäre wohl sehr unsauber. Wahrscheinlich hilft aber nicht einmal das weiter.


Delphi-Laie - Do 24.07.14 22:01

user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:
Mittlerweile sind schon 6000 Programmzeilen "weg". Schöner Nebeneffekt ist, dass die Exe von 9120 kbyte auf 8760 kByte reduziert ist. Ist zwar bei den heutigen Speichergrößen nicht notwendig, aber mir gefällt es.


Mir auch, denn ich bin ein Freund kompakten Codes. Ist für mich eleganter, ja sogar ästhetischer.

Ganz simpel, wenn auch unelegant, ist die Verwendung eines möglichst versionsniedrigen Delphis, um die Compilatsgrößen zu verringern. VCL-frei zu programmieren, wie es Luckie favorisiert, ist natürlich noch viel wirksamer, aber wesentlich einarbeitungs- und umsetzungsaufwendiger.


jaenicke - Fr 25.07.14 08:21

user profile iconDelphi-Laie hat folgendes geschrieben Zum zitierten Posting springen:
Ganz simpel, wenn auch unelegant, ist die Verwendung eines möglichst versionsniedrigen Delphis, um die Compilatsgrößen zu verringern.
Weshalb Inno Setup zum Beispiel mit Delphi 2 erstellt wird (wenn auch wohl nicht damit entwickelt). Allerdings sieht der Code auch entsprechend aus, aber das lässt sich dann auch nicht vermeiden.