Click here to Skip to main content
12,822,050 members (41,290 online)
Click here to Skip to main content
Add your own
alternative version


481 bookmarked
Posted 26 Mar 2008

WPF Diagram Designer - Part 4

, 26 Mar 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
A Frankenbuild
WPF FlowChart Designer
  • Part 1 - Drag, resize and rotate items on a canvas
  • Part 2 - Toolbox, drag & drop, rubberband selection
  • Part 3 - Connecting items


In this article, I have added the following commands:

  • Open, Save
  • Cut, Copy, Paste, Delete
  • Print
  • Group, Ungroup
  • Align (Left, Right, Top, Bottom, Centered horizontal, Centered vertical)
  • Distribute (horizontal, vertical)
  • Order (Bring forward, Bring to top, Send backward, Send to back)

Note: I will only support Visual Studio 8.0 on .NET 3.5 !


The way I use WPF commands is straight forward, as described in the WPF SDK documentation, no extra infrastructure.


My first approach to group items was to use a DesignerItem object that should work as a group container. For this, I created a new instance of the DesignerItem class with a Canvas object as its content. On this canvas, I planned to position the designer items to be grouped. But before I could put the items on the group canvas, I had to remove them from the designer canvas because in WPF an element cannot be a child of two elements. If you try, you will get an InvalidOperationException with the following message:

"Specified element is already the logical child of another element. 
Disconnect it first."

So I removed the items from the designer canvas and put them on the group canvas. Now it is interesting to understand what WPF did behind the scenes: as soon as I removed an item from the designer canvas, its template was unloaded and when I added it to the group canvas, a new template was loaded. Now do you remember the last article where I showed you how to connect designer items? There I connected items via connectors, connectors that were part of the designer item's template, a template that is lost as soon as I remove the item from the designer canvas. You see the problem? I have connected designer items via their templates and so the designer item itself has absolutely no information about existing connections. All connection related information is isolated in the designer item's template.

Imagine a database diagram where the designer item's content is a database table. The table would never recognize any relation to other tables. One solution would be to tunnel the information from the template to the designer item to the table. A better solution is to redesign the application and divide the whole bulk into separate parts, e.g.

  • Template (view)
  • Designer item (view model)
  • Database table (model)

I will not start redesigning this code in the midst of an article, instead I will ride this 'view-only-approach' until the end of this article. The more painful this ride is, the more welcome a better solution will be. (I will cover a model backed designer in a future article.)

So let's continue. An alternative approach to group designer items uses the following interface:

public interface IGroupable
    Guid ID { get; }
    Guid ParentID { get; set; }
    bool IsGroup { get; set; }

The idea is that the DesignerItem class has to implement this interface to become part of the grouping infrastructure, which works like this:

  • Create a new DesignerItem object with a unique ID and with its IsGroup property set to true
  • For each group member, set the ParentID to the ID of the group parent.

This is simple, but the real work happens when I modify items (Select, Move, Resize, Copy, ...); with each of these operations I have to consider an item's group status. Sounds like a lot of work, but it's not as painful as it would be without LINQ. For this, I have wrapped most of the work into the SelectionService class.

Note: The Connection class does not implement the IGroupable interface and so cannot directly be part of a group, but indirectly - since a connection is always attached to an item. This gives me the flexibility to re/connect items, no matter if they are members of a group or not.


To save a diagram, I have chosen to use a combination of XML and XAML. For the DesignerItem related data I use XML, and the content is serialized to XAML. Here again, please note that serializing a designer item's content to XAML only preserves the visual aspects and thus is used as a short term solution only. To create the XML file, I use LINQ. Since this is the first time I experiment with LINQ, don't expect it to be necessarily the "right" way to use it.

Here is an example of how I serialize designer items:

