Display Petri nets using MindFusion diagram component.

In this post we show how to build a Petri net using MindFusion.Diagramming for WinForms. Petri nets are used to model and study distributed systems. A net contains places, transitions and arcs. A place represents possible state of the system, and a transition represents the change from one state to another. Arcs connect places to transitions and show the flow direction.

First, create a new .NET Windows Forms project and add a Model.cs file to it where we’ll define Petri net model classes

public class Net
{
	public List Places { get; set; }
	public List Transitions { get; set; }
	public List Arcs { get; set; }

	public Net()
	{
		Places = new List();
		Transitions = new List();
		Arcs = new List();
	}
}

public class Node
{
	public string Label { get; set; }
}

public class Place : Node
{
	public int Tokens { get; set; }
}

public class Transition : Node
{
}

public class Arc
{
	// Arcs run from a place to a transition or vice versa,
	// never between places or between transitions.

	public Arc(Place input, Transition output)
	{
		Input = input;
		Output = output;
	}

	public Arc(Transition input, Place output)
	{
		Input = input;
		Output = output;
	}

	public Node Input { get; private set; }
	public Node Output { get; private set; }

	public int Multiplicity { get; set; }
}

Now we can create a simple Petri net:

Net CreateSampleNet()
{
	var net = new Net();

	var p1 = new Place { Label = "P1", Tokens = 1 };
	var p2 = new Place { Label = "P2", Tokens = 0 };
	var p3 = new Place { Label = "P3", Tokens = 2 };
	var p4 = new Place { Label = "P4", Tokens = 1 };

	net.Places.AddRange(new[] { p1, p2, p3, p4 });

	var t1 = new Transition { Label = "T1" };
	var t2 = new Transition { Label = "T2" };

	net.Transitions.AddRange(new[] { t1, t2 });

	var a1 = new Arc(p1, t1);
	var a2 = new Arc(t1, p2);
	var a3 = new Arc(t1, p3);
	var a4 = new Arc(p2, t2);
	var a5 = new Arc(p3, t2);
	var a6 = new Arc(t2, p4);
	var a7 = new Arc(t2, p1);

	net.Arcs.AddRange(new[] { a1, a2, a3, a4, a5, a6, a7 });

	return net;
}

Next, drop a DiagramView and Diagram objects on the form which we’ll use to visualize the net. Add the following method to create diagram elements representing the model objects, and run LayeredLayout to arrange them:

void BuildDiagram(Net net)
{
	var nodeMap = new Dictionary();

	var placeBounds = new RectangleF(0, 0, 16, 16);
	var transBounds = new RectangleF(0, 0, 6, 20);

	foreach (var place in net.Places)
	{
		var node = diagram.Factory.CreateShapeNode(placeBounds);
		node.Text = place.Label;
		node.TextFormat.LineAlignment = StringAlignment.Far;
		node.Shape = Shapes.Ellipse;
		node.Tag = place.Tokens;
		node.CustomDraw = CustomDraw.Additional;
		nodeMap[place] = node;
	}

	foreach (var trans in net.Transitions)
	{
		var node = diagram.Factory.CreateShapeNode(transBounds);
		node.Text = trans.Label;
		node.TextFormat.LineAlignment = StringAlignment.Far;
		node.Shape = Shapes.Rectangle;
		nodeMap[trans] = node;
	}

	foreach (var arc in net.Arcs)
	{
		var link = diagram.Factory.CreateDiagramLink(
			nodeMap[arc.Input], nodeMap[arc.Output]);
		link.Tag = arc.Multiplicity;
		link.HeadShape = ArrowHeads.PointerArrow;
	}

	var layout = new LayeredLayout();
	layout.Orientation = Orientation.Horizontal;
	layout.StraightenLongLinks = true;
	layout.Arrange(diagram);
}

We will use the DrawNode custom draw event to render marks associated with each place. Another possibility is to create a custom node class and override its Draw method.

