Form1.Invalidate()

  • Allgemein

Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von RushDen.

    Form1.Invalidate()

    Ich versuch grade 4-Gewinnt zu basteln & hab eine kleine Frage:
    Jetzt hab ich die Klasse Spielfeld, von dort aus muss ich jedoch die Form1 neuzeichnen lassen, weil mit dem Grafik objekt der Form1 gezeichnet wird (oder auf dem grafik objekt ^^)
    Ist es dann ok einfach Form1.invalidate() aufzurufen? oder gibts da eine andere Möglichkeit?
    Es wäre wahrscheinlich sinnvoller nur bestimmte teile neuzuzeichnen, und nicht das gesamte Spielfeld wenn es nicht sein muss.
    also vielleicht ist meine herangehensweise auch falsch, aber ich weiß jetzt nicht wie ich das realisieren sollte, hier mal der Ausschnitt:

    VB.NET-Quellcode

    1. Public Sub ClickDrawingField(e As MouseEventArgs) Implements IDrawable.ClickDrawingField
    2. If Not e.Button = MouseButtons.Left Then
    3. Return
    4. End If
    5. Dim clickedblocks = startBlocks.Where(Function(item) item.Contains(e.Location))
    6. If clickedblocks.Count = 0 Then
    7. Return
    8. End If
    9. playedblocks.Add(clickedblocks.First)
    10. Form1.Invalidate()
    11. End Sub


    Da ruf ich Form1.invalidate aufrufen, damit direkt dannach DrawStones aufgerufen wird & die Steine gezeichnet werden.
    playedblocks ist eine Liste wo sich die Blöcke befinden auf die geklickt wurde & die gefüllt werden sollen (Steine) (ich weiß, dass die Steine eigtl nach unten fallen müssten, das war jetzt erstmal nur ein Test) und hier werden die Steine dann gezeichnet:

    VB.NET-Quellcode

    1. Public Sub DrawStones(e As PaintEventArgs) Implements IDrawable.DrawStones
    2. With e.Graphics
    3. playedblocks.ForEach(Sub(item) .FillEllipse(Brushes.Black, item))
    4. End With
    5. End Sub


    Die beiden prozeduren werden dann noch in der paint bzw mousedown prozedur der Form1 eingetragen.

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

    RushDen schrieb:

    VB.NET-Quellcode

    1. Form1.Invalidate()
    Gugst Du hier.
    Du musst die richtige Instanz invalidisieren, egal, ob Form, Panel oder PictureBox.
    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 kommt darauf an, wie dus implementiert hast. Für gewöhnlich diehnt die Form in nem Spiel allein zur Darstellung, in ihr befindet sich (so gut wie) kein Code. In diesem Fall wird die komplette Form aus der eigentlichen Spiellogik heraus gesteuert, das Koordinationszentrum ist meist ein Modul/Statsische Klasse. Wenn das bei dir auch so ist, dann kannst du direkt Invalidate aufrufen.
    Allerdings sieht es bei dir eher so aus, als wäre die Form dein Koordinationszentrum und damit Mittelpunkt des Spieles. Somit muss in der Form alles zusammenlaufen, niemand außer die Form selbst hat was an der Form zu schaffen. In diesem Fall sollten die anderen Klassen ein Event auslösen, welches die Form dazu veranlasst sich selbst neuzuzeichen. Ich sage das aber gleich hier: nimm lieber die erste Methode und machs richtig, die Form hat rein programmlogisch gesehen absolut keinen anderen Zweck zu haben als die Darstellung. Wenn dus noch weiter treiben willst, dann sollte das Rendindering-Subsystem nichtmal was von der Form wissen (siehe dazu SGL, XGL oder GameUtils).
    Ja, also das ist mein bisheriger Stand:
    Könnt das sicher besser beurteilen, ob das richtig oder falsch ist.
    Nicht das ich am ende nur ranz gemacht habe

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private Spielfeld As New Spielfeld(20, 20)
    3. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    4. Me.DoubleBuffered = True
    5. End Sub
    6. Private Sub Form1_MouseDown(sender As Object, e As MouseEventArgs) Handles Me.MouseDown
    7. Spielfeld.ClickDrawingField(e)
    8. End Sub
    9. Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
    10. Spielfeld.Drawfield(e)
    11. Spielfeld.DrawStones(e)
    12. End Sub
    13. End Class
    14. Interface IDrawable
    15. Sub DrawField(e As PaintEventArgs)
    16. Sub ClickDrawingField(e As MouseEventArgs)
    17. Sub DrawStones(e As PaintEventArgs)
    18. End Interface
    19. Public Class Spielfeld
    20. Implements IDrawable
    21. Enum Playermove
    22. Player1 = -1
    23. Player2 = 0
    24. End Enum
    25. Public Property width As Integer
    26. Public Property height As Integer
    27. Public Sub New(width As Integer, height As Integer)
    28. Me.width = width
    29. Me.height = height
    30. End Sub
    31. Private startBlocks As New List(Of Rectangle)
    32. Public Sub DrawField(e As PaintEventArgs) Implements IDrawable.DrawField
    33. With e.Graphics
    34. For wid = 0 To width * 10 Step width
    35. For hei = 0 To height * 10 Step height
    36. startBlocks.Add(New Rectangle(wid, hei, width, height))
    37. .DrawEllipse(Pens.Black, startBlocks(startBlocks.Count - 1))
    38. Next
    39. Next
    40. End With
    41. End Sub
    42. Private playedblocks As New List(Of Rectangle)
    43. Public Sub ClickDrawingField(e As MouseEventArgs) Implements IDrawable.ClickDrawingField
    44. If Not e.Button = MouseButtons.Left Then
    45. Return
    46. End If
    47. Dim clickedblocks = startBlocks.Where(Function(item) item.Contains(e.Location))
    48. If clickedblocks.Count = 0 Then
    49. Return
    50. End If
    51. playedblocks.Add(clickedblocks.First)
    52. Form1.Invalidate()
    53. End Sub
    54. Public Sub DrawStones(e As PaintEventArgs) Implements IDrawable.DrawStones
    55. With e.Graphics
    56. playedblocks.ForEach(Sub(item) .FillEllipse(Brushes.Black, item))
    57. End With
    58. End Sub
    59. End Class


    Dachte jetzt, weil die Form1 "einzigartig" ist und ich keine weitere instanzen von ihr brauchen werde, könnte man diese auch ohne instanzierung ansprechen.
    Das Interface macht so überhaupt keinen Sinn. Ein IDrawable sollte etwas abstraktes darstellen, aber alle drei Methoden darin sind konkret auf die Spielfeld-Klasse bezogen.
    Wenn du ein gutes Spiel programmieren willst, dann musst du die Einzelteile wohl oder übel trennen, damit du sie später dynamischer ansprechen kannst, und du musst stärker abstrahieren, um dein Spiel erweiterbar zu halten. Das ist zwar anfangs mehr Arbeit, aber der einzig wirklich akzeptable Weg, und der erste Schritt wäre, von der Form als Mittelpunkt des Spiels wegzugehen.
    Also das Interface auflösen & einfach 3 Prozeduren daraus machen?
    Aber DrawField ist doch eigtl etwas abstraktes, da fast jedes Spiel ein "Feld" benötigt, das gezeichnet werden muss.

    Nungut, Hab das jetzt umgeändert & dazu ein Event benutzt, statt Form1.invalidate()
    Ist das so erstmal in Ordnung?

    VB.NET-Quellcode

    1. Public Class Form1
    2. Private WithEvents Spielfeld As New Spielfeld(20, 20)
    3. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    4. Me.DoubleBuffered = True
    5. End Sub
    6. Private Sub DrawAgain(sender As Object, e As EventArgs) Handles Spielfeld.DrawAgain
    7. Me.Invalidate()
    8. End Sub
    9. Private Sub Form1_MouseDown(sender As Object, e As MouseEventArgs) Handles Me.MouseDown
    10. Spielfeld.ClickDrawingField(e)
    11. End Sub
    12. Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
    13. Spielfeld.Drawfield(e)
    14. Spielfeld.DrawStones(e)
    15. End Sub
    16. End Class
    17. Public Class Spielfeld
    18. Enum Playermove
    19. Player1 = -1
    20. Player2 = 0
    21. End Enum
    22. Public Property width As Integer
    23. Public Property height As Integer
    24. Public Sub New(width As Integer, height As Integer)
    25. Me.width = width
    26. Me.height = height
    27. End Sub
    28. Private startBlocks As New List(Of Rectangle)
    29. Public Sub DrawField(e As PaintEventArgs)
    30. startBlocks.RemoveRange(0, startBlocks.Count)
    31. With e.Graphics
    32. For wid = 0 To width * 10 Step width
    33. For hei = 0 To height * 10 Step height
    34. startBlocks.Add(New Rectangle(wid, hei, width, height))
    35. .DrawEllipse(Pens.Black, startBlocks(startBlocks.Count - 1))
    36. Next
    37. Next
    38. End With
    39. End Sub
    40. Private playedblocks As New List(Of Rectangle)
    41. Public Sub ClickDrawingField(e As MouseEventArgs)
    42. If Not e.Button = MouseButtons.Left Then
    43. Return
    44. End If
    45. Dim clickedblocks = startBlocks.Where(Function(item) item.Contains(e.Location))
    46. If clickedblocks.Count = 0 Then
    47. Return
    48. End If
    49. playedblocks.Add(clickedblocks.First)
    50. RaiseEvent DrawAgain(Me, EventArgs.Empty)
    51. End Sub
    52. Public Event DrawAgain As eventhandler
    53. Public Sub DrawStones(e As PaintEventArgs)
    54. With e.Graphics
    55. playedblocks.ForEach(Sub(item) .FillEllipse(Brushes.Black, item))
    56. End With
    57. End Sub
    58. End Class

    RushDen schrieb:

    Also das Interface
    weglassen und Implements ... löschen.
    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!
    Also, ich hatte mal versucht mit SharpDX & Sfml einige kleine Spiele zu programmieren, bei SharpDX hatte ich einige Probleme & für Sfml gab's zuwenig Tutorials.
    Deswegen werd ich erstmal einiges mit GDI probieren, bis ich mir mehr zutraue. Doch würde mich interessieren was denn der richtige/bessere Ansatz wäre?
    Der grundsätzliche Ansatz hängt nicht mit der verwendeten Render-Engine zusammen. Du kannst mit GDI+ genau das gleiche machen, wie mit SharpDX. Nur die konkrete Implementierung unterscheidet sich dann, aber es geht ja zunächst mal nur um die abstrakte Programmstruktur.
    Schau einfach mal hier rein:
    [OpenSource][C#] Sharpex.GameLibrary
    Wie du mit diesem abstrakten Zeugs dann arbeitest hat ThuCommix auch sehr schön erklärt:
    [C#] [Sharpex.GameLibrary] Tutorialübersicht
    Nimm das einfach als Basis und streiche in deiner eigenen Implementierung die Sachen raus, die du nicht brauchst bzw. die halt zu abstrakt sind.
    Wenn du willst kannst du auch bei meiner hauseigenen Engine vorbeischauen, ist allerdings noch nicht fertig und hat deshalb auch keine Tutorialsektion:
    [C#] [OpenSource] [Projektvorstellung] Langzeitprojekt - GameUtils
    na erstmal OwnerDrawing ühaupt richtig einsetzen.

    mit den Blocks ist das schon gut - jeder Block sollte sich selbst zeichnen können. Den Sinn deiner Spielfeld-Klasse verstehe ich nicht.

    Wenn ich sowas ownerDrawing machen, dann habich auch eine Spielfeld-Klasse, aber die erbt von Control (und heißt bei mir immer Canvas), und wird aufs Form gezogen.
    Und dann heißts nicht Form1.Invalidate, sondern dann wird natürlich ein Rechteck aufm Canvas-Control invalidiert.

    Und das Canvas hat eine Liste aller Zeichenobjekte, und im OnPaint-Override werden alle ZeichenObjekte aufgerufen, sich zu zeichnen.

    Wenn sich iwas ändert, dann wird nur dieser Bereich invalidiert - deshalb muss jedes ZeichenObjekt nicht nur sich selbst jederzeit zeichnen können, sondern auch jederzeit angeben können, wo es sich befindet.

    gugge Control mit beweglicher Figur
    StoryCards

    Aber das ist der OwnerDrawing-Ansatz.
    Der Ansatz über eine Zeichen-Engine ist ganz anners. Dort wird nciht gewartet, bis sich ein Objekt ändert, sondern alle mw 30ms wird halt der Bildschirm neu gezeichnet.
    Mit GDI ist das ein ziemliches Trauerspiel - dem geht da bald die Puste aus - und es muss was hardwarebeschleunigtes her.
    Hardwarebeschleunigt ists dann möglich, Bewegungen des ganzen Bildschirms darzustellen, also auch eine subjektive Kamera in einer Welt (kannste mit Gdi vergessen).

    Aber für Pacman, Minesweeper, Snake, Tetris ist Gdi ausreichend.