Archive

A couple of weeks ago we announced our PostSharp 3.2 makes it much easier to implement undo/redo in your app. We didn’t want to publish the feature before giving it a serious try, but didn’t have an internal app to dogfood. So we looked for an open-source project that would greatly benefit from the feature. We selected VisualDesigner, a framework to build graphical editors as PowerPoint, Visio or Blend. VisualDesigner is hosted on GitHub and released under the permissive MS-PL.

VisualDesigner is being developed by José Manuel Nieto (@SuperJMN). José works at DocPath, building software to help customers to design and create dynamic documents. With years of experience in graphical editors, he understands that visualization and UI interaction are very important to DocPath’s product line. You can find José’s contact info on his GitHub profile page.

We asked José to talk about his project.

Jose Manuel Nieto

How did you come to the idea of creating VisualDesigner?

It all started as one of my many side projects. As part of my experience, I have seen that visual designers have a lot of potential. A great number of applications can benefit in a way or another from this concept. I realized that there was almost no information about how to develop a visual designer from scratch and most importantly, doing it the right way. Only a few basic samples, incomplete and not generic enough to be a solid start for others to make their own designers in a MVVM way. I took it as a challenge and felt I had enough knowledge to make something useful out of it while enjoying my passion for development.

Tell us more about VisualDesigner.

It’s a little framework that enables anybody to develop their own designer in minutes. It solves the most common scenarios in a designer and can be extended easily. It’s made specifically to apply the MVVM pattern.

The project consists of several libraries and 2 demo applications. One is a WPF demo. It was the first I developed.

WPF Demo App

Picture 1 WPF Demo application

As most of the code is shared through a Portable Class Library, it was very easy to create a demo application using other Windows platforms like Windows RT.

The Window RT demo is a very basic comic creator. You can drop a sample character, add speech bubbles and frames. You can also modify their properties like text, fonts, colors…

Picture 2 Windows Store Demo Application

90% of the code is shared, and it works out of the box, that it’s a very good thing.

What are the principal features of VisualDesigner?

I think the most important feature is that it’s domain-agnostic. It doesn’t require you to modify your model to work as expected.

Talking about the different operations that can be applied to a design, it features:

  • move, resize,
  • group, ungroup,
  • bring to front, send to back,
  • snapping, undo/redo.

It supports WPF and WinRT through Portable Class Librairies.

Picture 3 Snapping to edges is a nice feature

What were your architectural/design priorities?

As a die-hard Clean Code follower, I think that source code should be written to be read, not to be deciphered. I put emphasis in best practices and principles to make it robust and easy to understand. Also, having a clean domain model is a top priority for me. I come from applications developed from a Domain-Driven Design perspective and my natural tendency is to keep the model as the true protagonist.

For instance, take a look at this class:

    
public class SpeechBubble : Shape
{
    public string Text { get; set; }

    public Color TextColor { get; set; }

    public double FontSize { get; set; }
    public string FontName { get; set; }
}


It’s what we call a POCO. It cannot be clearer. It is part of the model, and in VisualDesigner we can work with instances of SpeechBubble directly on a DesignSurface without adding anything else.

How did PostSharp help achieve better code?

