Prioritäten der Events Timer.Tick, Form.MouseMove und Form.Paint

  • VB.NET

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

    Prioritäten der Events Timer.Tick, Form.MouseMove und Form.Paint

    Guten Abend.

    Ich stehe vor einem kleinen Problem.
    Die Reihenfolge, oder besser: die Prioritäten, die den Events

    VB.NET-Quellcode

    1. Me.MouseMove
    2. TimerStuff.Tick
    3. Me.Paint

    zugeordnet werden passt mir nicht ganz ins Konzept.

    Folgendes Beispiel:

    VB.NET-Quellcode

    1. Dim WithEvents TimerStuff As New Timer With {.Interval = 30, .Enabled = True}
    2. Dim PX As Integer = 0
    3. Dim PY As Integer = 0
    4. Dim X As Integer = 0
    5. Private Sub MeMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseMove
    6. PX = e.X
    7. PY = e.Y
    8. Me.Invalidate()
    9. End Sub
    10. Private Sub TimerTick() Handles TimerStuff.Tick
    11. X += 1
    12. If X > Me.ClientRectangle.Width Then
    13. X = 0
    14. End If
    15. Me.Invalidate()
    16. End Sub
    17. Private Sub MePaint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles Me.Paint
    18. e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    19. 'Mausposition mit gekreuzten Linien darstellen
    20. e.Graphics.DrawLine(Pens.Red, PX, 0, PX, Me.ClientRectangle.Height)
    21. e.Graphics.DrawLine(Pens.Red, 0, PY, Me.ClientRectangle.Width, PY)
    22. 'Irgendwas aufwendiges zeichnen
    23. For i As Integer = 0 To Me.ClientRectangle.Width
    24. e.Graphics.DrawLine(Pens.Green, 0, 0, X + i, Me.ClientRectangle.Height)
    25. e.Graphics.FillRectangle(Brushes.Green, X + i, Me.ClientRectangle.Height - 20, 20, 20)
    26. Next
    27. End Sub


    Wenn das ausgeführt wird (Me.DoubleBuffered auf True), dann werden ein paar grüne Rechtecke am unteren Rand gezeichnet und ein paar Linien von links oben zu den jeweilgen Punkten. Es werden auch zwei rote Linien horizontal und vertikal gezeichnet, um die Mausposition anzuzeigen.
    Wenn man die Maus nun auf der Form bewegt und in Bewegung hält, dann friert alles ein, was in der Sub TimerTick passiert (nämlich einen Wert inkrementieren).
    Das MouseMove Event "blockiert" sozusagen durch sein häufiges Auftreten das Tick Event des Timers.
    Da stellt sich mir aber die Frage wie das sein kann.
    Denn irgendwann ist der Intervall des Timers zu Ende und der Event wird ausgelöst und befindet sich in der "Warteschlange" (Ich glaube das hieß Message Queue).
    Durch Me.Invalidate() in der Sub MeMouseMove wurde auch das Paint Event ausgelöst. Abhängig, ob das vor oder nach dem Tick passiert ist wird beim Verlassen der Sub MeMouseMove entweder das Paint Event, oder das Tick Event behandelt.
    Das Problem ist allerdings: Es wird immer das Paint Event behandelt und dann sofort wieder ein neues MouseMove Event behandelt, ohne Acht auf das inzwischen schon lange eingetretene Tick Event zu geben.

    Meine Frage: Lässt sich das irgendwie steuern?
    Das Problem ist nämlich, dass die Animation weiterlaufen soll und gleichzeitig andere Dinge verschoben werden können sollen (... mögen müssen ^^).
    Falls es sich nicht steuern lässt: wie lässt sich das Problem anders lösen?
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    ErfinderDesRades schrieb:

    mir scheint, die Zeichnerei in deinem Paint-Event ist einfach zu aufwändig.

    Das ist nun wirklich nur ein Bruchteil seines Problems

    Niko Ortner schrieb:

    Wenn man die Maus nun auf der Form bewegt und in Bewegung hält, dann friert alles ein, was in der Sub TimerTick passiert (nämlich einen Wert inkrementieren).Das MouseMove Event "blockiert" sozusagen durch sein häufiges Auftreten das Tick Event des Timers.Da stellt sich mir aber die Frage wie das sein kann.

    Hallo, hast Du Dir eigentlich jemals Deinen eigenen Code kritisch angeschaut und gemerkt was Du Deinem Windows System da so zumutest ?

    Jedes MouseMove an sich ist schon extrem aufwändig, weil dazu im Hintergrund WM_Messages ausgelöst werden. Pack nen total überflüssigen 30ms Timer dazu und zeichne dann auch noch jeweils die ganze Form neu, natürlich alles nur auf dem GUI Thread. Zu allem Überfluss noche eine total sinnlose überfrachtete Paint Logik: ... und da wunderst Du Dich daß Dein System Dicke Backen macht ???

    Mein Vorschlag: kauf Dir einen neuen PC, so 2 Xeons + tolle Grafikkarte + ne Menge Speicher sollten die Performance doch um Einiges verbessern. Problem gelöst !

    Ansonsten, überleg Dir evtl mal wo Deine grössten Performance Schlucker sitzen und wie man die optimieren könnte:

    _ überlege Dir Deine Effekte und den Aufwand den sie verursachen genau: Zeichnen von Quadraten im Pixelabstand sind Blödsinn
    - zeichne nur in dem sichtbaren Rand: momentan zeichnest Du aus Faulheit mit der For-Schleife über den Clientbereich hinweg
    - muss wirklich in jedem MouseMove etwas geschehen , und genau was ???
    - wieso der Timer, wenn Du Positionsprüfungen im MouseMove Event abfangen kannst, sogar noch besser im MouseLeave, MouseEnter Event
    - was soll das Zeichnen der ganzen Form??? zeichne nur die Bereiche die wirklich neu gezeichnet werden müssen

    Letzteres wird vermutlich den drastischten Performanceschub geben: je kleiner Du die neu zu zeichnenden Rechtecke bestimmtst und nur diese invalidierst + zeichnest, desto kürzer das Paint Event

    Und nur das zählt ...
    Der von mir gepostete Code war auch nur ein Beispiel und ist deshalb nicht sinnvoll.
    Und der Code im Paint Event ist aufwändig, weil er einfach aufwändig ist. Das ist im Anwendungsfall auch so.

    Einen neuen Laptop kaufe ich bestimmt nicht wegen so 'ner sache.

    Bevor ich überlege erkläre ich mal genau was ich machen möchte:
    Ich möchte das inzwischen schon lange auf dem Friedhof liegende Programm Wave Simulator nochmal neu machen und dabei einige Dinge ändern. Und mir ist beim Testen aufgefallen, dass beim Herumziehen von Dingen auf dem Spielfeld die Simulation hängen bleibt (das phänomen, das ich meine).
    Und da ich nicht weiß, wie ich das lösen könnte (ohne einen Super Ultra 1337 PC kaufen zu müssen) habe ich gegoogelt (ohne Erfolg) und dann hier nachgefragt.

    Also ich überlege:
    -Die Kreise, die gezeichnet werden sind in der Tat sehr aufwändig, aber es ist nunmal Sinn des Programmes Kreise zu zeichnen.
    -Die Kreise werden auch im Programm teilweise außerhalb des sichtbaren Randes gezeichnet. Das ist allerdings nicht vermeidbar (und wenn der Radius die Diagonale + 10 überschreitet wird der Kreis entfernt).
    -Nicht in jedem, aber in den Situationen, wenn ein Objekt bewegt wird. Das ausgewählte Objekt wird nämlich 1. durch zwei Linien markiert, 2. neu gezeichnet.
    -Wie mir das MouseLeave bzw. MouseEnter Event hilft ist mir nicht ganz klar.
    Denn: Sobald die Maus bewegt wird soll neu gezeichnet werden, unabhängig davon, ob der Timer läuft oder nicht.
    Im der Sub TimerTick die Mausposition festzustellen ist auch möglich, aber das bringt zwei Nachteile:
    1. wird die Position dann nur neu gesetzt, wenn der Timer läuft, und
    2. passiert das im Falle eines großen Intervalles in sehr langen Abständen, was optisch nicht gerade schön aussieht.
    -Da die Kreise mehr oder weniger über die ganze Größe des Spielfeldes verteilt sind und recht häufig auftreten macht es wenig Sinn für jeden Kreis separat die Koordinaten für das zu invalidierende Rectangle auszurechnen, PictureBox_Bla.Invalidate(DasRechteck) aufzurufen und dann nochmal im der MePaint Sub alle Kreise zeichnen zu lassen.
    Ich denke da überwiegt die Dauer der Berechnung aller Koordinaten der Zeitersparnis des gezielten Invalidierens.
    Und es müssten die alten Koordinaten ebenfalls gespeichert werden, da diese ebenfalls invalidiert werden müssen, damit dort der Kreis vom Bildschirm verschwindet.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Niko Ortner schrieb:

    Der von mir gepostete Code war auch nur ein Beispiel und ist deshalb nicht sinnvoll.

    Aha, dann frag ich mich doch mal: warum postest Du ihn dann eigentlich überhaupt ? Wenn Dein eigentlicher Code doch Kreise malt statt Rechtecke, Du natürlich nicht sowohl beim Timer als auch beim MouseMove die ganze Form invalidierst, etc. etc. ???

    Ich schlag vor Du kommst mal wieder wenn Du Dir über solche geringfügigen Details abschliessende Gedanken gemacht hast. Vorher verschwendest Du nur jedermanns Zeit ...
    Weil genauso niemand glücklich ist, wenn ich den Code eins zu eins rüberkopier, weil niemand weiß was welches Objekt macht.
    Darum ein Codebeispiel, das genau diesen Fehler reproduziert.
    Aber lass es mich umschreiben:

    VB.NET-Quellcode

    1. Dim WithEvents TimerStuff As New Timer With {.Interval = 30, .Enabled = True}
    2. Dim PX As Integer = 0
    3. Dim PY As Integer = 0
    4. Private Sub MeMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseMove
    5. PX = e.X
    6. PY = e.Y
    7. Me.Invalidate()
    8. End Sub
    9. Private Sub TimerTick() Handles TimerStuff.Tick
    10. EinPaarWerteAddieren()
    11. Me.Invalidate()
    12. End Sub
    13. Private Sub MePaint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles Me.Paint
    14. KompliziertesZeichnen()
    15. End Sub


    In wie fern das aufwändige Zeichnen mit dem oben genannten Problem zu tun hat weiß ich aber nicht.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    ErfinderDesRades schrieb:

    GDI ist einfach ganz schnell überfordert damit, große Flächen in so schneller Folge neu zu zeichnen

    Richtig, deswegen zeichnet man ja auch nie grosse Flächen immer wieder im Mousemove Event:
    - man zeichnet nur die (möglichst winzigen) Flächen die sich geändert haben (Invalidate auf möglichst schmale Rechtecke, nie auf die ganze Form)
    - oder benutzt Block-Transfers wenn sich grosse Bereiche verschieben

    Ansonsten gilt immer noch was ich im ersten Post geschreiben hatte
    Hier das Problem nochmal in Kurzfassung:
    Paint Event und MouseMove Event werden permanent ausgelöst, und dabei wird das Tick Event des Timers ignoriert.



    Und um auf Kangaroo zu antworten: Das einzige, was ich gezielt invalidieren kann sind die beiden Linien:

    VB.NET-Quellcode

    1. Private Sub MeMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseMove
    2. 'Zuerst die alte Position invalidieren, damit's verschwindet
    3. Me.Invalidate(New Rectangle(PX, 0, 1, Me.ClientRectangle.Height))
    4. Me.Invalidate(New Rectangle(0, PY, Me.ClientRectangle.Width, 1))
    5. 'Neue Positionen übernehmen
    6. PX = e.X
    7. PY = e.Y
    8. 'Neue Positionen invalidieren, damit's dort sichtbar wird
    9. Me.Invalidate(New Rectangle(PX, 0, 1, Me.ClientRectangle.Height))
    10. Me.Invalidate(New Rectangle(0, PY, Me.ClientRectangle.Width, 1))
    11. End Sub


    Das hat aber nur in einem Fall einen Vorteil: Wenn soziemlich nix auf dem Feld los ist. Und dann wäre sowiso genug Rechenleistung vorhanden um alles auf einmal zu invalidieren. Aber ich lasse es mal so.

    Das Problem ist ganz einfach:
    Wenn ich viele Objekte auf dem Bildschirm habe lohnt es sich nicht mehr alle Bereiche einzeln zu invalidieren,
    weil die zusätzliche Zeit, die es benötigen würde die Rechtecke auszurechnen wohl die
    Zeitersparnis, die man erhält, wenn man nicht alles neu zeichnen muss,
    übersteigt.
    Beziehungsweise werden die Objekte irgendwann sowiso so groß, dass sie über das Feld hinausragen und erst wieder alles invalidiert wird.

    Und: Das klärt immer noch nicht warum das Tick Event ignoriert wird.
    So kann man vielleicht vorerst so weit gehen, dass es egal ist, aber irgendwann wird man wieder zu dem Punkt kommen, wo so viel gezeichnet wird, dass genau dieser Effekt wieder auftritt.
    Und dann darf ich nochmal hier nachfragen.

    Und meine Theorie zu überprüfen habe ich getestet, wie viel Zeitersparnis das Verwenden von gezieltem Invalidieren bringt.
    Hier der Code Allgemein (Me.Doublebffered natürlich auf True):
    Klasse "Emitters"

    VB.NET-Quellcode

    1. Public Class Emitters
    2. Public Class PulseType
    3. Public OnCount(7) As Boolean
    4. Public Factor As Integer
    5. Public Break As Integer
    6. Public Sub New(ByVal NewFactor As Integer, ByVal NewBreak As Integer, ByVal NewOn1 As Boolean, ByVal NewOn2 As Boolean, ByVal NewOn3 As Boolean, ByVal NewOn4 As Boolean, ByVal NewOn5 As Boolean, ByVal NewOn6 As Boolean, ByVal NewOn7 As Boolean, ByVal NewOn8 As Boolean)
    7. OnCount = New Boolean() {NewOn1, NewOn2, NewOn3, NewOn4, NewOn5, NewOn6, NewOn7, NewOn8}
    8. Factor = NewFactor
    9. Break = NewBreak
    10. End Sub
    11. Public Function GetString() As String
    12. Return Factor.ToString & "/" & (Break + 1).ToString & "/" & If(OnCount(0), "1", "0") & If(OnCount(1), "1", "0") & If(OnCount(2), "1", "0") & If(OnCount(3), "1", "0") & If(OnCount(4), "1", "0") & If(OnCount(5), "1", "0") & If(OnCount(6), "1", "0") & If(OnCount(7), "1", "0")
    13. End Function
    14. End Class
    15. Public MustInherit Class WaveBaseClass
    16. Public PositionX As Integer
    17. Public PositionY As Integer
    18. Public MaximumRadius As Double
    19. Public AutoMaximum As Boolean
    20. Public WaveColor As Color
    21. Public WaveWidth As Single
    22. Public Tag As String = "WaveBaseClass"
    23. Public Function CreateWave() As SingleWave
    24. Return New SingleWave(PositionX, PositionY, MaximumRadius, AutoMaximum, WaveColor, WaveWidth)
    25. End Function
    26. End Class
    27. Public Class SingleWave
    28. Inherits WaveBaseClass
    29. Public Shared Property SharedTag As String = "SingleWave"
    30. Public Radius As Single
    31. Public Sub New(ByVal NewPositionX As Integer, ByVal NewPositionY As Integer, ByVal NewMaximumRadius As Double, ByVal NewAutoMaximum As Boolean, ByVal NewWaveColor As Color, ByVal NewWaveWidth As Single, Optional ByVal InitialRadius As Single = 0)
    32. Tag = SharedTag
    33. PositionX = NewPositionX
    34. PositionY = NewPositionY
    35. Radius = InitialRadius
    36. MaximumRadius = NewMaximumRadius
    37. AutoMaximum = NewAutoMaximum
    38. WaveColor = NewWaveColor
    39. WaveWidth = NewWaveWidth
    40. End Sub
    41. Public Shared Function RadToDegree(ByVal RadAngle As Double) As Double
    42. Return (RadAngle * 360.0) / (Math.PI * 2.0)
    43. End Function
    44. End Class
    45. Public Class Pulser
    46. Inherits WaveBaseClass
    47. Public Shared Property SharedTag As String = "Pulser"
    48. Public Delay As Integer
    49. Public Counter As Integer
    50. Public Sub New(ByVal NewPositionX As Integer, ByVal NewPositionY As Integer, ByVal NewMaximumRadius As Double, ByVal NewAutoMaximum As Boolean, ByVal NewWaveColor As Color, ByVal NewWaveWidth As Single, ByVal NewDelay As Integer)
    51. Tag = "Pulser"
    52. PositionX = NewPositionX
    53. PositionY = NewPositionY
    54. MaximumRadius = NewMaximumRadius
    55. AutoMaximum = NewAutoMaximum
    56. WaveColor = NewWaveColor
    57. WaveWidth = NewWaveWidth
    58. Delay = NewDelay
    59. Counter = 0
    60. End Sub
    61. End Class
    62. Public Class IndividualPulser
    63. Inherits WaveBaseClass
    64. Public Shared Property SharedTag As String = "IndividualPulser"
    65. Public EmitterPulseType As PulseType
    66. Public PartCount As Integer
    67. Public Count As Integer
    68. Public Sub New(ByVal NewPositionX As Integer, ByVal NewPositionY As Integer, ByVal NewMaximumRadius As Double, ByVal NewAutoMaximum As Boolean, ByVal NewWaveColor As Color, ByVal NewWaveWidth As Single, ByVal NewPulseType As PulseType)
    69. Tag = SharedTag
    70. PositionX = NewPositionX
    71. PositionY = NewPositionY
    72. MaximumRadius = NewMaximumRadius
    73. AutoMaximum = NewAutoMaximum
    74. WaveColor = NewWaveColor
    75. WaveWidth = NewWaveWidth
    76. EmitterPulseType = NewPulseType
    77. PartCount = 0
    78. Count = 0
    79. End Sub
    80. End Class
    81. Public Class OnHitPulser
    82. Inherits WaveBaseClass
    83. Public Shared Property SharedTag As String = "OnHitPulser"
    84. Public Delay As Integer
    85. Public Counter As Integer
    86. Public Sub New(ByVal NewPositionX As Integer, ByVal NewPositionY As Integer, ByVal NewMaximumRadius As Double, ByVal NewAutoMaximum As Boolean, ByVal NewWaveColor As Color, ByVal NewWaveWidth As Single, ByVal NewDelay As Integer)
    87. Tag = SharedTag
    88. PositionX = NewPositionX
    89. PositionY = NewPositionY
    90. MaximumRadius = NewMaximumRadius
    91. AutoMaximum = NewAutoMaximum
    92. WaveColor = NewWaveColor
    93. WaveWidth = NewWaveWidth
    94. Delay = NewDelay
    95. Counter = 0
    96. End Sub
    97. Public Function WasIntersect(ByVal TargetedWave As SingleWave, ByVal Increment As Single) As Boolean
    98. Dim Difference As Double = Math.Sqrt((TargetedWave.PositionX - PositionX) ^ 2 + (TargetedWave.PositionY - PositionY) ^ 2) - TargetedWave.Radius
    99. Return Difference >= -Increment And Difference < 0
    100. End Function
    101. End Class
    102. End Class


    'Hauptformular

    VB.NET-Quellcode

    1. Public Class Form1
    2. Dim WithEvents TimerStuff As New Timer With {.Interval = 30, .Enabled = True}
    3. Dim PX As Integer = 0
    4. Dim PY As Integer = 0
    5. Dim AllWaves As New List(Of Emitters.SingleWave)
    6. Dim AllEmitters As New List(Of Emitters.WaveBaseClass)
    7. Dim RadiusIncrement As Single = 2
    8. Private Sub MeMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseMove
    9. Me.Invalidate(New Rectangle(PX, 0, 1, Me.ClientRectangle.Height))
    10. Me.Invalidate(New Rectangle(0, PY, Me.ClientRectangle.Width, 1))
    11. PX = e.X
    12. PY = e.Y
    13. Me.Invalidate(New Rectangle(PX, 0, 1, Me.ClientRectangle.Height))
    14. Me.Invalidate(New Rectangle(0, PY, Me.ClientRectangle.Width, 1))
    15. End Sub
    16. Private Function GetMaximumRadius() As Double
    17. Return Math.Sqrt((Me.ClientRectangle.Width) ^ 2 + (Me.ClientRectangle.Height) ^ 2) + 10
    18. End Function
    19. Private Sub MeMouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseDown
    20. Select Case e.Button
    21. Case Windows.Forms.MouseButtons.Left
    22. AllWaves.Add(New Emitters.SingleWave(e.X, e.Y, GetMaximumRadius, True, Color.Green, 1, 0))
    23. Case Windows.Forms.MouseButtons.Right
    24. AllEmitters.Add(New Emitters.Pulser(e.X, e.Y, GetMaximumRadius, True, Color.Red, 1, 40))
    25. End Select
    26. End Sub
    27. Private Sub TimerTick() Handles TimerStuff.Tick
    28. For Each i As Emitters.WaveBaseClass In AllEmitters
    29. Select Case i.Tag
    30. Case Emitters.Pulser.SharedTag
    31. With DirectCast(i, Emitters.Pulser)
    32. If .Counter = 0 Then
    33. AllWaves.Add(.CreateWave)
    34. End If
    35. .Counter += 1
    36. If .Counter >= .Delay Then
    37. .Counter = 0
    38. End If
    39. End With
    40. Case Emitters.IndividualPulser.SharedTag
    41. With DirectCast(i, Emitters.IndividualPulser)
    42. If .PartCount = 0 Then
    43. If .EmitterPulseType.OnCount(.Count) Then
    44. AllWaves.Add(.CreateWave)
    45. End If
    46. .Count += 1
    47. If .Count > .EmitterPulseType.Break Then
    48. .Count = 0
    49. End If
    50. End If
    51. .PartCount += 1
    52. If .PartCount >= .EmitterPulseType.Factor Then
    53. .PartCount = 0
    54. End If
    55. End With
    56. Case Emitters.OnHitPulser.SharedTag
    57. With DirectCast(i, Emitters.OnHitPulser)
    58. If .Counter = 0 Then
    59. Dim WillIntersect As Boolean = False
    60. For j As Integer = 0 To AllWaves.Count - 1
    61. If .WasIntersect(AllWaves(j), RadiusIncrement) Then
    62. WillIntersect = True
    63. Exit For
    64. End If
    65. Next
    66. If WillIntersect Then
    67. AllWaves.Add(.CreateWave)
    68. .Counter += 1
    69. End If
    70. ElseIf .Counter >= .Delay - 1 Then
    71. .Counter = 0
    72. Else
    73. .Counter += 1
    74. End If
    75. End With
    76. End Select
    77. Next
    78. Dim ThisIndex As Integer = 0
    79. Dim MaximumIndex As Integer = AllWaves.Count - 1
    80. Do Until ThisIndex > MaximumIndex
    81. With AllWaves(ThisIndex)
    82. If .AutoMaximum Then
    83. .MaximumRadius = GetMaximumRadius()
    84. End If
    85. If .Radius > .MaximumRadius Then
    86. ThisIndex += 1
    87. Else
    88. .Radius += RadiusIncrement
    89. End If
    90. End With
    91. ThisIndex += 1
    92. Loop
    93. Me.Invalidate()
    94. End Sub
    95. Private Sub MePaint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles Me.Paint
    96. e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    97. For Each i As Emitters.SingleWave In AllWaves
    98. e.Graphics.DrawEllipse(New Pen(i.WaveColor, i.WaveWidth), i.PositionX - i.Radius, i.PositionY - i.Radius, i.Radius * 2, i.Radius * 2)
    99. Next
    100. For Each i As Emitters.WaveBaseClass In AllEmitters
    101. e.Graphics.FillRectangle(New SolidBrush(i.WaveColor), i.PositionX - 1, i.PositionY - 1, 3, 3)
    102. Next
    103. e.Graphics.DrawLine(Pens.Red, PX, 0, PX, Me.ClientRectangle.Height)
    104. e.Graphics.DrawLine(Pens.Red, 0, PY, Me.ClientRectangle.Width, PY)
    105. End Sub
    106. End Class



    Und hier der Code für das Zeichnen mit gezieltem Invalidieren:
    Was sich geändert hat ist der untere Teil der TimerTick Sub.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub TimerTick() Handles TimerStuff.Tick
    2. '...
    3. Dim ThisIndex As Integer = 0
    4. Dim MaximumIndex As Integer = AllWaves.Count - 1
    5. Do Until ThisIndex > MaximumIndex
    6. With AllWaves(ThisIndex)
    7. If .AutoMaximum Then
    8. .MaximumRadius = GetMaximumRadius()
    9. End If
    10. If .Radius > .MaximumRadius Then
    11. ThisIndex += 1
    12. Else
    13. .Radius += RadiusIncrement
    14. End If
    15. Me.Invalidate(New Rectangle(CInt(.PositionX - .Radius - .WaveWidth / 2), _
    16. CInt(.PositionY - .Radius - .WaveWidth / 2), _
    17. CInt(.Radius * 2 + 1), _
    18. CInt(.Radius * 2 + 1)))
    19. End With
    20. ThisIndex += 1
    21. Loop
    22. End Sub


    Auch das behebt das Problem nicht.
    Und was schneller ist müsste man bei mehreren bestimmten Anzahlen von Objekten auf dem Feld messen lassen und in ein Diagramm packen. Dann sieht man, was schneller ist.
    Das könnte ich auch noch machen, wird aber etwas dauern.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    gezieltes Invalidieren des Fadenkreuzes bringt nix.

    Die beiden Aufrufe fließen zusammen, und neu gezeichnet wird dann das umschließende Rechteck beider Invalidierungen. Und das ist grad im Fall eines Fadenkreuzes die gesamte Control-Fläche.

    kannst dir auch die Comments von Control mit beweglicher Figur durchlesen (oder auch nicht) - da habich dasselbe geschrieben. Aber immerhin da bringt das was.
    Danke für die Informationen.
    Ich hab gesehen worum's geht.
    Allerdings werden die beiden Rectangles schön separat invalidiert. Möglicherweise werden intern Regions verwendet. Die können solche Formationen beinhalten.
    Der Test:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. 'Ein Timer, der so schnell wie möglich invalidiert
    2. Dim WithEvents TimerStuff As New Timer With {.Enabled = True, .Interval = 1}
    3. 'Positionen der beiden Linien
    4. Dim LastX As Integer = 0
    5. Dim LastY As Integer = 0
    6. 'Nacheinander werden unterschiedliche Farben gezeichnet
    7. Dim ColIndex As Integer = 0
    8. Dim Colors As New List(Of Color)
    9. 'Farben auswählen
    10. Private Sub Initialize() Handles MyBase.Load
    11. Colors.Add(Color.Red)
    12. Colors.Add(Color.Green)
    13. Colors.Add(Color.Blue)
    14. Colors.Add(Color.Yellow)
    15. Colors.Add(Color.Lime)
    16. Colors.Add(Color.Black)
    17. Colors.Add(Color.Pink)
    18. Colors.Add(Color.Cyan)
    19. End Sub
    20. 'Positionen übernehmen
    21. Private Sub MeMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseMove
    22. LastX = e.X
    23. LastY = e.Y
    24. End Sub
    25. 'Bereiche der Linien invalidieren
    26. Private Sub Calc() Handles TimerStuff.Tick
    27. Me.Invalidate(New Rectangle(LastX, 0, 1, Me.ClientRectangle.Height))
    28. Me.Invalidate(New Rectangle(0, LastY, Me.ClientRectangle.Width, 1))
    29. End Sub
    30. 'Zeichnen
    31. Private Sub MePaint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles Me.Paint
    32. 'Bei jeder Zeichnung wird die nächste Farbe gewählt
    33. ColIndex += 1
    34. If ColIndex >= Colors.Count Then
    35. ColIndex = 0
    36. End If
    37. 'Anschließend wird alles auf eine Farbe gesetzt;
    38. 'Nur die invalidierten Bereiche wechseln die Farbe
    39. e.Graphics.Clear(Colors(ColIndex))
    40. End Sub



    Weil beim Starten der Anwendung natürlich der gesamte Bereich invalidiert wird wird erst mal alles grün. Dann kann man gut erkennen, dass wirklich nur die Rectangles neu gezeichnet werden, die bei .Invalidate() angegeben wurden.


    Ich wollte noch mehr schreiben, aber eine Nachricht darf nur 15000 Zeichen enthalten. Darum kommt das Andere später.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Niko Ortner schrieb:

    Allerdings werden die beiden Rectangles schön separat invalidiert.

    jo, der Invalidate-Aufruf erfolgt separat. Der ist auch sehr schnell, und beide Aufrufe erfolgen, ehe das nächste Paint-Event feuert.
    Aber im Paint-Event das übergebene Graphics ist geclipt, und zwar auf das umschließende Rechteck aller zwischenzeitlich eingegangenen Invalidate-Aufrufe.
    Und dassis leider das ganze Control.

    kannst ja e.ClipRectangle im Debugfenster ausgeben.
    Ach soooo...
    Ich weiß nicht wie ich anfangen soll, darum frage ich gerade heraus:
    Was ist der Unterschied zwischen den invalidierten Bereichen und dem Clip-Rectangle des Graphics-Objektes.
    Gezeichnet wird Ändern tut sich's ja nur in den invalidierten Bereichen.


    Und was ich vorhin noch schreiben wollte:

    Ich habe mit mehreren Objekten Tests durchgeführt.
    Von 4 bis 24 Emitter in 4-er Schritten bin ich durchgegangen und habe gemessen, wie schnell das Tick-Event aufgerufen wird. Es wurde bis 500 Ticks gemessen.

    Auf der X-Achse ist die anzahl an vergangenen Ticks.
    Auf der Y Achse der Zeitunterschied des ersten Ticks zu den nachfolgenden Ticks. Die Anzahl an Objekten ist dabei durchschnittlich angestiegen (Das Maximum bei 24 Emittern war 600).
    Die einzelnen Farben zeigen die Anzahl an Emittern auf dem Feld (Je weiter oben, desto mehr Emitter waren es; ganz unten 4, dann 8, ganz oben 24).
    Dabei zeigt die hellere Linie die Zeiten ohne gezieltes Invalidieren an und die dunklere Linie mit gezieltem Invalidieren.

    Man erkennt: Nahezu kein Unterschied. Und wenn man die Ungenauigkeit des Verfahrens dazunimmt kann man den Unterschied gleich vergessen.

    Wer es testen will, hier der Code (Die Klasse Emitters ist gleich geblieben):
    Alles Invalidieren

    VB.NET-Quellcode

    1. Public Class Form1
    2. Dim WithEvents TimerStuff As New Timer With {.Interval = 1, .Enabled = True}
    3. Dim PX As Integer = 0
    4. Dim PY As Integer = 0
    5. Dim AllWaves As New List(Of Emitters.SingleWave)
    6. Dim AllEmitters As New List(Of Emitters.WaveBaseClass)
    7. Dim RadiusIncrement As Single = 2
    8. Private Sub Initialize() Handles Me.Shown
    9. Dim Rnd As New Random
    10. 'Hier wird angegeben, wie viele Emitter sich zum Testen auf dem Feld befinden sollen
    11. For i As Integer = 1 To 24
    12. AllEmitters.Add(New Emitters.Pulser(Rnd.Next(0, Me.ClientRectangle.Width), Rnd.Next(0, Me.ClientRectangle.Height), GetMaximumRadius, True, Color.Red, 1, 20))
    13. Next
    14. End Sub
    15. Private Sub MeMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseMove
    16. Me.Invalidate(New Rectangle(PX, 0, 1, Me.ClientRectangle.Height))
    17. Me.Invalidate(New Rectangle(0, PY, Me.ClientRectangle.Width, 1))
    18. PX = e.X
    19. PY = e.Y
    20. Me.Invalidate(New Rectangle(PX, 0, 1, Me.ClientRectangle.Height))
    21. Me.Invalidate(New Rectangle(0, PY, Me.ClientRectangle.Width, 1))
    22. End Sub
    23. Private Function GetMaximumRadius() As Double
    24. Return Math.Sqrt((Me.ClientRectangle.Width) ^ 2 + (Me.ClientRectangle.Height) ^ 2) + 10
    25. End Function
    26. Private Sub MeMouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseDown
    27. Select Case e.Button
    28. Case Windows.Forms.MouseButtons.Left
    29. AllWaves.Add(New Emitters.SingleWave(e.X, e.Y, GetMaximumRadius, True, Color.Green, 1, 0))
    30. Case Windows.Forms.MouseButtons.Right
    31. AllEmitters.Add(New Emitters.Pulser(e.X, e.Y, GetMaximumRadius, True, Color.Red, 1, 40))
    32. End Select
    33. End Sub
    34. #Region "Testen"
    35. Dim TimesList As New List(Of Double)
    36. Dim CountsList As New List(Of Integer)
    37. Dim FirstTime As DateTime = Now
    38. Private Sub WriteFile()
    39. Dim SB As New System.Text.StringBuilder
    40. For i As Integer = 0 To TimesList.Count - 1
    41. SB.AppendLine(TimesList(i).ToString & ";" & CountsList(i).ToString)
    42. Next
    43. System.IO.File.WriteAllText("C:\Users\Administrator\Desktop\Zeiten\Ohne\Times_" & AllEmitters.Count.ToString & ".txt", SB.ToString, System.Text.Encoding.Default)
    44. End Sub
    45. Private Sub TimerTick() Handles TimerStuff.Tick
    46. TimesList.Add(Now.Subtract(FirstTime).TotalMilliseconds)
    47. CountsList.Add(AllWaves.Count)
    48. If TimesList.Count = 500 Then
    49. TimerStuff.Enabled = False
    50. WriteFile()
    51. End If
    52. For Each i As Emitters.WaveBaseClass In AllEmitters
    53. Select Case i.Tag
    54. Case Emitters.Pulser.SharedTag
    55. With DirectCast(i, Emitters.Pulser)
    56. If .Counter = 0 Then
    57. AllWaves.Add(.CreateWave)
    58. End If
    59. .Counter += 1
    60. If .Counter >= .Delay Then
    61. .Counter = 0
    62. End If
    63. End With
    64. Case Emitters.IndividualPulser.SharedTag
    65. With DirectCast(i, Emitters.IndividualPulser)
    66. If .PartCount = 0 Then
    67. If .EmitterPulseType.OnCount(.Count) Then
    68. AllWaves.Add(.CreateWave)
    69. End If
    70. .Count += 1
    71. If .Count > .EmitterPulseType.Break Then
    72. .Count = 0
    73. End If
    74. End If
    75. .PartCount += 1
    76. If .PartCount >= .EmitterPulseType.Factor Then
    77. .PartCount = 0
    78. End If
    79. End With
    80. Case Emitters.OnHitPulser.SharedTag
    81. With DirectCast(i, Emitters.OnHitPulser)
    82. If .Counter = 0 Then
    83. Dim WillIntersect As Boolean = False
    84. For j As Integer = 0 To AllWaves.Count - 1
    85. If .WasIntersect(AllWaves(j), RadiusIncrement) Then
    86. WillIntersect = True
    87. Exit For
    88. End If
    89. Next
    90. If WillIntersect Then
    91. AllWaves.Add(.CreateWave)
    92. .Counter += 1
    93. End If
    94. ElseIf .Counter >= .Delay - 1 Then
    95. .Counter = 0
    96. Else
    97. .Counter += 1
    98. End If
    99. End With
    100. End Select
    101. Next
    102. Dim ThisIndex As Integer = 0
    103. Dim MaximumIndex As Integer = AllWaves.Count - 1
    104. Do Until ThisIndex > MaximumIndex
    105. With AllWaves(ThisIndex)
    106. If .AutoMaximum Then
    107. .MaximumRadius = GetMaximumRadius()
    108. End If
    109. If .Radius > .MaximumRadius Then
    110. ThisIndex += 1
    111. Else
    112. .Radius += RadiusIncrement
    113. End If
    114. End With
    115. ThisIndex += 1
    116. Loop
    117. Me.Invalidate()
    118. End Sub
    119. Private Sub MePaint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles Me.Paint
    120. e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    121. For Each i As Emitters.SingleWave In AllWaves
    122. e.Graphics.DrawEllipse(New Pen(i.WaveColor, i.WaveWidth), i.PositionX - i.Radius, i.PositionY - i.Radius, i.Radius * 2, i.Radius * 2)
    123. Next
    124. For Each i As Emitters.WaveBaseClass In AllEmitters
    125. e.Graphics.FillRectangle(New SolidBrush(i.WaveColor), i.PositionX - 1, i.PositionY - 1, 3, 3)
    126. Next
    127. e.Graphics.DrawLine(Pens.Red, PX, 0, PX, Me.ClientRectangle.Height)
    128. e.Graphics.DrawLine(Pens.Red, 0, PY, Me.ClientRectangle.Width, PY)
    129. End Sub
    130. #End Region
    131. End Class


    Gezieltes Invalidieren

    VB.NET-Quellcode

    1. Public Class Form1
    2. Dim WithEvents TimerStuff As New Timer With {.Interval = 1, .Enabled = True}
    3. Dim PX As Integer = 0
    4. Dim PY As Integer = 0
    5. Dim AllWaves As New List(Of Emitters.SingleWave)
    6. Dim AllEmitters As New List(Of Emitters.WaveBaseClass)
    7. Dim RadiusIncrement As Single = 2
    8. Private Sub Initialize() Handles Me.Shown
    9. Dim Rnd As New Random
    10. 'Hier wird angegeben, wie viele Emitter sich zum Testen auf dem Feld befinden sollen
    11. For i As Integer = 1 To 24
    12. AllEmitters.Add(New Emitters.Pulser(Rnd.Next(0, Me.ClientRectangle.Width), Rnd.Next(0, Me.ClientRectangle.Height), GetMaximumRadius, True, Color.Red, 1, 20))
    13. Next
    14. End Sub
    15. Private Sub MeMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseMove
    16. Me.Invalidate(New Rectangle(PX, 0, 1, Me.ClientRectangle.Height))
    17. Me.Invalidate(New Rectangle(0, PY, Me.ClientRectangle.Width, 1))
    18. PX = e.X
    19. PY = e.Y
    20. Me.Invalidate(New Rectangle(PX, 0, 1, Me.ClientRectangle.Height))
    21. Me.Invalidate(New Rectangle(0, PY, Me.ClientRectangle.Width, 1))
    22. End Sub
    23. Private Function GetMaximumRadius() As Double
    24. Return Math.Sqrt((Me.ClientRectangle.Width) ^ 2 + (Me.ClientRectangle.Height) ^ 2) + 10
    25. End Function
    26. Private Sub MeMouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseDown
    27. Select Case e.Button
    28. Case Windows.Forms.MouseButtons.Left
    29. AllWaves.Add(New Emitters.SingleWave(e.X, e.Y, GetMaximumRadius, True, Color.Green, 1, 0))
    30. Case Windows.Forms.MouseButtons.Right
    31. AllEmitters.Add(New Emitters.Pulser(e.X, e.Y, GetMaximumRadius, True, Color.Red, 1, 40))
    32. End Select
    33. End Sub
    34. #Region "Testen"
    35. Dim TimesList As New List(Of Double)
    36. Dim CountsList As New List(Of Integer)
    37. Dim FirstTime As DateTime = Now
    38. Private Sub WriteFile()
    39. Dim SB As New System.Text.StringBuilder
    40. For i As Integer = 0 To TimesList.Count - 1
    41. SB.AppendLine(TimesList(i).ToString & ";" & CountsList(i).ToString)
    42. Next
    43. System.IO.File.WriteAllText("C:\Users\Administrator\Desktop\Zeiten\Mit\Times_" & AllEmitters.Count.ToString & ".txt", SB.ToString, System.Text.Encoding.Default)
    44. End Sub
    45. Private Sub TimerTick() Handles TimerStuff.Tick
    46. TimesList.Add(Now.Subtract(FirstTime).TotalMilliseconds)
    47. CountsList.Add(AllWaves.Count)
    48. If TimesList.Count = 500 Then
    49. TimerStuff.Enabled = False
    50. WriteFile()
    51. End If
    52. For Each i As Emitters.WaveBaseClass In AllEmitters
    53. Select Case i.Tag
    54. Case Emitters.Pulser.SharedTag
    55. With DirectCast(i, Emitters.Pulser)
    56. If .Counter = 0 Then
    57. AllWaves.Add(.CreateWave)
    58. End If
    59. .Counter += 1
    60. If .Counter >= .Delay Then
    61. .Counter = 0
    62. End If
    63. End With
    64. Case Emitters.IndividualPulser.SharedTag
    65. With DirectCast(i, Emitters.IndividualPulser)
    66. If .PartCount = 0 Then
    67. If .EmitterPulseType.OnCount(.Count) Then
    68. AllWaves.Add(.CreateWave)
    69. End If
    70. .Count += 1
    71. If .Count > .EmitterPulseType.Break Then
    72. .Count = 0
    73. End If
    74. End If
    75. .PartCount += 1
    76. If .PartCount >= .EmitterPulseType.Factor Then
    77. .PartCount = 0
    78. End If
    79. End With
    80. Case Emitters.OnHitPulser.SharedTag
    81. With DirectCast(i, Emitters.OnHitPulser)
    82. If .Counter = 0 Then
    83. Dim WillIntersect As Boolean = False
    84. For j As Integer = 0 To AllWaves.Count - 1
    85. If .WasIntersect(AllWaves(j), RadiusIncrement) Then
    86. WillIntersect = True
    87. Exit For
    88. End If
    89. Next
    90. If WillIntersect Then
    91. AllWaves.Add(.CreateWave)
    92. .Counter += 1
    93. End If
    94. ElseIf .Counter >= .Delay - 1 Then
    95. .Counter = 0
    96. Else
    97. .Counter += 1
    98. End If
    99. End With
    100. End Select
    101. Next
    102. Dim ThisIndex As Integer = 0
    103. Dim MaximumIndex As Integer = AllWaves.Count - 1
    104. Do Until ThisIndex > MaximumIndex
    105. With AllWaves(ThisIndex)
    106. If .AutoMaximum Then
    107. .MaximumRadius = GetMaximumRadius()
    108. End If
    109. If .Radius > .MaximumRadius Then
    110. ThisIndex += 1
    111. Else
    112. .Radius += RadiusIncrement
    113. End If
    114. Me.Invalidate(New Rectangle(CInt(.PositionX - .Radius - .WaveWidth / 2), _
    115. CInt(.PositionY - .Radius - .WaveWidth / 2), _
    116. CInt(.Radius * 2 + 1), _
    117. CInt(.Radius * 2 + 1)))
    118. End With
    119. ThisIndex += 1
    120. Loop
    121. End Sub
    122. Private Sub MePaint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles Me.Paint
    123. e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    124. For Each i As Emitters.SingleWave In AllWaves
    125. e.Graphics.DrawEllipse(New Pen(i.WaveColor, i.WaveWidth), i.PositionX - i.Radius, i.PositionY - i.Radius, i.Radius * 2, i.Radius * 2)
    126. Next
    127. For Each i As Emitters.WaveBaseClass In AllEmitters
    128. e.Graphics.FillRectangle(New SolidBrush(i.WaveColor), i.PositionX - 1, i.PositionY - 1, 3, 3)
    129. Next
    130. e.Graphics.DrawLine(Pens.Red, PX, 0, PX, Me.ClientRectangle.Height)
    131. e.Graphics.DrawLine(Pens.Red, 0, PY, Me.ClientRectangle.Width, PY)
    132. End Sub
    133. #End Region
    134. End Class



    Das gilt vor allem für Kangaroo: Bitte sieh Dir das Diagramm an und sag mir ob das gezielte Invalidieren den Unterschied macht. (Dass das Problem dadurch nicht behoben wurde habe ich bereits in Post #9 erwähnt).
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Niko Ortner schrieb:

    Was ist der Unterschied zwischen den invalidierten Bereichen und dem Clip-Rectangle des Graphics-Objektes.

    zwischen 2 Paint-Vorgängen können mehrere gezielte Invalidierungen stattfinden. Intern wird der Clip als das umschließende Rechteck aller Invalidierungen berechnet., und dieses Rechteck wird im Paint gezeichnet.
    Aber mir leuchtet nicht ganz ein warum der Clip Bereich nötig ist, wenn sowiso nur die invalidierten Bereiche neu gezeichnet werden.
    Ich kenne die .SetClip() Methode, bei der auch GraphicsPaths, Regions, etc. angegeben werden können. Nur in wie fern unterscheidet sich das dann von den invalidierten Bereichen? Wirkt sich das Kleinhalten des Clip Bereiches signifikant auf die Performance aus? Wenn ja: Warum wird dann der Clip Bereich als umschließendes Rechteck ausgelegt, und nicht gleich genau so, wie es bei den Invalidierungen angegeben wurde.

    So wie ich es zur Zeit verstehe:

    VB.NET-Quellcode

    1. Dim Rec1 As New Rectangle(0, 0, 100, 100)
    2. Dim Rec2 As New Rectangle(200, 200, 100, 100)
    3. Me.Invalidate(Rec1)
    4. Me.Invalidate(Rec2)
    5. 'Ergibt als Clip-Rectangle:
    6. 'X=0, Y=0, Width = 300, Height=300
    7. 'Gezeichnet werden aber nur die Bereiche, die durch Rec1 und Rec2 bestimmt sind, und nicht der ganze Clip Bereich.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Du hast mit SetClip nix zu schaffe, das macht Windows für dich.
    Und Windows kann nicht jedesmal neu painten, wenn du Invalidate aufrufst - das wäre in anderen Fällen total unperformant, und ist glaub auch technisch nicht möglich (Bildwechselrate und solche sachen).

    Also werden, wenn sie schnell aufeinander folgen, die Rectangles mehrerer Invalidates zusammengefasst zu einem.
    Das einzelne Invalidate(rct) gibt ein Rectangle an, und merkt vor, dass bei nächster Gelegenheit neu gezeichnet wird.

    Und für die Performance ist entscheidend, wie groß das ClipRectangle schließlich ist, welches geneuzeichnet wird.

    Also du kannst ziemlich bedenkenlos den Code zum Zeichnen des gesamten Controls durchlaufen lassen - das Graphics-Objekt ignoriert davon alles, was nicht in seim ClipRectangle liegt.