using wpf to visualize a...endencies - codeproject

9
9,817,283 members (28,608 online) 300 Sign out Scad home quick answers discussions features community help S e a r c h f o r a r t i c l e s , q u e s t i o n s , t i p s Articles » Platforms, Frameworks & Libraries » Windows Presentation Foundation » General Article Browse Code Stats Revisions (9) Alternatives Comments & Discussions (37) Add your own alternative version About Article Reviews a WPF application that displays an object graph which can be rearranged by the user at runtime, and highlights circular dependencies in its nodes. Type Article Licence CPOL First Posted 15 Nov 2009 Views 59,407 Downloads 1,357 Bookmarked 114 times C#3.0 .NET3.5 VS2008 Architect Dev XAML WPF + Top News WebKit Group Strikes Back: Next Using WPF to Visualize a Graph with Circular Dependencies By Josh Smith , 15 Nov 2009 Download source code (requires Visual Studio 2008 SP1) - 88.7 KB Introduction This article presents a WPF application that renders an object graph which the user can rearrange via drag- and-drop. The graph highlights all circular dependencies between its nodes. The application was built in Visual Studio 2008, and compiled against the .NET Framework v3.5 with Service Pack 1. Background Recently at work, I wrote some code that analyzed an object graph in order to determine the order in which its objects should be initialized. The requirement was to initialize the objects on which an object depends before initializing that object. In a scenario like that, the object graph cannot contain any circular dependencies (i.e., where an object indirectly depends on itself, such as object A depends on object B, and object B depends on object A). After implementing that logic, I thought it would be interesting to create a user interface that showed an object graph and highlighted any circular dependencies it contained. This weekend, I had some free time, so I wrote the application described in this article which does exactly that. What the program does The simplest example of what this application does is shown in the following screenshot: 4.99 (42 votes) articles

Upload: sergiocosta28

Post on 01-Dec-2015

41 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Using WPF to Visualize a...Endencies - CodeProject

9,817,283 members (28,608 online) 300 Sign out

Scad

home quick answers discussions features community help Search for articles, questions, tips

Articles » Platforms, Frameworks & Libraries » Windows Presentation Foundation » General

Article

Browse Code

Stats

Revisions (9)

Alternatives

Comments &Discussions (37)

Add your ownalternative version

About Article

Reviews a WPF applicationthat displays an objectgraph which can berearranged by the user atruntime, and highlightscircular dependencies in itsnodes.

Type Article

Licence CPOL

First Posted 15 Nov 2009

Views 59,407

Downloads 1,357

Bookmarked 114 times

C#3.0 .NET3.5 VS2008

Architect Dev XAML WPF

+

Top News

WebKit Group Strikes Back:

Next

Using WPF to Visualize a Graph with CircularDependenciesBy Josh Smith, 15 Nov 2009

Download source code (requires Visual Studio 2008 SP1) - 88.7 KB

IntroductionThis article presents a WPF application that renders an object graph which the user can rearrange via drag-and-drop. The graph highlights all circular dependencies between its nodes. The application was built inVisual Studio 2008, and compiled against the .NET Framework v3.5 with Service Pack 1.

BackgroundRecently at work, I wrote some code that analyzed an object graph in order to determine the order inwhich its objects should be initialized. The requirement was to initialize the objects on which an objectdepends before initializing that object. In a scenario like that, the object graph cannot contain any circulardependencies (i.e., where an object indirectly depends on itself, such as object A depends on object B, andobject B depends on object A).

After implementing that logic, I thought it would be interesting to create a user interface that showed anobject graph and highlighted any circular dependencies it contained. This weekend, I had some free time,so I wrote the application described in this article which does exactly that.

What the program doesThe simplest example of what this application does is shown in the following screenshot:

4.99 (42 votes)

articles

Page 2: Using WPF to Visualize a...Endencies - CodeProject

Let's Remove Chrome

Get the Insider News free eachmorning.

Related Videos

Welcome toCodeProject.TV

Working with Forms

Related ArticlesDependency Inversion Principleand the Dependency InjectionPattern

A curry of DependencyInversion Principle (DIP),Inversion of Control (IoC),Dependency Injection (DI) andIoC Container