XElement serializedItems = new XElement("DesignerItems",
                         from item in designerItems
                         let contentXaml = XamlWriter.Save(((DesignerItem)item).Content)
                         select new XElement("DesignerItem",
                                     new XElement("Left", Canvas.GetLeft(item)),
                                     new XElement("Top", Canvas.GetTop(item)),
                                     new XElement("Width", item.Width),
                                     new XElement("Height", item.Height),
                                     new XElement("ID", item.ID),
                                     new XElement("zIndex", Canvas.GetZIndex(item)),
                                     new XElement("IsGroup", item.IsGroup),
                                     new XElement("ParentID", item.ParentID),
                                     new XElement("Content", contentXaml)

The let keyword allows you to store the result of a sub-expression in a variable that can be used in a subsequent expression. Here I use this feature to save the serialized content in the contentXaml variable, which I use a few lines below. Finally, I use the Save method of the XElement class to store the element's underlying XML tree:



When loading a diagram from an XML file, we have to start with the designer items because we need their connectors to create connections. We have learned that connectors are part of the item's template, so the designer item has to load its template before we can continue. Fortunately the Control class provides the ApplyTemplate() method which forces the WPF layout system to load the control template so that its parts can be referenced.

In the previous article, I provided a mechanism to customize the ConnectorDecorator template, which allows you to freely position connectors around a designer item. That solution did apply the customized template after the designer item's Loaded event was fired and that event is not fired before the item becomes visible on your screen. Now the screen cannot be redrawn before the command has ended. So the only way is to set the customized ConnectorDecorator template explicitly within the Open command, see the SetConnectorDecoratorTemplate(item) method.

Note: When defining customized connectors, you must set the x:Name property. A connection uses the name to identify its source and sink connectors.

<s:Connector x:Name="Left" Orientation="Left"
   VerticalAlignment="Center" HorizontalAlignment="Left"/>

Copy, Paste, Delete, Cut

The Copy and Paste commands work analogous to the Open and Save commands, except that they are applied only to the selected items and that they read and write the serialized content to the Clipboard. The Delete command simply removes all selected items from the designer canvas' Children collection, and the Cut command finally is a combination of Copy and Delete command.

Align, Distribute

Not much to say about these commands, except that the reference item for alignment is the item that was selected at first (also called primary selection). This works only when you select items with the LeftMouseButton + Ctrl, or LeftMouseButton + Shift, but not if you use rubberband selection.


The Panel class (from which Canvas is derived) provides an attached property named ZIndex that defines the order on the z-plane in which the children appear, so we only have to change that property to bring an item forward or backward.


  • 25th March, 2008 -- Original version submitted


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Austria Austria
No Biography provided

You may also be interested in...

Comments and Discussions

AnswerRe: Are there any license related constraints while using this code? Pin
Bharat Mane12-Jul-11 23:53
memberBharat Mane12-Jul-11 23:53 
QuestionAdding text inside the diagram Pin
sazzadur23-Jun-11 3:45
membersazzadur23-Jun-11 3:45 
QuestionGetting Namespace error while debugging Pin
sazzadur23-Jun-11 3:03
membersazzadur23-Jun-11 3:03 
GeneralWould you take on an assignment of porting this diagram for silverlight Pin
wmmihaa9-Jun-11 5:36
memberwmmihaa9-Jun-11 5:36 
GeneralMy vote of 5 Pin
hichem14723-May-11 9:06
memberhichem14723-May-11 9:06 
GeneralIssue in VB.Net Conversion of this project. Pin
Dhirendra Jain3-Mar-11 9:06
memberDhirendra Jain3-Mar-11 9:06 
QuestionHow to draw a line and connect it with flow controls Pin
Member 39538981-Mar-11 3:47
memberMember 39538981-Mar-11 3:47 
Generalgetting error Pin
Dhirendra Jain28-Feb-11 10:49
memberDhirendra Jain28-Feb-11 10:49 
Hi Sukram,

I have converted your project from C# to Vb.Net.
I have changed all of the codes to from C#.

But when I am building/compiling the solution it is giving me following errors.
I will be very grateful to you if you help me to rectify it.

Errors are as below:

Error 4 Cannot find the type 'DesignerCanvas'. Note that type names are case sensitive. J:\VB.Net\C# To VB.Net Conversion\28-Feb_2011 - 2\DiagramDesignerVB\DiagramDesignerVB\Resources\ApplicationToolbar.xaml 159 37 DiagramDesigner

Error 5 Cannot find the type 'DesignerCanvas'. Note that type names are case sensitive. J:\VB.Net\C# To VB.Net Conversion\28-Feb_2011 - 2\DiagramDesignerVB\DiagramDesignerVB\Resources\Connection.xaml 14 46 DiagramDesigner

Error 6 Cannot find the type 'DesignerCanvas'. Note that type names are case sensitive. J:\VB.Net\C# To VB.Net Conversion\28-Feb_2011 - 2\DiagramDesignerVB\DiagramDesignerVB\Resources\DesignerItem.xaml 30 38 DiagramDesigner

Error 7 The tag 'Toolbox' does not exist in XML namespace 'clr-namespace:DiagramDesigner'. Line 273 Position 4. J:\VB.Net\C# To VB.Net Conversion\28-Feb_2011 - 2\DiagramDesignerVB\DiagramDesignerVB\Resources\Stencils\FlowChartStencils.xaml 273 4 DiagramDesigner

Error 8 The tag 'Toolbox' does not exist in XML namespace 'clr-namespace:DiagramDesigner'. Line 28 Position 4. J:\VB.Net\C# To VB.Net Conversion\28-Feb_2011 - 2\DiagramDesignerVB\DiagramDesignerVB\Resources\Stencils\ShapeStencils.xaml 28 4 DiagramDesigner

Error 9 Cannot find the type 's:Toolbox'. Note that type names are case sensitive. Line 4 Position 10. J:\VB.Net\C# To VB.Net Conversion\28-Feb_2011 - 2\DiagramDesignerVB\DiagramDesignerVB\Resources\Toolbox.xaml 4 10 DiagramDesigner

Error 10 Cannot find the type 's:ToolboxItem'. Note that type names are case sensitive. Line 4 Position 10. J:\VB.Net\C# To VB.Net Conversion\28-Feb_2011 - 2\DiagramDesignerVB\DiagramDesignerVB\Resources\ToolboxItem.xaml 4 10 DiagramDesigner

Error 11 Cannot find the type 'DesignerCanvas'. Note that type names are case sensitive. J:\VB.Net\C# To VB.Net Conversion\28-Feb_2011 - 2\DiagramDesignerVB\DiagramDesignerVB\Window1.xaml 17 43 DiagramDesigner


QuestionHow can I draw a line on canvas? Pin
masoudfcr3-Feb-11 15:00
membermasoudfcr3-Feb-11 15:00 
AnswerRe: How can I draw a line on canvas? Pin
Nic_Gen17-Feb-11 4:46
memberNic_Gen17-Feb-11 4:46 
QuestionThe same project in MVVM way? Pin
Nic_Gen28-Jan-11 4:56
memberNic_Gen28-Jan-11 4:56 
GeneralStore some data DesignerItem Pin
Byakuya16-Jan-11 23:21
memberByakuya16-Jan-11 23:21 
QuestionToolBox dynamically Pin
javaprogrammer1014-Jan-11 11:55
memberjavaprogrammer1014-Jan-11 11:55 
GeneralDouble Click Dialog Box for Objects [modified] Pin
sashdude4-Jan-11 9:50
membersashdude4-Jan-11 9:50 
QuestionHow is possible create a web based version of this project? Pin
danimedo2-Jan-11 22:52
memberdanimedo2-Jan-11 22:52 
GeneralMy vote of 5 Pin
Dac17-Dec-10 18:36
memberDac17-Dec-10 18:36 
QuestionHow to create Connection programatically? Pin
tobyash16-Dec-10 7:19
membertobyash16-Dec-10 7:19 
AnswerRe: How to create Connection programatically? Pin
tobyash17-Dec-10 11:32
membertobyash17-Dec-10 11:32 
QuestionLabel on object Pin
Byakuya15-Dec-10 23:51
memberByakuya15-Dec-10 23:51 
AnswerRe: Label on object Pin
tobyash16-Dec-10 7:13
membertobyash16-Dec-10 7:13 
GeneralRe: Label on object Pin
Byakuya21-Dec-10 2:51
memberByakuya21-Dec-10 2:51 
QuestionHow to identify if a shape is being placed on top of another shape? Pin
DennisZweigle11-Nov-10 7:32
memberDennisZweigle11-Nov-10 7:32 
GeneralSelected Event Pin
Madhusudana Rao9-Nov-10 21:27
memberMadhusudana Rao9-Nov-10 21:27 
Generalmouse drag connection Pin
zahrakhazaei9-Nov-10 3:16
memberzahrakhazaei9-Nov-10 3:16 
GeneralMy vote of 5 Pin
_Newton_1-Nov-10 4:35
member_Newton_1-Nov-10 4:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170308.1 | Last Updated 26 Mar 2008
Article Copyright 2008 by sukram
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid