delphi march - Warum ist es schlecht, einen Eventhandler aus dem Code aufzurufen?




for our (9)

Angenommen, Sie entscheiden irgendwann, dass der Menüpunkt nicht mehr sinnvoll ist und Sie den Menüpunkt loswerden wollen. Wenn Sie nur über ein anderes Steuerelement verfügen, das auf den Ereignishandler des Menüelements zeigt, ist das möglicherweise kein großes Problem. Sie können den Code einfach in den Ereignishandler der Schaltfläche kopieren. Aber wenn Sie mehrere Möglichkeiten haben, den Code aufzurufen, müssen Sie viele Änderungen vornehmen.

Persönlich mag ich die Art und Weise, wie Qt damit umgeht. Es gibt eine QAction-Klasse mit einem eigenen Event-Handler, der süchtig gemacht werden kann. Dann ist die QAction allen Benutzeroberflächenelementen zugeordnet, die diese Aufgabe ausführen müssen.

Angenommen, Sie haben einen Menüeintrag und eine Schaltfläche, die dieselbe Aufgabe ausführen. Warum ist es eine schlechte Übung, den Code für die Aufgabe in das Aktionsereignis eines Steuerelements zu schreiben und dann von dem anderen Steuerelement einen Aufruf an dieses Ereignis zu senden? Delphi erlaubt dies genauso wie vb6 aber realbasic nicht und sagt, dass Sie den Code in eine Methode schreiben sollten, die dann sowohl über das Menü als auch über die Schaltfläche aufgerufen wird


Es ist eine Frage, wie Ihr Programm organisiert ist. In dem von Ihnen beschriebenen Szenario wird das Verhalten des Menüelements in Bezug auf die Schaltflächen definiert:

procedure TJbForm.MenuItem1Click(Sender: TObject);
begin
  // Three different ways to write this, with subtly different
  // ways to interpret it:

  Button1Click(Sender);
  // 1. "Call some other function. The name suggests it's the
  //    function that also handles button clicks."

  Button1.OnClick(Sender);
  // 2. "Call whatever method we call when the button gets clicked."
  //    (And hope the property isn't nil!)

  Button1.Click;
  // 3. "Pretend the button was clicked."
end;

Jede dieser drei Implementierungen funktioniert, aber warum sollte der Menüpunkt so abhängig von der Schaltfläche sein? Was ist das Besondere an dem Button, dass er den Menüpunkt definieren soll? Wenn ein neues UI-Design keine Schaltflächen mehr enthält, was würde dann mit dem Menü passieren? Ein besserer Weg besteht darin, die Aktionen des Event-Handlers zu berücksichtigen, damit er unabhängig von den Steuerelementen ist, an die er angehängt ist. Dafür gibt es mehrere Möglichkeiten:

  1. Eine besteht darin, die MenuItem1Click Methode vollständig zu Button1Click und der MenuItem1.OnClick Ereigniseigenschaft die Button1Click Methode MenuItem1.OnClick . Es ist verwirrend, Methoden für Schaltflächen zu haben, die den Ereignissen von Menüelementen zugeordnet sind. Daher sollten Sie den Ereignishandler umbenennen, aber das ist in Ordnung, denn im Gegensatz zu VB definieren die Methodennamen von Delphi nicht , welche Ereignisse sie behandeln. Sie können jedem Ereignishandler eine beliebige Methode zuweisen, solange die Signaturen übereinstimmen. Die OnClick Ereignisse beider Komponenten sind vom Typ TNotifyEvent , sodass sie sich eine einzelne Implementierung teilen können. Nenne Methoden für das, was sie tun, nicht was ihnen gehört.

  2. Eine andere Möglichkeit besteht darin, den Event-Handler-Code der Schaltfläche in eine separate Methode zu verschieben und diese Methode dann von den Ereignisbehandlungsroutinen beider Komponenten aus aufzurufen:

    procedure HandleClick;
    begin
      // Do something.
    end;
    
    procedure TJbForm.Button1Click(Sender: TObject);
    begin
      HandleClick;
    end;
    
    procedure TJbForm.MenuItem1Click(Sender: TObject);
    begin
      HandleClick;
    end;
    

    Auf diese Weise ist der Code, der wirklich funktioniert, nicht direkt an eine der beiden Komponenten gebunden. Dies gibt Ihnen die Freiheit, diese Steuerelemente einfacher zu ändern , z. B. durch Umbenennen oder durch andere Steuerelemente. Die Trennung des Codes von der Komponente führt uns zum dritten Weg:

  3. Die TAction Komponente, die in Delphi 4 eingeführt wurde, wurde speziell für die von Ihnen beschriebene Situation entwickelt, bei der mehrere UI-Pfade zu demselben Befehl vorhanden sind. (Andere Sprachen und Entwicklungsumgebungen stellen ähnliche Konzepte bereit; Delphi ist nicht eindeutig.) TAction Sie Ihren Ereignisbehandlungscode in den TAction -Ereignishandler der TAction und weisen Sie diese Aktion dann der Action Eigenschaft der Schaltfläche und des Menüelements zu.

    procedure TJbForm.Action1Click(Sender: TObject);
    begin
      // Do something
      // (Depending on how closely this event's behavior is tied to
      // manipulating the rest of the UI controls, it might make
      // sense to keep the HandleClick function I mentioned above.)
    end;
    

    Möchten Sie ein weiteres UI-Element hinzufügen, das wie die Schaltfläche funktioniert? Kein Problem. Fügen Sie es hinzu, legen Sie die Action Eigenschaft fest, und Sie sind fertig. Sie müssen nicht mehr Code schreiben, damit das neue Steuerelement wie das alte aussieht und sich verhält. Sie haben diesen Code bereits einmal geschrieben.

    TAction geht über nur Event-Handler hinaus. Dadurch können Sie sicherstellen, dass Ihre UI-Steuerelemente über einheitliche Eigenschaften verfügen , darunter Beschriftungen, Hinweise, Sichtbarkeit, Aktivierung und Symbole. Wenn ein Befehl zu diesem Zeitpunkt nicht gültig ist, legen Sie die Enabled Eigenschaft der Aktion entsprechend fest, und alle verknüpften Steuerelemente werden automatisch deaktiviert. Sie müssen sich keine Sorgen machen, dass ein Befehl über die Symbolleiste deaktiviert wird, aber beispielsweise über das Menü aktiviert bleibt. Sie können sogar das OnUpdate Ereignis der Aktion verwenden, damit sich die Aktion basierend auf den aktuellen Bedingungen aktualisieren kann, anstatt dass Sie immer wissen müssen, wenn etwas passiert, bei dem Sie möglicherweise sofort die Eigenschaft Enabled festlegen müssen.