Dependency Injection for LooseCoupling

WPF Tutorial - DependencyProperty

A Project Dependency GraphUtility for Visual Studio 2008

Dependency Injection Pattern(Loose Coupling)

Dependency Inversion Principle,IoC Container, and DependencyInjection (Part - 1)

Automatic Build Dependencies(ABD) add-in for VC project in.NET 2003

Property dependencygeneration in Visual Studio

Simplify Code Using NDepend

Dependency InjectionFrameworks - Part 1 -Introduction

Propagator in C# - AnAlternative to the ObserverDesign Pattern

Dependency Inversion Principle,IoC Container & DependencyInjection (Part - 2)

The story about Ninja, UnityApplication Block andContextual DependencyInjection

Dependant Name Hell

SQL Compare and SQLPackager

Tool for Converting VC++2005Project to Linux Makefile

Modular InnoSetupDependency Installer

Exploring Caching in ASP.NET

ASP.NET Caching Dependencies

The object graph seen above has only four nodes and no circular dependencies. Each node in a graph canbe moved by the user so that he or she can arrange the objects into whatever layout makes the mostsense. After rearranging the graph seen previously, it can look like this:

Now, let’s take a look at a graph that contains a circular dependency:

The nodes and node connectors that are part of the circular dependency are highlighted in red. Noticehow this graph has a button in the top right corner. Clicking on that button causes the graph to perform a3D flip, and shows a detailed listing of all circular dependencies in the graph, as seen below:

Page 3: Using WPF to Visualize a...Endencies - CodeProject

There is a more complicated graph in the application that contains three circular dependencies:

Notice how the mouse cursor over the node connector on the left side has brought up a tooltip thatexplains which nodes are associated with that connector. In this example, the node connector links twonodes that both depend on each other. If we were to flip this graph over, the details view would list allthree circular dependencies in the graph, like this:

Now that we’ve seen what this application does, let’s take a look at how it works.

Building a graphThe data shown by a graph could come from anywhere. In this application, the data for each graph comesfrom an XML file included in the source code. The following XML describes the simplest graph in theapplication (the first graph seen in the previous section of this article):

Collapse | Copy Code

Page 4: Using WPF to Visualize a...Endencies - CodeProject

<?xml version="1.0" encoding="utf-8" ?><graph title="Simple Graph"> <node id="A" x="50" y="50"> <dependency id="B" /> </node> <node id="B" x="50" y="150"> <dependency id="C" /> <dependency id="D" /> </node> <node id="C" x="50" y="250"> <dependency id="D" /> </node> <node id="D" x="200" y="175" /></graph>

This XML will be converted into an instance of the Graph class, which is seen in the following diagram:

As seen in the diagram above, the Graph class has a collection of Node objects and a collection ofCircularDependency objects. Those types can be seen in the following diagram:

The CircularDependency class will come into play in the next section. The process of building an objectgraph only involves the Graph and Node classes. A Graph object is created by the GraphBuilder class. Itreads an XML file and creates a Graph full of Nodes. That logic is seen below:

Collapse | Copy Code

// In GraphBuilder.csstatic Graph BuildGraph(string xmlFileName){ string path = string.Format(@"Graphs\{0}", xmlFileName); XDocument xdoc = XDocument.Load(path);

// Create a graph. var graphElem = xdoc.Element("graph"); string title = graphElem.Attribute("title").Value; var graph = new Graph(title);

var nodeElems = graphElem.Elements("node").ToList();

// Create all of the nodes and add them to the graph. foreach (XElement nodeElem in nodeElems) {

Page 5: Using WPF to Visualize a...Endencies - CodeProject

string id = nodeElem.Attribute("id").Value; double x = (double)nodeElem.Attribute("x"); double y = (double)nodeElem.Attribute("y");

var node = new Node(id, x, y); graph.Nodes.Add(node); }

// Associate each node with its dependencies. foreach (Node node in graph.Nodes) { var nodeElem = nodeElems.First( elem => elem.Attribute("id").Value == node.ID);

var dependencyElems = nodeElem.Elements("dependency"); foreach (XElement dependencyElem in dependencyElems) { string depID = dependencyElem.Attribute("id").Value; var dep = graph.Nodes.FirstOrDefault(n => n.ID == depID); if (dep != null) node.NodeDependencies.Add(dep); } }