void OnDrawNode(object sender, DrawNodeEventArgs e)
{
	var node = e.Node;
	var g = e.Graphics;

	if (node.Tag is int)
	{
		var tokens = (int)node.Tag;
		var cx = node.Bounds.Width / 2;
		var cy = node.Bounds.Height / 2;

		if (tokens == 1)
		{
			float r = cx / 2;
			DrawMark(cx, cy, r, g);
		}
		else if (tokens == 2)
		{
			float r = 2 * cx / 5;
			DrawMark(cx / 2, cy, r, g);
			DrawMark(3 * cx / 2, cy, r, g);
		}
		else if (tokens == 3)
		{
			float r = cx / 3;
			float y2 = 4 * cy / 3;
			DrawMark(cx, 2 * cy / 5, r, g);
			DrawMark(cx / 2, y2, r, g);
			DrawMark(3 * cx / 2, y2, r, g);
		}
	}
}

void DrawMark(float x, float y, float r, IGraphics g)
{
	g.FillEllipse(Brushes.Black, x - r, y - r, r * 2, r * 2);
}

Finally, set some appearance properties and call the methods above to build the diagram:

public MainForm()
{
	InitializeComponent();

	diagram.ShadowsStyle = ShadowsStyle.None;
	diagram.DiagramLinkStyle.Brush = new MindFusion.Drawing.SolidBrush(Color.Black);
	diagram.ShapeNodeStyle.Brush = new MindFusion.Drawing.SolidBrush(Color.White);
	diagram.ShapeNodeStyle.FontSize = 10f;
	diagram.ShapeNodeStyle.FontStyle = FontStyle.Bold;

	var textAbove = new[]
	{
		new LineTemplate(-100, -100, 200, -100),
		new LineTemplate(200, -100, 200, 0),
		new LineTemplate(200, 0, -100, 0),
		new LineTemplate(-100, 0, -100, -100)
	};
	Shapes.Ellipse.TextArea = textAbove;
	Shapes.Rectangle.TextArea = textAbove;

	var net = CreateSampleNet();
	BuildDiagram(net);
}

The final result is displayed below.
Petri net diagram

The complete sample project is available for download here:
PetriNet.zip

For more information on Petri nets, see this Wikipedia article:
http://en.wikipedia.org/wiki/Petri_net

All MindFusion.Diagramming libraries expose the same programming interface, so most of the sample code shown above will work with only a few modifications in WPF, ASP.NET, Silverlight and Java versions of the control.

Enjoy!

Create a musical score writer using MindFusion diagram component.

In this example we’ll show how to use various features of MindFusion.Diagramming API to create a musical score editor:

Custom node types
We’ll create a StaffNode class to represent the staff, and NoteNode class to represent a musical note.

Grouping
NoteNodes will be attached to the StaffNode they were dropped onto (or nearby). If users move the staff around, the notes from the group will follow it.

Custom drawing logic
We’ll show how to draw custom graphics by overriding DrawLocal method of base DiagramNode class.

Using SVG images
We’ll show how to load an SVG image (for the G clef) and draw it as part of staff graphics.

NodeListView control
NodeListView contains prototypical node instances whose clones are added to the diagram using drag-and-drop operations. We’ll add a staff and several notes to the list to let users drag them to the score diagram.

The completed sample project can be downloaded from this link:
ScoreWriter.zip

Let’s start by defining StaffNode class to draw staves in the score diagram, and implement its Draw methods to draw five lines:

public class StaffNode : DiagramNode
{
	public StaffNode()
	{
		var rect = Bounds;
		rect.Width = 200;
		SetBounds(rect, false, false);

		// disable vertical resize
		EnabledHandles =
			AdjustmentHandles.ResizeMiddleLeft |
			AdjustmentHandles.Move |
			AdjustmentHandles.ResizeMiddleRight;
	}

	public StaffNode(StaffNode prototype) : base(prototype)
	{
	}