Es ist offensichtlich sauberer. Aber auch Benutzerfreundlichkeit und Produktivität sind natürlich immer wichtig.

In Delphi unterlasse ich es generell in ernsthaften Apps, aber ich benenne Eventhandler in kleinen Dingen. Wenn kleine Dinge irgendwie zu etwas Größerem werden, dann räume ich auf und vergrößere normalerweise die Logik-UI-Trennung.

Ich weiß aber, dass es in Lazarus / Delphi keine Rolle spielt. Andere Sprachen haben möglicherweise ein spezielleres Verhalten als Eventhandler.


Dies ist eine erweiterte Antwort, wie versprochen. Im Jahr 2000 haben wir begonnen, eine Anwendung mit Delphi zu schreiben. Dies war eine EXE und einige DLLs mit Logik. Dies war Filmindustrie, also gab es Kunden DLL, Booking DLL, Box Office DLL und Billing DLL. Wenn Benutzer abrechnen wollte, öffnete er das entsprechende Formular, wählte einen Kunden aus einer Liste, dann OnSelectItem logic geladenen Kunden Theater zum nächsten Kombinationsfeld, dann nach der Auswahl Theater nächstes OnSelectItem Ereignis gefüllt dritten Kombinationsfeld mit Informationen über die Filme, die nicht gewesen in Rechnung gestellt. Der letzte Teil des Prozesses war das Drücken der Schaltfläche "Do Invoice". Alles wurde als Ereignisprozedur ausgeführt.

Dann entschied jemand, dass wir umfangreiche Tastaturunterstützung haben sollten. Wir haben aufrufende Event-Handler von einem anderen, gleichmäßigen Handler hinzugefügt. Der Workflow von Event-Handlern begann sich zu verkomplizieren.