// Tell the graph to inspect itself for circular dependencies. graph.CheckForCircularDependencies();

return graph;}

The last part of the method seen above is a call to Graph’s CheckForCircularDependencies method.That method is the subject of the next section. Hold on, things are about to get interesting…

Detecting circular dependenciesThe process of locating all circular dependencies in a graph is a cooperative effort between a Graph and allof its Nodes. The CheckForCircularDependencies method of the Graph class asks each node if it ispart of circular dependencies. A node returns a CircularDependency object for each circle that it findsitself in. A node will continue to look for circular dependencies until it cannot find any more that includeitself. Here is the Graph method that gets the processing underway:

Collapse | Copy Code

// In Graph.cspublic void CheckForCircularDependencies(){ foreach (Node node in this.Nodes) { var circularDependencies = node.FindCircularDependencies(); if (circularDependencies != null) this.ProcessCircularDependencies(circularDependencies); } this.CircularDependencies.Sort();}

void ProcessCircularDependencies(List<CircularDependency> circularDependencies){ foreach (CircularDependency circularDependency in circularDependencies) { if (circularDependency.Nodes.Count == 0) continue;

if (this.CircularDependencies.Contains(circularDependency)) continue;

// Arrange the nodes into the order in which they were discovered. circularDependency.Nodes.Reverse();

this.CircularDependencies.Add(circularDependency);

// Inform each node that it is a member of the circular dependency. foreach (Node dependency in circularDependency.Nodes) dependency.CircularDependencies.Add(circularDependency); }}

The real logic in this algorithm lies in Node’s FindCircularDependencies method. Let’s now turn ourattention to that:

Collapse | Copy Code

// In Node.cspublic List<CircularDependency> FindCircularDependencies(){ if (this.NodeDependencies.Count == 0) return null;

var circularDependencies = new List<CircularDependency>();

var stack = new Stack<NodeInfo>(); stack.Push(new NodeInfo(this));

NodeInfo current = null; while (stack.Count != 0) { current = stack.Peek().GetNextDependency();

Page 6: Using WPF to Visualize a...Endencies - CodeProject

if (current != null) { if (current.Node == this) { var nodes = stack.Select(info => info.Node); circularDependencies.Add(new CircularDependency(nodes)); } else { bool visited = stack.Any(info => info.Node == current.Node); if (!visited) stack.Push(current); } } else { stack.Pop(); } } return circularDependencies;}

private class NodeInfo{ public NodeInfo(Node node) { this.Node = node; _index = 0; }

public Node Node { get; private set; }

public NodeInfo GetNextDependency() { if (_index < this.Node.NodeDependencies.Count) { var nextNode = this.Node.NodeDependencies[_index++]; return new NodeInfo(nextNode); } return null; }

int _index;}

This logic only looks for a circular dependency that includes the node on which theFindCircularDependencies method was invoked. It makes use of the NodeInfo class to keep track ofwhich dependencies to process next after processing all of a node's dependencies.

Rendering nodesBuilding a graph of nodes and detecting circular dependencies is an interesting programming exercise, butthe fun doesn’t stop there! An equally challenging and interesting problem to solve is rendering thatgraph to the screen. This section of the article explains how my app renders a graph of nodes andhighlights circular dependencies.

A graph of nodes is displayed by the GraphView user control. It contains an ItemsControl whoseItemsSource is bound to the Nodes property of a Graph object. To enable coordinate-based positioningof the nodes, I could use a Canvas as the ItemsPanel. Instead, I chose to use my DragCanvas panel, sothat the user can move the nodes around. The relevant XAML from GraphView is listed below:

Collapse | Copy Code

<!-- In GraphView.xaml --><ItemsControl Background="LightGray" ItemsSource="{Binding Path=Nodes}" > <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <jas:DragCanvas /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel>

<ItemsControl.ItemTemplate> <DataTemplate> <Border Style="{StaticResource NodeBorderStyle}"> <TextBlock Text="{Binding Path=ID}" /> </Border> </DataTemplate> </ItemsControl.ItemTemplate>

<ItemsControl.ItemContainerStyle> <Style TargetType="{x:Type ContentPresenter}"> <Setter Property="Canvas.Left" Value="{Binding Path=LocationX, Mode=TwoWay}" /> <Setter Property="Canvas.Top" Value="{Binding Path=LocationY, Mode=TwoWay}" /> </Style> </ItemsControl.ItemContainerStyle></ItemsControl>

Page 7: Using WPF to Visualize a...Endencies - CodeProject

The nodes are positioned based on their LocationX and LocationY properties. Those properties arebound to by the ItemsContainerStyle. Those bindings link a node’s LocationX property to theCanvas.Left attached property on the ContentPresenter that displays the node, and likewise for theLocationY and Canvas.Top properties.

Notice that the ItemTemplate seen above contains a Border element whose Style property referencesa Style whose key is ‘NodeBorderStyle’. That Style contains a DataTrigger which highlights a nodeif it is in a circular dependency, as seen below:

Collapse | Copy Code

<!-- In GraphView.xaml --><Style x:Key="NodeBorderStyle" TargetType="{x:Type Border}"> <Setter Property="Background" Value="LightGreen" /> <Setter Property="BorderBrush" Value="Gray" /> <Setter Property="BorderThickness" Value="3" /> <Setter Property="BorderBrush" Value="Gray" /> <Setter Property="Height" Value="{Binding Path=NodeHeight}" /> <Setter Property="Padding" Value="4" /> <Setter Property="TextElement.FontWeight" Value="Normal" /> <Setter Property="Width" Value="{Binding Path=NodeWidth}" /> <Style.Triggers> <DataTrigger Binding="{Binding Path=HasCircularDependency}" Value="True" > <Setter Property="Background" Value="Red" /> <Setter Property="BorderBrush" Value="Black" /> <Setter Property="TextElement.FontWeight" Value="Bold" /> </DataTrigger> </Style.Triggers></Style>

The node connectors are highlighted in a similar manner. Speaking of node connectors...

Rendering node connectorsA dependency between two nodes is depicted by drawing an arrow that points from the node that has adependency to the node on which it depends. I made use of Charles Petzold’s excellent ArrowLineelement to render an arrow. In my application, I subclassed ArrowLine to create NodeConnector.NodeConnector takes care of moving itself when its associated nodes are moved by the user. It alsoexposes the IsPartOfCircularDependency property used by a Style’s trigger in order to highlightthe connector if it points to two nodes in the same circular dependency.

Here is the code from NodeConnector that initializes an instance and ensures that the connector isalways pointing to its nodes:

Collapse | Copy Code

// In NodeConnector.cspublic NodeConnector(Node startNode, Node endNode){ _startNode = startNode; _endNode = endNode;

this.SetIsPartOfCircularDependency(); this.SetToolTip(); this.UpdateLocations(false);

_startObserver = new PropertyObserver<Node>(_startNode) .RegisterHandler(n => n.LocationX, n => this.UpdateLocations(true)) .RegisterHandler(n => n.LocationY, n => this.UpdateLocations(true));

_endObserver = new PropertyObserver<Node>(_endNode) .RegisterHandler(n => n.LocationX, n => this.UpdateLocations(true)) .RegisterHandler(n => n.LocationY, n => this.UpdateLocations(true));}

