Реализация операций отмены/возврата для добавления/удаления элементов ListView (часть 2)

Что ж, здесь я должен поговорить о второй части этого вопроса Операции отмены/возврата для добавления/удаления элементов ListView и других Расширить этот класс до Undo/Redo в Listview.

Я пытаюсь реализовать операции Undo/Redo для добавления/удаления элементов ListView.

Я продвинулся еще немного с кодированием этого кода LV UndoManager, но мне всегда очень сложно, когда я пытаюсь продвинуться вперед.

На данный момент я могу добавлять отдельные элементы, а затем я могу отлично отменить/повторить ДОБАВЛЕННЫЕ элементы, не более того.

Проблемы, которые у меня есть, следующие:

· Когда я удаляю один элемент из списка, я не могу выполнить «отмену», чтобы снова добавить этот удаленный элемент в LV.

· Когда я добавляю диапазон, если элементы, которые я не могу отменить, Когда я вызываю UndoLastAction, возникает исключение System.Reflection.TargetParameterCountException

· Когда я удаляю ряд элементов, я не могу отменить/повторить операцию и запускаю то же исключение.

В резюме, если я добавлю один элемент, я могу отменить/повторить его идеально, если я удалю один элемент, который я не могу отменить правильно, также я не могу отменить/повторить ряд ListViewItems.

Мне нужен кто-то, кто мог бы помочь мне решить эти проблемы... или хотя бы одну из них, проявив терпение.

Код немного великоват, поэтому я думаю, что может потребоваться меньше времени, чтобы понять и найти ошибки, открывая и тестируя этот исходный проект, который я загрузил.

Вот full Источник:

http://elektrostudios.tk/UndoManager%20Test%20Application.zip

Просто изображение:

введите здесь описание изображения

вот класс UndoManager:

Class ListView_UndoManager

    Private action As ListView_Action = Nothing

    Public Property Undostack As New Stack(Of ListView_Action)
    Public Property Redostack As New Stack(Of ListView_Action)

    ' Public Property IsDoingUndo As Boolean = False
    ' Public Property IsDoingRedo As Boolean = False

    ''' <summary>
    ''' Undo the last action.
    ''' </summary>
    ''' <remarks></remarks>
    Sub UndoLastAction()

        If Undostack.Count = 0 Then Exit Sub ' Nothing to Undo.

        action = Undostack.Pop ' Get the Action from Stack and remove it.
        action.Operation.DynamicInvoke(action.data) ' Invoke the undo Action.

    End Sub

    ''' <summary>
    ''' Redo the last action.
    ''' </summary>
    ''' <remarks></remarks>
    Sub RedoLastAction()

        If Redostack.Count = 0 Then Exit Sub ' Nothing to Redo.

        action = Redostack.Pop() ' Get the Action from Stack and remove it.
        action.Operation.DynamicInvoke(action.data) ' Invoke the redo Action.

    End Sub

End Class

Class ListView_Action

    ''' <summary>
    ''' Name the Undo / Redo Action
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Property name As String

    ''' <summary>
    ''' Points to a method to excecute
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Property Operation As [Delegate]

    ''' <summary>
    ''' Data Array for the method to excecute
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Property data As ListViewItem()

End Class

Вот пользовательский элемент управления ListView, который я использую, я публикую его, потому что важны события, которые я запускаю: ItemAdded, ItemRemoved, RangeItemAdded и RangeItemRemoved.

Public Class LV : Inherits ListView

Public Shared Event ItemAdded As EventHandler(Of ItemAddedEventArgs)
Public Class ItemAddedEventArgs : Inherits EventArgs
    Public Property Item As ListViewItem
End Class

Public Shared Event ItemRemoved As EventHandler(Of ItemRemovedEventArgs)
Public Class ItemRemovedEventArgs : Inherits EventArgs
    Public Property Item As ListViewItem
End Class

Public Shared Event RangeItemAdded As EventHandler(Of RangeItemAddedEventArgs)
Public Class RangeItemAddedEventArgs : Inherits EventArgs
    Public Property Items As ListViewItem()
End Class

Public Shared Event RangeItemRemoved As EventHandler(Of RangeItemRemovedEventArgs)
Public Class RangeItemRemovedEventArgs : Inherits EventArgs
    Public Property Items As ListViewItem()