PostSharp has a wide variety of features that can improve the quality of the code. The NotifyPropertyChanged aspect is particularly useful when working with the UI.

    
[NotifyPropertyChanged]
    public sealed class DesignSurface : ListBox, IDesignSurface
    {
…

Marking a type with this annotation (aspect) makes PostSharp to automatically raise the PropertyChanged event whenever a property changes.

It’s really nice to see that this feature works in perfect consonance with the rest of the features, like Undo/Redo. This is especially valuable. A solid Undo/Redo mechanism can be quite hard to develop and always needs to add a lot of extra code in the model. PostSharp provides it and works without any hassle. It works recording changes in the state of the objects using aspects. The Recordable aspect indicates to PostSharp to record a history of changes made to instances of that type:

Look at this snippet.

       
   [Recordable]
    public class CanvasModelItem : CanvasItem
    {
        [Child]
        private readonly CanvasItemCollection items = new CanvasItemCollection();

        [Parent]
        private ICanvasItemContainer parent;
…

Later, you can use the Recorder object to go to previous states.

    RecordingServices.DefaultRecorder.Undo();

It is as simple as it seems. In the snippet you can also see other interesting annotations: Child and Parent. It helps to automate the aggregation relationship between instances.

How did you handle undo/redo “continuous operations” like move or resize?

It’s very common to find operations that raise a big amount of changes in a period of time. For instance, when the user drags an item its coordinates change continuously while the user moves the item with the mouse. We may not want all those changes to be recorded so each time a continuous operation starts, we can define a “recording scope”. This way, the recorder will know that changes between the start and end signals in the scope should be ignored. Then, the operation can be undone (or redone) as a whole, in an atomic way, that is a more convenient and useful. Otherwise, the recorder history would be filled with irrelevant changes.

For instance:

    
        private void Rectangle_PointerPressed(object sender, PointerRoutedEventArgs e)
        {
            if ( this.recordingScope != null )
            {
                throw new InvalidOperationException("There is already an active recording scope.");
            }

            this.recordingScope = RecordingServices.DefaultRecorder.StartAtomicScope();
            bDetectColor = true;
        }

This event handler is called when the user click (or presses in a touch screen). You can see the StartAtomicScope being called. It signals makes the Recorder aware of the operation about to begin.

            
        private void Border_PointerReleased(object sender, PointerRoutedEventArgs e)
        {
            if (this.recordingScope == null)
                throw new InvalidOperationException("There is no active recording scope,");

            GetHue(sender, e);

            bDetectColor = false;

            this.recordingScope.Complete();
            this.recordingScope = null;
        }


This is the complementary handler that is called when the user released the mouse/finger. The recording scope is set to complete calling the Complete() method. As simple as it sounds.

What is the next thing you would like to focus on?

At the moment I’m thinking of a version 2.0 of VisualDesigner. I know that some code can be simplified and improved. The current version was thought to be a WPF application, using a whole set of features that are specific to the platform. When porting to WinRT I had some difficulties that exposed some weaknesses of VisualDesigner itself. For example, the concept of Adorners don’t exist in WinRT, and the current code relies on them to provide visual aids like handles and snapping guides to the user. It will be even easier to use and to extend, and since I have PostSharp from the beginning I’m sure the development will be faster and the code cleaner and way easy to understand.

I want VisualDesigner to become a great tool and a sample about how to deal with some complex topics. If it works for me, it would work for somebody else. I’m always proud to collaborate with the community and I want to give my best to other people. So yes, a new and improved version will be the next step.

Thank you, José. Happy PostSharping!

-gael

 

This article is part of a series of 5 about undo/redo:

  1. Announcement and introduction
  2. Getting started – tutorial
  3. Logical operations, scopes, naming
  4. Recorders, recorder providers, callbacks
  5. Case study: Visual Designer

NOTE: This blog post is about an available pre-release of PostSharp. You can install PostSharp 3.2 only using NuGet by enabling the “Include pre-release” option. Undo/Redo is implemented in the package PostSharp.Patterns.Model.

In the last three blog posts, I introduced our new undo/redo feature and described how to customize it. So far, we’ve lived with the assumption that there is only a single global recorder to which all objects would send their changes. In this post, I will show how to cope with several recorders. Then, I will see how recordable objects can react to undo/redo operations by implementing a callback interface.    

This article is part of a series of 5 about undo/redo:

  1. Announcement and introduction
  2. Getting started – tutorial
  3. Logical operations, scopes, naming
  4. Recorders, recorder providers, callbacks
  5. Case study: Visual Designer

NOTE: This blog post is about an available pre-release of PostSharp. You can install PostSharp 4.0 only using NuGet by enabling the “Include pre-release” option. Undo/Redo is implemented in the package PostSharp.Patterns.Model.

Coping with several recorders in an application

In my previous examples, the global recorder was automatically and transparently assigned to all new recordable objects. This behavior is the result of the default value of two settings:

  • The AutoRecord property of the [Recordable] custom attribute (set to true by default,) determines whether the object should be assigned to the ambient recorder upon instantiation.

  • The RecordingServices.RecorderProvider property points to a chain of responsibility that provides recorders to recordable objects. The default implementation unconditionally returns a single global instance.

Note that the RecorderProvider is only used when AutoRecord is set to true. Otherwise, the recorder must be assigned manually, as we will see below.

Overriding the default RecorderProvider

If you want to customize which recorder is being assigned to recordable by default, you need to derive a class from RecorderProvider and implement the GetRecorderImpl method. The parameter contains the object for which the recorder is required. Note that instances of RecorderProvider form a chain of responsibility; if one of the instances return null, the next instance will be invoked.

class CustomRecorderProvider : RecorderProvider
{
    public CustomRecorderProvider(RecorderProvider next) : base(next)
    {
    }

    protected override Recorder GetRecorderImpl(object obj)
    {
    throw new NotImplementedException();
    }
}

The second step is to add the custom provider to the chain of responsibility:

RecordingServices.RecorderProvider = new CustomRecorderProvider(RecordingServices.RecorderProvider);

Assigning recorders manually

Another way to assign recorders to objects is to disable the AutoRecord behavior:

[Recordable(AutoRecord = false)]
class TableBooking
{
    // Details skipped.
}

Then you can attach a recorder to an object using the Recorder.Attach method:

Recorder recorder = new Recorder();
TableBooking booking = new TableBooking();
recorder.Attach(booking);

The symmetric method is Recorder.Detach. Note that invoking any of the Attach or Detach results in adding the attach or detach operation to the list of undoable operations. This seems a bit unintuitive but is important to conserve the ability to undo consistently.

Recordable and Aggregatable

As a rule, an object must always have the same recorder as its parent, unless the parent has no recorder. Therefore, the parent’s recorder is automatically assigned to any child object. Note that the recorder is not detached from a child when the child is detached from its parent.

This rule allows to easily work with real-world object models, where the undo/redo behavior of an object is often derived from its parent.

Undo/redo callbacks

There are situations where you may want to execute custom code before or after an object is affected by an undo or redo operation. For instance, if you have a custom implementation of INotifyPropertyChanged, you may want to raise the PropertyChanged event after undo or redo. This can be done by having your recordable object implement the IRecordableCallback interface.

This is illustrated in the following example, where we want to count the number of operations performed on a child list, but we don’t want this counter to be affected by undo/redo operations.

[Recordable]
class IntList : IRecordableCallback
{
    [NotRecorded]
    private bool replaying;
    [Child]
    public readonly AdvisableCollection<int> List = new AdvisableCollection<int>();
        [NotRecorded]
    public int OperationCount { get; private set; }
    public IntList()
    {
        this.List.CollectionChanged += ListOnCollectionChanged;
    }
    private void ListOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
    {
if ( !this.replaying ) this.OperationCount++; } void IRecordableCallback.OnReplaying(ReplayKind kind, ReplayContext context) { this.replaying = true; } void IRecordableCallback.OnReplayed(ReplayKind kind, ReplayContext context) { this.replaying = false; } }

Conclusion

This is the last of a 4-part blog series about the new undo/redo feature found in PostSharp 4.0. We tried to design it to be super easy to get started with, but customizable enough for real-world applications. Thanks to PostSharp, implementing undo/redo in your own applications should become affordable and reasonably easy.

You may wonder how we tested the feature. Of course we have dozens of unit tests, but unit tests don’t make good usability tests. Since as a compiler-building company we don’t have any UI application to dogfood, we chose an open-source project and added the undo/redo feature to it. More in my next blog post.

Happy PostSharping!

-gael

UPDATE: Change product version from PostSharp 3.2 to PostSharp 4.0.

In the previous posts, I described the new undo/redo feature of PostSharp 4.0 and looked at most common use cases. In this post, I will show how you can customize its default behavior.

This article is part of a series of 5 about undo/redo:

  1. Announcement and introduction
  2. Getting started – tutorial
  3. Logical operations, scopes, naming
  4. Recorders, recorder providers, callbacks
  5. Case study: Visual Designer

NOTE: This blog post is about an available pre-release of PostSharp. You can install PostSharp 4.0 only using NuGet by enabling the “Include pre-release” option. Undo/Redo is implemented in the package PostSharp.Patterns.Model.

This blog post covers the following topics:

  • A conceptual introduction to logical operations

  • Defining the operation name declaratively

  • Avoiding automatic scopes

  • Defining atomic scopes declaratively

  • Defining scopes imperatively

  • Customizing scope names

Logical Operations

The Recordable patterns record primitive changes to an object model into a Recorder object. Primitive changes are typically field value changes or primitive collections operations such as Add or Remove. The Recorder itself is basically a list (i.e. a linear collection) of atomic changes. This simple and beautiful structure is sufficient to implement the undo changes and revert the object model to any point in the past.

In practice however, you don’t want to undo to any random point in the past, but only to safe points. Typically, work on an object model is separated into logical operations, which must be undone or redone as a whole. Users always want to undo an operation, not a primitive change. Logical operations are implemented using the concept of recording scope. All changes done within a scope are grouped into a single operation.

By default, any public method of a Recordable object automatically executes inside a scope. That is, all changes done by a single method call will always be undone as a whole.

Note that scopes don’t necessarily define new operations. Because scopes can be nested but operations cannot, only the outermost will open an operation. Nested scope will be ignored unless they are atomic scopes (see below).

Defining the operation name declaratively

Because they are apparent to users, operations must have a name that is meaningful to the user. One way to do that is to use the [RecordingScope] custom attribute. Let’s see this on an example:

[Recordable]
class TableBooking
{
    public string CustomerName { get; set; }
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }
[RecordingScope("Postpone booking")] public void Postpone(TimeSpan time) { this.StartTime += time; this.EndTime += time; } }

Avoiding automatic scopes

There are situations where you want to prevent the default behavior that executes each public method in a scope. To opt-out, you can use the [RecordingScope] custom attribute and define the RecordingScopeOption.Skip flag.

[Recordable]
class TableBooking
{
    public string CustomerName { get; set; }
    public DateTime StartTime { get; set; }
    public DateTime EndTime { get; set; }

    [RecordingScope(RecordingScopeOption.Skip)]
    public void Postpone(TimeSpan time)
    {
        this.StartTime += time;
        this.EndTime += time;
    }
}

Defining atomic scopes declaratively

An atomic scope is a scope whose changes get rolled back if the code running inside the scope is not successful. Atomic scopes give the feeling that the code runs in a “transaction”. However, unlike real transactional systems, atomic recording scopes don’t provide transaction isolation. Changes are visible from other threads as soon as they are performed; there is no Commit semantic.

In the following code snippet, the [RecordingScope] attribute ensures that any change performed by the LoadFile method to the object will be rolled back to its initial state in case the input file contains a line that cannot be parsed into an integer.

[Recordable]
class IntList
{
    [Child] 
    private readonly AdvisableCollection<int> list = new AdvisableCollection<int>();
[RecordingScope(RecordingScopeOption.Atomic)] public void LoadFile(string file) { foreach (string line in File.ReadAllLines(file)) { this.list.Add(int.Parse(line)); } } }

Defining scopes imperatively

So far, we’ve seen how to define scopes declaratively using the [RecordingScope] custom attribute. Declarative programming is convenient, but sometimes more flexibility is needed. For these situations, the Recorder.OpenScope method must be used. It allows you to open a scope, set its name, and determine whether it should be atomic. The OpenScope method returns a RecordingScope object, which must be disposed at the end of the scope. For atomic scopes, you must invoke the Complete method before disposing the object, otherwise the changes performed in this scope will be rolled back.

The code snippet below uses the [RecordingScope] attribute to opt-out from the default scope, then uses OpenScope to open a new scope with a custom name.

[Recordable]
class IntList
{
    [Child] 
    readonly AdvisableCollection<int> list = new AdvisableCollection<int>();
[RecordingScope(RecordingScopeOption.Skip)] public void LoadFile(string file) { string scopeName = string.Format("Loading from {0}", file);
using ( RecordingScope scope = RecordingServices.DefaultRecorder.OpenScope(scopeName, RecordingScopeOption.Atomic)) { foreach (string line in File.ReadAllLines(file)) { this.list.Add(int.Parse(line)); } scope.Complete(); } } }

Customizing scope names

In the example above, I’ve shown how to specify a custom scope name when the scope is opened. This approach requires to mix UI code (generation of human-readable names) into domain code. It also demands more boilerplate code and can be cumbersome if you need have a large number of operations to customize. For instance, what if you need to have a proper name for the operation of setting every property in your classes?

This use case is covered by the OperationFormatter class. PostSharp comes with a default formatter, which can be overridden thanks to the RecordingServices.OperationFormatter property. Formatters form a chain of responsibility; if one formatter is not able to provide a name for an operation, it should return null, which causes the next formatter to be invoked.

Formatters have access to the operation descriptor, which is an object describing the operation. The IOperationDescriptor interface has a single property named OperationKind, which informs you to which class the descriptor must be cast.

In the following code snippet, we will show how to create a custom formatter that generates the same name as in the previous example.

class CustomFormatter : OperationFormatter
{
    public CustomFormatter(OperationFormatter next)
        : base(next)
    {
    }
protected override string FormatImpl(IOperationDescriptor operation) { if (operation.OperationKind != OperationKind.Method) return null;
MethodExecutionOperationDescriptor descriptor = (MethodExecutionOperationDescriptor) operation; if (descriptor.Method.DeclaringType == typeof (IntList) && descriptor.Method.Name == "LoadFile") { string file = (string) descriptor.Arguments[0]; return string.Format("Loading from {0}", file); } return null; } }

The following code inserts our custom formatter in the chain of responsibility:

RecordingServices.OperationFormatter = new CustomFormatter(RecordingServices.OperationFormatter);    

Of course, the example above is rather naïve because there is too much coupling between the formatter and the domain code. A more serious implementation would probably rely on other custom attributes such as [DisplayName] of the System.ComponentModel namespace. PostSharp is agnostic about the implementation of custom formatters and I just wanted to keep this example simple.

Summary

Scopes are the mechanism through which logical operations are defined. Logical operations are the “things” that are exposed to the user and that can be undone or redone. Therefore, they must be named. PostSharp provides three ways to define scope: implicitly, declaratively, and imperatively. It also provides, orthogonally, two ways to name operations: by setting the name when the scope is created, or lazily, based on the concept of operation formatter, which allows to better separate UI concerns from business logic. Finally, PostSharp defines the notion of atomic scope, which causes all changes to be rolled back in case of exception.

These few concepts allow to customize how your code maps to undoable operations.

But there is more. In the next post, I will talk about multiple recorders and custom operations.

Happy PostSharping!

-gael

UPDATE: Change product version from PostSharp 3.2 to PostSharp 4.0.