- Part 1 - Drag, resize and rotate items on a canvas
- Part 2 - Toolbox, drag & drop, rubberband selection
- Part 3 - Connecting items
Introduction
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 !
Commands
The way I use WPF commands is straight forward, as described in the WPF SDK documentation, no extra infrastructure.
Grouping
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.
Save
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:
XElement.Save(fileName)
Open
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.
Order
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.
History
- 25th March, 2008 -- Original version submitted
|
|
 |
 | About the save function Yen Yen | 4:21 19 Jan '10 |
|
 |
Hello,
This is the first time i am using microsoft visual C#, i am trying to build a system which has TabControl with two TabItem. The problem that i facing is the way to save both diagrams in a single xml file. Based on your explanation above, i found that you are design an xml file to store the diagram information. However, i can't get the code in your system. May i know where can i found it or where should i declare it. Is that anyone can help? Thank a lot for your help.
Thank.
|
|
|
|
 |
 | Zoom el06b150 | 6:29 19 Nov '09 |
|
 |
Hallo,
Ich wollte fragen, ob es möglich ist, in Part4 die ZoomBox aus Part2 einzubauen oder irgend eine andere Zoom-Funktion??? 
MfG Stefan
|
|
|
|
 |
|
 |
Replace your designer canvas with this one
<s:DesignerCanvas Focusable="true" x:Name="UIcanvasDesigner" Background="{StaticResource WindowBackgroundBrush}" Margin="10" FocusVisualStyle="{x:Null}" ContextMenu="{StaticResource DesignerCanvasContextMenu}"> <s:DesignerCanvas.LayoutTransform> <ScaleTransform ScaleX="{Binding Path=Value, ElementName=zoomSlider}" ScaleY="{Binding Path=Value, ElementName=zoomSlider}"/> </s:DesignerCanvas.LayoutTransform> </s:DesignerCanvas>
and add this slider somewhere above your DesignerCanvas
<Slider x:Name="zoomSlider" Minimum="0.5" Maximum="2" Value="1" Width="100"/>
|
|
|
|
 |
 | Delete Key on a Connection caldrak@gmail.com | 6:02 27 Oct '09 |
|
 |
When running the code, hitting del on the keyboard will not delete a selected connection. I can't figure out what to change in order to make this work. Does anybody have any ideas?
|
|
|
|
 |
|
 |
Hi, I've stucked on same problem. It looks like that Delete command does not bubble when you have selected (focused) only Connections items. I've created a button to delete all selected connections as a improvized solution but I'm not happy with it.
foreach (Connection element in UIcanvasDesigner.SelectionService.CurrentSelection.OfType<Connection>()) { this.UIcanvasDesigner.Children.Remove(element); }
Did anyone find better solution how to make selected Connections react on Delete command?
|
|
|
|
 |
 | Thumb inside a Thumb fernubio | 10:24 20 Oct '09 |
|
 |
Excelent article. This help me a lot to understand Thumb and WPF templates.
I have a problem. I was trying but I´not sure if it is possible to put several Thumbs inside a Thumb.
thank you in advance
|
|
|
|
 |
|
 |
Hope this helps.
You have to create a DesignerItem with a DesignerCanvas inside. Put this code i.e. on button clicked event handler in Window1.xaml.cs.
DesignerItem it = new DesignerItem();
it.MinWidth = 200d; it.MinHeight = 200d;
DesignerCanvas c = new DesignerCanvas(); c.AllowDrop = true; c.IsHitTestVisible = false;
it.Content = c;
it.Position = new Point(X, Y); this.UIcanvasDesigner.Children.Add(it);
this.UIcanvasDesigner.SetConnectorDecoratorTemplate(it); this.UIcanvasDesigner.SelectionService.SelectItem(it);
Now you can add any other block into this one.
|
|
|
|
 |
 | WPF VB.NET code - Need help Member 4113793 | 4:56 27 Jul '09 |
|
 |
