Introduction to the Infragistics Undo / Redo Framework for XAML applications

Damyan Petev / Friday, April 27, 2012

Application with heavy CRUD capabilities for the end user? Would the result be a chaos caused by users insisting for that one thing they just deleted? Turn that around (literally!) by providing some Undo action in your XAML application. That is exactly what the brand new Undo/Redo Framework does and it is available as you’d guess in both the Silverlight and WPF packages. Even if you conclude that there is like no chance the end user would delete something by mistake, still adding such capability would give reassurance to those using it – it’s just seeing that iconic Undo button and knowing that your data is still safe even if you accidentally delete it. It’s widespread, even more so – it’s pretty much expected and now it is made available to you! This new framework helps you implement both Undo and Redo functionalities, provides a plethora of fundamentals to build upon for an unlimited linear(meaning the order of events will always be preserved) Undo-s.

A word of caution – it is a framework, not an UI control and all the pretty arrows and menus you see below or in our samples are separate controls like ribbons, menus, etc.

The Experience

Let’s start with what can make this so appealing – the functionality and experience you can deliver. Since it is non-visual it’s main goes it to provide functionality – and that it does by the dozen. It’s the reason why this blog would be an introduction only as it would be become a bit overwhelming if everything is presented. One other thing this lead to is that the experience would also heavily be affected by the type of UI you provide, so let’s have an example!

As usual our Customers from Northwind being the main content of a XamGrid. Say you want to delete some rows but you end up selecting a few more and hitting delete. Then you go and add some rows and delete some more. Now what? It’s all good as they have now all landed inside the framework-provided history and are available to undo:

Undo / Redo Framework with XamGrid and XamMenu: Undo on multiple items.

And as by this image if you go and click the user with id ‘BERGS’ just like adding multiple records it will restore multiple actions – specifically following the line to reach to the one we selected. Those won’t just be restored to you, but they would activate the Redo functionality and become part of its history now:

Undo / Redo Framework with XamGrid and XamMenu: Results from Undo on multiple items and Redo history collection.

Of course, the Undo/Redo providing UI control (so to speak) is the Infragistics XamMenu, but you are not restricted to any control. You can go for the XamRibbon (or add the functionality to the one already in your application) for a complete Microsoft Office feel. Then again you can implement the basic functionality with two simple buttons as well.

Getting started

We’ll keep this as simple as possible, we already mentioned where our data is coming from and now we can shed some light on the other thing you need – the base Infragistics assembly and the Undo assembly (InfragisticsSL5.Undo.v12.1 or the equivalent in WPF) are a must. And then whatever you need for your UI – in this example that would be the XamGrid along with the XamMenu. If you want to get an app with such functionality up and running as soon as possible you can approach it in this way – we have our Customer from Northwind, the framework provides you with an Undo Manager (its beating heart in a way). The manager can keeps track of changes and performs operations, provides various helpers and information. The framework also includes a special type of collection inhering from the ObservableCollectionExtended with added Undo Manager that records the collection’s changes. Those are the essentials required for basic functionality and here’s how we can use them:

  1. // define a new managed and collectio inside your Page/Window class:
  2. UndoManager um = new UndoManager();
  3. ObservableCollectionExtendedWithUndo<Customer> customers;
  4.  
  5. private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
  6. {
  7.     //initialize/associate your collectionWithUndo with the manager instance
  8.     customers = new ObservableCollectionExtendedWithUndo<Customer>(um);
  9.     //set the context for the menu
  10.     this.xamMenu.DataContext = um;
  11.     //load the data from the server:
  12.     NorthwindDomainContext nw = new NorthwindDomainContext();
  13.     nw.Load(nw.GetCustomersQuery(), Customers_Loaded, null);
  14. }
  15.  
  16. public void Customers_Loaded(System.ServiceModel.DomainServices.Client.LoadOperation<Customer> result)
  17. {
  18.     //populate with your data and set the grid's context
  19.     customers.AddRange(result.Entities);
  20.     this.xamGrid.DataContext = customers;
  21.     //clear the history (91 new items just got added after all :P )
  22.     customers.UndoManager.ClearHistory();
  23. }

