WPF Diagramming. Undo/Redo service and simple commands.


Undo/Redo functionality can be regarded as one of the most important parts of application and not because of it’s complexity. This feature can influence the architecture of your application very much (of course if you didn’t implement the commands pattern from the beginning) and can make you spend a lot of time for refactoring.

Each action in the application can be regarded as some single command. The command knows how to execute an action and so it should know how to undo or redo it easily. In order to provide the list of user-friendly names of commands to be undone the it can also contain some title to be distinguished in the UI. So the basic interface for it can be as following

public interface IDiagramCommand
  {
    /// <summary>
    /// Executes the actual action
    /// </summary>
    void Execute();

    /// <summary>
    /// Executes an action corresponding to an undo
    /// </summary>
    void Undo();

    /// <summary>
    /// Executes an action corresponding to redo in case it has been undone.
    /// </summary>    
    void Redo();

    /// <summary>
    /// Title of the command.
    /// </summary>
    /// <remarks>Typically to be used in undo list or undo stack description.</remarks>
    string Title { get; set; }
  }

Assume you are adding a shape to the canvas and this action should be undone on demand. The action command should be common for all your elements based on UIElement and know the surface it is operating. In my case extended canvas will be the command surface. This is the most common information needed the first simple command.

I’ve implemented some additional helper methods for the canvas and pass my common interface over the commands.

public interface IDrawingSurface: IInputElement
  {
    /// <summary>
    /// Get/Set the value indicating whether elements dragging is allowed for the canvas.
    /// </summary>
    bool IsDragEnabled { get; set; }

    /// <summary>
    /// Add generic element to the surface
    /// </summary>
    /// <typeparam name="T">UIElement type</typeparam>
    /// <param name="element">Element to be added to the surface</param>
    void AddElement<T>(T element);

    /// <summary>
    /// Insert generic element to the surface at a specified index position.
    /// </summary>
    /// <typeparam name="T">UIElement type</typeparam>
    /// <param name="index">The position of element to be placed</param>
    /// <param name="element">Element to be inserted to the surface</param>
    void InsertElement<T>(int index, T element);

    /// <summary>
    /// Remove generic element from the surface
    /// </summary>
    /// <typeparam name="T">UIElement type</typeparam>
    /// <param name="element">Element to be removed from the surface</param>
    void RemoveElement<T>(T element);
  }

 

Using generics in this case help me very much eliminating the type casting and the stuff like that. As canvas deals with UIElement based elements it becomes rather difficult maintaining the collection because I definitely sure you won’t deal with UIElement objects all over you application. It becomes more convenient to wrap the basic child processing logic into some sort of generic helpers.

The simple pieces of code for the canvas exposing such an interface can be as following

/// <summary>
/// Add generic element to the surface
/// </summary>
/// <typeparam name="T">UIElement type</typeparam>
/// <param name="element">Element to be added to the surface</param>
public void AddElement<T>(T element)
{
  UIElement uiElement = element as UIElement;
  if (uiElement != null && !Children.Contains(uiElement))
    Children.Add(uiElement);
}

 

The remove or insert methods are too obvious to be mentioned here. You can implement you own subset of functionality extending the canvas using your own demands.

As I’ve mentioned earlier the simple add element command that can satisfy the undo/redo service should have the possibility of adding some kind element to the drawing surface (command execution), remove the element from the surface (undo operation) and rollback the undo operation so add the element again to the surface (redo operation).