End Class

Public Sub New()

    Me.Name = "ListView_Elektro"
    Me.GridLines = True
    Me.FullRowSelect = True
    Me.MultiSelect = True
    Me.View = View.Details

End Sub

''' <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

    RaiseEvent ItemAdded(Me, New ItemAddedEventArgs With { _
                             .Item = Item
                       })

    Return MyBase.Items.Add(Item)

End Function

''' <summary>
''' Adds a range of Items to the ListView,
''' to monitor when an Item is added to the ListView.
''' </summary>
Public Sub AddItem_Range(ByVal Items As ListViewItem())

    RaiseEvent RangeItemAdded(Me, New RangeItemAddedEventArgs With { _
                                  .Items = Items
                            })

    MyBase.Items.AddRange(Items)

End Sub

''' <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)

    RaiseEvent ItemRemoved(Me, New ItemRemovedEventArgs With { _
                               .Item = Item
                         })

    MyBase.Items.Remove(Item)

End Sub

''' <summary>
''' Removes a range of Items from the ListView
''' to monitor when an Item is removed from the ListView.
''' </summary>
Public Sub RemoveItem_Range(ByVal Items As ListViewItem())

    RaiseEvent RangeItemRemoved(Me, New RangeItemRemovedEventArgs With { _
                                    .Items = Items
                              })

    For Each Item As ListViewItem In Items
        MyBase.Items.Remove(Item)
    Next

End Sub

End Class

И, наконец, вот код Form1 тестового приложения, вот то, что я использую для добавления/удаления элементов и вызова отмены/повторения, но я вызываю методы моего пользовательского элемента управления ListView, поэтому вы должны заметить, что... :

