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.
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.
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:
- Announcement and introduction
- Getting started – tutorial
- Logical operations, scopes, naming
- Recorders, recorder providers, callbacks
- 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.