locked
VB - Can't find a way to undo graphics RRS feed

  • Question

  • Hey

    Lately I started a project on graphics. Well I added a button, for undoing the last action. However the only solution I have figured is to save a new picture after every single action, which I don't really want to undo in this way. Is there any other ways of undoing?

    Current code:

    Dim g As graphics
    Dim r As Random
    Dim pen As Pen = New Pen(Color.red)
    Private Sub Form1_Load(sender As object, e As eventArgs) Handles Me.Load
    g = me.createGraphics()
    g.drawline(pen,R.Next(1,100),R.Next(1,100),R.Next(1,100),R.Next(1,100))
    End Sub
    Private Sub Button1_Click(sender As object, e As eventArgs) Handles Button1.Click
    'Some undoing code
    End Sub


    Wednesday, May 23, 2018 11:48 AM

All replies

  • Lately I started a project on graphics. Well I added a button, for undoing the last action. However the only solution I have figured is to save a new picture after every single action, which I don't really want to undo in this way. Is there any other ways of undoing?

    Replace the code that draws the line with code that adds the line information (start point, end point) to a collection (such as a List) of objects that can store that line information.   Then do the drawing in the form's Paint event handler by iterating through the collection.   To 'undo' a line, remove the last item from the list, and refresh the image so that the drawing is done again from the beginning (but, of course, without the last item in the list).  See:
    https://vbdotnetblog.wordpress.com/graphics/graphics-methodology/

    • Proposed as answer by Reed KimbleMVP Wednesday, May 23, 2018 1:35 PM
    Wednesday, May 23, 2018 12:31 PM
  • Hi

    Here is some overly simple code that allows line points, and color with UNDO and REDO.

    ' Form1 with
    ' Button1 for Undo
    ' Button2 for Redo
    Option Strict On
    Option Explicit On
    Public Class Form1
      Dim lines As New List(Of Ln)
      Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        lines.Add(New Ln With {.dr = True, .pn = New Pen(Color.Red, 3), .p1 = New Point(50, 50), .p2 = New Point(100, 200)})
    
        lines.Add(New Ln With {.dr = True, .pn = New Pen(Color.Blue, 3), .p1 = New Point(250, 50), .p2 = New Point(40, 250)})
    
        lines.Add(New Ln With {.dr = True, .pn = New Pen(Color.Green, 3), .p1 = New Point(100, 50), .p2 = New Point(240, 250)})
    
      End Sub
      Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles MyBase.Paint
        For Each l As Ln In lines
          If l.dr Then e.Graphics.DrawLine(l.pn, l.p1, l.p2)
        Next
      End Sub
      Class Ln
        Property dr As Boolean
        Property pn As Pen
        Property p1 As Point
        Property p2 As Point
      End Class
    
      Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        ' undo
        If lines.Count < 1 Then Exit Sub
        For i As Integer = lines.Count - 1 To 0 Step -1
          If lines(i).dr Then
            lines(i).dr = False
            Exit For
          End If
        Next
        Invalidate()
      End Sub
    
      Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        ' redo
        If lines.Count < 1 Then Exit Sub
        For i As Integer = 0 To lines.Count - 1
          If Not lines(i).dr Then
            lines(i).dr = True
            Exit For
          End If
        Next
        Invalidate()
      End Sub
    End Class


    Regards Les, Livingston, Scotland


    • Edited by leshay Wednesday, May 23, 2018 12:36 PM
    Wednesday, May 23, 2018 12:35 PM
  • As noted, if you think of your drawing routines like vector graphics rather than raster graphics, a "drawing" becomes a series of operations to be performed rather than a collection of pixel-color data.  When framed in this way, you can create a collection of graphic operations that can be modified.

    Consider making a class to represent this, like "Public Class GraphicOperation", and give it members that allow you to represent the various drawing operations that your program supports.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Wednesday, May 23, 2018 1:38 PM
  • Compared to some contributors in this forum, I'm really an absolute beginner if it is about graphics. 

    Therefore your problem did the first time cost me as well a lot of time. 

    On this page on our website is the first problem you are handling currently. 

    http://www.vb-tips.com/CreateImageAndCopyFromScreen.ASPX

    For those who know really something of drawing this is mostly ignored in the forums. 


    Success
    Cor


    Wednesday, May 23, 2018 1:42 PM
  • Lately I started a project on graphics. Well I added a button, for undoing the last action. However the only solution I have figured is to save a new picture after every single action, which I don't really want to undo in this way. Is there any other ways of undoing?

    Replace the code that draws the line with code that adds the line information (start point, end point) to a collection (such as a List) of objects that can store that line information.   Then do the drawing in the form's Paint event handler by iterating through the collection.   To 'undo' a line, remove the last item from the list, and refresh the image so that the drawing is done again from the beginning (but, of course, without the last item in the list).  See:
    https://vbdotnetblog.wordpress.com/graphics/graphics-methodology/

    I agree.

    I will add that after years of complex undo/redo code to reverse specific drawing operations in my CAD program, IMHO the BEST way for a complex program to undo/redo to operate is to save another copy of the drawing data (or the line list in Acamar and Les examples) to in memory at key moments. To undo you restore the last saved undo copy. Same for redo.

    Dont try to manually use code to update your line list one line at a time. Eventually you will have arcs and text and etc and it gets way too hard. Just go back to the last copy of the entire list for undo.

    Doing undo by saving the data file is very easy to code and it works perfectly. And I never have to modify and fight with undo/redo anymore. Best simple code change I made to my app in the last 10 years.

    :)


    Wednesday, May 23, 2018 1:53 PM
  • Well your current code draws to the Form during Form load. So in the Buttons code you could place Me.Invalidate which would erase the graphics on the Form since you are not using persisted graphics.

    None of your code shows an image used or storing an image.

    This code would let you undo a line drawn in a PictureBox but does not have Redo.

    Option Strict On
    
    Public Class Form1
    
        Dim DrawnLines As New List(Of Point())
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Me.Location = New Point(CInt((Screen.PrimaryScreen.WorkingArea.Width / 2) - (Me.Width / 2)), CInt((Screen.PrimaryScreen.WorkingArea.Height / 2) - (Me.Height / 2)))
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            If DrawnLines.Count > 0 Then
                DrawnLines.RemoveAt(DrawnLines.Count - 1)
            End If
            PictureBox1.Refresh()
        End Sub
    
        Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
            If DrawnLines.Count > 0 Then
                For i = 0 To DrawnLines.Count - 1
                    e.Graphics.DrawLine(Pens.Black, DrawnLines(i)(0), DrawnLines(i)(1))
                Next
            End If
        End Sub
    
        Dim StartPt As Point = New Point(0, 0)
        Dim MouseLeftDown As Boolean = False
    
        Private Sub PictureBox1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseDown
            If e.Button = Windows.Forms.MouseButtons.Left Then
                MouseLeftDown = True
                StartPt = New Point(e.X, e.Y)
            End If
        End Sub
    
        Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
            If MouseLeftDown = True Then
                Using g As Graphics = Graphics.FromHwnd(PictureBox1.Handle)
                    PictureBox1.Refresh()
                    g.DrawLine(Pens.Black, StartPt.X, StartPt.Y, e.X, e.Y)
                End Using
            End If
        End Sub
    
        Private Sub PictureBox1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseUp
            If e.Button = Windows.Forms.MouseButtons.Left Then
                DrawnLines.Add({New Point(StartPt.X, StartPt.Y), New Point(e.X, e.Y)})
                MouseLeftDown = False
                PictureBox1.Refresh()
            End If
        End Sub
    
    End Class


    La vida loca


    Wednesday, May 23, 2018 3:45 PM