Public Class Form1

    Dim _undoManager As New ListView_UndoManager
    Dim LVItem As ListViewItem

    Delegate Sub AddDelegate(item As ListViewItem)
    Delegate Sub RemoveDelegate(item As ListViewItem)

    Delegate Sub AddRangeDelegate(item As ListViewItem())
    Delegate Sub RemoveRangeDelegate(item As ListViewItem())

    ' Adds a single item
    Private Sub Button_AddItem_Click(sender As Object, e As EventArgs) _
    Handles Button_AddItem.Click

        Dim index As String = CStr(LV1.Items.Count + 1)

        LVItem = New ListViewItem With {.Text = index}
        LVItem.SubItems.AddRange({"Hello " & index, "World " & index})

        LV1.AddItem(LVItem)

    End Sub

    ' Adds a range of 2 items to the ListView
    Private Sub Button_AddRange_Of_Items_Click(sender As Object, e As EventArgs) Handles Button_AddRange_Of_Items.Click

        Dim index As String = CStr(LV1.Items.Count + 1)

        Dim lvitem As New ListViewItem With {.Text = index}
        lvitem.SubItems.AddRange({"Hello " & index, "World " & index})

        Dim lvitem2 As New ListViewItem With {.Text = index + 1}
        lvitem2.SubItems.AddRange({"Hello " & index + 1, "World " & index + 1})

        LV1.AddItem_Range({lvitem, lvitem2})

    End Sub

    ' Removes the last item
    Private Sub Button_RemoveLastItem_Click(sender As Object, e As EventArgs) _
    Handles Button_RemoveLastItem.Click

        If LV1.Items.Count <> 0 Then

            LV1.RemoveItem(LV1.Items.Cast(Of ListViewItem).Last)

        End If

    End Sub

    ' Clear all items
    Private Sub Button_Clear_Items_Click(sender As Object, e As EventArgs) _
    Handles Button_Clear_Items.Click

        LV1.Items.Clear()

    End Sub

    ' Clear the Undo/Redo Stacks
    Private Sub Button_Clear_Stacks_Click(sender As Object, e As EventArgs) _
    Handles Button_Clear_Stacks.Click

        _undoManager.Undostack = New Stack(Of ListView_Action)
        _undoManager.Redostack = New Stack(Of ListView_Action)

        Label_UndoCount_Value.Text = CStr(0)
        Label_RedoCount_Value.Text = CStr(0)

    End Sub

    ' Refreshes the Stacks Count
    Private Sub Refresh_StackCount()

        Label_UndoCount_Value.Text = CStr(_undoManager.Undostack.Count)
        Label_RedoCount_Value.Text = CStr(_undoManager.Redostack.Count)

    End Sub

    ' Monitors when an Item is added
    Private Sub ListView_ItemAdded(sender As Object, e As LV.ItemAddedEventArgs) _
    Handles LV1.ItemAdded

        ' // Crate an Undo Action
        Dim u As New ListView_Action()
        With u
            .name = "Remove Item"
            .Operation = New RemoveDelegate(AddressOf LV1.RemoveItem)
            .data = {e.Item}
        End With

        _undoManager.Undostack.Push(u)

        Refresh_StackCount()

    End Sub

    ' Monitors when a range of Items are added
    Private Sub ListView_RangeItemAdded(sender As Object, e As LV.RangeItemAddedEventArgs) _
    Handles LV1.RangeItemAdded

        ' // Crate an Undo Action
        Dim u As New ListView_Action()
        With u
            .name = "Remove Item Range"
            .Operation = New RemoveRangeDelegate(AddressOf LV1.RemoveItem_Range)
            .data = e.Items
        End With

        _undoManager.Undostack.Push(u)

        Refresh_StackCount()

    End Sub

    ' Monitors when an Item is removed
    Private Sub ListView_ItemRemoved(sender As Object, e As LV.ItemRemovedEventArgs) _
    Handles LV1.ItemRemoved

        ' // Create a Redo Action
        Dim r As New ListView_Action()
        With r
            .name = "Add Item"
            .Operation = New AddDelegate(AddressOf LV1.AddItem)
            .data = {e.Item}
        End With

        _undoManager.Redostack.Push(r)

        Refresh_StackCount()

    End Sub

    ' Monitors when a range of Items are removed
    Private Sub ListView_RangeItemRemoved(sender As Object, e As LV.RangeItemRemovedEventArgs) _
    Handles LV1.RangeItemRemoved

        ' // Create a Redo Action
        Dim r As New ListView_Action()
        With r
            .name = "Add Item"
            .Operation = New AddRangeDelegate(AddressOf LV1.AddItem_Range)
            .data = e.Items
        End With

        _undoManager.Redostack.Push(r)

        Refresh_StackCount()

    End Sub

    ' Undo
    Private Sub Button_Undo_Click(sender As Object, e As EventArgs) _
    Handles Button_Undo.Click

        _undoManager.UndoLastAction()

    End Sub

    ' Redo
    Private Sub Button_Redo_Click(sender As Object, e As EventArgs) _
    Handles Button_Redo.Click

        _undoManager.RedoLastAction()

    End Sub

    Private Sub Button_Remove_Range_Of_Items_Click(sender As Object, e As EventArgs) Handles Button_Remove_Range_Of_Items.Click

    If LV1.Items.Count > 1 Then

        Dim lvi1 As ListViewItem = LV1.Items(LV1.Items.Count - 1)
        Dim lvi2 As ListViewItem = LV1.Items(LV1.Items.Count - 2)

        LV1.RemoveItem_Range({lvi1, lvi2})

    End If


    End Sub

End Class

PS: Как я уже сказал, действительно было бы гораздо удобнее скачать исходный код и протестировать его.


person ElektroStudios    schedule 04.11.2013    source источник
comment
Мне нравится ваш дизайн графического интерфейса!   -  person user2896540    schedule 05.11.2013
comment
@user2896540 user2896540 спасибо, но это не собственный дизайн, это просто из-за темной темы, которую я использую в Windows.   -  person ElektroStudios    schedule 05.11.2013
comment
почему это не AddRange, а просто цикл For/Next в AddItem? Зачем нужны еще 2 интерфейса?   -  person Ňɏssa Pøngjǣrdenlarp    schedule 05.11.2013
comment
@Plutonix хорошая логика, спасибо ... Я улучшу ее. может быть, это может быть решением, чтобы исправить некоторые вещи. Что ж, я тестирую это сейчас, когда я добавляю диапазон, я не могу отменить весь диапазон элементов, он просто идет один за другим ... но, по крайней мере, это работает, и я доволен этим, одной вещью меньше.   -  person ElektroStudios    schedule 05.11.2013