	public override void DrawLocal(IGraphics graphics, RenderOptions options)
	{
		base.DrawLocal(graphics, options);

		for (int i = 0; i < 5; i++)
		{
			float y = i * Bounds.Height / 4;
			using (var pen = EffectivePen.CreateGdiPen())
				graphics.DrawLine(pen, 0, y, Bounds.Width, y);
		}
	}

	public override void DrawShadowLocal(IGraphics graphics, RenderOptions options)
	{
	}
}

Next, load an SVG drawing representing G clef and draw it at appropriate position. We'll also override GetRepaintRect method to accommodate for parts of the clef that are drawn outside the staff's boundaries:

static SvgContent gClef;

static StaffNode()
{
	gClef = new SvgContent();
	gClef.Parse("GClef.svg");
}

public override void DrawLocal(IGraphics graphics, RenderOptions options)
{
	// ...

	var rect = GetLocalBounds();
	rect.Inflate(0, 8);
	rect.X = 2;
	rect.Width = 14;
	gClef.Draw(graphics, rect);
}

public override RectangleF GetRepaintRect(bool includeConnected)
{
	var rect = base.GetRepaintRect(includeConnected);
	rect.Inflate(0, 8);
	return rect;
}

Create an initial StaffNode instance from Form.Load event:

var initialStaff = new StaffNode();
initialStaff.Move(10, 10);
diagram.Nodes.Add(initialStaff);

If you run the project now, you should see the following diagram:
score writer diagram in c#

Next, define the Duration enumeration and NoteNode class to represent musical notes of various durations:

enum Duration
{
	Whole,
	Half,
	Quarter,
	Eighth,
	Sixteenth
}

class NoteNode : DiagramNode
{
	public NoteNode()
	{
		Bounds = new RectangleF(0, 0, 6, 6);
		Duration = Duration.Whole;
	}

	public NoteNode(Duration duration)
	{
		Bounds = new RectangleF(0, 0, 6, 6);
		Duration = duration;
	}

	public Duration Duration { get; set; }

	int position = 0;
}

Implement NoteNode.Draw methods as follows:

public override void DrawLocal(IGraphics graphics, RenderOptions options)
{
	base.DrawLocal(graphics, options);

	var cx = Bounds.Width / 2;
	var cy = Bounds.Height / 2;

	var gs = graphics.Save();
	graphics.TranslateTransform(cx, cy);
	graphics.RotateTransform(-10);
	graphics.TranslateTransform(-cx, -cy);

	var bounds = GetLocalBounds();
	bounds.Inflate(0, -bounds.Width / 10);
	var path = new GraphicsPath();
	path.AddEllipse(bounds);

	if (Duration == Duration.Whole || Duration == Duration.Half)
	{
		bounds.Inflate(-bounds.Width / 8, -bounds.Width / 6);
		path.AddEllipse(bounds);
	}
	graphics.FillPath(Brushes.Black, path);

	graphics.Restore(gs);

	if (position < -1 || position > 8)
	{
		// draw ledger lines if above or below staff
		var pen = EffectivePen.CreateGdiPen();
		var staff = (StaffNode)MasterGroup.MainItem;
		var yoff = staff.Bounds.Y - Bounds.Y;
		int i1 = position < -1 ? position : 9;
		int i2 = position < -1 ? -2 : position;
		for (int i = i1; i <= i2; i++)
		{
			if (i % 2 != 0)
				continue;
			var y = yoff + i * staff.Bounds.Height / 8;
			graphics.DrawLine(pen, -2, y, Bounds.Width + 2, y);
		}
		pen.Dispose();
	}

	if (Duration != Duration.Whole)
	{
		// draw stem
		float x = Bounds.Width;
		float y = Bounds.Height / 2;
		var pen = new System.Drawing.Pen(Color.Black, 0.5f);
		graphics.DrawLine(pen,
				            x - pen.Width / 2, y,
				            x - pen.Width / 2, y - Bounds.Height * 2);
		pen.Dispose();
	}

	if (Duration == Duration.Eighth || Duration == Duration.Sixteenth)
	{
		DrawFlag(graphics,
				    bounds.Width,
				    bounds.Height / 2 - bounds.Height * 2,
				    bounds.Width + 1,
				    bounds.Height);
	}

	if (Duration == Duration.Sixteenth)
	{
		DrawFlag(graphics,
				    bounds.Width,
				    bounds.Height - bounds.Height * 2,
				    bounds.Width + 1,
				    bounds.Height);
	}
}