void UpdateLocations(bool animate){ var start = ComputeLocation(_startNode, _endNode); var end = ComputeLocation(_endNode, _startNode);

if (animate) { base.BeginAnimation(ArrowLine.X1Property, CreateAnimation(base.X1, start.X)); base.BeginAnimation(ArrowLine.Y1Property, CreateAnimation(base.Y1, start.Y)); base.BeginAnimation(ArrowLine.X2Property, CreateAnimation(base.X2, end.X)); base.BeginAnimation(ArrowLine.Y2Property, CreateAnimation(base.Y2, end.Y)); } else { base.X1 = start.X; base.Y1 = start.Y; base.X2 = end.X; base.Y2 = end.Y; }}

static AnimationTimeline CreateAnimation(double from, double to){ return new EasingDoubleAnimation { Duration = _Duration,

Page 8: Using WPF to Visualize a...Endencies - CodeProject

Equation = EasingEquation.ElasticEaseOut, From = from, To = to };}

The constructor creates two PropertyObservers, which is a class in my MVVM Foundation library. Whenthe observers detect that either node’s LocationX or LocationY properties have changed, theUpdateLocations method is invoked. UpdateLocations is also invoked from the constructor, but theanimate argument is false. This ensures that when a connector first appears, it immediately shows up atthe proper location. When nodes move, however, the animate argument is true, causing the connectorto gently bounce to its new position on the screen. The bounce effect is created by using anEasingDoubleAnimation, from my Thriple library, with its Equation property set to ElasticEaseOut.