Ответы (1)


When I remove a single Item from the Listview - легкий.

RemoveItem удаляет элемент из списка И добавляет его в стек ReDo, но он все еще находится в стеке UnDo!!! Если вы добавите 5, уберете 1, а затем отмените, вы получите 2 копии элемента 5 в Redo!

Во-первых, вы должны изменить механизм AddItem на прямой счетчик, чтобы упростить отладку.

    nLVItemIndex += 1
    Dim index As String = (nLVItemIndex).ToString

    newItem = New ListViewItem
    newItem.Text = "Item " & index
    newItem.SubItems.Add("Hello " & index)
    newItem.SubItems.Add("World " & index)

    AddItem(newItem)

Использование CStr для счетчика элементов ListView создает имена, которые уже могут существовать в стеке UnDo/Redo, и усложняет отладку.

Я должен думать, что уровень графического интерфейса, вызываемое пользователем действие, такое как RemoveItem, попадет в стек UnDo. Вы приравниваете AddItem к UnDO и RemoveItem к Redo, что неверно. Все с уровня GUI Form должно попасть в стек Undo, а единственный способ, которым это должно попасть в ReDo, — это метод UM.Undo.

Перемещение его в стек UnDo обнаружит другую проблему: ваш менеджер UnDo делает очень мало для себя и использует уровень формы AddItem/RemoveItem, а не свои собственные внутренние процедуры (он даже не может создавать свои собственные действия UnDo/Redo). ВСЕ действия Additem Поместите действие Remove в стек UnDo; и All RemoveItems подталкивают действие ReDo, которое НЕдопустимо, поскольку вы хотите отменить действие Remove!

Конечным результатом является то, что UM.UndoLastAction появляется из UnDo (хорошо), затем DynamicInvoke запускает Form.AddItem, который выдает UnDo Push (очень плохо, потому что один только что выскочил - на самом деле это то, что мы все еще делаем - поэтому в оригинале были флаги IsRedoing). UnDo Manager нуждается в серьезной операции на головном мозге, чтобы выполнять свою работу, потому что действия «Добавить/удалить» на уровне графического интерфейса — это не то же самое, что «Отменить/Повторить».

  • GUI Добавить элемент ----> Нажать действие удаления
  • GUI Удалить ----> Нажмите кнопку добавления действия
  • UM Pop Add ------> Добавить элемент; Нажмите Удалить на ReDo
  • UM Pop Remove ------> Удалить; Нажмите «Добавить» на «Повторить»

Затем это показывает, что UnDoManager не имеет ссылки на элемент управления, которым он «управляет», не говоря уже о возможности контролировать более одного LV. Я бы подумал, что подход AddRange только усугубит проблемы, описанные выше (не могу найти основы в стене кода).

Наконец, действительно ли необходимо размещать все заголовки комментариев XML в стене текста? Все ли переопределения Draw связаны с Undo? Нет.

ИЗМЕНИТЬ

Вот примерно то, что должен сделать UnDoManager.UnDo (из моей переделки того раздутого, с которого вы начали):

Friend Function UnDo() As Boolean
    If _undoStack.Count = 0 Then Exit Function

    Dim ur As UndoAction         ' ie Command

    _IgnoreChange = True          ' ie IsUnDoing so you dont Push while Popping
    ur = _undoStack.Pop           ' get the Undo, such as LV.RemoveItem
    ur.Undo()                     ' Undo whatever it is (could be a checkbox etc)
    _IgnoreChange = False         ' open for business
    _redoStack.Push(ur)           ' push the same Action onto the ReDo
                                  ' I dont bother changing a code (yet) because
                                  ' if it is in Undostack it is an UnDo
   return True
End Function

Мой UnDoAction - это просто отмена контроля и Data As Object. Так как в БОЛЬШИНСТВЕ элементов управления есть только одна вещь, с которой пользователь может столкнуться, нет проблем. LV имеет 2 законных пользовательских действия (Проверить и Редактировать метку), поэтому, чтобы иметь возможность выполнять любое из них, его необходимо расширить.