public class AddElementCommand : IDiagramCommand
  {
    public const string DefaultCommandTitle = "Add Element";

    #region Properties
    /// <summary>
    /// Gets or sets the surface.
    /// </summary>
    /// <value>The surface.</value>
    public IDrawingSurface Surface { get; set; }

    /// <summary>
    /// Gets or sets the element.
    /// </summary>
    /// <value>The element.</value>
    public UIElement Element { get; set; }

    /// <summary>
    /// Title of the command.
    /// </summary>
    /// <value></value>
    /// <remarks>Typically to be used in undo list or undo stack description.</remarks>
    public string Title { get; set; }
    #endregion

    #region ctor
    /// <summary>
    /// Initializes a new instance of the <see cref="AddElementCommand"/> class.
    /// </summary>
    /// <param name="surface">The surface.</param>
    /// <param name="element">The element.</param>
    public AddElementCommand(IDrawingSurface surface, UIElement element) : this(surface, element, null) { }

    /// <summary>
    /// Initializes a new instance of the <see cref="AddElementCommand"/> class.
    /// </summary>
    /// <param name="surface">The surface.</param>
    /// <param name="element">The element.</param>
    /// <param name="title">The title.</param>
    public AddElementCommand(IDrawingSurface surface, UIElement element, string title)
    {
      if (surface == null) throw new ArgumentNullException("surface");
      if (element == null) throw new ArgumentNullException("element");
      
      Surface = surface;
      Element = element;
      Title = (!string.IsNullOrEmpty(title)) ? title : DefaultCommandTitle;
    }
    #endregion

    #region IDiagramCommand Members
    /// <summary>
    /// Executes the actual action
    /// </summary>
    public void Execute()
    {
      Surface.AddElement(Element);
    }

    /// <summary>
    /// Executes an action corresponding to an undo
    /// </summary>
    public void Undo()
    {
      Surface.RemoveElement(Element);
    }

    /// <summary>
    /// Executes an action corresponding to redo in case it has been undone.
    /// </summary>
    public void Redo()
    {
      Surface.AddElement(Element);
    }
    #endregion
  }

The command itself doesn’t create the UI element because it is not responsible for that. It even doesn’t distinguish your objects being passed. It only assumes the objects belong to UIElement type and that’s all. As far as you can understand the undo/redo service is also blind to the content of the commands it deals with. It will process the IDiagramCommand objects and will attach it’s undo/redo functionality to each command.

Here’s the what could be the simple interface for basic undo/redo service

public interface IUndoService
  {
    /// <summary>
    /// Get the undo commands history.
    /// </summary>
    Stack<IDiagramCommand> UndoCommands { get; }

    /// <summary>
    /// Get the redo commands history.
    /// </summary>
    Stack<IDiagramCommand> RedoCommands { get; }

    /// <summary>
    /// Get the undo commands titles.
    /// </summary>
    ObservableCollection<string> UndoTitles { get; }

    /// <summary>
    /// Get the redo commands titles.
    /// </summary>
    ObservableCollection<string> RedoTitles { get; }

    /// <summary>
    ///  Gets a value indicating whether there is anything that can be undone.
    /// </summary>
    bool CanUndo { get; }

    /// <summary>
    /// Gets a value indicating whether there is anything that can be rolled forward.
    /// </summary>
    bool CanRedo { get; }

    /// <summary>
    /// This method puts the command to the Undo stack and then executes it.
    /// </summary>
    /// <param name="command">The command to be executed.</param>
    void Execute(IDiagramCommand command);

    /// <summary>
    /// Rollback the last command.
    /// </summary>
    void Undo();

    /// <summary>
    /// Rollback the last undone command.
    /// </summary>
    void Redo();

    /// <summary>
    /// Clear the undo history.
    /// </summary>
    void ClearUndoHistory();

    /// <summary>
    /// Clear the redo history.
    /// </summary>
    void ClearRedoHistory();

    /// <summary>
    /// Clear all the undo and redo history.
    /// </summary>
    void ClearHistory();
  }

Hope the code is self explaining. We introduce two stacks for commands according to undo and redo implementation. Also we have two observable collections giving the titles. Also we provide the obvious Execute/Undo/Redo methods alongside with a set of helper methods and properties as clearing the history or returning a boolean value determining whether this or that functionality as possible at execution time.

Here’s the Undo/Redo service that expose the interface mentioned above

