Angenommen, Sie haben einen Menüpunkt und eine Schaltfläche, die dieselbe Aufgabe ausführen. Warum ist es nicht ratsam, den Code für die Aufgabe in das Aktionsereignis eines Steuerelements einzufügen und dieses Ereignis dann vom anderen Steuerelement aus aufzurufen? Delphi erlaubt dies ebenso wie vb6, realbasic jedoch nicht und sagt, dass Sie den Code in eine Methode einfügen sollten, die dann sowohl vom Menü als auch von der Schaltfläche aufgerufen wird
73
cooked up
einigespaghetti
Anwendungen, die ein wahrer Albtraum sind, und das ist schade, da die Apps sehr nett waren. Aber ich hasste meine eigene Schöpfung. Robs Antwort ist wirklich nett und erschöpfend, IMO.Antworten:
Es ist eine Frage, wie Ihr Programm organisiert ist. In dem von Ihnen beschriebenen Szenario wird das Verhalten des Menüelements anhand der folgenden 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 der Schaltfläche, dass sie den Menüpunkt definieren soll? Was würde mit dem Menü passieren, wenn durch ein neues UI-Design die Schaltflächen wegfallen würden? Eine bessere Möglichkeit besteht darin, die Aktionen des Ereignishandlers herauszufiltern, damit er unabhängig von den Steuerelementen ist, mit denen er verknüpft ist. Dafür gibt es einige Möglichkeiten:
Eine besteht darin, die
MenuItem1Click
Methode vollständig zu entfernen und dieButton1Click
Methode derMenuItem1.OnClick
Ereigniseigenschaft zuzuweisen . Es ist verwirrend, Methoden für Schaltflächen zu haben, die den Ereignissen von Menüelementen zugewiesen sind. Daher sollten Sie den Ereignishandler umbenennen. Dies ist jedoch in Ordnung, da die Methodennamen von Delphi im Gegensatz zu VB nicht definieren, welche Ereignisse behandelt werden. Sie können jedem Ereignishandler eine beliebige Methode zuweisen, solange die Signaturen übereinstimmen. DieOnClick
Ereignisse beider Komponenten sind vom TypTNotifyEvent
, sodass sie eine einzige Implementierung gemeinsam nutzen können. Nennen Sie Methoden für das, was sie tun, nicht für das, wozu sie gehören.Eine andere Möglichkeit besteht darin, den Ereignishandlercode der Schaltfläche in eine separate Methode zu verschieben und diese Methode dann von den Ereignishandlern beider Komponenten 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 Dinge erledigt, nicht direkt an eine der Komponenten gebunden, und Sie haben die Freiheit, diese Steuerelemente einfacher zu ändern , z. B. indem Sie sie umbenennen oder durch andere Steuerelemente ersetzen. Das Trennen des Codes von der Komponente führt uns zum dritten Weg:
Die
TAction
in Delphi 4 eingeführte Komponente wurde speziell für die von Ihnen beschriebene Situation entwickelt, in der mehrere UI-Pfade zu demselben Befehl vorhanden sind. (Weitere Sprachen und Entwicklungsumgebungen bieten ähnliche Konzepte, es zu Delphi nicht eindeutig zuzuordnen ist.) Setzen Sie Ihren Event-Handling - Code in denTAction
‚sOnExecute
Event - Handler, und weisen Sie dann die Aktion an dieAction
Eigenschaft sowohl der Taste und den Menüpunktes.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 sich wie die Schaltfläche verhält? Kein Problem. Fügen Sie es hinzu, legen Sie seine
Action
Eigenschaft fest und Sie sind fertig. Sie müssen keinen weiteren Code schreiben, damit das neue Steuerelement wie das alte aussieht und sich so verhält. Sie haben diesen Code bereits einmal geschrieben.TAction
geht über nur Event-Handler hinaus. Damit können Sie sicherstellen, dass Ihre UI-Steuerelemente einheitliche Eigenschafteneinstellungen haben , einschließlich Beschriftungen, Hinweisen, Sichtbarkeit, Aktiviertheit und Symbolen. Wenn ein Befehl zu diesem Zeitpunkt nicht gültig ist, legen Sie dieEnabled
Eigenschaft der Aktion entsprechend fest. Alle verknüpften Steuerelemente werden automatisch deaktiviert. Sie müssen sich keine Sorgen machen, dass ein Befehl über die Symbolleiste deaktiviert, aber dennoch über das Menü aktiviert wird. Sie können dasOnUpdate
Ereignis der Aktion sogar verwenden, damit sich die Aktion basierend auf den aktuellen Bedingungen selbst aktualisieren kann, anstatt zu wissen, wann etwas passiert, bei dem Sie dieEnabled
Eigenschaft möglicherweise sofort festlegen müssen .quelle
Weil Sie die interne Logik von einer anderen Funktion trennen und diese Funktion aufrufen sollten ...
Dies ist eine elegantere Lösung und viel einfacher zu warten.
quelle
Dies ist eine Erweiterungsantwort, wie versprochen. Im Jahr 2000 haben wir begonnen, eine Anwendung mit Delphi zu schreiben. Dies war eine EXE-Datei und einige DLLs, die Logik enthielten. Dies war Filmindustrie, also gab es Kunden-DLL, Buchungs-DLL, Kassen-DLL und Abrechnungs-DLL. Wenn der Benutzer die Abrechnung durchführen wollte, öffnete er das entsprechende Formular, wählte den Kunden aus einer Liste aus, lud die OnSelectItem-Logik die Kunden-Theater in das nächste Kombinationsfeld und füllte nach Auswahl des nächsten OnSelectItem-Ereignisses das dritte Kombinationsfeld mit Informationen zu den Filmen, die noch nicht vorhanden waren noch in Rechnung gestellt. Der letzte Teil des Prozesses war das Drücken der Schaltfläche "Rechnung machen". Alles wurde als Event-Prozedur durchgeführt.
Dann entschied jemand, dass wir eine umfassende Tastaturunterstützung haben sollten. Wir haben aufrufende Ereignishandler von anderen geraden Handlern hinzugefügt. Der Workflow von Ereignishandlern begann sich zu verkomplizieren.
Nach zwei Jahren entschied sich jemand, eine andere Funktion zu implementieren, sodass dem Benutzer, der mit Kundendaten in einem anderen Modul (Kundenmodul) arbeitet, die Schaltfläche "Diesen Kunden in Rechnung stellen" angezeigt werden sollte. Diese Schaltfläche sollte das Rechnungsformular auslösen und in einem solchen Zustand anzeigen, als wäre es der Benutzer, der alle Daten manuell ausgewählt hat (der Benutzer sollte in der Lage sein, die Daten anzuzeigen, einige Anpassungen vorzunehmen und die magische Schaltfläche „Rechnung erstellen“ zu drücken ). Da es sich bei den Kundendaten um eine DLL und bei der Abrechnung um eine andere handelte, wurden EXE-Nachrichten an EXE weitergeleitet. Die offensichtliche Idee war also, dass der Kundendatenentwickler eine einzige Routine mit einer einzigen ID als Parameter haben wird und dass sich all diese Logik innerhalb des Abrechnungsmoduls befindet.
Stellen Sie sich vor, was passiert ist. Da sich ALLE Logik in Ereignishandlern befand, haben wir viel Zeit damit verbracht, keine Logik zu implementieren, sondern Benutzeraktivitäten nachzuahmen - wie das Auswählen von Elementen, das Anhalten von Application.MessageBox in Ereignishandlern mithilfe von GLOBAL-Variablen usw. Stellen Sie sich vor, wenn wir sogar einfache logische Prozeduren hätten, die in Ereignishandlern aufgerufen werden, hätten wir die boolesche Variable DoShowMessageBoxInsideProc in die Prozedursignatur einführen können. Eine solche Prozedur könnte mit true-Parametern aufgerufen worden sein, wenn sie vom Ereignishandler aufgerufen wurde, und mit FALSE-Parametern, wenn sie von einem externen Ort aufgerufen wurde.
Das hat mich also gelehrt, Logik nicht direkt in GUI-Ereignishandler zu integrieren, mit einer möglichen Ausnahme von kleinen Projekten.
quelle
Trennung von Bedenken. Ein privates Ereignis für eine Klasse sollte in dieser Klasse gekapselt und nicht von externen Klassen aufgerufen werden. Dies erleichtert es Ihrem Projekt, sich später zu ändern, wenn Sie starke Schnittstellen zwischen Objekten haben und das Auftreten mehrerer Einstiegspunkte minimieren.
quelle
Implements
SchlüsselwortPrivate
standardmäßig Methoden für die Implementierung erstellt werden, mit der Ausnahme, dass Ereignisse und Ereignishandler speziell behandelt werden (dh Sie müssen keine Handler für alle Ereignisse implementieren, die von einer Klasse verfügbar gemacht werden, fügt der Compiler ein leeres Ereignis ein Handler zur Kompilierungszeit).Angenommen, Sie entscheiden irgendwann, dass der Menüpunkt keinen Sinn mehr ergibt, und möchten den Menüpunkt entfernen. Wenn Sie nur ein anderes Steuerelement haben, das auf die Ereignisbehandlungsroutine des Menüelements verweist, ist dies möglicherweise kein großes Problem. Sie können den Code einfach in die Ereignisbehandlungsroutine der Schaltfläche kopieren. Wenn Sie jedoch verschiedene 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 Ereignishandler, der verknüpft werden kann. Anschließend wird die QAction allen UI-Elementen zugeordnet, die diese Aufgabe ausführen müssen.
quelle
Ein weiterer wichtiger Grund ist die Testbarkeit. Wenn Ereignisbehandlungscode in der Benutzeroberfläche vergraben ist, können Sie dies nur durch manuelles Testen oder automatisiertes Testen testen, das stark an die Benutzeroberfläche gebunden ist. (zB Menü A öffnen, Schaltfläche B anklicken). Jede Änderung der Benutzeroberfläche kann dann natürlich Dutzende von Tests unterbrechen.
Wenn der Code in ein Modul umgestaltet wird, das sich ausschließlich mit dem Job befasst, den er ausführen muss, wird das Testen viel einfacher.
quelle
Es ist offensichtlich ordentlicher. Aber auch Benutzerfreundlichkeit und Produktivität sind natürlich immer wichtig.
In Delphi verzichte ich in seriösen Apps generell darauf, aber ich rufe Eventhandler in kleinen Dingen an. Wenn sich kleine Dinge irgendwie in etwas Größeres verwandeln, räume ich es auf und erhöhe normalerweise gleichzeitig die Trennung zwischen Logik und Benutzeroberfläche.
Ich weiß jedoch, dass es in Lazarus / Delphi keine Rolle spielt. Andere Sprachen weisen möglicherweise ein spezielleres Verhalten bei Eventhandlern auf.
quelle
Warum ist es schlechte Praxis? Weil es viel einfacher ist, Code wiederzuverwenden, wenn er nicht in UI-Steuerelemente eingebettet ist.
Warum kannst du das nicht in REALbasic machen? Ich bezweifle, dass es einen technischen Grund gibt. Es ist wahrscheinlich nur eine Designentscheidung, die sie getroffen haben. Es erzwingt sicherlich bessere Codierungspraktiken.
quelle
Angenommen, Sie haben irgendwann entschieden, dass das Menü etwas anders aussehen soll. Vielleicht geschieht diese neue Änderung nur unter bestimmten Umständen. Sie vergessen die Schaltfläche, aber jetzt haben Sie auch ihr Verhalten geändert.
Wenn Sie dagegen eine Funktion aufrufen, ist es weniger wahrscheinlich, dass Sie ihre Funktionsweise ändern, da Sie (oder der nächste) wissen, dass dies schlimme Folgen hat.
quelle