Ahora le añadí lo básico para hacer undo/redo para añadir o eliminar items.
Una pequeña demostración:
Un ejemplo de uso:
Código
Public Class Form1 Private Sub Form_Load(sender As Object, e As EventArgs) Handles MyBase.Load ' Enable the Undo/Redo Manager ListView_Elektro1.Enable_UndoRedo_Manager = True ' Create an Item Dim LVItem As New ListViewItem With {.Text = "Hello World"} ' Add the item ListView_Elektro1.AddItem(LVItem) ' Remove the item 'ListView_Elektro1.RemoveItem(LVItem) End Sub ' Undo an operation Private Sub Button_Undo_Click(sender As Object, e As EventArgs) Handles Button_Undo.Click ListView_Elektro1.Undo() End Sub ' Redo an operation Private Sub Button_Redo_Click(sender As Object, e As EventArgs) Handles Button_Redo.Click ListView_Elektro1.Redo() End Sub ' Handles when an Undo or Redo operation is performed Private Sub UndoRedo_Performed(sender As Object, e As ListView_Elektro.UndoneRedoneEventArgs) _ Handles ListView_Elektro1.UndoRedo_IsPerformed MsgBox(e.Operation.ToString) MsgBox(e.Method.ToString) MsgBox(e.Item.Text) End Sub ' Handles when a Undo or Redo stack size changed Private Sub UndoRedo_StackSizeChanged(sender As Object, e As ListView_Elektro.StackSizeChangedEventArgs) _ Handles ListView_Elektro1.UndoRedo_StackSizeChanged MsgBox(e.UndoStackIsEmpty) MsgBox(e.RedoStackIsEmpty) End Sub End Class
El código completo del UserControl listo para ser compilado:
Código
' /* *\ ' |#* ListView Elektro *#| ' \* */ ' ' // By Elektro H@cker ' ' Properties: ' ----------- ' · Disable_Flickering ' · Double_Buffer ' · GridLineColor ' · ItemHighlightColor ' · ItemNotFocusedHighlighColor ' · DrawCustomGridLines ' · UseDefaultGridLines ' · Enable_ProgressBar ' · Progressbar_Column ' · ProgressBar_BackColor ' · ProgressBar_BorderColor ' · ProgressBar_FillColor1 ' · ProgressBar_FillColor2 ' · Percent ' · Percent_Decimal ' · Percent_Font ' · Percent_Text ' · Percent_Forecolor ' · Percent_Text_Allignment ' · Enable_UndoRedo_Manager ' Events: ' ------- ' · ItemAdded ' · ItemRemoved ' · UndoRedo_IsPerformed ' · UndoRedo_StackSizeChanged ' ' Methods: ' -------- ' · AddItem ' · AddItems ' · RemoveItem ' · RemoveItems ' · Undo ' · Redo Public Class ListView_Elektro : Inherits ListView Public Event ItemAdded As EventHandler(Of ItemAddedEventArgs) Public Class ItemAddedEventArgs : Inherits EventArgs Property Item As ListViewItem End Class Public Event ItemRemoved As EventHandler(Of ItemRemovedEventArgs) Public Class ItemRemovedEventArgs : Inherits EventArgs Property Item As ListViewItem End Class Private _Disable_Flickering As Boolean = True Private _gridLines As Boolean = False Private _useDefaultGridLines As Boolean = False Private _gridLineColor As Color = Color.Black Private _itemHighlightColor As Color = Color.FromKnownColor(KnownColor.Highlight) Private _itemNotFocusedHighlighColor As Color = Color.FromKnownColor(KnownColor.MenuBar) Private _enable_progressbar As Boolean = False Private _progressbar_column As Integer = Nothing Private _percent As Double = 0 Private _percent_decimal As Short = 2 Private _percent_text As String = "%" Private _percent_text_allignment As StringAlignment = StringAlignment.Center Private _percent_stringformat As StringFormat = New StringFormat With {.Alignment = _percent_text_allignment} Private _percent_font As Font = Me.Font Private _percent_forecolor As SolidBrush = New SolidBrush(Color.Black) Private _progressBar_backcolor As SolidBrush = New SolidBrush(Color.Red) Private _progressBar_bordercolor As Pen = New Pen(Color.LightGray) Private _progressBar_fillcolor1 As Color = Color.YellowGreen Private _progressBar_fillcolor2 As Color = Color.White Public Sub New() Me.Name = "ListView_Elektro" Me.DoubleBuffered = True Me.UseDefaultGridLines = True ' Set Listview OwnerDraw to True, so we can draw the progressbar inside. If Me.Enable_ProgressBar Then Me.OwnerDraw = True Me.GridLines = True Me.FullRowSelect = True Me.MultiSelect = True Me.View = View.Details End Sub #Region " Properties " ''' <summary> ''' Enable/Disable any flickering effect on the ListView. ''' </summary> Protected Overrides ReadOnly Property CreateParams() As CreateParams Get If _Disable_Flickering Then Dim cp As CreateParams = MyBase.CreateParams cp.ExStyle = cp.ExStyle Or &H2000000 Return cp Else Return MyBase.CreateParams End If End Get End Property ''' <summary> ''' Set the Double Buffer. ''' </summary> Public Property Double_Buffer() As Boolean Get Return Me.DoubleBuffered End Get Set(ByVal Value As Boolean) Me.DoubleBuffered = Value End Set End Property ''' <summary> ''' Enable/Disable the flickering effects on this ListView. ''' ''' This property turns off any Flicker effect on the ListView ''' ...but also reduces the performance (speed) of the ListView about 30% slower. ''' This don't affect to the performance of the application itself, only to the performance of this control. ''' </summary> Public Property Disable_Flickering() As Boolean Get Return _Disable_Flickering End Get Set(ByVal Value As Boolean) Me._Disable_Flickering = Value End Set End Property ''' <summary> ''' Changes the gridline color. ''' </summary> Public Property GridLineColor() As Color Get Return _gridLineColor End Get Set(ByVal value As Color) If value <> _gridLineColor Then _gridLineColor = value If _gridLines Then Me.Invalidate() End If End If End Set End Property ''' <summary> ''' Changes the color when item is highlighted. ''' </summary> Public Property ItemHighlightColor() As Color Get Return _itemHighlightColor End Get Set(ByVal value As Color) If value <> _itemHighlightColor Then _itemHighlightColor = value Me.Invalidate() End If End Set End Property ''' <summary> ''' Changes the color when the item is not focused. ''' </summary> Public Property ItemNotFocusedHighlighColor() As Color Get Return _itemNotFocusedHighlighColor End Get Set(ByVal value As Color) If value <> _itemNotFocusedHighlighColor Then _itemNotFocusedHighlighColor = value Me.Invalidate() End If End Set End Property Private ReadOnly Property DrawCustomGridLines() As Boolean Get Return (_gridLines And Not _useDefaultGridLines) End Get End Property Public Shadows Property GridLines() As Boolean Get Return _gridLines End Get Set(ByVal value As Boolean) _gridLines = value End Set End Property ''' <summary> ''' use the default gridlines. ''' </summary> Public Property UseDefaultGridLines() As Boolean Get Return _useDefaultGridLines End Get Set(ByVal value As Boolean) If _useDefaultGridLines <> value Then _useDefaultGridLines = value End If MyBase.GridLines = value MyBase.OwnerDraw = Not value End Set End Property #End Region #Region " Procedures " ''' <summary> ''' Adds an Item to the ListView, ''' to monitor when an Item is added to the ListView. ''' </summary> Public Function AddItem(ByVal Item As ListViewItem) As ListViewItem Me.Items.Add(Item) RaiseEvent ItemAdded(Me, New ItemAddedEventArgs With {.Item = Item}) Return Item End Function Public Function AddItem(ByVal Text As String) As ListViewItem Dim NewItem As New ListViewItem(Text) Me.Items.Add(NewItem) RaiseEvent ItemAdded(Me, New ItemAddedEventArgs With {.Item = NewItem}) Return NewItem End Function ''' <summary> ''' Removes an Item from the ListView ''' to monitor when an Item is removed from the ListView. ''' </summary> Public Sub RemoveItem(ByVal Item As ListViewItem) Me.Items.Remove(Item) RaiseEvent ItemRemoved(Me, New ItemRemovedEventArgs With {.Item = Item}) End Sub ''' <summary> ''' Removes an Item from the ListView at given Index ''' to monitor when an Item is removed from the ListView. ''' </summary> Public Sub RemoveItem_At(ByVal Index As Integer) RemoveItem(Me.Items.Item(Index)) End Sub ''' <summary> ''' Removes an Item from the ListView at given Index ''' to monitor when an Item is removed from the ListView. ''' </summary> Public Sub RemoveItems_At(ByVal Indexes As Integer()) Array.Sort(Indexes) Array.Reverse(Indexes) For Each Index As Integer In Indexes RemoveItem(Me.Items.Item(Index)) Next End Sub ''' <summary> ''' Adds a range of Items to the ListView, ''' to monitor when an Item is added to the ListView. ''' </summary> Public Sub AddItems(ByVal Items As ListViewItem()) For Each item As ListViewItem In Items AddItem(item) Next End Sub Public Sub AddItems(ByVal Items As ListViewItemCollection) For Each item As ListViewItem In Items AddItem(item) Next End Sub ''' <summary> ''' Removes a range of Items from the ListView ''' to monitor when an Item is removed from the ListView. ''' </summary> Public Sub RemoveItems(ByVal Items As ListViewItem()) For Each item As ListViewItem In Items RemoveItem(item) Next End Sub Public Sub RemoveItems(ByVal Items As ListViewItemCollection) For Each item As ListViewItem In Items RemoveItem(item) Next End Sub Public Sub RemoveItems(ByVal Items As SelectedListViewItemCollection) For Each item As ListViewItem In Items RemoveItem(item) Next End Sub Protected Overrides Sub OnDrawColumnHeader(ByVal e As System.Windows.Forms.DrawListViewColumnHeaderEventArgs) e.DrawDefault = True MyBase.OnDrawColumnHeader(e) End Sub Protected Overrides Sub OnLostFocus(ByVal e As System.EventArgs) For Each selectedIndex As Integer In MyBase.SelectedIndices MyBase.RedrawItems(selectedIndex, selectedIndex, False) Next MyBase.OnLostFocus(e) End Sub Protected Overrides Sub OnDrawSubItem(ByVal e As System.Windows.Forms.DrawListViewSubItemEventArgs) Dim drawAsDefault As Boolean = False Dim highlightBounds As Rectangle = Nothing Dim highlightBrush As SolidBrush = Nothing 'FIRST DETERMINE THE COLOR If e.Item.Selected Then If MyBase.Focused Then highlightBrush = New SolidBrush(_itemHighlightColor) ElseIf HideSelection Then drawAsDefault = True Else highlightBrush = New SolidBrush(_itemNotFocusedHighlighColor) End If Else drawAsDefault = True End If If drawAsDefault Then e.DrawBackground() Else 'NEXT DETERMINE THE BOUNDS IN WHICH TO DRAW THE BACKGROUND If FullRowSelect Then highlightBounds = e.Bounds Else highlightBounds = e.Item.GetBounds(ItemBoundsPortion.Label) End If 'ONLY DRAW HIGHLIGHT IN 1 OF 2 CASES 'CASE 1 - FULL ROW SELECT (AND DRAWING ANY ITEM) 'CASE 2 - NOT FULL ROW SELECT (AND DRAWING 1ST ITEM) If FullRowSelect Then e.Graphics.FillRectangle(highlightBrush, highlightBounds) ElseIf e.ColumnIndex = 0 Then e.Graphics.FillRectangle(highlightBrush, highlightBounds) Else e.DrawBackground() End If End If e.DrawText() If _gridLines Then e.Graphics.DrawRectangle(New Pen(_gridLineColor), e.Bounds) End If If FullRowSelect Then e.DrawFocusRectangle(e.Item.GetBounds(ItemBoundsPortion.Entire)) Else e.DrawFocusRectangle(e.Item.GetBounds(ItemBoundsPortion.Label)) End If MyBase.OnDrawSubItem(e) End Sub #End Region #Region " ProgressBar Properties " ''' <summary> ''' Enables the drawing of a ProgressBar ''' This property should be "True" to use any of the ProgressBar properties. ''' </summary> Public Property Enable_ProgressBar As Boolean Get Return _enable_progressbar End Get Set(ByVal value As Boolean) Me.OwnerDraw = value _enable_progressbar = value End Set End Property ''' <summary> ''' The column index to draw the ProgressBar ''' </summary> Public Property Progressbar_Column As Integer Get Return _progressbar_column End Get Set(ByVal value As Integer) _progressbar_column = value End Set End Property ''' <summary> ''' The ProgressBar progress percentage ''' </summary> Public Property Percent As Double Get Return _percent End Get Set(ByVal value As Double) _percent = value End Set End Property ''' <summary> ''' The decimal factor which should be displayed for the ProgressBar progress percentage ''' </summary> Public Property Percent_Decimal As Short Get Return _percent_decimal End Get Set(ByVal value As Short) _percent_decimal = value End Set End Property ''' <summary> ''' The Font to be used as the ProgressBar Percent text ''' </summary> Public Property Percent_Font As Font Get Return _percent_font End Get Set(ByVal value As Font) _percent_font = value End Set End Property ''' <summary> ''' The additional text to add to the ProgressBar Percent value ''' </summary> Public Property Percent_Text As String Get Return _percent_text End Get Set(ByVal value As String) _percent_text = value End Set End Property ''' <summary> ''' The ForeColor of the ProgressBar Percent Text ''' </summary> Public Property Percent_Forecolor As Color Get Return _percent_forecolor.Color End Get Set(ByVal value As Color) _percent_forecolor = New SolidBrush(value) End Set End Property ''' <summary> ''' The text allignment to use for the ProgressBar ''' </summary> Public Property Percent_Text_Allignment As StringAlignment Get Return _percent_stringformat.Alignment End Get Set(ByVal value As StringAlignment) _percent_stringformat.Alignment = value End Set End Property ''' <summary> ''' The ProgressBar BackColor ''' </summary> Public Property ProgressBar_BackColor As Color Get Return _progressBar_backcolor.Color End Get Set(ByVal value As Color) _progressBar_backcolor = New SolidBrush(value) End Set End Property ''' <summary> ''' The ProgressBar BorderColor ''' </summary> Public Property ProgressBar_BorderColor As Color Get Return _progressBar_bordercolor.Color End Get Set(ByVal value As Color) _progressBar_bordercolor = New Pen(value) End Set End Property ''' <summary> ''' The First ProgressBar Gradient color ''' </summary> Public Property ProgressBar_FillColor1 As Color Get Return _progressBar_fillcolor1 End Get Set(ByVal value As Color) _progressBar_fillcolor1 = value End Set End Property ''' <summary> ''' The Last ProgressBar Gradient color ''' </summary> Public Property ProgressBar_FillColor2 As Color Get Return _progressBar_fillcolor2 End Get Set(ByVal value As Color) _progressBar_fillcolor2 = value End Set End Property #End Region #Region " ProgressBar EventHandlers " ' ListView [DrawColumnHeader] Public Sub Me_DrawColumnHeader(ByVal sender As Object, ByVal e As DrawListViewColumnHeaderEventArgs) _ Handles Me.DrawColumnHeader e.DrawDefault = True ' Draw default ColumnHeader. End Sub ' ListView [DrawItem] Public Sub Me_DrawItem(ByVal sender As Object, ByVal e As DrawListViewItemEventArgs) _ Handles Me.DrawItem e.DrawDefault = False ' Draw default main item. End Sub ' ListView [DrawSubItem] Public Sub Me_DrawSubItem(ByVal sender As Object, ByVal e As DrawListViewSubItemEventArgs) _ Handles Me.DrawSubItem If Not Enable_ProgressBar OrElse Progressbar_Column = Nothing Then Exit Sub End If ' Item is highlighted. ' If (e.ItemState And ListViewItemStates.Selected) <> 0 Then ' e.Graphics.FillRectangle(SystemBrushes.Highlight, e.Bounds) ' End If ' Draw the progressbar. If e.ColumnIndex = Progressbar_Column Then ' Background color of the progressbar. e.Graphics.FillRectangle(_progressBar_backcolor, e.Bounds) ' Gradient to fill the progressbar. Dim brGradient As Brush = _ New System.Drawing.Drawing2D.LinearGradientBrush(New Rectangle(e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height), _ ProgressBar_FillColor1, ProgressBar_FillColor2, 270, True) ' Draw the actual progressbar. e.Graphics.FillRectangle(brGradient, _ e.Bounds.X + 1, e.Bounds.Y + 2, _ CInt(((Percent) / 100) * (e.Bounds.Width - 2)), e.Bounds.Height - 3) ' Draw the percentage number and percent sign. e.Graphics.DrawString(Percent.ToString("n" & Percent_Decimal) & Percent_Text, _ Percent_Font, _percent_forecolor, _ CSng(e.Bounds.X + (e.Bounds.Width / 2)), e.Bounds.Y + 3, _ _percent_stringformat) ' Draw a light gray rectangle/border around the progressbar. e.Graphics.DrawRectangle(_progressBar_bordercolor, _ e.Bounds.X, e.Bounds.Y + 1, _ e.Bounds.Width - 1, e.Bounds.Height - 2) Else ' e.DrawDefault = True End If End Sub #End Region #Region " Undo/Redo Manager " ''' <summary> ''' Enable or disble the Undo/Redo monitoring. ''' </summary> Public Property Enable_UndoRedo_Manager As Boolean = False ' Stacks to store Undo/Redo actions. Public Undostack As New Stack(Of ListView_Action) Public Redostack As New Stack(Of ListView_Action) ' Flags to check if it is doing a Undo/Redo operation. Private IsDoingUndo As Boolean = False Private IsDoingRedo As Boolean = False ' Delegate to Add an Item for Undo/Redo operations. Private Delegate Sub AddDelegate(item As ListViewItem) ' Delegate to Remove an Item for Undo/Redo operations. Private Delegate Sub RemoveDelegate(item As ListViewItem) ' The Undo/Redo action. Private action As ListView_Action = Nothing ' The operation. Public Enum Operation As Short Undo = 0 Redo = 1 End Enum ' The method for the Undo/Redo operation. Public Enum Method As Short Add = 0 Remove = 1 End Enum ''' <summary> ''' Creates a Undo/Redo Action. ''' </summary> Class ListView_Action ''' <summary> ''' Names the Undo/Redo Action. ''' </summary> Property Name As String ''' <summary> ''' Points to a method to excecute. ''' </summary> Property Operation As [Delegate] ''' <summary> ''' Method of the Undo/Redo operation. ''' </summary> Property Method As Method ''' <summary> ''' Data Array for the method to excecute. ''' </summary> Property Data As ListViewItem End Class ''' <summary> ''' This event is raised after an Undo/Redo action is performed. ''' </summary> Public Event UndoRedo_IsPerformed As EventHandler(Of UndoneRedoneEventArgs) Public Class UndoneRedoneEventArgs : Inherits EventArgs Property Operation As Operation Property Method As Method Property Item As ListViewItem Property UndoStack As Stack(Of ListView_Action) Property RedoStack As Stack(Of ListView_Action) End Class ''' <summary> ''' This event is raised when Undo/Redo Stack size changed. ''' </summary> Public Event UndoRedo_StackSizeChanged As EventHandler(Of StackSizeChangedEventArgs) Public Class StackSizeChangedEventArgs : Inherits EventArgs Property UndoStack As Stack(Of ListView_Action) Property RedoStack As Stack(Of ListView_Action) Property UndoStackIsEmpty As Boolean Property RedoStackIsEmpty As Boolean End Class ''' <summary> ''' Undo the last action. ''' </summary> Public Sub Undo() If Me.Undostack.Count = 0 Then Exit Sub ' Nothing to Undo. Me.IsDoingUndo = True Me.action = Me.Undostack.Pop ' Get the Action from the Stack and remove it. Me.action.Operation.DynamicInvoke(Me.action.Data) ' Invoke the undo Action. Me.IsDoingUndo = False Raise_UndoRedo_IsPerformed(Operation.Undo, Me.action.Method, Me.action.Data) End Sub ''' <summary> ''' Redo the last action. ''' </summary> Public Sub Redo() If Me.Redostack.Count = 0 Then Exit Sub ' Nothing to Redo. Me.IsDoingRedo = True Me.action = Me.Redostack.Pop() ' Get the Action from the Stack and remove it. Me.action.Operation.DynamicInvoke(Me.action.Data) ' Invoke the redo Action. Me.IsDoingRedo = False Raise_UndoRedo_IsPerformed(Operation.Redo, Me.action.Method, Me.action.Data) End Sub ' Reverses an Undo/Redo action Private Function GetReverseAction(ByVal e As UndoneRedoneEventArgs) As ListView_Action Me.action = New ListView_Action Me.action.Name = e.Item.Text Me.action.Data = e.Item Me.action.Operation = If(e.Method = Method.Add, _ New RemoveDelegate(AddressOf Me.RemoveItem), _ New AddDelegate(AddressOf Me.AddItem)) Me.action.Method = If(e.Method = Method.Add, _ Method.Remove, _ Method.Add) Return Me.action End Function ' Raises the "UndoRedo_IsPerformed" Event Private Sub Raise_UndoRedo_IsPerformed(ByVal Operation As Operation, _ ByVal Method As Method, _ ByVal Item As ListViewItem) RaiseEvent UndoRedo_IsPerformed(Me, New UndoneRedoneEventArgs _ With {.Item = Item, _ .Method = Method, _ .Operation = Operation, _ .UndoStack = Me.Undostack, _ .RedoStack = Me.Redostack}) Raise_UndoRedo_StackSizeChanged() End Sub ' Raises the "UndoRedo_StackSizeChanged" Event Private Sub Raise_UndoRedo_StackSizeChanged() RaiseEvent UndoRedo_StackSizeChanged(Me, New StackSizeChangedEventArgs _ With {.UndoStack = Me.Undostack, _ .RedoStack = Me.Redostack, _ .UndoStackIsEmpty = Me.Undostack.Count = 0, _ .RedoStackIsEmpty = Me.Redostack.Count = 0}) End Sub ' This handles when an Undo or Redo operation is performed. Private Sub UndoneRedone(ByVal sender As Object, ByVal e As UndoneRedoneEventArgs) _ Handles Me.UndoRedo_IsPerformed Select Case e.Operation Case Operation.Undo ' Create a Redo Action for the undone action. Me.Redostack.Push(GetReverseAction(e)) Case Operation.Redo ' Create a Undo Action for the redone action. Me.Undostack.Push(GetReverseAction(e)) End Select End Sub ' Monitors when an Item is added to create an Undo Operation. Private Sub OnItemAdded(sender As Object, e As ItemAddedEventArgs) _ Handles Me.ItemAdded If Me.Enable_UndoRedo_Manager _ AndAlso (Not Me.IsDoingUndo And Not Me.IsDoingRedo) Then Me.Redostack.Clear() ' // Crate an Undo Action Me.action = New ListView_Action Me.action.Name = e.Item.Text Me.action.Operation = New RemoveDelegate(AddressOf Me.RemoveItem) Me.action.Data = e.Item Me.action.Method = Method.Remove Me.Undostack.Push(action) Raise_UndoRedo_StackSizeChanged() End If End Sub ' Monitors when an Item is removed to create an Undo Operation. Private Sub OnItemRemoved(sender As Object, e As ItemRemovedEventArgs) _ Handles Me.ItemRemoved If Me.Enable_UndoRedo_Manager _ AndAlso (Not Me.IsDoingUndo And Not Me.IsDoingRedo) Then Me.Redostack.Clear() ' // Crate an Undo Action Me.action = New ListView_Action Me.action.Name = e.Item.Text Me.action.Operation = New AddDelegate(AddressOf Me.AddItem) Me.action.Data = e.Item Me.action.Method = Method.Add Me.Undostack.Push(action) Raise_UndoRedo_StackSizeChanged() End If End Sub #End Region End Class