Apr 4th, 2011, 11:33 AM
#1
Thread Starter
Fanatic Member
[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.
Last edited by bergerkiller; Apr 4th, 2011 at 11:36 AM .
Apr 4th, 2011, 05:26 PM
#2
Re: Infinite undo/redo: how do you implement it?
Attached Files
Coding Examples:
Features:
Online Games:
Compiled Games:
Apr 4th, 2011, 05:37 PM
#3
Thread Starter
Fanatic Member
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.
Apr 4th, 2011, 05:47 PM
#4
Re: Infinite undo/redo: how do you implement it?
ok. here's an extension of my previous example:
vb Code:
Public Class Form1
Private Structure restorePoint
Dim caretPosition As Integer
Dim RTF As String
End Structure
Dim undoStack As New Stack(Of restorePoint)
Dim redoStack As New Stack(Of restorePoint)
Private Sub BoldToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BoldToolStripMenuItem.Click
undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
RichTextBox1.SelectionFont = New Font(RichTextBox1.SelectionFont, RichTextBox1.SelectionFont.Style Or FontStyle.Bold)
End Sub
Private Sub ItalicToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ItalicToolStripMenuItem.Click
undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
RichTextBox1.SelectionFont = New Font(RichTextBox1.SelectionFont, RichTextBox1.SelectionFont.Style Or FontStyle.Italic)
End Sub
Private Sub UnderlineToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles UnderlineToolStripMenuItem.Click
undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
RichTextBox1.SelectionFont = New Font(RichTextBox1.SelectionFont, RichTextBox1.SelectionFont.Style Or FontStyle.Underline)
End Sub
Private Sub ColorToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ColorToolStripMenuItem.Click
If ColorDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
RichTextBox1.SelectionColor = ColorDialog1.Color
End If
End Sub
Private Sub FontToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles FontToolStripMenuItem.Click
If FontDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
RichTextBox1.SelectionFont = FontDialog1.Font
End If
End Sub
Private Sub InsertImageToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles InsertImageToolStripMenuItem.Click
Dim ofd As New OpenFileDialog
ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then
Dim img As New Bitmap(ofd.FileName)
Clipboard.SetImage(img)
undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
RichTextBox1.Paste()
End If
End Sub
Private Sub RichTextBox1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles RichTextBox1.KeyDown
If e.KeyCode = Keys.Space Then
undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
End If
End Sub
Private Sub btnUndo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnUndo.Click
redoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
Dim rp As restorePoint = undoStack.Pop
RichTextBox1.Rtf = rp.RTF
RichTextBox1.SelectionStart = rp.caretPosition
RichTextBox1.ScrollToCaret()
btnUndo.Enabled = undoStack.Count > 0
btnRedo.Enabled = redoStack.Count > 0
End Sub
Private Sub btnRedo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRedo.Click
undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
Dim rp As restorePoint = redoStack.Pop
RichTextBox1.Rtf = rp.RTF
RichTextBox1.SelectionStart = rp.caretPosition
RichTextBox1.ScrollToCaret()
btnUndo.Enabled = undoStack.Count > 0
btnRedo.Enabled = redoStack.Count > 0
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
undoStack.Push(New restorePoint With {.caretPosition = RichTextBox1.SelectionStart, .RTF = RichTextBox1.Rtf})
End Sub
End Class
Coding Examples:
Features:
Online Games:
Compiled Games:
Apr 4th, 2011, 05:50 PM
#5
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
Coding Examples:
Features:
Online Games:
Compiled Games:
Apr 4th, 2011, 05:56 PM
#6
Thread Starter
Fanatic Member
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.
Apr 4th, 2011, 06:08 PM
#7
Re: Infinite undo/redo: how do you implement it?
try this:
vb Code:
Imports System.Runtime.InteropServices
Public Class Form1
Private Structure restorePoint
Dim scrollPosition As Point
Dim RTF As String
End Structure
Dim undoStack As New Stack(Of restorePoint)
Dim redoStack As New Stack(Of restorePoint)
Private Sub BoldToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BoldToolStripMenuItem.Click
undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
RichTextBox1.SelectionFont = New Font(RichTextBox1.SelectionFont, RichTextBox1.SelectionFont.Style Or FontStyle.Bold)
End Sub
Private Sub ItalicToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ItalicToolStripMenuItem.Click
undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
RichTextBox1.SelectionFont = New Font(RichTextBox1.SelectionFont, RichTextBox1.SelectionFont.Style Or FontStyle.Italic)
End Sub
Private Sub UnderlineToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles UnderlineToolStripMenuItem.Click
undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
RichTextBox1.SelectionFont = New Font(RichTextBox1.SelectionFont, RichTextBox1.SelectionFont.Style Or FontStyle.Underline)
End Sub
Private Sub ColorToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ColorToolStripMenuItem.Click
If ColorDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
RichTextBox1.SelectionColor = ColorDialog1.Color
End If
End Sub
Private Sub FontToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles FontToolStripMenuItem.Click
If FontDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
RichTextBox1.SelectionFont = FontDialog1.Font
End If
End Sub
Private Sub InsertImageToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles InsertImageToolStripMenuItem.Click
Dim ofd As New OpenFileDialog
ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then
Dim img As New Bitmap(ofd.FileName)
Clipboard.SetImage(img)
undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
RichTextBox1.Paste()
End If
End Sub
Private Sub RichTextBox1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles RichTextBox1.KeyDown
If e.KeyCode = Keys.Space Then
undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
btnUndo.Enabled = undoStack.Count > 0
End If
End Sub
Private Sub btnUndo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnUndo.Click
redoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
Dim rp As restorePoint = undoStack.Pop
RichTextBox1.Rtf = rp.RTF
setScrollPosition(rp.scrollPosition)
btnUndo.Enabled = undoStack.Count > 0
btnRedo.Enabled = redoStack.Count > 0
End Sub
Private Sub btnRedo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRedo.Click
undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
Dim rp As restorePoint = redoStack.Pop
RichTextBox1.Rtf = rp.RTF
setScrollPosition(rp.scrollPosition)
btnUndo.Enabled = undoStack.Count > 0
btnRedo.Enabled = redoStack.Count > 0
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
undoStack.Push(New restorePoint With {.scrollPosition = getScrollPosition(), .RTF = RichTextBox1.Rtf})
End Sub
Public Declare Function GetScrollInfo Lib "user32" Alias "GetScrollInfo" (ByVal hWnd As IntPtr, _
ByVal n As Integer, <MarshalAs(UnmanagedType.Struct)> ByRef lpScrollInfo As SCROLLINFO) As Integer
<StructLayout(LayoutKind.Sequential)> _
Public Structure SCROLLINFO
Public cbSize As Integer
Public fMask As Integer
Public nMin As Integer
Public nMax As Integer
Public nPage As Integer
Public nPos As Integer
Public nTrackPos As Integer
End Structure
Const SBS_HORZ As Integer = 0
Const SBS_VERT As Integer = 1
Const SIF_RANGE As Integer = 1
Const SIF_PAGE As Integer = 2
Const SIF_POS As Integer = 4
Const SIF_TRACKPOS As Integer = 10
Const SIF_ALL As Integer = (SIF_RANGE Or SIF_PAGE Or SIF_POS Or SIF_TRACKPOS)
Private Function getScrollPosition() As Point
Dim si As New SCROLLINFO
si.fMask = SIF_ALL
si.cbSize = Marshal.SizeOf(si)
GetScrollInfo(RichTextBox1.Handle, SBS_HORZ, si)
Dim x As Integer = si.nPos
si = New SCROLLINFO
si.fMask = SIF_ALL
si.cbSize = Marshal.SizeOf(si)
GetScrollInfo(RichTextBox1.Handle, SBS_VERT, si)
Dim y As Integer = si.nPos
Return New Point(x, y)
End Function
Private Declare Function SetScrollInfo Lib "user32.dll" ( _
ByVal hWnd As IntPtr, ByVal nBar As Integer, _
ByVal lpScrollInfo As SCROLLINFO, _
ByVal fRedraw As Boolean) As Integer
Private Sub setScrollPosition(ByVal scrollValue As Point)
Dim si As New SCROLLINFO
si.nPos = scrollValue.X
si.cbSize = Marshal.SizeOf(si)
SetScrollInfo(RichTextBox1.Handle, SBS_HORZ, si, True)
si = New SCROLLINFO
si.nPos = scrollValue.Y
si.cbSize = Marshal.SizeOf(si)
SetScrollInfo(RichTextBox1.Handle, SBS_VERT, si, True)
End Sub
End Class
Coding Examples:
Features:
Online Games:
Compiled Games:
Apr 4th, 2011, 09:14 PM
#8
Re: Infinite undo/redo: how do you implement it?
Apr 5th, 2011, 10:13 AM
#9
Thread Starter
Fanatic Member
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:
Public Class ScrollData
Sub New(ByVal control As Control, ByVal scroll As Bar, Optional ByVal flags As Mask = Mask.ALL)
Me.SetData(control.Handle, scroll, flags)
End Sub
Sub New(ByVal handle As IntPtr, ByVal scroll As Bar, Optional ByVal flags As Mask = Mask.ALL)
Me.SetData(handle, scroll, flags)
End Sub
Public Sub SetData(ByVal handle As IntPtr, ByVal scroll As Bar, Optional ByVal flags As Mask = Mask.ALL)
Me.flags = flags
Dim s As New SCROLLINFO(Me.flags)
SCROLLINFO.GetScrollInfo(handle, scroll, s)
Me.handle = handle
Me.scroll = scroll
Me.Minimum = s.nMin
Me.Maximum = s.nMax
Me.Position = s.nPos
Me.Page = s.nPage
Me.TrackPosition = s.nTrackPos
End Sub
Public flags As mask
Public handle As IntPtr
Public scroll As Bar
Public Minimum As Integer
Public Maximum As Integer
Public Page As UInteger
Public Position As Integer
Public TrackPosition As Integer
Public Function Update(Optional ByVal redraw As Boolean = True) As Integer
Dim s As New SCROLLINFO(Me.flags)
s.nMin = Me.Minimum
s.nMax = Me.Maximum
s.nPos = Me.Position
s.nPage = Me.Page
s.nTrackPos = Me.TrackPosition
Return SCROLLINFO.SetScrollInfo(Me.handle, Me.scroll, s, redraw)
End Function
Public Enum Bar As Integer
Horizontal = 0
Vertical = 1
Client = 2
Both = 3
End Enum
Public Enum Mask As UInteger
Range = &H1
Page = &H2
Position = &H4
DisableNoScroll = &H8
TrackPosition = &H10
ALL = Range Or Page Or Position Or TrackPosition
End Enum
Private Structure SCROLLINFO
Public Declare Function GetScrollInfo Lib "user32" (ByVal hWnd As IntPtr, ByVal fnBar As Bar, ByRef lpScrollInfo As SCROLLINFO) As Boolean
Public Declare Function SetScrollInfo Lib "user32" (ByVal hWnd As IntPtr, ByVal fnBar As Bar, ByRef lpScrollInfo As SCROLLINFO, ByVal redraw As Boolean) As Integer
Sub New(ByVal mask As Mask)
Me.cbSize = Marshal.SizeOf(Me)
Me.fMask = mask
End Sub
Private cbSize As UInteger
Private fMask As Mask
Public nMin As Integer
Public nMax As Integer
Public nPage As UInteger
Public nPos As Integer
Public nTrackPos As Integer
End Structure
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.
Last edited by bergerkiller; Apr 5th, 2011 at 12:55 PM .
Posting Permissions
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
Forum Rules
Click Here to Expand Forum to Full Width