Nach zwei Jahren entschied sich jemand, ein weiteres Feature zu implementieren - so dass Benutzer, die mit Kundendaten in einem anderen Modul (Kundenmodul) arbeiten, mit einer Schaltfläche mit dem Titel "Rechnung dieses Kunden" versehen werden sollten. Diese Schaltfläche sollte das Rechnungsformular auslösen und in einem solchen Zustand darstellen, wie es der Benutzer war, der alle Daten manuell ausgewählt hat (der Benutzer konnte sehen, einige Anpassungen vornehmen und die magische Schaltfläche "Rechnung ausführen" drücken) ). Da es sich bei den Kundendaten um eine DLL und bei der Abrechnung um eine andere handelte, war es EXE, die Nachrichten weitergab. Die naheliegende Idee war, dass der Kundendatenentwickler eine einzelne Routine mit einer einzelnen ID als Parameter haben wird und dass diese gesamte Logik innerhalb des Abrechnungsmoduls sein wird.
Stell dir vor, was passiert ist. Da ALL-Logik in Event-Handlern war, haben wir sehr viel Zeit damit verbracht, Logik nicht zu implementieren, sondern Benutzeraktivitäten nachzuahmen - wie Elemente auszuwählen, Application.MessageBox in Event-Handlern mit GLOBAL-Variablen zu suspendieren, und so weiter. Stellen Sie sich vor - hätten wir sogar einfache logische Prozeduren, die als interne Ereignisbehandlungsroutinen bezeichnet werden, wären wir in der Lage gewesen, die boolesche Variable DoShowMessageBoxInsideProc in die Prozedursignatur einzuführen. Solch eine Prozedur hätte mit true-Parametern aufgerufen werden können, wenn sie vom Event-Handler aufgerufen werden, und mit FALSE-Parametern, wenn sie von extern aufgerufen werden.

Das hat mich gelehrt, Logik nicht direkt in GUI-Event-Handlern zu platzieren, mit einer möglichen Ausnahme von kleinen Projekten.


Angenommen, Sie haben irgendwann entschieden, dass das Menü etwas anders machen sollte. Vielleicht geschieht diese neue Veränderung nur unter bestimmten Umständen. Sie vergessen den Knopf, aber jetzt haben Sie auch sein Verhalten geändert.

Auf der anderen Seite, wenn Sie eine Funktion aufrufen, werden Sie weniger wahrscheinlich ändern, was es tut, da Sie (oder der nächste Typ) weiß, dass dies schlimme Folgen haben wird.


Ein weiterer wichtiger Grund ist die Testbarkeit. Wenn der Ereignisbehandlungscode in der Benutzeroberfläche verborgen ist, besteht die einzige Möglichkeit, dies zu testen, entweder durch manuelles Testen oder automatisches Testen, das stark an die Benutzeroberfläche gebunden ist. (zB Menü A öffnen, Taste B drücken). Jede Änderung in der Benutzeroberfläche kann dann natürlich Dutzende von Tests unterbrechen.

Wenn der Code in ein Modul umstrukturiert wird, das sich ausschließlich mit dem zu erfüllenden Job beschäftigt, wird das Testen viel einfacher.


Weil Sie interne Logik auf eine andere Funktion trennen und diese Funktion aufrufen sollten ...

  1. von beiden Ereignishandlern
  2. getrennt vom Code, wenn Sie benötigen

Dies ist eine elegantere Lösung und viel einfacher zu warten.


Warum ist es schlecht? Weil es viel einfacher ist, Code wiederzuverwenden, wenn er nicht in UI-Steuerelemente eingebettet ist.

Warum können Sie es nicht in REALbasic tun? Ich bezweifle, dass es irgendeinen technischen Grund gibt; es ist wahrscheinlich nur eine Designentscheidung, die sie getroffen haben. Es erzwingt sicherlich bessere Kodierungspraktiken.


Ah ha. Ich habe lange darüber nachgedacht und sogar lesen ein zwei Zoll dickes Buch auf VBA im Grunde sagt, verwenden Sie es nicht, wenn Sie die Suchfunktion des VBE verwenden möchten, um Anrufe in großen Projekten leicht zu finden.

Aber ich habe gerade eine andere Verwendung gefunden.

Wir wissen, dass es möglich ist, Codezeilen mit dem Doppelpunkt zu verketten, zum Beispiel:

Function Test(mode as Boolean) 
    if mode = True then x = x + 1 : Exit Sub
    y = y - 1
End Sub

Wenn Sie dies jedoch mit Prozeduraufrufen am Anfang einer Zeile tun, geht der VBE davon aus, dass Sie auf eine Beschriftung verweisen und alle Einzüge entfernen, indem Sie die Linie am linken Rand ausrichten (obwohl die Prozedur wie vorgesehen aufgerufen wird):

Function Test()
Function1 : Function2
End Function

Die Verwendung der Call- Anweisung ermöglicht die Verkettung von Prozeduraufrufen unter Beibehaltung der Codeeinzüge:

Function Test()
    Call Function1 : Call Function2
End Function

Wenn Sie im obigen Beispiel nicht die Call- Anweisung verwenden, wird VBE davon ausgehen, dass "Function1" eine Beschriftung ist und richtet es im Codefenster aus, obwohl es keinen Fehler verursacht.







delphi vb6 coding-style realbasic