Мой и другой основаны на полиморфизме, где undoStack (2) может быть действием отмены в списке, а undoStack (9) может быть действием со списком - наблюдатели (мониторы) ЗНАЮТ, какой тип создать И как отменить / повторить действие. Отмена текста (TextBox, Combo, MaskedEdit и DateTimePicker) — это просто:

Friend Overrides Function Undo() As Boolean

    _Ctl.Text = _UndoData
    Return True

End Function

Что меня интересует, так это то, что теперь вы просто делаете LastItem, а как насчет RemoveSelectedItem? Как привести его в порядок? Если вы сохраните ЛЮБУЮ ссылку на ордер, она может быть недействительной, потому что этой ссылки может уже не быть.

person Ňɏssa Pøngjǣrdenlarp    schedule 04.11.2013
comment
Этот простой вопрос легко понять, но его нелегко решить, я не могу его решить, сейчас я пробую другую точку зрения. Я создал 2 события в классе undomanager, события отменяются и повторяются, отменяется срабатывает, когда я удаляю стек и повторяю то же самое для повторного стека, я попытаюсь создать действия повтора только тогда, когда запускается событие Undone, и я сделаю действие отмены для событий ItemAdded и ItemRemoved LV, но у меня есть беспорядок, всегда ошибка везде ничего не может работать так, как ожидалось... ну, как вы думаете, я трачу время на эту логику или это совсем неплохая идея? банкомат не работает - person ElektroStudios; 05.11.2013
comment
fiiiiiiuuuu... наконец, я думаю, что у меня правильно работает ундоменеджер с новой логикой, здесь, в моей стране, уже слишком поздно, поэтому завтра я обновлю новый код, я хотел бы знать ваши наблюдения/предложения, когда я обновлю новый код, если бы вы могли, может быть, вы могли бы увидеть ошибку в коде, чтобы исправить ее, спасибо @Plutonix. - person ElektroStudios; 05.11.2013
comment
а) есть ли (хорошая) причина, по которой отмена LV переходит в другой стек, чем все остальные элементы управления? будут ли ваши также делать Label Edit и Checkbox? б) Добавлена ​​информация «UnDoManager.UnDo» в) лучше создать новый вопрос - person Ňɏssa Pøngjǣrdenlarp; 05.11.2013
comment
A) Я вас не понимаю, вы хотите сказать, что мне лучше объединить unostack и redostack?, и да, конечно, я реализую редактирование ярлыков и отметку/снятие флажков... Я иду шаг за шагом... так медленно. C) На самом деле я не собираюсь больше задавать вопросы по этому поводу... ни один мальчик не смог бы написать полный код, и когда я провожу дни за написанием чего-то, мне нужно переписать его полностью заново. ни за что. - person ElektroStudios; 05.11.2013
comment
Почему ваш LV не рисует флажки? Или куда ты его спрятал? В качестве упражнения я пытался понять, как использовать одно действие UnDoAction для захвата текста, проверки и изменения элемента. Трудно отлаживать без проверок. - person Ňɏssa Pøngjǣrdenlarp; 06.11.2013
comment
Поскольку, как я уже сказал, я иду шаг за шагом, процесс кодирования унд-менеджера медленный для моих навыков, я не могу добавить все необходимые проверки одновременно, если самая важная проверка (Добавить/удалить элементы) не работает. работают так, как ожидается в данный момент. Я видел изменения вашего ответа, но мне нужно время, чтобы попробовать все, у меня есть еще проекты. спасибо за ваше время и вашу помощь - person ElektroStudios; 06.11.2013
comment
В ПОРЯДКЕ! Я думал, что это было учтено или что я что-то сломал. Я разработал, как удалить выбранный элемент и вернуть его обратно, простую отмену проверки и отмену редактирования метки, что сложнее, чем кажется, потому что LV скрывает, КАКОЙ элемент редактируется в событии, и говорит, что старое значение НИЧТО вместо того, что вы установили как Text. Проверка отмены также была проблематичной, потому что LV запускает событие ItemChecked, когда вы добавляете новый элемент. ... так что СЕЙЧАС я готов к одному из этих вопросов о наградах, LOL. - person Ňɏssa Pøngjǣrdenlarp; 06.11.2013