We already explained the UI and here’s how it looks it XAML:

  1. <ig:XamMenu x:Name="xamMenu" Height="20">
  2.     <ig:XamMenu.Resources>
  3.         <DataTemplate x:Key="historyItemTemplate">
  4.             <TextBlock Text="{Binding LongDescription}" />
  5.         </DataTemplate>
  6.         <DataTemplate x:Key="menuItem">
  7.             <ig:XamMenuItem>
  8.                 <ig:Commanding.Command>
  9.                     <ig:UndoManagerCommandSource CommandType="UndoRedoHistoryItem"
  10.                                ParameterBinding="{Binding}"
  11.                                EventName="Click" />
  12.                 </ig:Commanding.Command>
  13.             </ig:XamMenuItem>
  14.         </DataTemplate>
  15.     </ig:XamMenu.Resources>
  16.     
  17.     <ig:XamMenuItem Header="Undo"
  18.           IsEnabled="{Binding CanUndo}"
  19.           ItemsSource="{Binding UndoHistory}"
  20.           DefaultItemsContainer="{StaticResource menuItem}"
  21.           ItemTemplate="{StaticResource historyItemTemplate}">
  22.         <ig:Commanding.Command>
  23.             <ig:UndoManagerCommandSource EventName="SubmenuOpened"
  24.                            CommandType="PreventMerge"
  25.                            ParameterBinding="{Binding}" />
  26.         </ig:Commanding.Command>
  27.         <ig:XamMenuItem.HeaderTemplate>
  28.             <DataTemplate>
  29.                 <StackPanel Orientation="Horizontal">
  30.                     <Image Stretch="None" Source="/UndoRedoFrameworkDemo;component/Images/undoArrow_white.png" />
  31.                     <TextBlock Text="Undo" ></TextBlock>
  32.                 </StackPanel>
  33.             </DataTemplate>
  34.         </ig:XamMenuItem.HeaderTemplate>
  35.     </ig:XamMenuItem>
  36.     
  37.     <ig:XamMenuItem
  38.           IsEnabled="{Binding CanRedo}"
  39.           ItemsSource="{Binding RedoHistory}"
  40.           DefaultItemsContainer="{StaticResource menuItem}"
  41.           ItemTemplate="{StaticResource historyItemTemplate}" >
  42.         <ig:XamMenuItem.HeaderTemplate>
  43.             <DataTemplate>
  44.                 <StackPanel Orientation="Horizontal" >
  45.                     <Image Stretch="None" Source="/UndoRedoFrameworkDemo;component/Images/redoArrow_white.png"/>
  46.                 <TextBlock Text="Redo" ></TextBlock>
  47.                 </StackPanel>
  48.             </DataTemplate>
  49.         </ig:XamMenuItem.HeaderTemplate>
  50.     </ig:XamMenuItem>
  51. </ig:XamMenu>
  52. <ig:XamGrid x:Name="xamGrid"
  53.             ItemsSource="{Binding}"
  54.             DeleteKeyAction="DeleteSelectedRows"
  55.             CellExitedEditMode="xamGrid_CellExitedEditMode"
  56.             AutoGenerateColumns="True" Grid.Row="1">
  57.     <ig:XamGrid.SelectionSettings>
  58.         <ig:SelectionSettings CellClickAction="SelectRow" RowSelection="Multiple"></ig:SelectionSettings>
  59.     </ig:XamGrid.SelectionSettings>
  60.     <ig:XamGrid.AddNewRowSettings>
  61.         <ig:AddNewRowSettings AllowAddNewRow="Top"/>
  62.     </ig:XamGrid.AddNewRowSettings>
  63.     <ig:XamGrid.PagerSettings>
  64.         <ig:PagerSettings AllowPaging="Bottom"></ig:PagerSettings>
  65.     </ig:XamGrid.PagerSettings>
  66. </ig:XamGrid>

And, of course, the result from all this are the screenshots from above (Metro theme applied).

Flexibility and Functionality

There are quite a few conclusions you can draw from the code above - clearing the history from the initializing process isn’t the only way (although this implementation is somewhat asking for it). You can suspend the manager’s recording while you add items or you can go for a completely different implementation – the “take-away” from all of this is that you have been given with plenty of control over what happens, when and how.

Moreover, this implementation is meant for a quick start and out of the box this would enable just the basic monitoring of collection changes like adding and removing items. For tracking property changes (cell edits in this case) it would be good if your underlying observable model implements the Undo Manager and soon enough you will see full samples following this process from scratch.

Control

You can manually add changes based on your own event to the Undo Manager – methods with multiple overloads let you define descriptions to enter the history, types of changes and even your own actions to be executed when performing Undo or Redo.

The framework defines a basic Undo Unit  to represent the occurred changes and speaking of changes… I showed above that deleting multiple rows can result in multiple history entries and when you don’t put a limit on it it’s really ok. But then again it it’s like 100 new records, why make your user scroll through that? The Undo manager’s method called StartTransaction will start a transaction that can be used to group changes(or merge actions if you will). What this would do is compress your 100 added rows into a single undo-able history entry. The manager also exposes a root transaction and newly started transactions will become nested and ..let’s not go that far for now.

Commands

As you may have noticed the XamMenu that is bound to the Undo Managed not only uses it to enable it’s items and populate its lists, but it also uses Commanding. The framework provides several commands that can be used in XAML code – all the things you’d expect are available – prevent merging of Undo units as explained above, invoke Undo/Redo methods or even execute them up to a chosen history item.

Conclusion

The new Undo / Redo framework provides excellent infrastructure for functionality that is not a luxury at all – it’s something you just should have. Unlimited, linear, easy to get going, deep integration and a full range of customization points for when you are in need of more. Add some nice User Interface controls on top of it and you are surely going to have an awesome Silverlight / WPF application and satisfied end users!

Stay tuned for dome project, links and more.