public class UndoService : IUndoService
  {
    #region ctor
    public UndoService()
    {
      UndoCommands = new Stack<IDiagramCommand>();
      UndoTitles = new ObservableCollection<string>();
      RedoCommands = new Stack<IDiagramCommand>();
      RedoTitles = new ObservableCollection<string>();
    } 
    #endregion

    #region Routed events bindng support
    public void OnExecuteUndo(object sender, ExecutedRoutedEventArgs e)
    {
      Undo();
    }

    public void OnCanExecuteUndo(object sender, CanExecuteRoutedEventArgs e)
    {
      e.CanExecute = CanUndo;
    }

    public void OnExecuteRedo(object sender, ExecutedRoutedEventArgs e)
    {
      Redo();
    }

    public void OnCanExecuteRedo(object sender, CanExecuteRoutedEventArgs e)
    {
      e.CanExecute = CanRedo;
    }
    #endregion

    #region IUndoService Members
    /// <summary>
    /// Get the undo commands history.
    /// </summary>
    public Stack<IDiagramCommand> UndoCommands { get; protected set; }

    /// <summary>
    /// Get the redo commands history.
    /// </summary>
    public Stack<IDiagramCommand> RedoCommands { get; protected set; }
    
    /// <summary>
    /// Get the undo commands titles.
    /// </summary>
    public ObservableCollection<string> UndoTitles { get; protected set; }

    /// <summary>
    /// Get the redo commands titles.
    /// </summary>
    public ObservableCollection<string> RedoTitles { get; protected set; }

    /// <summary>
    ///  Gets a value indicating whether there is anything that can be undone.
    /// </summary>
    public bool CanUndo { get { return UndoCommands.Count > 0; } }

    /// <summary>
    /// Gets a value indicating whether there is anything that can be rolled forward.
    /// </summary>
    public bool CanRedo { get { return RedoCommands.Count > 0; } }

    /// <summary>
    /// This method puts the command to the Undo stack and then executes it.
    /// </summary>
    /// <param name="command">The command to be executed.</param>
    public void Execute(IDiagramCommand command)
    {
      if (command == null) return;
      // Execute command
      command.Execute();
      // Push command to undo history
      if (command is IDiagramCommand)
      {
        UndoCommands.Push(command);
        UndoTitles.Insert(0, command.Title);
        // Clear the redo history upon adding new undo entry. This is a typical logic for most applications
        RedoCommands.Clear();
        RedoTitles.Clear();
      }
    }

    /// <summary>
    /// Rollback the last command.
    /// </summary>
    public void Undo()
    {
      if (CanUndo)
      {
        IDiagramCommand command = UndoCommands.Pop();
        command.Undo();
        UndoTitles.RemoveAt(0);
        RedoCommands.Push(command);
        RedoTitles.Insert(0, command.Title);
      }
    }

    /// <summary>
    /// Rollback the last undone command.
    /// </summary>
    public void Redo()
    {
      if (CanRedo)
      {
        IDiagramCommand command = RedoCommands.Pop();
        RedoTitles.RemoveAt(0);
        //Execute(command);
        if (command != null)
        {
          command.Execute();

          if (command is IDiagramCommand)
          {
            UndoCommands.Push(command);
            UndoTitles.Insert(0, command.Title);
          }
        }        
      }
    }

    /// <summary>
    /// Clear the undo history.
    /// </summary>
    public void ClearUndoHistory()
    {
      UndoCommands.Clear();
      UndoTitles.Clear();
    }

    /// <summary>
    /// Clear the redo history.
    /// </summary>
    public void ClearRedoHistory()
    {
      RedoCommands.Clear();
      RedoTitles.Clear();
    }

    /// <summary>
    /// Clear all the undo and redo history.
    /// </summary>
    public void ClearHistory()
    {
      ClearRedoHistory();
      ClearUndoHistory();
    }
    #endregion
  }

The code above mostly doesn’t require long supplementary information. On each command execution the redo stack is cleared to reflect the most common behavior in all applications. The undo and redo methods mainly do the manipulations with theirs stacks and corresponding title collections. The main entry point for the undo service is Execute method. It executes the command and adds it to the undo stack (great thanks to François for that idea)

The only part that can be a point of detailed interest is the region "Routed events binding support". These four methods provide an easy and convenient possibility of binding the undo/redo commands to the UI. Usually you’ll have two buttons and maybe each button will contain the list of operation titles for undoing of redoing.

In my sample projects I usually implement two buttons bound to the standard application commands.

<Button Margin="0,3" Name="cmdUndo" Command="ApplicationCommands.Undo">
    <StackPanel>
        <Image Source="Images/undo.png" Width="32" Height="32"/>
        <TextBlock>Undo</TextBlock>
    </StackPanel>
</Button>
<Button Margin="0,3" Name="cmdRedo" Command="ApplicationCommands.Redo">
    <StackPanel>
        <Image Source="Images/redo.png" Width="32" Height="32"/>
        <TextBlock>Redo</TextBlock>
    </StackPanel>
</Button>

Take a look at the "Command" property of each button. "ApplicationCommands" is a standard class so if interested you look through MSDN to get more information and use cases.

Upon the window loading I attach my undo/redo logic to the list of command bindings for the window so that buttons accessing "ApplicationCommands.Undo" and "ApplicationCommands.Redo" automatically go to my UndoService.

Something like the following

