PictureBox Image mittels GDI+ zeichnen

  • VB.NET

Es gibt 19 Antworten in diesem Thema. Der letzte Beitrag () ist von nafets3646.

    PictureBox Image mittels GDI+ zeichnen

    Hey Community!

    Ich versuche einen "Fraktal-Zeichner" zu coden, genauer gesagt das Hopalong-Fraktal.

    Ich habe also meine Form, ein paar TBs für die Parameter (a, b, c, limit), und eine PB(DrawingField) für das Fraktal.
    Um in die PB zu zeichnen, kann ich ja einfach:

    VB.NET-Quellcode

    1. Dim img as New Bitmap(DrawingField.Width, DrawingField.Height)
    2. Using graph As Graphics = Graphics.FromImage(img)
    3. 'Algorithm
    4. End Using
    5. DrawingField.Image = img

    Aber dann, sieht man ja leider nicht die 'schöne' Entwicklung, sondern nur das fertige Bild :/

    Wenn ich es aber direkt auf die PB zeichne, da geht es zwar, aber dann kommt folgendes Problem:
    Gezeichnet werden soll ja im Paint-Event, aber nur auf ButtonClick --> Lösung: Globale Variable (Boolean)
    Aber dann wird ja bei jedem Refresh entweder das Bild (teilweise) entfernt, oder immer wieder neu gezeichnet :(
    »There's no need to "teach" atheism. It's the natural result of education without indoctrination.« — Ricky Gervais
    Das geht auch nicht anders. Zumindest nicht so einfach.
    Wenn Du eine Entwicklung anzeigen willst, solltest Du das anders machen.
    Lass den Algorithmus durchlaufen, generiere jeden Schritt und speichere die nötigen Informationen in einer List(Of Irgendwas).
    Wenn der Algorithmus fertig ist, verwendest Du einen Timer, um durch die Schritte zu iterieren. Bei jedem Tick wechselst Du zum nächsten Schritt. Gleichzeitig rufst Du DrawingField.Invalidate() auf. Dadurch wird die PictureBox neu gezeichnet. Du abbonierst das Paint-Event der PictureBox und zeichnest dort den aktuellen Schritt.

    Nimm nicht Application.DoEvents(). Das ist unauber.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    @Niko Ortner:
    - Warum eine List und kein Array ?
    - Warum einen Timer und nicht eine simple For-Schleife ?
    - Wird nicht das dann ziemlich laggy, weil es immer so 100000-500000 Iterationen sind, und sooft das .Invalidate kann ich mir nicht gesund vorstellen :)
    »There's no need to "teach" atheism. It's the natural result of education without indoctrination.« — Ricky Gervais
    Naja, wenn Du vorher schon weißt, wie viele Schritte der Algorithmus generieren wird, dann ist ein Array natürlich besser geeignet.

    Die GUI hat einen Thread. Der muss alles machen. Mausinteraktionen verarbeiten, zeichnen und natürlich Deinen Algorithmus ausführen. Wenn der Thread gerade Deinen Algorithmus ausführt, kann er nicht gleichzeitig neu zeichnen. Das könnte man mit Application.DoEvents() erzwingen, aber wie gesagt, das ist extrem unsauber.

    Ein Beispiel für das, was ich meine:
    Der Algorithmus soll alle Ganzzahlen von 0 bis n generieren. Diese einzelnen Schritte sollen nacheinander angezeigt werden:

    VB.NET-Quellcode

    1. Dim WithEvents StepTimer As New Timer With {.Interval = 100, .Enabled = False}
    2. Dim CurrentStep As Integer = -1
    3. Dim Steps As AlgoStep()
    4. Protected Overrides Sub OnShown(e As System.EventArgs)
    5. Algo()
    6. StepTimer.Enabled = True
    7. MyBase.OnShown(e)
    8. End Sub
    9. Private Sub Algo()
    10. Dim MaxValue As Integer = 20
    11. Steps = New AlgoStep(MaxValue) {}
    12. For i = 0 To MaxValue
    13. Dim CurrentStepValue = i.ToString 'Also vom Prinzip her das, was der Algorithmus ausspuckt.
    14. Steps(i) = New AlgoStep(CurrentStepValue)
    15. Next
    16. End Sub
    17. Private Sub NextStep() Handles StepTimer.Tick
    18. If CurrentStep < Steps.Count - 1 Then
    19. CurrentStep += 1
    20. Else
    21. StepTimer.Enabled = False
    22. End If
    23. DrawField.Invalidate()
    24. End Sub
    25. Private Sub PBoxPaint(sender As Object, e As PaintEventArgs) Handles DrawField.Paint
    26. If Not CurrentStep = -1 Then
    27. e.Graphics.DrawString(Steps(CurrentStep).Value, Me.Font, Brushes.Red, 0, 0)
    28. End If
    29. End Sub
    30. Class AlgoStep
    31. Public Value As String
    32. Public Sub New(NewValue As String)
    33. Value = NewValue
    34. End Sub
    35. End Class
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Okay, ich werde das mal umarbeiten... :/

    Aber wird nicht im Paint-Event immer nur der neuste Punkt gezeichnet, d.h. das alle anderen bei Fensterüberlappung bspw. weg sind ?
    »There's no need to "teach" atheism. It's the natural result of education without indoctrination.« — Ricky Gervais
    @ThePlexian:: Das Paint-Event muss das aktuelle Bild "aus dem Gedächtnis" zeichnen können.
    Stell also in der Klasse nicht nur die Daten, sondern auch eine Reihe von Flags und Countern bereit, die das von-bis und das ob-ob nicht steuern.
    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!
    @RodFromGermany:
    Ich habe kein Ahnung was flags sind, muss ich nachher googeln.


    Ich hatte da aber noch eine Idee:
    2 Globale Boolean - "drawing_enabled" und "painting_completed"

    Auf Button Click wird drawing_enabled auf true gesetzt, und nach Fertigstellung wieder auf false

    Painting_completed ist false bis die Zeichnung fertig ist.

    Im paintevent:
    Gleichzeitig in pb und bitmap malen

    wenn drawing_enabled und nicht painting_completed dann zeichnen sonst wenn drawing_enabled und painting_completed dann bitmap laden
    :?:

    Sry für diese kindersprache, ich kann grade keine snippets zeigen weil ich nicht zuhause bin ;)
    »There's no need to "teach" atheism. It's the natural result of education without indoctrination.« — Ricky Gervais
    Wird das denn bei den vielen Iterationen, wenn du sie im RAM hälst, nicht etwas zu viel? Ich würde es eher so machen (Pseudocode):

    VB.NET-Quellcode

    1. Dim bmp As New Bitmap(Size)
    2. For Counter = 1 To IterationCount
    3. Using g = Graphics.FromImage(bmp)
    4. DrawFractalIteration(g, IterationCount)
    5. End Using
    6. PictureBox1.Image = bmp
    7. Next
    @sonne75:
    Oh :D ^^

    @nafets3646:
    Aber ich kann soweit ich weiß nicht step x des fraktals berechnen, ohne step x-1 berechnet zu haben :-/
    »There's no need to "teach" atheism. It's the natural result of education without indoctrination.« — Ricky Gervais

    ThePlexian schrieb:

    ohne step x-1 berechnet zu haben
    Ja und?

    nafets3646 schrieb:

    VB.NET-Quellcode

    1. DrawFractalIteration(g, IterationCount)
    ==>

    VB.NET-Quellcode

    1. DrawFractalIteration(g, i)
    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!
    Das habe ich doch so gemacht :D
    Mach mal ne Form mit nem Button und ner TextBox und mach das in den Code:

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    3. Dim t As New Threading.Thread(AddressOf Me.Generate)
    4. t.Start()
    5. End Sub
    6. Dim Iterations As Integer = 50
    7. Private Sub Generate() 'Generiert unser Pseudofraktal
    8. Dim abc As String = "" 'Wie: Dim bmp As New Bitmap(Size)
    9. For Counter = 1 To Iterations 'Wie oben
    10. abc = abc & "A" 'Wie: DrawFractalIteration(g, IterationCount)
    11. Me.Invoke(Sub() Me.TextBox1.Text = abc) 'Wie: PictureBox1.Image = bmp
    12. Threading.Thread.Sleep(5) 'Um Arbeit zu simulieren, später nicht nötig
    13. Next
    14. End Sub
    15. End Class
    Genau das darf man nicht machen (@nafets3646:).
    Was allerdings geht: Die Schritte "on the fly" berechnen.

    VB.NET-Quellcode

    1. Dim WithEvents StepTimer As New Timer With {.Interval = 100, .Enabled = True}
    2. Dim CurrentStep As AlgoStep
    3. Dim CurrentStepIndex As Integer = -1
    4. Private Sub Algo()
    5. Dim CurrentStepValue = CurrentStepIndex.ToString 'Also vom Prinzip her das, was der Algorithmus ausspuckt.
    6. CurrentStep = New AlgoStep(CurrentStepValue)
    7. End Sub
    8. Private Sub NextStep() Handles StepTimer.Tick
    9. If CurrentStepIndex < Steps.Count - 1 Then
    10. CurrentStepIndex += 1
    11. CurrentStep = Algo()
    12. Else
    13. StepTimer.Enabled = False
    14. End If
    15. DrawField.Invalidate()
    16. End Sub
    17. Private Sub PBoxPaint(sender As Object, e As PaintEventArgs) Handles DrawField.Paint
    18. If Not CurrentStepIndex = -1 Then
    19. e.Graphics.DrawString(CurrentStep.Value, Me.Font, Brushes.Red, 0, 0)
    20. End If
    21. End Sub
    22. Class AlgoStep
    23. Public Value As String
    24. Public Sub New(NewValue As String)
    25. Value = NewValue
    26. End Sub
    27. End Class
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    @nafets3646: Dein Code:

    VB.NET-Quellcode

    1. Dim bmp As New Bitmap(Size)
    2. For Counter = 1 To IterationCount
    3. Using g = Graphics.FromImage(bmp)
    4. DrawFractalIteration(g, IterationCount)
    5. End Using
    6. PictureBox1.Image = bmp
    7. Next

    Das geht nicht so, wie Du das willst.
    Die PictureBox muss sich neu zeichnen. Das macht sie entweder (1) sofort oder (2) sobald die entsprechende Meldung in der Query verarbeitet wird.
    Bei 2. gibt's das Problem, dass das Bild IterationCount-Mal überschrieben wird und dann wird die PictureBox neu gezeichnet. Man sieht also nur das letzte Bild.
    Bei 1. gibt's das Problem, dass man ganz kurz das aktuelle Bild sieht, und dann kommt auch schon das nächste... und das nächste. Das heißt, man kann die Geschwindigkeit nicht regulieren. Man könnte den Thread mit Threading.Thread.Sleep() anhalten, um das zu verlangsamen, aber das wäre unsauber, weil dann die ganze Form hängt.

    Man muss die Infos eines Schrittes zwischenspeichern und dann wieder zur Message-Query zurückkehren (also die methode verlassen), damit sich die GUI neu zeichnen kann. Und dann kann man mit dem Paint-Event mitzeichnen.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Sry iwie ist das alles merkwürdig :o

    Bei mir ist der letzte Post vom 29.08, aber ich habe danach noch einiges gepostet :o
    »There's no need to "teach" atheism. It's the natural result of education without indoctrination.« — Ricky Gervais