void DrawFlag(IGraphics graphics, float x, float y, float w, float h)
{
	float sh = h / 2;
	float sw = w / 3;

	var pen = new System.Drawing.Pen(Color.Black, 0.5f);
	x -= pen.Width / 2;
	graphics.DrawBezier(pen,
			            x, y,
			            x, y + sh,
			            x + sw * 1.2f, y + 2 * sh,
			            x + sw, y + 3 * sh);
	pen.Dispose();
}

public override void DrawShadowLocal(IGraphics graphics, RenderOptions options)
{
}

public override RectangleF GetRepaintRect(bool includeConnected)
{
	var r = Bounds;
	r.Y -= r.Height * 2;
	r.Height *= 3;
	r.Width *= 2;
	return r;
}

Now, drag a NodeListView to the form and populate it from Load handler:

nodeListView.AddNode(new StaffNode());

nodeListView.DefaultNodeSize = new SizeF(6, 6);
nodeListView.AddNode(new NoteNode(Duration.Whole));
nodeListView.AddNode(new NoteNode(Duration.Half));
nodeListView.AddNode(new NoteNode(Duration.Quarter));
nodeListView.AddNode(new NoteNode(Duration.Eighth));
nodeListView.AddNode(new NoteNode(Duration.Sixteenth));

Drag and drop will not work just yet. First, we must enable the DiagramView.AllowDrop property to accept drag-and-drop events. Next, the custom classes must implement a copy constructor and serialization methods to be able to instantiate them through OLE drag events:

public NoteNode(NoteNode prototype) : base(prototype)
{
	Duration = prototype.Duration;
}

protected override void SaveTo(System.IO.BinaryWriter writer, PersistContext context)
{
	base.SaveTo(writer, context);
	context.Writer.Write((int)Duration);
}

protected override void LoadFrom(System.IO.BinaryReader reader, PersistContext context)
{
	base.LoadFrom(reader, context);
	Duration = (Duration)context.Reader.ReadInt32();
}

As a final touch for this example, let's implement aligning notes to staves' lines and spaces. First lets declare a helper method that returns the nearest StaffNode at specified location in diagram:

static class DiagramExtensions
{
	static public StaffNode NearestStaff(this Diagram diagram, PointF point)
	{
		var staves = diagram.Nodes.OfType();

		StaffNode nearest = null;
		float minDist = float.MaxValue;

		foreach (var staff in staves)
		{
			if (staff.ContainsPoint(point))
				return staff;

			var borderPoint = staff.GetNearestBorderPoint(point);
			var dist = Utilities.Distance(borderPoint, point);
			if (dist < minDist)
			{
				minDist = dist;
				nearest = staff;
			}
		}

		return minDist < 20 ? nearest : null;
	}
}

Next, implement StaffNode.Align method that aligns its argument to a line or space in the staff:

public PointF Align(PointF point, out int position)
{
	// align to pitch line/space

	float h = Bounds.Height / 8;
	float offset = point.Y - Bounds.Y;
	position = (int)Math.Round(offset / h);
	offset = (float)Math.Round(offset / h) * h;
	point.Y = Bounds.Y + offset;
	return point;
}

Add NoteNode.AlignToStaff method that will find nearest StaffNode and align the note's position to the staff.

public StaffNode AlignToStaff()
{
	position = 0;

	var staff = Parent.NearestStaff(GetCenter());
	if (staff == null)
		return null;

	var alignedPoint = staff.Align(GetCenter(), out position);
	alignedPoint.X -= Bounds.Width / 2;
	alignedPoint.Y -= Bounds.Height / 2;
	Move(alignedPoint.X, alignedPoint.Y);

	return staff;
}

