Benutzer mit den meisten Antworten
EventHandler Warnung

Frage
-
Hallo,
bei meinem aktuellen Projekt in VB.NET express 2010 schreibe ich gerade an einer MDI-Anwendung. Im Elternfenster habe ich ein paar Buttons auf einem ToolStrip. Das aufgerufene Kindfenster enthält jeweils die Methoden, um die Buttons mit einer Funktion zu versehen (AddHandler). Soweit ist alles schön und läuft. Dann möchte ich beim Schließen des Kindfensters "RemoveHandler" verwenden, um die Funktionen wieder von den Buttons zu nehmen. Ich bekomme beim Kompilieren immer die Warnung, dass ich den AddressOf-Ausdruck in einer Variablen speichern und diese dann beim AddressOf angeben soll. Und nun weiß ich nicht mehr weiter. Ich habe so das Gefühl, dass man diese Sache irgendwie über Delegates lösen müsste. Aber wie geht das? Tausend Dank im Voraus für Eure Hilfe!Gruß
mjanz
Antworten
-
Hallo Marcus,
ich habe mir Deinen Code jetzt nocnmal richtig angesehen,
nachdem ich mit Deiner angedeuteten Warnung zugegeben nichts anfangen konnte.
Aber da sich Compiler niemals aufregen - als Immer-Rechthaber brauchen sie das nicht ;-)
mußte was dran sein...Kern des Übels ist ein fehlendes Option Strict On und die Warnung (BC42328 )
resultiert aus dem, was Visual Basic dann aus Deinem Code macht.
(Ich verwende das nie und kenne die Meldung deswegen gar nicht)Da Deine Methoden-Signatur von CopyToClipBoard() usw. fehlerhaft ist,
erzeugt der Compiler einen anonymen Delegaten (eine leider nicht sehr präzise Beschreibung).Die müssten allerdings zwischengespeichert werden, wenn man sie wieder entfernen will.
Ich führe das zunächst nicht weiter aus, da es Dir nicht wirklich weiterhilft.
Resultat ist: Der Delegat bleibt bestehen und am Ende bleibt das Formular im Speicher
=> bei mehrfachem Öffnen dürfte es irgendwann lustig werden :-()Zur Lösung:
Aktiviere Option Strict On (am besten für das gesamte Projekt )
damit werden die Warnungen zu Fehlern.Danach korrigiere die Signaturen der Methoden, d. h. die Methoden-Parameter
müssen dem entsprechen was das Ereignis erwartet. Beim Click wäre das
Sub Click(ByVal sender As Object, ByVal e As EventArgs)Stellvertretend für die Click Handler z. B. CopyToClipBoard:
' Private reicht, wenn im Formular selbst ... Private Sub CopyToClipBoard(ByVal sender As Object, ByVal e As EventArgs) ' Casten in den konkreten Typ Dim frmParent = TryCast(Me.MdiParent, frmMain) ' Nur wenn es das "richtige" Formular ist If frmParent IsNot Nothing Then ' ... End If End Sub
Nebenher tauchen damit einige weitere Fehler auf, wie die untypisierten Verweise auf frmParent.
Exemplarisch oben gezeigt (inkl. Code sparen in Verbindung mit Option Infer On ).
TryCast und die Nothing Prüfung vermeiden Probleme, falls das Formular mal
anders als über frmMain aufgerufen werden sollte.Gruß Elmar
- Als Antwort markiert mjanz Donnerstag, 2. September 2010 12:59
Alle Antworten
-
Hallo,
mit etwas Code (auch nicht funktionierendem) könnte man besser sehen, was Du dort vorhast.
Ein möglicher alternativer Ansatz wäre:
Frage in der Eignisbehandlung des ToolStripButtons die ActiveMdiChild -Eigenschaft ab
und rufe den dazugehörigen Befehl dort auf. Siehe auch:
Gewusst wie: Bestimmen des aktiven untergeordneten MDI-ElementsGruß Elmar
-
Hallo Elmar,
hier der erste Codeabschnitt:
Private Sub frmKundenListe_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim frmParent As frmMain = Me.MdiParent frmParent.titelLabel.Text = Me.Text Dim iWidthStandard As Integer = frmParent.standardToolStrip.Width Dim iHeightStandard As Integer = frmParent.standardToolStrip.Height Dim iWidthXtra As Integer = frmParent.kundenToolStrip.Width Dim iHeightXtra As Integer = frmParent.kundenToolStrip.Height frmParent.kundenToolStrip.Visible = True frmParent.standardToolStrip.SetBounds(0, 0, iWidthStandard, iHeightStandard) frmParent.kundenToolStrip.SetBounds(0, 0, iWidthXtra, iHeightXtra) frmParent.KopierenToolStripButton.Enabled = True frmParent.KopierenToolStripMenuItem.Enabled = True frmParent.DruckenToolStripButton.Enabled = True frmParent.DruckenToolStripMenuItem.Enabled = True frmParent.KundenToolStripMenuItem.Enabled = False AddHandler frmParent.kundeaendernTSButton.Click, AddressOf aendernButton_Click AddHandler frmParent.kundeneuTSButton.Click, AddressOf neuButton_Click AddHandler frmParent.KopierenToolStripButton.Click, AddressOf CopyToClipBoard AddHandler frmParent.KopierenToolStripMenuItem.Click, AddressOf CopyToClipBoard AddHandler frmParent.EinfügenToolStripButton.Click, AddressOf PasteToDataBase AddHandler frmParent.EinfügenToolStripMenuItem.Click, AddressOf PasteToDataBase AddHandler frmParent.DruckenToolStripButton.Click, AddressOf ListeDrucken AddHandler frmParent.DruckenToolStripMenuItem.Click, AddressOf ListeDrucken LadeDaten() End Sub
Das ist das FormLoad-Event. Und bei dem FormClosed-Event verwende ich diesen Code:
Private Sub frmKundenListe_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed Dim frmParent As frmMain = Me.MdiParent frmParent.titelLabel.Text = "" frmParent.kundenToolStrip.Visible = False frmParent.KopierenToolStripButton.Enabled = False frmParent.KopierenToolStripMenuItem.Enabled = False frmParent.DruckenToolStripButton.Enabled = False frmParent.DruckenToolStripMenuItem.Enabled = False frmParent.EinfügenToolStripButton.Enabled = False frmParent.EinfügenToolStripMenuItem.Enabled = False frmParent.KundenToolStripMenuItem.Enabled = True RemoveHandler frmParent.kundeaendernTSButton.Click, AddressOf aendernButton_Click RemoveHandler frmParent.kundeneuTSButton.Click, AddressOf neuButton_Click RemoveHandler frmParent.KopierenToolStripButton.Click, AddressOf CopyToClipBoard RemoveHandler frmParent.KopierenToolStripMenuItem.Click, AddressOf CopyToClipBoard RemoveHandler frmParent.EinfügenToolStripButton.Click, AddressOf PasteToDataBase RemoveHandler frmParent.EinfügenToolStripMenuItem.Click, AddressOf PasteToDataBase RemoveHandler frmParent.DruckenToolStripButton.Click, AddressOf ListeDrucken RemoveHandler frmParent.DruckenToolStripMenuItem.Click, AddressOf ListeDrucken End Sub
Das Hinzufügen der Handler im FormLoad-Event funktioniert perfekt. Aber "RemoveHandler" funktioniert nicht so richtig für die Methoden CopyToClipBoard, PasteToDatabase und ListeDrucken.
Public Sub CopyToClipBoard() Dim frmParent As frmMain = Me.MdiParent For Each r As DataGridViewRow In ergebnisDataGridView.SelectedRows With _kunden .KundenID = r.Cells(0).Value .Firma = r.Cells(1).Value .Kostenstelle = r.Cells(2).Value .Strasse = r.Cells(3).Value .PLZ = r.Cells(4).Value End With Next frmParent.EinfügenToolStripButton.Enabled = True frmParent.EinfügenToolStripMenuItem.Enabled = True End Sub
Public Sub PasteToDataBase() Dim frmParent As frmMain = Me.MdiParent Dim _data As New CAccess Dim strSQL As String = "" With _kunden strSQL = "INSERT INTO tblkunden(Firma, Kostenstelle, Strasse, PLZ, Ort) VALUES('" & _ .Firma & "', '" & _ .Kostenstelle & "', '" & _ .Strasse & "', " & _ .PLZ & ", '" & _ .Ort & "')" _data.FullTextInsertUpdateDelete(strSQL) End With LadeDaten() End Sub
Public Sub ListeDrucken() Dim _Excel As New CExcel _Excel.Kundenliste() End Sub
Ich meine, der Code wird ausgeführt, aber spätestens beim Erstellen des Setups führen die Warnungen zu einem Fehler beim Erstellen. Vielleicht wird jetzt deutlich, was ich machen möchte... Tausend Dank!
Gruß
Marcus -
Das ist das FormLoad-Event. Und bei dem FormClosed-Event verwende ich diesen Code:Das ist etwas spät. Nimm besser das FormClosing-Ereignis.
--
Viele Gruesse
Peter- Als Antwort vorgeschlagen Robert Breitenhofer Mittwoch, 1. September 2010 09:18
-
Hallo Marcus,
nach Deiner vorherigen Antwort bin ich davon ausgeganen, dass Du das Problem gelöst hättest -
Peters Antwort reicht und somit keine weiteren Ideen notwendig sind ...Das RemoveHandler an sich dürfte (syntaktisch) nicht das Problem sein.
Allerdings stiess mir das Enable = False für die Schaltflächen auf,
da es bei weiteren MDI-Fenstern die Ereignisbehandlung unterdrücken würde -
weil der Knopf nicht zugänglich ist.
Ganz davon abgesehen, dass ich gerade sehe, dass Du ein Toolstrip unsichtbar machst
(ob das OK ist hängt davon ab, welche Funktionen dahinter stehen)Will man die Ereignisse immer nur an das aktive (MDI-)Formular schicken
so wäre (neben dem bereits genannten Weg) sinnvoller die Ereignissse beim
Activated Ereignis zu registrieren und beim Deactivate-Ereignis abzumelden.Gruß Elmar
-
Hallo Elmar,
erstmal danke für Deine Rückmeldung. Enable = False benutze ich, um immer nur die Funktionen zur Verfügung zu stellen, die das Kindfenster benötigt. Jedes Kindfenster soll neben den Standardfunktionen "eigene" Funktionen haben, die ich dafür auf einen Extra-Toolstrip gelegt habe. Der soll dann natürlich nur sichtbar/erreichbar sein, wenn das entsprechende Kindfenster aufgerufen wird. Beim Schließen des Fensters wird er somit auch wieder unsichtbar gemacht.
Ich benutze die Ereignisse Load und FormClosing, weil ich darauf achte, immer nur ein bis drei Kindfenster in der MDI-Anwendung offen sind, nämlich die, die für das jeweilige "Thema" wichtig sind. Andere Fenster werden geschlossen. Deswegen nutze ich die o. g. Ereignisse nur dann, wenn eben eine geschlossene "Gruppe" von Fenstern offen ist.
Nicht falsch verstehen, das Programm läuft, es werden beim Kompilieren keine Fehler angezeigt, aber eben Warnungen. Das letzte mal, als ich dann ein Setup-Programm gebaut habe, hat sich der Compiler beim Erstellen der Software wegen solcher Warnungen aufgeregt und hat mir Fehlermeldungen gezeigt. Das will ich für die Zukunft vermeiden...:-).Trotzdem tausend Dank für Eure Hilfe!
Gruß
Marcus
Der erste Tag, an dem ich nichts Neues lerne, wird der Tag sein, an dem sich der Deckel über mir schließt... -
Hallo Marcus,
ich habe mir Deinen Code jetzt nocnmal richtig angesehen,
nachdem ich mit Deiner angedeuteten Warnung zugegeben nichts anfangen konnte.
Aber da sich Compiler niemals aufregen - als Immer-Rechthaber brauchen sie das nicht ;-)
mußte was dran sein...Kern des Übels ist ein fehlendes Option Strict On und die Warnung (BC42328 )
resultiert aus dem, was Visual Basic dann aus Deinem Code macht.
(Ich verwende das nie und kenne die Meldung deswegen gar nicht)Da Deine Methoden-Signatur von CopyToClipBoard() usw. fehlerhaft ist,
erzeugt der Compiler einen anonymen Delegaten (eine leider nicht sehr präzise Beschreibung).Die müssten allerdings zwischengespeichert werden, wenn man sie wieder entfernen will.
Ich führe das zunächst nicht weiter aus, da es Dir nicht wirklich weiterhilft.
Resultat ist: Der Delegat bleibt bestehen und am Ende bleibt das Formular im Speicher
=> bei mehrfachem Öffnen dürfte es irgendwann lustig werden :-()Zur Lösung:
Aktiviere Option Strict On (am besten für das gesamte Projekt )
damit werden die Warnungen zu Fehlern.Danach korrigiere die Signaturen der Methoden, d. h. die Methoden-Parameter
müssen dem entsprechen was das Ereignis erwartet. Beim Click wäre das
Sub Click(ByVal sender As Object, ByVal e As EventArgs)Stellvertretend für die Click Handler z. B. CopyToClipBoard:
' Private reicht, wenn im Formular selbst ... Private Sub CopyToClipBoard(ByVal sender As Object, ByVal e As EventArgs) ' Casten in den konkreten Typ Dim frmParent = TryCast(Me.MdiParent, frmMain) ' Nur wenn es das "richtige" Formular ist If frmParent IsNot Nothing Then ' ... End If End Sub
Nebenher tauchen damit einige weitere Fehler auf, wie die untypisierten Verweise auf frmParent.
Exemplarisch oben gezeigt (inkl. Code sparen in Verbindung mit Option Infer On ).
TryCast und die Nothing Prüfung vermeiden Probleme, falls das Formular mal
anders als über frmMain aufgerufen werden sollte.Gruß Elmar
- Als Antwort markiert mjanz Donnerstag, 2. September 2010 12:59
-
Hallo Elmar,
"option strict on" hat mal eben zu über 90 Fehlern geführt...! Aber wow, da wird Programmieren mal eben 'ne ganze Ecke schwieriger, zumindest für den Anfang... Bin halt nur Ingenieur, kein Informatiker... Aber es rennt jetzt wie Lola!
Eine Frage habe ich jetzt noch für die Zukunft: wann benutze ich TryCast, wann CType und wann DirectCast? Gibt es da eine Faustregel? Tausend Dank im Voraus für Deine Hilfe!
Gruß
MarcusP. S.: Kennst Du ein gutes Beispiel für die Verwendung von Delegates? Ich habe bisher nix finden können, was mich dem Thema Delegates näher gebracht hätte. Irgendwie fehlt mir wahrscheinlich der Horizont dafür...
Der erste Tag, an dem ich nichts Neues lerne, wird der Tag sein, an dem sich der Deckel über mir schließt... -
Hallo Marcus,
als Ingenieur hast Du wesentlich bessere Voraussetzungen,
denn Du weisst, was bei einer schlechten Konstruktion passieren kann -
Software-Entwickler (mit oder ohne Diplom) müssen sich das erst vorstellen ;-)Zum Thema Casten steht das meiste unter TryCast (jetzt deutscher Link).
TryCast verendet man dann, wenn die Konvertierung schief gehen könnte.
Denn sowohl DirectCast wie auch CType können zur Laufzeit fehlschlagen
und eine InvalidCastException auslösen (was das Programm zurückwirft).Als Beispiel für oben konstruiert:
Wenn MdiParent nicht mehr frmMain sondern frmMainDasZweite ist,
z. B. weil das Formular auf anderem Wege eröffnet wurde, wird dort Nothing geliefert.
Und man kann entsprechend reagieren - wie mit dem Test gezeigt.
Denn alles andere (Toolstrip/Button) existieren ebenfalls nicht.DirectCast verwendet man dann, wenn die Umwandlung (mit 99,999% Sicherheit)
nicht fehlschlagen kann und man keine Ausnahme erwarten muß.Ein Beispiel wäre die Ereignisbehandlung.
Dort wird generell sender AS Object als erster Parameter verwendet.
Da man dort "weiss", dass ein Button.Click immer ein Button ist, kann man dort schreiben:Private Sub OKCancelButton_Click(ByVal sender As Object, ByVal e As EventArgs) _ Handles OKButton.Click, CancelButton.Click Dim button = DirectCast(sender, Button) End Sub
(Das "Wissen" ist natürlich nur eine berechtigte Annahme und gilt nur, wenn man sich an die Regeln hält.)
Beide Operatoren funktonieren nur wenn die Typen direkt ableitbar sind,
und sind häufig die schnelleren.CType wäre die allgemeinste Variante.
Eingesetzt wird sie bei Werttypen (Structure) - siehe Strukturen und Klassen
Sie ist oft die langsamste - was auch daran liegt, dass Visual Basic u. U. einiges auf Vorrat dazustrickt,
weil man Strict Off und VB Classic Kompatibilität berücksichtigen muß (C# kennt das alles nicht).
Für die .NET Standardtypen kann man die eingebauten Operatoren wie CInt verwenden,
siehe Implizite und explizite Konvertierungen .
Zum Thema Delegaten:
Du hast sie oben (und anders wo) in Form von Ereignissen schon reichlich verwendet.Ereignisse (Event ) basieren auf Delegaten, die per Konvention Eventhandler am Ende heissen.
Der einfachste wäre EventHandler , wenn keine Argumente übergeben werden müssen.
Benötigt man Argumente, so werden sie per Konvention von der Klasse EventArgs abgeleitet.
Und weil das (fast) immer so ist, kann man Ereignisklassen (seit .NET 2.0) über den
EventHandler< (Of < ( TEventArgs > ) > ) -Delegat erstellen.Man kann Delegaten nicht nur für Ereignisse verwenden, sondern überall da,
wo man eine Methode übergeben will, die die Arbeit erledigt.Da .NET nur typsichere Aufrufe zulässt, müssen Signaturen der Methoden (d. h. Parametertypen)
Da man für jede Typ-Kombination einen braucht, hat man für die häufigsten Varianten in .NET 2.0
vorher festgelegt werden. Das geschieht mit der Delegate-Anweisung . Womit im Hintergrund
eine Klasse erzeugt wird, die von MulticastDelegate abgeleitet ist - EventHandler stammt auch davon ab.
die generischen Action-Delegaten , für Prozedur (Sub) Aufrufe und die Func(TResult)-Delegaten
für Funktionen (Function) eingeführt. Dazu kommen noch einige weitere wie Predicate(T) uam.
Ein Bereich, von man sie im Einsatz sieht, wären die Methoden von List(Of T) wie ForEach oder Find uam.Und da es für eine oder wenige Zeilen etwas mühsam ist, jeweils eine Methode zu erstellen,
hat man anonyme Methoden eingeführt, allgemein als Lambda-Ausdruck bezeichnet.
Die bilden wiederum die Grundlage für LINQ (aber das in einem späteren Beitrag).Für eine Standard-Anwendungsentwicklung muß man sie nicht zwangsläufig einsetzen,
auch wenn es den Code manchmal einfacher und flexibel machen kann.
Und wo man sie täglich verwendet (Ereignisse, LINQ) merkt man es meist gar nicht
(bis auf solchige wie das obige Problem ;-).Gruß Elmar