oops.UndoRedo.Framework 1.0.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package oops.UndoRedo.Framework --version 1.0.1                
NuGet\Install-Package oops.UndoRedo.Framework -Version 1.0.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="oops.UndoRedo.Framework" Version="1.0.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add oops.UndoRedo.Framework --version 1.0.1                
#r "nuget: oops.UndoRedo.Framework, 1.0.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install oops.UndoRedo.Framework as a Cake Addin
#addin nuget:?package=oops.UndoRedo.Framework&version=1.0.1

// Install oops.UndoRedo.Framework as a Cake Tool
#tool nuget:?package=oops.UndoRedo.Framework&version=1.0.1                

oops

First open source, cross-platform Do/Undo/Redo Framework that works in a user-driver application that's worth a dime. Available on nuget here

Premise

It is common for many user-facing applications to want some kind of undo/redo functionality for a user-driven action. This framework should be cross-platform, configurable, and loose in its structure to allow for unforeseen usage requirements.

Definition and Goals

The difficulty in creating a general purpose undo/redo framework lies in tackling (at least) two core issues:

Being able to define a range of actions from a user as undoable, or making every single thing the user does undoable
  • Making every character typed undoable, for example, can result in a klunky user experience, but hey it's a start.
  • The ideal framework would allow the code to aggregate changes together
  • oops allows either configuration or somewhere in-between. It's all about the scope of the Accumulator you 'new' up and use.

Observing changes in all objects in the system in an ordered fashion without explicitly knowing what all of them even are at the point of the user action.

For example, there may be 50 objects that are created and/or modified for any given user action and it would be tedious (at best) or impossible (for any real-world use-case) to funnel all changes into one undo action for the user by explicitly telling each object at the time of the change how to do it, and then how to undo it. oops manages all of this for you by observing any and all property changes in a ViewModel (derived from TrackableViewModel) and any collection changes (TrackableCollection - list, stack, queue - or TrackableDictionary), and accumulating them into one undo action either globally or locally.

Why distinguish between global and local changes? Glad you asked.

Presume you have an application with a main window/form/page of some sort. Anything that goes on in the app will observed and coalesced into undo actions through a menu-driven undo/redo system (like the back and forward arrows we're all used to).

Now, presume you have a dialog that pops up that does some undoable stuff, and also pops a different dialog that also does some undoable stuff. Depending on the project manager's whimsy, all of those actions from both dialogs should be aggregated into one undoable action for the user, or maybe there should be two. Either scenario (and really any scenario I can think of) is available through oops with the option to create singleton/global scopes or local scopes, either of which can go onto the global undo stack.

Components to support Undo/Redo for any ViewModel or Collection change

  • TrackableCollection - a beefed up version of ObservableCollection:

    • Takes care of context switching to the UI thread for you (if it is set up to fire on the UI thread)
    • Seamlessly tracks all operations and makes them undoable - this can be turned on/off at any time
    • Fires CollectionChanged and PropertyChanged events like the UI needs, in the right order, on the right thread, and synchronously with the caller's thread - this can be turned off at any time
    • Handles concurrency just fine (hammer it from multiple threads and it'll all get sorted out)
    • Works as a Stack<> or a Queue<> as well (because sometimes you need those bound to your UI)
    • Highly performant (is that still a word?), as much as the UI will allow
  • TrackableDictionary - super-charged version of Dictionary

    • Fires CollectionChanged and PropertyChanged events like the UI needs, in the right order, on the right thread, and synchronously with the caller's thread
    • Handles concurrency just fine (hammer it from multiple threads and it'll all get sorted out)
    • Takes care of context switching to the UI thread for you (if it is set up to fire on the UI thread)
    • Seamlessly tracks all operations and makes them undoable - this can be turned off at any time
    • Highly performant, as much as the UI will allow if it is bound to the UI
  • ConcurrentList

    • A highly performant List<> that handles concurrency just fine
    • Has lots of helper methods like RemoveAndGetIndex, ReplaceAll, etc. that are inherently thread-safe
    • Also works as a Stack<> or a Queue<>
  • Accumulator

    • Records all actions for undoing them later
    • Can be used a Singleton for application-scoped actions, or as an instance for locally-scoped actions (like in a dialog)
  • AccumulatorManager

    • Manages the Undo and Redo stacks of accumulators
    • Auto-records redo operations for any undo, and vice-versa
  • TrackableViewModel

    • Auto-tracks all changes to its properties in its referenced Accumulator (either the Singleton or a local one)
    • Changes are tracked by using custom Get<>/Set<> methods for any property change
  • TrackableScope

    • Utility class to easily create an Accumulator and push it to the AccumulatorManager on Dispose
    • All changes within its
    using(new TrackableScope("testing"))
    

    block are aggregated into one Undo action for the User

Demo app

  • See the oops.Demo WPF application

Show me some ❤️ and star this repo to support my project

Pull Requests

I welcome and encourage all pull requests. It usually will take me within 24-72 hours to respond to any issue or request. Here are some basic rules to follow to ensure timely addition of your request:

  1. Match coding style (braces, spacing, etc.), or I will do it for you
  2. If its a feature, bugfix, or anything please only change code to what you specify.
  3. Please keep PR titles easy to read and descriptive of changes, this will make them easier to merge 😃
  4. Pull requests must be made against develop branch. Any other branch (unless specified by the maintainers) will get rejected.
  5. Check for existing issues first, before filing an issue.
  6. Make sure you follow the set standard as all other projects in this repo do

Created & Maintained By

Brent Bulla (@outbred) (Insta)

If you found this project helpful or you learned something from the source code and want to thank me, consider buying me a cup of <img src="https://vignette.wikia.nocookie.net/logopedia/images/a/ad/Dr._Pepper_1958.jpg/revision/latest?cb=20100924201743" height="25em" /> - PayPal

License

MIT License

Copyright (c) 2018 Brent Bulla

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Getting Started

TODO

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.2 1,507 10/8/2018
1.0.1 1,216 10/4/2018
1.0.0 1,298 10/4/2018

Fixed a bug in the AccumulatorManager for scoping each change (auto-tracking every change into its own undo).