We can align notes after drag-and-drop from NodeListView by handling diagram's NodeCreated event. We'll use the same handler to attach notes to that staff, so that if users move a StaffNode, its attached NoteNodes will follow.

private void OnNodeCreated(object sender, NodeEventArgs e)
{
	var note = e.Node as NoteNode;
	if (note != null)
	{
		var staff = note.AlignToStaff();
		if (staff != null)
			note.AttachTo(staff, AttachToNode.TopLeft);

		note.HandlesStyle = HandlesStyle.MoveOnly;
	}
}

Finally, override NoteNode.CompleteModify to align notes after user moves them to a different position on the staff or to another staff in the score:

protected override void CompleteModify(PointF end, InteractionState ist)
{
	base.CompleteModify(end, ist);

	var staff = AlignToStaff();
	if (staff != null)
		AttachTo(staff, AttachToNode.TopLeft);
	else
	{
		Detach();
	}
}

Let's run the project and compose some music 🙂
.net diagram control

A fully-featured scorewriter software would also allow for drawing rest, sharp and flat symbols, C and F clefs, and some other musical notation features, but these are left as exercise to the reader 😉

The code above uses MindFusion’s .NET API and can be used with Windows Forms, WPF, Silverlight and ASP.NET diagramming components. The Java API for Android and desktop Swing application will look similar, with setter method calls instead of property assignments.

You can download the trial version of any MindFusion.Diagramming component from this page.

Enjoy!

Combine layout algorithms

Apply FractalLayout and SpringLayout to generate a tag cloud

In a series of posts we’ll explore ways to combine graph layout algorithms for various purposes, such as improving layout speed or achieving specific layout constraints.

In this topic we’ll show how to create a tag cloud using FractalLayout and SpringLayout algorithms from MindFusion diagramming API. You can download the complete project here:

TagCloud.zip

The sample code will show several features of the Diagram control:

  • FractalLayout
  • SpringLayout
  • custom node placement
  • text-only nodes

We assume words frequencies are already counted and listed as “word: frequency” entries in a sorted file. The example uses tags extracted from the contents of Wikipedia’s Tag cloud page. Let’s start by parsing the file and creating a node for each word. We’ll assign the word frequency to the Weight property of nodes for future reference. Weight is also used by some layout algorithms (such as TreeMapLayout for creating tree maps) that could visually represent word frequencies as well:

RectangleF defaultBounds = new RectangleF(0, 0, 10, 20);
ShapeNode root;
		
private void MainForm_Load(object sender, EventArgs e)
{
	// read the tags file
	var reader = new StreamReader("words.txt");
	string line;
	while ((line = reader.ReadLine()) != null)
	{
		// each line contains "word: frequency" entries
		var parts = line.Split(new[] { ':' });
		var word = parts[0];
		var frequency = int.Parse(parts[1]);

		// create a diagram node for each word in the file
		var node = diagram.Factory.CreateShapeNode(defaultBounds);
		node.Weight = frequency;
		node.Text = word;

		// set font size corresponding to frequency
		node.Font = new Font(
			"Arial",
			8 + (float)Math.Log(node.Weight, 1.15),
			GraphicsUnit.Point);

		// resize the node to fit text
		var size = TextRenderer.MeasureText(node.Text, node.Font);
		node.Resize(
			2 + (float)MeasureUnit.Pixel.Convert(size.Width, diagram.MeasureUnit, null),
			2 + (float)MeasureUnit.Pixel.Convert(size.Height, diagram.MeasureUnit, null));

		// show only text, hide geometry
		node.Transparent = true;
	}
}

Next, let’s build a tree that will distribute largest nodes roughly uniformly when arranged by FractalLayout, where larger parent nodes are circled by their smaller child nodes:

