Results 1 to 9 of 9

Thread: [RESOLVED] Infinite undo/redo: how do you implement it?

  1. #1

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2009
    Posts
    629

    Resolved [RESOLVED] Infinite undo/redo: how do you implement it?

    Hello everyone.

    I did this before, but it was really REALLY (really) messy and all it did was basically add and remove text and selection information from a stack. Result: When using Undo/Redo the selection got messed up, the text moved, sometimes causing lag of epic proportions. I need a way of having complete and smooth undo/redo class for storing all information I would possible need.

    How to know whether or not to add an undo stack is easy, I just checked if the user clicked in the text or moved using the arrow keys. As soon a previous marking was given for "next text change needs to be backed up", it was backed up. At that point I stored the RTF and selection start/length.

    Up to the question. What do I have to store of a RichTextbox control to be able to restore it to a previous state, and how can I reset it back to a previous state? See it as having a "RichTextBoxState" class:
    Code:
    Public Class RichTextBoxState
        Sub New(ByVal RTB As RichTextBox)
            Me.text = RTB.RTF
            Me.selectionstart = RTB.SelectionStart
            Me.selectionlength = RTB.SelectionLength
            '???
        End Sub
        Public text As String
        Public selectionstart As Integer
        Public selectionlength As Integer
        Public ??? As ???
        Public Sub Restore(ByVal RTB As RichTextBox)
            RTB.RTF = text
            RTB.Select(selectionstart, selectionlength)
            '???
        End Sub
    End Class
    After restoring it must display the EXACT same thing as when it was saved as a state.

    Any help greatly appreciated.

  2. #2
    eXtreme Programmer .paul.'s Avatar
    Join Date
    May 2007
    Location
    Chelmsford UK
    Posts
    26,424

    Re: Infinite undo/redo: how do you implement it?

    try this:
    Attached Files Attached Files

  3. #3

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2009
    Posts
    629

    Re: Infinite undo/redo: how do you implement it?

    Obviously I came that far as well, but that does not solve the entire problem. It is more for "user experience". For example, user is scrolling up and down typing text (LONG text) and mid-way thinks: mmh..I made a mistake. Let's undo.

    What happens at first is the text gets cleared. The caret as well. Now I selected the original part, but it did not scroll to it. So I used "ScrollToCaret", but now it causes strange glitches with the selected text being all on top while you didn't even scroll.

    Basic problem, I guess, is scrolling. ScrollToCaret won't do for me, since it has to properly display how it was before.

    Guess I can add a side path to the resolution: how can I obtain the H and V scrollbars, and set them again upon restore?
    Using some Window API's may be, but only noticed some commands to hide and show the bars, not to read or set the value.

  4. #4
    eXtreme Programmer .paul.'s Avatar
    Join Date
    May 2007
    Location
    Chelmsford UK
    Posts
    26,424

    Re: Infinite undo/redo: how do you implement it?

    ok. here's an extension of my previous example:

    vb Code:
    1. Public Class Form1
    2.  
    3.     Private Structure restorePoint
    4.         Dim caretPosition As Integer
    5.         Dim RTF As String
    6.     End Structure
    7.  
    8.     Dim undoStack As New Stack(Of restorePoint)
    9.     Dim redoStack As New Stack(Of restorePoint)
    10.  
    11.     Private Sub BoldToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BoldToolStripMenuItem.Click
    12.         undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
    13.         btnUndo.Enabled = undoStack.Count > 0
    14.         RichTextBox1.SelectionFont = New Font(RichTextBox1.SelectionFont, RichTextBox1.SelectionFont.Style Or FontStyle.Bold)
    15.     End Sub
    16.  
    17.     Private Sub ItalicToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ItalicToolStripMenuItem.Click
    18.         undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
    19.         btnUndo.Enabled = undoStack.Count > 0
    20.         RichTextBox1.SelectionFont = New Font(RichTextBox1.SelectionFont, RichTextBox1.SelectionFont.Style Or FontStyle.Italic)
    21.     End Sub
    22.  
    23.     Private Sub UnderlineToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles UnderlineToolStripMenuItem.Click
    24.         undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
    25.         btnUndo.Enabled = undoStack.Count > 0
    26.         RichTextBox1.SelectionFont = New Font(RichTextBox1.SelectionFont, RichTextBox1.SelectionFont.Style Or FontStyle.Underline)
    27.     End Sub
    28.  
    29.     Private Sub ColorToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ColorToolStripMenuItem.Click
    30.         If ColorDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
    31.             undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
    32.             btnUndo.Enabled = undoStack.Count > 0
    33.             RichTextBox1.SelectionColor = ColorDialog1.Color
    34.         End If
    35.     End Sub
    36.  
    37.     Private Sub FontToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles FontToolStripMenuItem.Click
    38.         If FontDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
    39.             undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
    40.             btnUndo.Enabled = undoStack.Count > 0
    41.             RichTextBox1.SelectionFont = FontDialog1.Font
    42.         End If
    43.     End Sub
    44.  
    45.     Private Sub InsertImageToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles InsertImageToolStripMenuItem.Click
    46.         Dim ofd As New OpenFileDialog
    47.         ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
    48.         If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then
    49.             Dim img As New Bitmap(ofd.FileName)
    50.             Clipboard.SetImage(img)
    51.             undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
    52.             btnUndo.Enabled = undoStack.Count > 0
    53.             RichTextBox1.Paste()
    54.         End If
    55.     End Sub
    56.  
    57.     Private Sub RichTextBox1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles RichTextBox1.KeyDown
    58.         If e.KeyCode = Keys.Space Then
    59.             undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
    60.             btnUndo.Enabled = undoStack.Count > 0
    61.         End If
    62.     End Sub
    63.  
    64.     Private Sub btnUndo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnUndo.Click
    65.         redoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
    66.         Dim rp As restorePoint = undoStack.Pop
    67.         RichTextBox1.Rtf = rp.RTF
    68.         RichTextBox1.SelectionStart = rp.caretPosition
    69.         RichTextBox1.ScrollToCaret()
    70.         btnUndo.Enabled = undoStack.Count > 0
    71.         btnRedo.Enabled = redoStack.Count > 0
    72.     End Sub
    73.  
    74.     Private Sub btnRedo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRedo.Click
    75.         undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
    76.         Dim rp As restorePoint = redoStack.Pop
    77.         RichTextBox1.Rtf = rp.RTF
    78.         RichTextBox1.SelectionStart = rp.caretPosition
    79.         RichTextBox1.ScrollToCaret()
    80.         btnUndo.Enabled = undoStack.Count > 0
    81.         btnRedo.Enabled = redoStack.Count > 0
    82.     End Sub
    83.  
    84.     Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    85.         undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
    86.     End Sub
    87.  
    88. End Class

  5. #5
    eXtreme Programmer .paul.'s Avatar
    Join Date
    May 2007
    Location
    Chelmsford UK
    Posts
    26,424

    Re: Infinite undo/redo: how do you implement it?

    you could add a selectionlength property to the restorePoint structure if you need to re-highlight the previous selection

  6. #6

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2009
    Posts
    629

    Re: Infinite undo/redo: how do you implement it?

    Sigh, this code:
    Code:
    RichTextBox1.ScrollToCaret()
    Causes the control to scroll the selection either all on top or all down.
    This is esp. annoying when the user is editing text mid-way of a long text and presses undo.

    Instead of moving the selection nicely to where it was a few keypresses ago, WITHOUT scrolling the screen, it now causes the User to lose the focus on what he was editing.

    Example: top of screen view:
    I am editing mid way a long text file
    I will now write a wrong word
    My selection SQUAAR is HERE|
    Now the user does an undo, where the last line didn't exist. He had his cursor after the "word" at the second line. Now this happens:
    I will now write a wrong word|
    The top line is all gone, scrolled incorrectly because of ScrollToCaret.

    So yeah, I need a way of saving the scroll state and use it on the next restore. I have no problems with storing the RTF or Selection bounds, see my first post.

  7. #7
    eXtreme Programmer .paul.'s Avatar
    Join Date
    May 2007
    Location
    Chelmsford UK
    Posts
    26,424

    Re: Infinite undo/redo: how do you implement it?

    try this:

    vb Code:
    1. Imports System.Runtime.InteropServices
    2.  
    3. Public Class Form1
    4.  
    5.     Private Structure restorePoint
    6.         Dim scrollPosition As Point
    7.         Dim RTF As String
    8.     End Structure
    9.  
    10.     Dim undoStack As New Stack(Of restorePoint)
    11.     Dim redoStack As New Stack(Of restorePoint)
    12.  
    13.     Private Sub BoldToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BoldToolStripMenuItem.Click
    14.         undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
    15.         btnUndo.Enabled = undoStack.Count > 0
    16.         RichTextBox1.SelectionFont = New Font(RichTextBox1.SelectionFont, RichTextBox1.SelectionFont.Style Or FontStyle.Bold)
    17.     End Sub
    18.  
    19.     Private Sub ItalicToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ItalicToolStripMenuItem.Click
    20.         undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
    21.         btnUndo.Enabled = undoStack.Count > 0
    22.         RichTextBox1.SelectionFont = New Font(RichTextBox1.SelectionFont, RichTextBox1.SelectionFont.Style Or FontStyle.Italic)
    23.     End Sub
    24.  
    25.     Private Sub UnderlineToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles UnderlineToolStripMenuItem.Click
    26.         undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
    27.         btnUndo.Enabled = undoStack.Count > 0
    28.         RichTextBox1.SelectionFont = New Font(RichTextBox1.SelectionFont, RichTextBox1.SelectionFont.Style Or FontStyle.Underline)
    29.     End Sub
    30.  
    31.     Private Sub ColorToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ColorToolStripMenuItem.Click
    32.         If ColorDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
    33.             undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
    34.             btnUndo.Enabled = undoStack.Count > 0
    35.             RichTextBox1.SelectionColor = ColorDialog1.Color
    36.         End If
    37.     End Sub
    38.  
    39.     Private Sub FontToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles FontToolStripMenuItem.Click
    40.         If FontDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
    41.             undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
    42.             btnUndo.Enabled = undoStack.Count > 0
    43.             RichTextBox1.SelectionFont = FontDialog1.Font
    44.         End If
    45.     End Sub
    46.  
    47.     Private Sub InsertImageToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles InsertImageToolStripMenuItem.Click
    48.         Dim ofd As New OpenFileDialog
    49.         ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
    50.         If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then
    51.             Dim img As New Bitmap(ofd.FileName)
    52.             Clipboard.SetImage(img)
    53.             undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
    54.             btnUndo.Enabled = undoStack.Count > 0
    55.             RichTextBox1.Paste()
    56.         End If
    57.     End Sub
    58.  
    59.     Private Sub RichTextBox1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles RichTextBox1.KeyDown
    60.         If e.KeyCode = Keys.Space Then
    61.             undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
    62.             btnUndo.Enabled = undoStack.Count > 0
    63.         End If
    64.     End Sub
    65.  
    66.     Private Sub btnUndo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnUndo.Click
    67.         redoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
    68.         Dim rp As restorePoint = undoStack.Pop
    69.         RichTextBox1.Rtf = rp.RTF
    70.         setScrollPosition(rp.scrollPosition)
    71.         btnUndo.Enabled = undoStack.Count > 0
    72.         btnRedo.Enabled = redoStack.Count > 0
    73.     End Sub
    74.  
    75.     Private Sub btnRedo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRedo.Click
    76.         undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
    77.         Dim rp As restorePoint = redoStack.Pop
    78.         RichTextBox1.Rtf = rp.RTF
    79.         setScrollPosition(rp.scrollPosition)
    80.         btnUndo.Enabled = undoStack.Count > 0
    81.         btnRedo.Enabled = redoStack.Count > 0
    82.     End Sub
    83.  
    84.     Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    85.         undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
    86.     End Sub
    87.  
    88.     Public Declare Function GetScrollInfo Lib "user32" Alias "GetScrollInfo" (ByVal hWnd As IntPtr, _
    89. ByVal n As Integer, <MarshalAs(UnmanagedType.Struct)> ByRef lpScrollInfo As SCROLLINFO) As Integer
    90.  
    91.     <StructLayout(LayoutKind.Sequential)> _
    92.     Public Structure SCROLLINFO
    93.         Public cbSize As Integer
    94.         Public fMask As Integer
    95.         Public nMin As Integer
    96.         Public nMax As Integer
    97.         Public nPage As Integer
    98.         Public nPos As Integer
    99.         Public nTrackPos As Integer
    100.     End Structure
    101.  
    102.     Const SBS_HORZ As Integer = 0
    103.     Const SBS_VERT As Integer = 1
    104.     Const SIF_RANGE As Integer = 1
    105.     Const SIF_PAGE As Integer = 2
    106.     Const SIF_POS As Integer = 4
    107.     Const SIF_TRACKPOS As Integer = 10
    108.     Const SIF_ALL As Integer = (SIF_RANGE Or SIF_PAGE Or SIF_POS Or SIF_TRACKPOS)
    109.  
    110.     Private Function getScrollPosition() As Point
    111.         Dim si As New SCROLLINFO
    112.         si.fMask = SIF_ALL
    113.         si.cbSize = Marshal.SizeOf(si)
    114.         GetScrollInfo(RichTextBox1.Handle, SBS_HORZ, si)
    115.  
    116.         Dim x As Integer = si.nPos
    117.  
    118.         si = New SCROLLINFO
    119.         si.fMask = SIF_ALL
    120.         si.cbSize = Marshal.SizeOf(si)
    121.         GetScrollInfo(RichTextBox1.Handle, SBS_VERT, si)
    122.  
    123.         Dim y As Integer = si.nPos
    124.  
    125.         Return New Point(x, y)
    126.  
    127.     End Function
    128.  
    129.     Private Declare Function SetScrollInfo Lib "user32.dll" ( _
    130.             ByVal hWnd As IntPtr, ByVal nBar As Integer, _
    131.             ByVal lpScrollInfo As SCROLLINFO, _
    132.             ByVal fRedraw As Boolean) As Integer
    133.  
    134.     Private Sub setScrollPosition(ByVal scrollValue As Point)
    135.         Dim si As New SCROLLINFO
    136.         si.nPos = scrollValue.X
    137.         si.cbSize = Marshal.SizeOf(si)
    138.  
    139.         SetScrollInfo(RichTextBox1.Handle, SBS_HORZ, si, True)
    140.  
    141.         si = New SCROLLINFO
    142.         si.nPos = scrollValue.Y
    143.         si.cbSize = Marshal.SizeOf(si)
    144.  
    145.         SetScrollInfo(RichTextBox1.Handle, SBS_VERT, si, True)
    146.  
    147.     End Sub
    148.  
    149. End Class

  8. #8

  9. #9

    Thread Starter
    Fanatic Member
    Join Date
    Jul 2009
    Posts
    629

    Re: Infinite undo/redo: how do you implement it?

    <post cleared, time for final result>

    Got it all working very nicely, but it took some workaround:
    vb Code:
    1. Public Class ScrollData
    2.         Sub New(ByVal control As Control, ByVal scroll As Bar, Optional ByVal flags As Mask = Mask.ALL)
    3.             Me.SetData(control.Handle, scroll, flags)
    4.         End Sub
    5.         Sub New(ByVal handle As IntPtr, ByVal scroll As Bar, Optional ByVal flags As Mask = Mask.ALL)
    6.             Me.SetData(handle, scroll, flags)
    7.         End Sub
    8.         Public Sub SetData(ByVal handle As IntPtr, ByVal scroll As Bar, Optional ByVal flags As Mask = Mask.ALL)
    9.             Me.flags = flags
    10.             Dim s As New SCROLLINFO(Me.flags)
    11.             SCROLLINFO.GetScrollInfo(handle, scroll, s)
    12.             Me.handle = handle
    13.             Me.scroll = scroll
    14.             Me.Minimum = s.nMin
    15.             Me.Maximum = s.nMax
    16.             Me.Position = s.nPos
    17.             Me.Page = s.nPage
    18.             Me.TrackPosition = s.nTrackPos
    19.         End Sub
    20.         Public flags As mask
    21.         Public handle As IntPtr
    22.         Public scroll As Bar
    23.         Public Minimum As Integer
    24.         Public Maximum As Integer
    25.         Public Page As UInteger
    26.         Public Position As Integer
    27.         Public TrackPosition As Integer
    28.         Public Function Update(Optional ByVal redraw As Boolean = True) As Integer
    29.             Dim s As New SCROLLINFO(Me.flags)
    30.             s.nMin = Me.Minimum
    31.             s.nMax = Me.Maximum
    32.             s.nPos = Me.Position
    33.             s.nPage = Me.Page
    34.             s.nTrackPos = Me.TrackPosition
    35.             Return SCROLLINFO.SetScrollInfo(Me.handle, Me.scroll, s, redraw)
    36.         End Function
    37.         Public Enum Bar As Integer
    38.             Horizontal = 0
    39.             Vertical = 1
    40.             Client = 2
    41.             Both = 3
    42.         End Enum
    43.         Public Enum Mask As UInteger
    44.             Range = &H1
    45.             Page = &H2
    46.             Position = &H4
    47.             DisableNoScroll = &H8
    48.             TrackPosition = &H10
    49.             ALL = Range Or Page Or Position Or TrackPosition
    50.         End Enum
    51.         Private Structure SCROLLINFO
    52.             Public Declare Function GetScrollInfo Lib "user32" (ByVal hWnd As IntPtr, ByVal fnBar As Bar, ByRef lpScrollInfo As SCROLLINFO) As Boolean
    53.             Public Declare Function SetScrollInfo Lib "user32" (ByVal hWnd As IntPtr, ByVal fnBar As Bar, ByRef lpScrollInfo As SCROLLINFO, ByVal redraw As Boolean) As Integer
    54.             Sub New(ByVal mask As Mask)
    55.                 Me.cbSize = Marshal.SizeOf(Me)
    56.                 Me.fMask = mask
    57.             End Sub
    58.             Private cbSize As UInteger
    59.             Private fMask As Mask
    60.             Public nMin As Integer
    61.             Public nMax As Integer
    62.             Public nPage As UInteger
    63.             Public nPos As Integer
    64.             Public nTrackPos As Integer
    65.         End Structure
    66.     End Class

    Usage:
    Code:
            Dim s As New ScrollData(TextBox1, ScrollData.Bar.Vertical)
            s.Position += 1
            s.Update()
    Added the API structure inside a class so I was able to hold the scroll type loaded and the Handle of it, since passing the arguments all the time is a bit useless.

    Thanks all, especially Paul for reviving the ScrollInfo idea and for the previous examples.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width