#region Configure command bindings
UndoService undoService = designer.GetService<IUndoService>() as UndoService;
if (undoService != null)
{
  this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Undo, undoService.OnExecuteUndo, undoService.OnCanExecuteUndo));
  this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Redo, undoService.OnExecuteRedo, undoService.OnCanExecuteRedo));
}
#endregion

OnCanExecuteUndo and OnCanExecuteRedo methods are returning the CanUndo and CanRedo properties of the underlying Undo/Redo service. The last two mentioned are bound to the corresponding stacks and return true if the stack contains any command.

This gives you a very interesting feature. Your buttons will themselves control their state and will become enabled/disabled according to the contents of undo/redo stacks.

This is a very basic pattern to get the idea of implementing undo/redo functionality and commands. It’s up to you what commands will be executed and what will they do. The undo/redo service is usually implemented in the way it doesn’t take care of that. It deals with interface part of commands and that’s all.

Thanks for your attention. The sources can be found at my skydrive under "Diagramming" folder or in the previous article.

WPF Diagramming. Simple diagramming CTP project for process designing.


While preparing material and samples for my next 5 or 6 articles towards the diagramming I’ve come to conclusion that it would be perfect to implement some kind of generic UI for testing everything connected to diagramming. I’ve spent the weekend preparing the draft project that could be close to my vision and needs.

Again I must mention the Unfold project that inspired me very much. This is still a best free implementation and starter kit that can be found all over the Internet for present moment and the sources are worth looking at. I really appreciate the work that was done to perform it.

When I started developing my own vision of diagramming approach I promised myself 2 things to remember: not to use the Unfold sources and not to mix my actual job sources and approaches so keeping solution in a "home project" mode as long as possible. As I’m reading and reviewing the Unfold sources almost every day to get some new ideas how this or that part could be done in the way I would prefer some parts may seem familiar to you, for example IMouseListener, IShapeCreator, Undo/Redo functionality. This was done from the scratch because I hate copy-pasting approaches and I’ve left the names and some pieces for convenience purposes. Later all the parts will be definitely changed to expose the architecture I’m implementing actually.

Here’s what you can download and play with

designer2

I made a strict decision for using a service-oriented approach exposing IServiceProvider for my further samples towards diagramming. This give me everything I need for the further framework extensions and the most easiest ways of maintaining the blocks. I won’t dwell on the blocks I’ve used for this project in this article as I’m going to give more details explanations in the later posts.

The project is a very very rough and draft implementation. If you become interested in it I advice you to stay on the line with the blog I’ll definitely provide a lot of new information towards diagramming 😉 Any feedback and questions are appreciated so don’t hesitate

The key stuff I concentrated on:

1. Selection Service

Very simple service for shape selection on the drawing surface providing a basic circle adorner. Each selectable shape can contain own adorners and expose Select/Unselect calls managed by the service. I’m going to provide additional sample for selection service that can replace existing one and that implements it’s own way of selection without shapes.

2. Undo/Redo Service

Basic implementation of undo/redo functionality. Now supports only shape addition to canvas and deletion. It still requires a lot of refactoring that I’m going to finish it as soon as possible.

3. Tool Service

I tried to turn everything into tools. The connectors are not yet finished but the idea is the same. Tool service fully maintain the tools and later I’m going to move them to external assemblies to provide the possibilities of separate development and loading. The same is for shapes.

4. Sample connector tools

For this project I’ve provided three sample connectors: plain line, arrow line and bezier line (taken from Microsoft VPL designer). I liked the idea of shape creators implemented in Unfold project so I’ve left the same naming for now but guess I would better call them "Materials" at later implementations. Each tool can have different materials (this can be seen from the tool registration). Also there’s some simple binding converters for automatic line positioning according to drag operations.

5. Main design host

Main abstraction layer for the designer that is used for hosting and maintaining  all the services registered.

 

And a lot of other interesting blocks that can be easily changed or extended. At least I try to keep this way.

Still continue to work hard on the project…

designer1

Source code for the article

Getting Aero theme with WPF on Windows XP


For those guys who like Aero very much but cannot migrate their projects at a moment there’s quite a good news 🙂 You don’t have to buy third party controls styled like Aero or Luna or Royale theme today because there’s some dirty tricks to enable that stuff in your application.

When you install .net 3.0 framework you automatically get some candies into your GAC that might help you styling your applications. Assuming you have the most common system path try to navigate the "C:WindowsassemblyGAC_MSIL" folder.