while ((line = reader.ReadLine()) != null)
{
	// ...
	if (diagram.Nodes.Count == 1)
	{
		// save reference to the first node
		root = node;
	}
	else
	{
		// build a tree where each node has up to six children
		diagram.Factory.CreateDiagramLink(
			diagram.Nodes[diagram.Nodes.Count / 6],
			node);
	}
}

// use FractalLayout for initial placement. if the file entries are sorted, each circular
// branch will contain larger parent node centered between its smaller children
new FractalLayout().Arrange(diagram);

If you run the application now, you should see the following layout:

a tree arranged using fractal layout

FractalLayout allocates some space for links, and we’ll reclaim it by deleting the links and compressing the initial layout of nodes:

// remove the links
while (diagram.Links.Count > 0)
	diagram.Links.RemoveAt(diagram.Links.Count - 1);

// pull all nodes towards the root to eliminate empty space that was occupied by links
var center = root.GetCenter();
foreach (var node in diagram.Nodes.Where(n => n != root))
{
	var relativePos = new Vector(center, node.GetCenter());
	var newPos = center + relativePos / 10;
	node.Move(
		newPos.X - node.Bounds.Width / 2,
		newPos.Y - node.Bounds.Height / 2);
}

Now apply SpringLayout to make distances between closely placed nodes more uniform, running only a few iterations to complete faster:

// run SpringLayout to distribute nodes more uniformly
var sl = new SpringLayout();
sl.Randomize = false;
sl.SplitGraph = false;
sl.NodeDistance = 3;
sl.IterationCount = 40;
sl.Arrange(diagram);

Run the following method to remove any overlaps remaining after SpringLayout. RemoveOverlaps works by starting from specified node and offsetting any nodes that overlap it, continuing by spiraling away while processing other nodes. This method could also be useful in an interactive application if you want to disperse overlapping nodes introduced by the user when they move a node:

void RemoveOverlaps(DiagramNode modifiedNode, float minDist)
{
	var queue = new Queue();
	queue.Enqueue(modifiedNode);

	while (queue.Count > 0)
	{
		var node = queue.Dequeue();
		var nodeCenter = node.GetCenter();
		var overlaps = FindOverlaps(node, minDist);
		foreach (var overlap in overlaps)
		{
			var ovrCenter = overlap.GetCenter();
			var ovrBounds = overlap.Bounds;
			var dx = ovrCenter.X - nodeCenter.X;
			var dy = ovrCenter.Y - nodeCenter.Y;
			if (Math.Abs(dx) > Math.Abs(dy))
			{
				// offset horizontally
				if (dx < 0)
					ovrBounds.X = node.Bounds.Left - ovrBounds.Width - minDist;
				else
					ovrBounds.X = node.Bounds.Right + minDist;
			}
			else
			{
				// offset vertically
				if (dy < 0)
					ovrBounds.Y = node.Bounds.Top - ovrBounds.Height - minDist;
				else
					ovrBounds.Y = node.Bounds.Bottom + minDist;
			}

			// shifting the node might introduce new overlaps, continue processing
			overlap.Bounds = ovrBounds;
			queue.Enqueue(overlap);
		}
	}
}

List FindOverlaps(DiagramNode modifiedNode, float minDist)
{
	var bounds = modifiedNode.Bounds;
	bounds.Inflate(minDist - 1, minDist - 1);

	var overlaps = new List();
	foreach (var node in diagram.Nodes)
	{
		if (modifiedNode == node)
			continue;
		if (bounds.IntersectsWith(node.Bounds))
			overlaps.Add(node);
	}
	return overlaps;
} 

Finally run a few more iterations of SpringLayout to equalize distances again and zoom the diagram to show the whole tag cloud:

// remove any remaining overlaps
RemoveOverlaps(root, 0.1f);
sl.Arrange(diagram);

// show everything inside view
diagram.ResizeToFitItems(5);
diagramView.ZoomToFit();

If you run the application now, you should see the following image:

tag cloud generated using MindFusion diagram control

The code above uses MindFusion’s .NET API and can be used with Windows Forms, WPF, Silverlight and ASP.NET diagramming components. The Java API for Android and desktop Swing application will look similar, with setter method calls instead of property assignments.

