none
EventHandler Warnung RRS feed

  • 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

    Montag, 30. August 2010 12:54

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
    Donnerstag, 2. September 2010 10:11

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-Elements

    Gruß Elmar

    Montag, 30. August 2010 14:30
  • 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

    Montag, 30. August 2010 15:10
  •  
    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

    Montag, 30. August 2010 15:37
  • Hallo Peter,

    danke für den Tipp!

    Gruß
    Marcus

    Montag, 30. August 2010 15:46
  • Hat keiner von Euch eine Idee? Schade... Trotzdem Danke für Eure Mühe!

    Gruß
    Marcus

    Mittwoch, 1. September 2010 17:34
  • 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

    Mittwoch, 1. September 2010 17:57
  • 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...
    Donnerstag, 2. September 2010 07:29
  • 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
    Donnerstag, 2. September 2010 10:11
  • 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ß
    Marcus

    P. 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...
    Donnerstag, 2. September 2010 12:58
  • 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)
    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.

    Da man für jede Typ-Kombination einen braucht, hat man für die häufigsten Varianten in .NET 2.0
    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

    Donnerstag, 2. September 2010 15:02