There you can find several folders starting from "PresentationFramework" plus the name of the theme. At my laptop I now see following:

PresentationFramework.Aero

PresentationFramework.Classic

PresentationFramework.Luna

PresentationFramework.Royale

Let’s take Aero folder… 🙂

Inside you can find the following folder

3.0.0.0__31bf3856ad364e35  (Note that the name of the folder can be different from mentioned)

It contains the assembly called "PresentationFramework.Aero.dll"

Fine, that’s enough information for us to bind the theme to our application. How should we do that?

Open your "App.xaml" and move to the resources section. All you need to do is to add one single line defining the resource dictionary like in the sample below

<Application x:Class="Vista.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml">
<Application.Resources>
        <ResourceDictionary 
            Source="/PresentationFramework.Aero, 
            Version=3.0.0.0, 
            Culture=neutral, 
            PublicKeyToken=31bf3856ad364e35, 
            ProcessorArchitecture=MSIL;
            component/themes/aero.normalcolor.xaml" />
    </Application.Resources>

</Application>

So what do we set here… PresentationFramework.Aero is the source for resources. Version is taken by you from the folder "3.0.0.0__31bf3856ad364e35" in my case it is "3.0.0.0". Culture is neutral. Public key token is also taken from the folder "3.0.0.0__31bf3856ad364e35" and has the value "31bf3856ad364e35". The rest should be typed as it is given in the sample.

Now place some controls on the canvas and you can immediately see the changes in the Visual Studio WPF designer (I used VS 2008 TS beta 2 for preparing the sample). This is also a perfect feature of Visual Studio :). Of course you won’t get the glass effect for the whole window, but at least all your controls are styled well.

I’ve prepared one small screenshot for you to get the idea.

aero with windows xp

And don’t forget that you are running Windows XP 😉

Guess I must leave you playing with this stuff for yourselves. There are also other themes mentioned so you may play with them a bit.

Have a nice coding.

Source code for the article

WPF Diagramming. The WPF way of binding line endpoints to shapes.


In the previous articles of this series I was presenting some "Windows Forms" idea of drawing line connectors and updating their endpoints according to the positions of shapes being dragged. We’ve handled the "DragDelta" event for each Thumb based shape moved the shape and processed all the corresponding starting and ending lines (their positions).

Any simple connector can be represented as some kind of "Data Model – Presentation View" structure. You have the geometry with UI for graphical presentation and have some data assigned to it to handle the underlying business objects and other stuff. So it is obvious that you might want to separate the drawing and business logic.

When I’ve learned a bit of new data binding features I’ve started to dig to find out whether MS guys at last gave us some new property binding sugar. Yesterday evening I was again surfing my Visual Studio MSDN library and came across the MultiBinding class . I’ve started testing that stuff and after getting the idea of the power provided by multiple dependency properties binding I’ve started implementing that immediately to my diagramming sample. Today in the morning I’ve found some similar solutions with Google but the sample is already prepared and I’ve started writing the article… 🙂

I assume you already know how the binding is performed with xaml markup and reviewed some samples towards this. MS guys give you the possibility of binding one dependency property simultaneously to several other ones. This means that you can get rid of all "always the same" calculations moving the code somewhere outside.

multibinding 

Trying to connect one property to multiple other ones you nevertheless need to get one value at the end and assign it to your line endpoint. That’s why you need some converter to resolve the incoming values and return processed result to be applied to the property at the other end. In our case we’ll try to connect each endpoint property of the line to four properties of the corresponding shape.

Again we have to get "Canvas.Left", "Canvas.Top", "ActualWidth" and "ActualHeight" to center the line endpoint properly. Fist we do is implementing the appropriate binding converter to get all this values and return one Point for the center of the shape.