I had the code entirely converted from C# to VB.NET but there remains a few ertrors and I am hoping anyone out there who had a successful conversion can help me out. The problem begins at runtime that when I drag two items onto the canvas and try to connect them the breakpoint halts at this line of code in ConnectorAdorner.vb.
code Private m_hitConnector As Connector Private Property HitConnector() As Connector Get Return m_hitConnector End Get Set(ByVal value As Connector) If Not m_hitConnector.Equals(value) Then m_hitConnector = value End If End Set End Property
Can anybody tell me what is wrong with this piece of code. Is there a problem with the If Not operator (the equivalent of sukram C# code is If hitconnector != value) I have been tearing out at this problem for a couple of hours but still no solution. I understand that overloading of this operator <> can be done in VB.NET but I dont see anything wrong here. Anybody out there please help!
Thanks
Ricky
|
|
|
|
 |
 | Silverlight flowchart Madhuribala | 2:17 20 Jul '09 |
|
 |
Hi... Could someone help me to do the same thing in silverlight.I want to open the saved xml file in silverlight only for viewing purposes. When i try to use this project in silverlight , many of the features of wpf arent in silverlight and hence i am unable to work on it. Kindly give some tips on the same.
Thank you.
|
|
|
|
 |
 | Connector title Morales_01 | 6:19 13 Jul '09 |
|
 |
Hi and congratulations for your work. I have modified the FlowChartStencils for accept a TextBox and add a title to them, then I could get this text from each DesignerItem by reading the content attribute of each one. Now, I want to do the same thing for the connectors but I just got to write them a name inside, but I could not get this text from code because they do not have an attribute "Content". How do you think I can do this?
Thanks and good bye!
|
|
|
|
 |
|
 |
Hi Morales, can u tell me how u went about adding textbox and reading content attribute.Please explain in detail as I am totally new to wpf.
Thnx n bye, Niveditha
|
|
|
|
 |
 | Window.XAML won't load in VS2008 SP1 Designer (FIX) TheArchitectualizer | 8:00 2 Jul '09 |
|
 |
Had some problems loading the Window.xaml in the designer in VS2008 SP1 Team. There were no path bindings for DesignerItem.xaml & Conection.xaml. If you set the Path=. it load fine in the designer.
DesignerItem.xaml Line 135:27 -----------------------------
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent},Path=.}" ContextMenu="{StaticResource DesignerItemContextMenu}">
----------------------------- Conection.xaml Line 85:27 -----------------------------
<Canvas DataContext="{Binding RelativeSource={RelativeSource TemplatedParent},Path=.}" ContextMenu="{StaticResource ConnectionContextMenu}">
----------------------------- Optionally, a period (.) path can be used to bind to the current source. For example, Text=”{Binding}” is equivalent to Text=”{Binding Path=.}”. Binding.Path[^]
Cool App!

|
|
|
|
 |
 | Part 5 CyberDev | 0:07 1 Jul '09 |
|
 |
Is there progress on a Part 5 of this series?
Thanks.
|
|
|
|
 |
 | Connect two node by code ale.capu80 | 7:10 10 Jun '09 |
|
 |
Hi, I want create a diagram with code.
I've created two node like this:
DesignerItem node1 = new DesignerItem(); node1.Content = content1; MyDesignerCanvas.Children.Add(node1);
DesignerItem node2 = new DesignerItem(); node2.Content = content1; MyDesignerCanvas.Children.Add(node2);
Now... how can I create a link from node1 and node2 via code ???
Tnks
|
|
|
|
 |
 | Properties for each item Paralias | 23:24 11 May '09 |
|
 |
Hallo there and Congratulations for this great article.
I would like to ask you how can we have properties for each item (node,line,etc...)
I mean: when you click on a start node for example, at the right side of the page having a PropertyGrid (like in Win Form Controls - or something similar) so we can fill some properties, like NodeName,NextNode or anything else...
Can we do that?
|
|
|
|
 |
 | Get type of designer item and automatically display in canvas Member 4113793 | 22:41 3 May '09 |
|
 |
Hi
Is it possible to get say for example in your series Part 4 the ellipse and display the ellipses on both sides of a draggable item that I drag say for example a rectangle? If so how do we get the ellipses and join them to the rectangle by means of lines(i.e) connecting all the three?
I have been searching through the forums but to no avail. Any sample code or conceptual view would be helpful. I am also in the process of learning WPF quite a bit and I wanted to extend your example to make it like a Visio type diagram where dynamic connectors can be drawn like when we drag a shape over a connector the connector resizes itself to connect to that shape that was just dragged into the canvas. Is such a thing possible with WPF?
Thanks
Ricky
|
|
|
|
 |
 | How to dynamically edit content ? cieszak | 22:47 27 Apr '09 |