You can download the trial version of any MindFusion.Diagramming component from this page.

Enjoy!

Creating a proprietary invoice editor

In this post we will show how to create an invoice editing application (using MindFusion.Reporting) for the end users of an organization. The source code of the sample is available for download from here:

http://mindfusion.eu/_samples/ReportingInvoiceEditor.zip

Introduction
We start off by creating a new Windows Forms Application in Visual Studio 2010 or later. Change the target framework of the application to “.NET Framework 4” (or later). The ReportEditor component that will be used as an in-place invoice editor requires at least .NET 4.

Add the ReportEditor component to the main form, set its Dock to Fill.

The invoice template
The invoice template displayed by the application is stored in an XML file. The original template is created beforehand and is located in Invoice.xml. All modifications to the template done by the end users will be stored back to the XML file upon exiting the application. Add the following line to the main form’s constructor to load the invoice template when the main form is constructed:

reportEditor1.OpenReport(@"Invoice.xml");

Adding the data source
From the “Data -> Add New Data Source…” menu in Visual Studio create a new data source from the nwind.mdb database. Select the Orders table and the Invoices query in the Data Source Configuration Wizard. In the XML Schema (nwindDataSet.xsd) ensure that there is a relation between the Orders and Invoices table adapters. The relation should link the OrderID fields of the two tables and should be named “Orders_Invoices”. Build the application so that Visual Studio creates the classes for the data set and the selected table adapters. Go back to the main form designer and add nwindDataSet, InvoicesTableAdapter, and OrdersTableAdapter components to the form. In the constructor of the form, add the following lines in order to fill the data set with the data from the source database:

invoicesTableAdapter1.Fill(nwindDataSet1.Invoices);
ordersTableAdapter1.Fill(nwindDataSet1.Orders);

In addition, we need to register the two tables as data sources in the report editor. This is essential because these data sources are used by the invoice report. It is also important that the data sources are registered before the report is initially loaded through the OpenReport method.

reportEditor1.AddDataSource(nwindDataSet1.Orders, "Orders");
reportEditor1.AddDataSource(nwindDataSet1.Invoices, "Invoices");

Saving the template
Override the OnClosing event of the form and add the following line to ensure that all changes to the invoice template are written back to the XML file:

reportEditor1.SaveReport(@"Invoice.xml");

Adding the menu
Create a menu strip for the application with the following structure:

  • File
    • Print
    • Print Preview
    • Exit
  • Edit
    • Undo
    • Redo

Add the following event handlers for the menu items:

private void printToolStripMenuItem_Click(object sender, EventArgs e)
{
	var printer = new ReportPrinter();
	printer.Report = reportEditor1.Report;
	printer.Report.Run();
	printer.Print();
}

private void printPreviewToolStripMenuItem_Click(object sender, EventArgs e)
{
	var printer = new ReportPrinter();
	printer.Report = reportEditor1.Report;
	printer.Report.Run();

	var preview = new PrintPreviewForm();
	preview.Document = printer;
	preview.ShowDialog();
}

private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
	Close();
}

private void undoToolStripMenuItem_Click(object sender, EventArgs e)
{
	reportEditor1.Undo();
}

private void redoToolStripMenuItem_Click(object sender, EventArgs e)
{
	reportEditor1.Redo();
}

The image below illustrates the running application:

reporting-invoiceeditor

The MindFusion.Reporting component can be downloaded from here:

http://www.mindfusion.eu/ReportingTrial.zip

Enjoy!

Database schema diagram

In this post we’ll show how to use TableNode objects to display tabular data, more specifically database schema information. A Visual Studio sample project containing the code from this post is available for download here:

DatabaseSchema.zip

To start, create a new Windows Forms application, and place a text field for connection string, a button and a DiagramView on the form. In the code-behind file, add following field to map table name to respective TableNode objects:

Dictionary tables = new Dictionary();