ConnectorBindingConverter.cs

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace HomeDiagramming.Connectors.Converters
{
    public class ConnectorBindingConverter : IMultiValueConverter
    {
        #region IMultiValueConverter Members

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            double left = System.Convert.ToDouble(values[0]);
            double top = System.Convert.ToDouble(values[1]);
            double actualWidth = System.Convert.ToDouble(values[2]);
            double actualHeight = System.Convert.ToDouble(values[3]);
            return new Point(left + actualWidth / 2, top + actualHeight / 2);
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

The code doesn’t require commenting because the logic is clear I think. Converter receives an array of collected values and returns a point based on some calculations performed according to them.

Next we implement a helper method for generating a MultiBinding collection according to the shape provided

private MultiBinding CreateConnectorBinding(IConnectable connectable)
        {
            // Create a multibinding collection and assign an appropriate converter to it
            MultiBinding multiBinding = new MultiBinding();
            multiBinding.Converter = new ConnectorBindingConverter();

            // Create binging #1 to IConnectable to handle Left
            Binding binding = new Binding();
            binding.Source = connectable;
            binding.Path = new PropertyPath(Canvas.LeftProperty);
            multiBinding.Bindings.Add(binding);
            
            // Create binging #2 to IConnectable to handle Top
            binding = new Binding();
            binding.Source = connectable;
            binding.Path = new PropertyPath(Canvas.TopProperty);
            multiBinding.Bindings.Add(binding);

            // Create binging #3 to IConnectable to handle ActualWidth
            binding = new Binding();
            binding.Source = connectable;
            binding.Path = new PropertyPath(FrameworkElement.ActualWidthProperty);
            multiBinding.Bindings.Add(binding);

            // Create binging #4 to IConnectable to handle ActualHeight
            binding = new Binding();
            binding.Source = connectable;
            binding.Path = new PropertyPath(FrameworkElement.ActualHeightProperty);
            multiBinding.Bindings.Add(binding);

            return multiBinding;
        }

Here IConnectable is my custom interface all the shapes are expose. Check out the source code for the article to get a better understanding of it.

Now we need another helper method that will give us the opportunity of connecting to IConnectable objects and so binding two endpoints of the line to the provided shapes. The method is very simple in this case

public void AddConnection(IConnectable source, IConnectable target)
        {
            ShapeConnectorBase conn = new ShapeConnectorBase();
            conn.SetBinding(ShapeConnectorBase.StartPointProperty, CreateConnectorBinding(source));
            conn.SetBinding(ShapeConnectorBase.EndPointProperty, CreateConnectorBinding(target));            
            this.DiagramView.Children.Insert(0, conn);
        }

"DiagramView" is a Canvas placed to the window and ShapeConnectorBase is a sample connector object. To keep simple I decided again to use LineGeometry. But LineGeometry doesn’t have a "SetBinding" method and I did a small walkaround wrapping it into the shape exposing two dependency properties "StartPoint" and "EndPoint". So as you can see now from the code our method creates a connector, binds it to the both IConnectable objects and puts the connector geometry to the canvas.

Here’s the complete source for ShapeConnectorBase.cs

using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

namespace HomeDiagramming.Connectors
{
    public class ShapeConnectorBase : Shape, IShapeConnector
    {
        LineGeometry linegeo;

        public static readonly DependencyProperty StartPointProperty = DependencyProperty.Register("StartPoint", typeof(Point), typeof(ShapeConnectorBase), new FrameworkPropertyMetadata(new Point(0, 0), FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty EndPointProperty = DependencyProperty.Register("EndPoint", typeof(Point), typeof(ShapeConnectorBase), new FrameworkPropertyMetadata(new Point(0, 0), FrameworkPropertyMetadataOptions.AffectsMeasure));

        public Point StartPoint
        {
            get { return (Point)GetValue(StartPointProperty); }
            set { SetValue(StartPointProperty, value); }
        }

        public Point EndPoint
        {
            get { return (Point)GetValue(EndPointProperty); }
            set { SetValue(EndPointProperty, value); }
        }

        public ShapeConnectorBase()
        {
            linegeo = new LineGeometry();
                        
            this.Stroke = Brushes.Black;
            this.StrokeThickness = 1;
        }

        protected override Geometry DefiningGeometry
        {
            get
            {                
                linegeo.StartPoint = StartPoint;
                linegeo.EndPoint = EndPoint;
                return linegeo;
            }
        }
    }
}

Finally at your main code you can do something like the following to link two shapes together

// Setup connections for predefined thumbs
designer.AddConnection(myThumb1, myThumb2);
designer.AddConnection(myThumb2, myThumb3);
designer.AddConnection(myThumb3, myThumb4);
designer.AddConnection(myThumb4, myThumb1);

You have no need of handling the line position on the shape movement as it will be handled automatically by the binding object. So from now you may concentrate on business logic better and not on UI part and geometry repositioning.

Have a good coding…

Note: You may find a lot of other interesting things in the source code provided for the article. As I’m preparing to present some really complex samples in the nearest future I just don’t have time to clear the solution 😉 Again building the sample will require you to have Visual Studio 2005 beta 2.

Source code for the article