|
 |
Hi
I have a polygon which the user can edit (move the vertices). In the easiest way to do it based on your example?
Regards, Cieszak
|
|
|
|
 |
 | WPF Drag and Drop item over arrow splits automatically Member 4113793 | 19:13 17 Apr '09 |
|
 |
Hi
In regard to this article I couldn't find a way that would works as follows
When we drag an item over already two joining items connected by an arrow it would split the arrows and that item would be joined automatically between the two with the arrows repositioned.... Is there such a way in DiagramDesigner???
a small sample code or some tutorials might help me out...I have been trying to figure this out
Thanks
Ricky
|
|
|
|
 |
 | ListView - Connection leo uri | 4:06 16 Apr '09 |
|
 |
hi all,
This article is GREAT it was sehr helpful, and now i want to make some modifications.
i want to add a Listview with observablecollection for all connections. but i got a argumentexception.
I add one String-Property Name in DesignerItem the collection is in the Class designercanvas and i modify the OnMouseUP event of the class connector adorner.
(...)
this.designercanvas.children.Add(newConnection); this.designercanvas.ConnectioncollectionListview.add(newConnection);
(..)
i got this exception
Argumentexception
the specified child element must be of current parent visual object to be separated before it a new parent Visual object added.
I did the same with the DesignerItem and all works great but with the connections doesn't work.
thanks
regards
Leonardo
|
|
|
|
 |
 | Need help in VB.NET Member 4113793 | 21:05 15 Apr '09 |
|
 |
Hi Sukram
The article was indeed helpful and when I translated this into VB.NET I keep getting the following errors
1) operator = or <> is not defined for types Object and DiagramDesigner.DesignerCanvas - Source of error happens in DesignerCanvas.vb The same case happens in the rest of other programs where operator are being used....Is there any way to go about this I did used the VB.NET conversion tool to translate the entire source code of your..
I have been fighting this error for the past 2 days but cant seem to get a solution. I believe this has to do something with Operator Overloading but if u could help me in this that will be great
Thanks
Ricky
|
|
|
|
 |
|
 |
Were you successful?
I need to do exactly the same, since I am not skilled in C#.
I don´t even know how to convert to vb.net.
Could you give me your code?
I would appreciate that very much.
|
|
|
|
 |
 | Adding an another flowchart item to toolbar in the left asdsdasda | 12:53 18 Mar '09 |
|
 |
I want to convert this software to a ER diagram modeler instead of creating a flow chart. So I have to add ER diagram shapes to the toolbar like strong entity weak entity vs. Also I have to remove the arrow in the connector. Do you have solution for this problem?
|
|
|
|
 |
|
 |
Hi,
you can change this particular file DiagramDesigner\Resources\Stencils\ShapeStencils.xaml which has list of shapes defined in XAML path element.
You can Use Microsoft Blend to create ER diagram shapes and convert that into path element and paste it in above file. now you can see ER Diagram shapes in left hand box!
and secondly, the connector is created during runtime (check connector.cs class file). however, you might need to explore the class in detail. but, I am pretty sure, you can achieve that.
HTH
KJ
|
|
|
|
 |
 | Regarding Overlap of DesignerItems Sandeep Srinivas Kulkarni | 17:22 15 Mar '09 |
|
 |
Hi Sukram,
Great article, hats off.
I had one small doubt. Could we by any means raise an event when one designer item gets placed on another designer item in the canvas. I mean not on drag and drop. But if we are auto drawing without location co-ordinates.
Is there any way to achieve this.
Can you please help
Regards Sandeep
|
|
|
|
 |
 | Simulo -- a codeplex project that uses that DiagramDesigner [modified] king_rollo | 8:07 23 Feb '09 |
|
 |
Hello,
Simulo is a project made by me, which can simulate digital circuits. It uses the wonderful DiagramDesigner code files. I would like to thank the author sukram of that article series. Link to the project: www.codeplex.com/Simulo
Benjamin Gentner
modified on Monday, February 23, 2009 1:20 PM
|
|
|
|
 |
|
|