Fehlermeldung (ReleaseHdc nach GetHdc) beim Zeichnen mit GDI+

  • VB.NET
  • .NET (FX) 4.0

Es gibt 15 Antworten in diesem Thema. Der letzte Beitrag () ist von WilliamSpiderWeb.

    Fehlermeldung (ReleaseHdc nach GetHdc) beim Zeichnen mit GDI+

    Hallo zusammen,

    ich habe eine Software geschrieben, die über die RS232 Schnittstelle mit einem Mikrocontroller kommuniziert und die übermittelten Daten grafisch darstellt. Gezeichnet wird auf dem Control "Panel". Die Routine, in der das Zeichnen der Diagrammpunkte passiert sieht so aus.

    VB.NET-Quellcode

    1. Private Sub DrawDiagramPoint(MeasureValue As CheckHfpController.MeasureValueEventArgs)
    2. Dim DP As Integer ' Punktdurchmesser in Pixeln
    3. Dim BgImage As Image ' Hintergrundbild für das Diagramm
    4. Dim InvertY As Boolean ' Soll der Y-Wert Vorzeichen-invertiert aufgetragen werden?
    5. Dim YpHeight As Integer ' Gesamte Bildhöhe (Für Nullpunktumkehrung)
    6. Dim Xp0 As Integer ' X-Koordinate des 0-Punktes in Pixeln
    7. Dim Yp0 As Integer ' Y-Koordinate des 0-Punktes in Pixeln
    8. Dim XpMax As Integer ' X-Koordinate des Max-Wertes in Pixeln
    9. Dim YpMax As Integer ' Y-Koordinate des Max-Wertes in Pixeln
    10. Dim XMaxVal As Single ' X-Wert der maximalen X-Pixel-Koordinate
    11. Dim YMaxVal As Single ' Y-Wert der maximalen Y-Pixel-Koordinate
    12. ' [...] Hier werden ein paar Berechnungen mit den Punkten angestellt, die ich zur besseren Übersicht ausgeblendet habe
    13. With ScreenProduction.ProductPanels.Item(0) // Das sind meine leicht abgewandelten Panels
    14. If Not .ImageManipulated Then
    15. .Image = BgImage
    16. .ImageManipulated = True
    17. End If
    18. Dim g As Graphics = Graphics.FromImage(.Image)
    19. g.FillEllipse(Brushes.Red, New Rectangle(Xp - DP / 2, Yp - DP / 2, DP, DP))
    20. g.DrawImage(.Image, 0, 0)
    21. .Image = .Image
    22. g.Dispose()
    23. End With
    24. End Sub


    Da mein Kollege und ich uns aufgeteilt haben (er die Hardware und hardwarenahe Programmierung, ich die PC-Software), habe ich mir zum Testen ein kleines Programm geschrieben, welches die Daten vorübergehend über die RS232 Schnittstelle schubst. Einfach damit ich testen kann, auch wenn die Hardware noch nicht so weit ist. Das hat auch wunderbar funktioniert.

    Jetzt wo ich die Hardware angeschlossen und gestartet habe, bekomme ich in der Zeile "Dim g As Graphics = Graphics.FromImage(.Image)" die folgende Fehlermeldung.


    Invalid OperationException wurde nicht behandelt.
    Das Objekt wird bereits an anderer Stelle verwendet.
    Rufen Sie die ReleaseHdc-Methode auf, wenn Sie das Gradikobjekt nach der GetHdc-Methode verwenden.


    Ich vermute, dass der Fehler erst mit der Hardware auftritt, weil die ihre Daten in einem höheren Tempo rüberschiebt als ich mit meinem Testprorgamm.

    Ich habe mich versucht, in das Hdc-Thema einzulegen, hab es aber nicht verstanden.
    Vielleicht kann mir hier jemand behilflich sein. Das wäre sehr nett.

    Danke im Voraus.
    Viele Grüße,
    Alexander

    WilliamSpiderWeb schrieb:

    Das Objekt wird bereits an anderer Stelle verwendet.

    Ich habe die Zeile

    VB.NET-Quellcode

    1. .Image = BgImage
    in Verdacht, lass die mal weg.
    Was soll die Zeile

    VB.NET-Quellcode

    1. .Image = .Image

    Ansonsten mach ein kleines Testprojekt, dsas den Fehler reproduziert.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Hallo Rod,

    danke für Deine Unterstützung.

    Warum denkst Du, dass die Zeile

    VB.NET-Quellcode

    1. .Image = BgImage

    ein Problem darstellt?

    Die zweite von Dir angemerkte Zeile muss mir irgendwie da reingerutzt sein. Die ergibt auch für mich keinen Sinn.

    Ein Testprogramm ist ein bisschen schwierig, wenn ich nicht weiß, in welche Richtung ich gehen könnte.
    Wie gesagt, meine Vermutung ist, dass mir die Häufigkeit des Aufrufs Probleme bereitet. Was denkst Du?

    Coldfire schrieb:

    Mich verwirrt etwas, das die Fehlermeldung behauptet, du würdest GetHDC aufrufen, welches ich aber nirgendwo im Quellcode finden kann.


    Ich habe die Fehlermeldung so verstanden, dass ich mit GetHdc und ReleaseHdc arbeiten soll. Allerdings weiß ich nicht wie - sollte es denn nötig sein.

    [EDIT]
    Ich jetzt ein kleines Testprogramm geschrieben und es hat definitiv mit der Häufigkeit der Aufrufe zu tun. Das heißt, wird die Sub erneut aufgerufen, bevor der voherige Aufruf abgearbeitet ist, crasht es. Die Frage ist, wie kann ich das elegant lösen. :)

    Hier ist mein Testprogramm

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private m_D As Integer = 50
    3. Private m_X As Integer = 2 * m_D
    4. Private m_Y As Integer = 2 * m_D
    5. Private aTimer As System.Timers.Timer
    6. Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    7. PictureBox1.Image = My.Resources.Sanduhr
    8. 'Timer1.Interval = 1
    9. 'Timer1.Enabled = True
    10. aTimer = New System.Timers.Timer(120)
    11. AddHandler aTimer.Elapsed, AddressOf Timer1_Tick
    12. aTimer.Start()
    13. End Sub
    14. Private Sub DrawPoint()
    15. m_X = m_X + m_D
    16. If m_X > PictureBox1.Width - 2 * m_D Then
    17. m_X = 2 * m_D
    18. m_Y = m_Y + m_D
    19. End If
    20. Dim g As Graphics = Graphics.FromImage(PictureBox1.Image)
    21. g.FillEllipse(Brushes.Red, New Rectangle(m_X, m_Y, m_D, m_D))
    22. g.DrawImage(PictureBox1.Image, 0, 0)
    23. PictureBox1.Image = PictureBox1.Image
    24. g.Dispose()
    25. End Sub
    26. Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) 'Handles Timer1.Tick
    27. DrawPoint()
    28. End Sub
    29. End Class

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „WilliamSpiderWeb“ ()

    @WilliamSpiderWeb
    Ohne Testcode war das einfach ein Schuss ins Blaue.
    Sieh Dir mal dies an:
    learn.microsoft.com/de-de/dotn…ements/synclock-statement
    Ersatzweise kannst Du den Handler während der Abarbeitung temporär entfernen:

    VB.NET-Quellcode

    1. Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) 'Handles Timer1.Tick
    2. RemoveHandler aTimer.Elapsed, AddressOf Timer1_Tick
    3. DrawPoint()
    4. AddHandler aTimer.Elapsed, AddressOf Timer1_Tick
    5. End Sub
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Hallo Rod,

    Danke für den Hinweis. Das SyncBlock Statement sieht interessant aus. Ich verstehe das so, dass sobald der Codeblock betreten wird, alle Änderungen an dem geblockten Objekt pausiert werden. Wird der Codeblock verlassen, werden die pausierten Anweisungen fortgeführt - quasi wie in einer Warteschlange.

    Der Vorschlag mit dem RemoveHandler hätte zur Folge, dass ich Messwerte verliere, während die ersten Daten abgearbeitet werden.

    In diesem Fall gefällt mir Variante 1 besser.

    Ich habe das in dem Testprojekt mal so ausprobiert.

    VB.NET-Quellcode

    1. Private Sub DrawPoint()
    2. m_X = m_X + m_D
    3. If m_X > PictureBox1.Width - 2 * m_D Then
    4. m_X = 2 * m_D
    5. m_Y = m_Y + m_D
    6. End If
    7. SyncLock PictureBox1.Image
    8. Dim g As Graphics = Graphics.FromImage(PictureBox1.Image)
    9. g.FillEllipse(Brushes.Red, New Rectangle(m_X, m_Y, m_D, m_D))
    10. g.DrawImage(PictureBox1.Image, 0, 0)
    11. PictureBox1.Image = PictureBox1.Image
    12. g.Dispose()
    13. End SyncLock
    14. End Sub


    Hat aber leider nicht den gewünschten Effekt.

    WilliamSpiderWeb schrieb:

    dass ich Messwerte verliere, während die ersten Daten abgearbeitet werden.
    Ändere Deine Herangehensweise:
    Führe die Messwertaufnahme in einem anderen Thread durch, das sichert, dass nichts verloren geht.
    Packe die Messwerte mit einer entsprechende Struktur in eine List(Of T) und signalisiere der GUI per Event, dass Daten vorhanden sind.
    Die GUI arbeitet alle Daten ab, zeigt an und speichert, was anzuzeigen und abzuspeichern ist.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Coldfire schrieb:

    aber konnte eigtlich jemand die Fehlermeldung reproduzieren ?


    Ich habs nicht probiert, aber ausschliessen würde ich es nicht das so ein Fehler vorkommen kann. Ich habe die Vermutung, das er das Bild irgendwo anzeigt, aber eine Referenz hat, keine Kopie. Immerhin deutet das: Das Objekt wird bereits an anderer Stelle verwendet darauf hin.
    Zitat von mir 2023:
    Was interessiert mich Rechtschreibung? Der Compiler wird meckern wenn nötig :D

    Coldfire schrieb:

    Es mag es hier an dieser Stelle möglicherweise übergriffig erscheinen, aber konnte eigtlich jemand die Fehlermeldung reproduzieren ? Ich nämlich nicht.


    Hallo Coldfire,

    Deiner Frage entnehme ich, dass du das Testprogramm genommen und ausprobiert hast. Die Abarbeitungsgeschwindigkeit der Routine, die vom Timer aufgerufen wird, ist ja stark abhängig von der Performance Deiner Maschine.

    Wenn Du die Zahl in der Zeile

    VB.NET-Quellcode

    1. aTimer = New System.Timers.Timer(120)

    von 120 auf etwas kleineres setzt, wird bei Dir bestimmt auch irgendwann eine Fehlermeldung geworfen werden. Bei mir lag bei 120 ms die Grenze. Anhand der Ellipsen, die in meine PictureBox gemalt wurden, habe ich gesehen, dass der Fehler mal nach 5 Durchgängen und mal erst nach 20 Durchgängen geworfen wurde.

    WilliamSpiderWeb schrieb:

    aTimer = New System.Timers.Timer(120)


    Wenn du einen System.Windows.Forms.Timer nimmst, anstatt des System.Timers.Timer sollte das nicht vorkommen. Hab einen Forms-Timer mit interval 1 ticken gehabt, da flog mir keine Exception um die Ohren. Forms.Timer-Events laufen immer im UI Thread, Timers.Timer-Events entweder im UI Thread oder einem nebenläufigen Thread. Da könnte ein Zusammenhang bestehen. Was genau da nun die Ursache ist, kann ich allerdings nicht sagen. Bei läufts mit dem Forms-Timer stabil, habe das 6 Minuten laufen lassen mit interval = 1.
    Zitat von mir 2023:
    Was interessiert mich Rechtschreibung? Der Compiler wird meckern wenn nötig :D

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „DTF“ ()

    Coldfire schrieb:

    dein Rehner ist mindestens 120 mal schneller als der von William.


    Habs nicht auf meinem HighEnd-Rechner laufen gehabt. Lief auf so nem ollen Ding mit Ryzen 5600G + RTX 2060, RAM tuckert auch nur mit 3.2GHz rum.

    PS @Coldfire Mit dem Timers.Timer hatte auch genau die selbe Exception, dauert erst ein wenig, aber nachdem ich von der PB Dock auf Fill und SizeMode auf StretchImage hatte und das Form maximiert(UHD) direkt beim starten hats geknallt, aber mit dem Forms.Timer lief es stabil. Ich denke durch die engere Kopplung zum UI-Thread beim Forms-Timer rappelts nicht.
    Zitat von mir 2023:
    Was interessiert mich Rechtschreibung? Der Compiler wird meckern wenn nötig :D

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „DTF“ ()

    Vollzitat eines Vorposts an dieser Stelle entfernt ~VaporiZed

    Der System.Windows.Forms.Timer läuft ja mit der UI und wird entsprechend "gedrosselt". Im ersten Anlauf hatte ich den Timer verwendet, was dazu geführt hat, dass die Zeichenoperationen mit 1 ms nicht häufiger aufgerufen wurden als mit 120 ms.

    Ist aber an der Stelle auch Nebensache. Die Korrekte Lösung ist das Trennen der Daten von der Anzeige. Und das werde ich jetzt auch machen. Danke an Alle.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „VaporiZed“ ()