The node connectors are rendered in the adorner layer. I created a custom adorner that renders all of agraph’s node connectors, called NodeConnectionAdorner. When that adorner’s Graph property is set,the adorner adds a node connector for each node dependency, as seen below:

Collapse | Copy Code

// In NodeConnectionAdorner.cspublic Graph Graph{ get { return _graph; } set { if (value == _graph) return;

_graph = value;

if (_graph != null) this.ProcessGraph(); }}

void ProcessGraph(){ foreach (Node node in _graph.Nodes) foreach (Node dependency in node.NodeDependencies) this.AddConnector(node, dependency);}

void AddConnector(Node startNode, Node endNode){ var connector = new NodeConnector(startNode, endNode);

_nodeConnectors.Add(connector);

// Add the connector to the visual and logical tree so that // rendering and resource inheritance will work properly. base.AddVisualChild(connector); base.AddLogicalChild(connector);}

An instance of NodeConnectionAdorner is applied to the ItemsControl that contains a graph’s nodes.This occurs in the NodeConnectionAdornerDecorator class, which is the parent element of theItemsControl.

Collapse | Copy Code

<!-- In GraphView.xaml --><local:NodeConnectionAdornerDecorator Graph="{Binding Path=.}" > <ItemsControl ItemsSource="{Binding Path=Nodes}"> <!-- We saw this ItemsControl previously... --> </ItemsControl></local:NodeConnectionAdornerDecorator>

As seen above, the decorator has a Graph dependency property which is bound to the inheritedDataContext; which just so happens to be a Graph object. When the decorator element is loaded intothe UI, it puts a NodeConnectionAdorner into the adorner layer and hands off the Graph to theadorner so that it can create node connectors. That code is shown below:

Collapse | Copy Code

// In NodeConnectionAdornerDecorator.csvoid OnLoaded(object sender, RoutedEventArgs e){ var layer = AdornerLayer.GetAdornerLayer(this); if (layer == null) return;

_adorner = new NodeConnectionAdorner(this); layer.Add(_adorner); this.GiveGraphToAdorner();}

void GiveGraphToAdorner(){ if (_adorner != null && this.Graph != null) { _adorner.Graph = this.Graph; }}

Page 9: Using WPF to Visualize a...Endencies - CodeProject

Article Top 0 TweetTweet 3

Rate this: Poor Excellent Vote

There is a lot more going on in this app than what we’ve reviewed here, so if you are interested, be sure todownload the source code from the top of this article and dig in!

External referencesCharles Petzold’s blog – where ArrowLine came from.MVVM Foundation – where PropertyObserver and ObservableObject came from.WPF.JoshSmith – where DragCanvas came from.Thriple – where EasingDoubleAnimation and ContentControl3D came from.

Revision historyNovember 17, 2009 - Updated the article and source code to use a new and improved algorithmfor detecting all circular dependencies in a graph. Also, the node connectors now bounce into place,instead of sluggishly sliding like they originally did.November 15, 2009 - Created article.

LicenseThis article, along with any associated source code and files, is licensed under The Code Project OpenLicense (CPOL)

About the Author

Josh SmithSoftware Developer(Senior) CynergySystems

United States Member

Follow on Twitter

Josh creates software, for iOS and Windows. He works at Cynergy Systems as a Senior ExperienceDeveloper. Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps byleveraging your existing .NET skills. Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go. Check out his Advanced MVVM[^] book. Visit his WPF blog[^] or stop by his iOS blog[^].

Comments and Discussions

Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown andhit 'Update'.

Like 0