Add a RectangleF that stores default size passed to CreateTableNode method:

RectangleF defaultSize = new RectangleF(0, 0, 30, 30);

Create a ReadTables method, which provided an SqlConnection, parses its schema information and creates diagram nodes:

void ReadTables(SqlConnection connection)
{
	// get table schema definitions from connection
	var schema = connection.GetSchema("Tables");
	foreach (DataRow row in schema.Rows)
	{
		// fetch table name
		var name = row["TABLE_NAME"].ToString();

		// create respective node
		var table = diagram.Factory.CreateTableNode(defaultSize);
		table.Caption = name;
		table.Shape = SimpleShape.RoundedRectangle;
		table.Brush = new MindFusion.Drawing.SolidBrush(Color.LightGray);

		// register node in dictionary for future foreign key reference
		tables[name.Replace(" ", "_")] = table;
		ReadFields(table, connection,
			row["TABLE_CATALOG"].ToString(), null, name);
	}

	ReadForeignKeys(connection);
}

The ReadFields method takes table node and name parameters and creates node cells that will show information for the column name and type of database tables:

void ReadFields(TableNode node,
	SqlConnection connection, string db, string owner, string tableName)
{
	// remove default cells
	node.RowCount = 0;

	// reserve one column for name and one for data type
	node.ColumnCount = 2;

	// read column definitions of specified table
	var schema = connection.GetSchema("Columns", new[] { db, owner, tableName });
	foreach (DataRow row in schema.Rows)
	{
		// add a new row to the node
		int r = node.AddRow();

		// set cells' text to the column name and type
		node[0, r].Text = row["COLUMN_NAME"].ToString();
		node[1, r].Text = row["DATA_TYPE"].ToString();

	}

	// make table cells big enough to show all text
	node.ResizeToFitText(false);
}

The ReadForeignKeys method creates DiagramLink connectors between table nodes to show the relationships between database tables:

void ReadForeignKeys(SqlConnection connection)
{
	var schema = connection.GetSchema("ForeignKeys");
	foreach (DataRow row in schema.Rows)
	{
		// read foreign key information
		string fkName = row["CONSTRAINT_NAME"].ToString();
		string tableName = row["TABLE_NAME"].ToString().Replace(" ", "_");
		string prefix = "FK_" + tableName + "_";
		if (fkName.StartsWith(prefix))
		{
			string targetName = fkName.Substring(prefix.Length);

			// get table nodes registered for specified names
			if (tables.ContainsKey(targetName) && tables.ContainsKey(tableName))
			{
				var table = tables[tableName];
				var targetTable = tables[targetName];

				// create a link between the nodes to show relationship
				diagram.Factory.CreateDiagramLink(table, targetTable);
			}
		}
	}
}

Finally handle the button’s click event to open specified connection and call ReadTables. Apply AnnealLayout to arrange the tables so that they do not overlap:

private void btnOpen_Click(object sender, System.EventArgs e)
{
	diagram.ClearAll();

	try
	{
		var connection = new SqlConnection(tbConnection.Text);
		connection.Open();

		// read schema and create corresponding diagram items
		ReadTables(connection);

		connection.Close();
	}
	catch (Exception exception)
	{
		MessageBox.Show(exception.Message);
		diagram.ClearAll();
	}

	// arrange the tables to remove overlaps
	var layout = new AnnealLayout();
	layout.SplitGraph = true;
	layout.Randomize = false;
	layout.MultipleGraphsPlacement = MultipleGraphsPlacement.MinimalArea;
	layout.Margins = new SizeF(10, 10);
	layout.Arrange(diagram);
}

If you run the project and open the Northwind sample database by Microsoft, you should see this diagram:

database schema layout

The code above uses MindFusion’s .NET API and can be used with Windows Forms, WPF, Silverlight and ASP.NET diagramming components. The Java API for Android and desktop Swing application will look similar, with setter method calls instead of property assignments.

You can download the trial version of any MindFusion.Diagramming component from this page.

Enjoy!