HomeDigital EditionSearch Dotnet Cd
ASP.NET C# Certification Exams The CLI Data Access Editorials Extending .NET Fundamentals Interoperability Interviews Migrate Mobile .NET Mono .NET Interface Object-Oriented Programming Open Source Optimization Product/Book Reviews Security Source Code UML Visual Studio .NET

I like to hang out in the microsoft.public.dotnet. framework.drawing newsgroup. Call me a geek, an attention seeker, or just plain helpful - I get a kick from helping others out with their drawing-related problems, and often from just seeing how other people got together to solve a particular problem. Questions that come up almost every day are "How do I create a program like Visio?," "How do I draw and lay out an interactive flowchart." and "Dudes, where can I find a sweet process re-engineering diagramming component?"

There's a good reason for these questions. With the release of the .NET Framework, rich-client applications have suddenly become fashionable again. People have realized that Web pages can offer only so much and then no more. Hooking up a rich client to a Web service provides you with the best of both worlds - you can provide users with a rich interface experience while still receiving the benefits of remote connectivity and deployment.

Out of the box though, Windows Forms still doesn't cut it for many of the types of applications developers need to write these days, such as systems driven by flowcharts, UML diagrams, or workflow. You've either got a lot of coding ahead of you or you need to buy a component to do all the hard work. Writing a modeling component from scratch is not a trivial task. A good understanding of graphics, GDI+, and a bit of trigonometry is essential. However, a well-written component will save you time and money and reduce the risk to your project. Years of expertise and rich functionality are available for download in minutes and at a tiny fraction of the cost it would take to develop yourself.

Choosing the right component is important. The component should allow for simple and rapid development, but be fine-grained enough to allow the creation of specific features you want to implement that make your system unique. The component should also be well supported, ideally having examples in the development language of your choice (I'm thinking VB.NET or C# here). All feature-rich components should also have a comprehensive and accessible help file. You may have to spend time upfront understanding the component's object model and style of implementation, so a tutorial section and help that's integrated directly into the IDE is always nice to have.

If you choose to develop using Windows Forms in the .NET Framework, the component should support tight integration with the GDI+ namespace (system.drawing) for maximum flexibility. The component should be written in 100% managed code - you really don't want the deployment hassles associated with any COM-based controls. The main component classes should be inheritable to add those features that are missing or specific to your application. A good modeling component should support large models at least 10,000 pixels high and wide, as most models tend to turn out much larger than the display. For this reason your choice should also support zooming in and scaling out of the model surface. A separate overview control that provides a smaller, simplified version of a model so that the user can get an overall idea of what your model or chart looks like, as well as supporting navigation over large models, is always nice in this situation.

Entity relationship-based models are typically made up of shapes (nodes) and connected by lines. Top components should also provide ways to lay out nodes depending on the type of application. Network diagrams will probably require force-directed layouts, whereas hierarchical models such as organization or flowcharts require a flow type of layout. Layout is a complex and inexact science, and you should be aware that some vendors will charge you extra for these features, available as separate component add-ons, and that the quality of layout may differ from vendor to vendor. A good component should shield you from these complexities but be flexible enough for your needs and provide ways for you to improve or modify the layout routines yourself.

Finally, there are always some nice-to-have features. A component that takes full advantage of GDI+ will have support for anti-aliasing, gradients, and partial opacity that uses color or alpha blending, making an item seem partially transparent. Using a rich-client interface means you can take advantage of these features, which translates into a more attractive and interesting experience for users of your application. GDI+ doesn't rely on high-end graphics cards such as the ones found in games machines and applications, so components and applications driven by GDI+ can be used by any modern PC.

Getting Down to Business
Enough talk; lets get down to some coding. Based on the above criteria I'm going to use Crainiate's ERM .NET component to look at some sample implementations of Windows modeling applications. Other choices include Northwood Software's GoDiagram products or Lassalle Technology's AddFlow component. These components can be downloaded directly from the vendors or from sites like www.devdirect.com or www.componentsource.com.

Although the elements they contain may be called by different names, all models or entity relationship diagrams consist basically of shapes (or nodes) connected by lines (or links). I'll start by creating a simple program with two shapes and a connecting line to show the basic principles behind creating entity relationship diagrams.

Start by creating a new windows application with a form called form1 that should automatically be created for you. Add the ERM component to the toolbox if you haven't already done this and place a Model component on form1. In the Load event handler, enter the code in Listing 1 and run the program. You should see something similar to Figure 1.

A blue ellipse and a red square are created and connected by a line. The drawing2d.graphicspath class is used by the component to draw almost any shape, which gives great flexibility. The shapes are selectable and sizable, and can be moved at runtime as well. The line remains docked to the nearest side of each shape it has been connected to. Note: When one shape is dragged over another shape or line it is partially transparent and the lines and borders of the shapes are smoothed by anti-aliasing. This is thanks to the rich features offered by GDI+.

Add a button to the form and enter the code in Listing 2 , run the program, and click the button to see more interesting features offered by GDI+, such as custom graphics paths, rotation, and regions used by the component. It's interesting to note that the elements created on the form aren't real controls, although they behave like controls. Each raises their own mouse events, receives keyboard strokes, and can contain images and text, but that's probably where the comparison ends. Because models often contain hundreds or thousands of items, it simply isn't desirable for a model to use a full Windows control for each item; this would seriously degrade performance and remove a whole lot of flexibility.

A Real-World Application
The previous example certainly looked very nice (to me anyway) and showed the power available to be harnessed from GDI+, but it doesn't really look like anything that could be used as an application. In the next example we'll create a simple flowchart that represents a credit check at a bank.

Add another form, named form2, to the project and make it the startup object. This time place a Flowchart component on it. In the Load event handler, enter the code in Listing 3 . If you use Visual Basic, then add an Imports Crainiate.ERM statement to the top of the form, or a using statement if you use C#. You should see something similar to Figure 2.

The flowchart component class is inherited from the model class we used in the previous example. The flowchart builds on the basic functionality provided in the model and adds the code necessary to draw flowchart shapes, automatically link them, and apply layout to the flowchart. This shows one of my favorite features of the .NET Framework - a developer can build on features from another developer or vendor without requiring access to the source code in any way. By applying object-oriented programming techniques to the Model class you could quickly create your own type of chart, say a Nassi-Shneiderman chart, without having to re-invent the wheel. This hypothetical class could be called NSChart and would be subclassed from the Model class, while the elements contained in the NChart - such as processes and decisions - could be inherited from the Shape class. This type of development is a tremendous leap forward from the ActiveX and COM days of old and is set to become the way future applications are developed.

Conclusion
I enjoy developing rich-client applications with Microsoft Visual Studio .NET, and I love it when those applications include anything that requires development with graphics components and GDI+. By combining the .NET Framework with a well-written component you can quickly and cost-effectively create robust, attractive, and functional Windows modeling applications that will have your customers and users coming back for more.

About The Author
James Westgate has created commercial solutions with Microsoft products for over 10 years. His areas of expertise range from Visual Basic, COM+, and SQL to HTML and ASP - and more recently GDI+ and C# using the .NET Framework. He has helped author many popular interface and modeling components and enjoys real-time strategy and online first-person shooter games. Chances are you've fragged him on many occasions. james.westgate@crainiate.com



Listing 1

VB.NET
Dim objShape As Crainiate.ERM.Shape

'Add the first shape
objShape = Model1.Shapes.Add("rect", New Point(100, 50))

objShape.BackColor = System.Drawing.Color.White
objShape.GradientColor = Color.CornflowerBlue
objShape.GradientMode = Drawing.Drawing2D.LinearGradientMode.ForwardDiagonal
objShape.ShowGradient = True
objShape.Opacity = 80
objShape.GraphicsPath.AddEllipse(New System.Drawing.Rectangle(0, 0, 50, 50))
objShape.Insert()

'Add the second shape as a protoype
objShape = Model1.Shapes.Add("elipse", New Point(250, 50), objShape)
objShape.GradientColor = Color.Red
objShape.GraphicsPath.AddRectangle(New System.Drawing.Rectangle(0, 0, 50, 50))
objShape.Insert()

Dim objLine As Crainiate.ERM.Line
Dim objCap As New Drawing2D.AdjustableArrowCap(5, 4)

'Add a line between the two shapes
objLine = Model1.Lines.Add("line", Model1.Shapes("rect"), Model1.Shapes("elipse")) 
 objLine.AutoDocks = True
objLine.EndCap = Drawing.Drawing2D.LineCap.Custom
objLine.CustomEndCap = objCap
objLine.Update()

'Refresh the model
Model1.Refresh()

C#
Crainiate.ERM.Shape objShape;

//Add the first shape
objShape = model1.Shapes.Add("rect", new Point(100, 50));

objShape.BackColor = System.Drawing.Color.White;
objShape.GradientColor = Color.CornflowerBlue;
objShape.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.ForwardDiagonal;
objShape.ShowGradient = true;
objShape.Opacity = 80;
objShape.GraphicsPath.AddEllipse(new System.Drawing.Rectangle(0, 0, 50, 50));
objShape.Insert();

//Add the second shape as a protoype
objShape = model1.Shapes.Add("elipse", new Point(250, 50), objShape);
objShape.GradientColor = Color.Red;
objShape.GraphicsPath.AddRectangle(new System.Drawing.Rectangle(0, 0, 50, 50));
objShape.Insert();

Crainiate.ERM.Line objLine;
System.Drawing.Drawing2D.AdjustableArrowCap objCap = new
 System.Drawing.Drawing2D.AdjustableArrowCap(5, 4);

//Add a line between the two shapes
objLine = model1.Lines.Add("line", model1.Shapes["rect"], model1.Shapes["elipse"]);
objLine.AutoDocks = true;
objLine.EndCap = System.Drawing.Drawing2D.LineCap.Custom;
objLine.CustomEndCap = objCap;
objLine.Update();

//Refresh the model
model1.Refresh();


Listing 2

VB.NET
Dim objShape As Crainiate.ERM.Shape

'Suspend drawing in the model until we have added our 'new elements
Model1.SuspendModel()

'Rotate a shape
objShape = Model1.Shapes.Add("rotate", New Point(80, 160))

objShape.GradientColor = System.Drawing.Color.Orange
objShape.GradientMode = Drawing.Drawing2D.LinearGradientMode.ForwardDiagonal
objShape.ShowGradient = True
objShape.Opacity = 80

objShape.GraphicsPath.AddRectangle(New System.Drawing.Rectangle(0, 0, 50, 75))
objShape.Insert()
objShape.RotateShape(60)

'Hollow elipse
objShape = Model1.Shapes.Add("hollow", New Point(250, 160), objShape)
objShape.GradientColor = System.Drawing.Color.Lime

objShape.GraphicsPath.AddEllipse(New System.Drawing.Rectangle(0, 0, 80, 80))
objShape.GraphicsPath.AddEllipse(New System.Drawing.Rectangle(20, 20, 40, 40))
objShape.Insert()

'Add a line 
Dim objLine As Crainiate.ERM.Line

objLine = Model1.Lines.Add("line2", Model1.Shapes("rotate"), Model1.Shapes("hollow"))
objLine.StartCap = Drawing.Drawing2D.LineCap.Custom
objLine.CustomStartCap = New Drawing2D.AdjustableArrowCap(5, 4)

'Make the line curved
objLine.AddPivot(1, New Point(200, 220))
objLine.Style = Crainiate.ERM.Line.LineStyleEnum.Curve
objLine.AutoDocks = True
objLine.Update()

Model1.ResumeModel()
Model1.Refresh()

Listing 3

VB.NET
Dim objPrototype As FlowShape
Dim objFlowShape As FlowShape

FlowChart1.SuspendModel()
FlowChart1.ModelSize = New Size(1200, 900)

objPrototype = FlowChart1.AddFlowShape("start", New Point(300, 50),
 Crainiate.ERM.FlowShape.FlowShapeType.Connector, New Size(100, 60))
objPrototype.Text = "Start"
objPrototype.GradientColor = Color.Beige
objPrototype.Opacity = 80
objPrototype.ShowGradient = True
objPrototype.TextEdit = True

objFlowShape = FlowChart1.AddFlowShape("details", FlowChart1.Shapes("start"),
  FlowShape.FlowShapeType.Decision, objPrototype, Line.LineStyleEnum.Curve)
  FlowChart1.NewFlowShape.Text = "Check Details"

objFlowShape = FlowChart1.AddFlowShape("reject", FlowChart1.Shapes("details"),
 FlowShape.FlowShapeType.Terminator, objPrototype, Line.LineStyleEnum.Curve)
 objFlowShape.Text = "Reject"
 FlowChart1.NewFlowLine.Text = "Incomplete"

objFlowShape = FlowChart1.AddFlowShape("credit", FlowChart1.Shapes("details"),
 FlowShape.FlowShapeType.Decision, objPrototype, Line.LineStyleEnum.Curve)
objFlowShape.Text = "Check Credit"
FlowChart1.NewFlowLine.Text = "Complete"

objFlowShape = FlowChart1.AddFlowShape("deposit", FlowChart1.Shapes("credit"),
 FlowShape.FlowShapeType.Decision, objPrototype, Line.LineStyleEnum.Curve)
objFlowShape.Text = "Require Deposit"
FlowChart1.NewFlowLine.Text = "Fail"

objFlowShape = FlowChart1.AddFlowShape("reject2", FlowChart1.Shapes("deposit"),
 FlowShape.FlowShapeType.Terminator, objPrototype, Line.LineStyleEnum.Curve)
objFlowShape.Text = "Reject"
FlowChart1.NewFlowLine.Text = "Declined"

objFlowShape = FlowChart1.AddFlowShape("accept", FlowChart1.Shapes("deposit"),
 FlowShape.FlowShapeType.Terminator, objPrototype, Line.LineStyleEnum.Curve)
objFlowShape.Text = "Accept Conditions"
FlowChart1.NewFlowLine.Text = "Accepted"

objFlowShape = FlowChart1.AddFlowShape("reject3", FlowChart1.Shapes("accept"),
 FlowShape.FlowShapeType.Terminator, objPrototype, Line.LineStyleEnum.Curve)
objFlowShape.Text = "Reject"
FlowChart1.NewFlowLine.Text = "Declined"

objFlowShape = FlowChart1.AddFlowShape("approve", FlowChart1.Shapes("accept"),
 FlowShape.FlowShapeType.Terminator, objPrototype, Line.LineStyleEnum.Curve)
objFlowShape.Text = "Loan Approved"
FlowChart1.NewFlowLine.Text = "Accepted"

FlowChart1.AddFlowLine("creditaccept", FlowChart1.Shapes("credit"),
 FlowChart1.Shapes("accept"), Line.LineStyleEnum.Curve)
FlowChart1.NewFlowLine.Text = "Approved"

FlowChart1.ResumeModel()
FlowChart1.DoLayout()

C#
FlowShape objPrototype;
FlowShape objFlowShape;

FlowChart1.SuspendModel();
FlowChart1.ModelSize = new Size(1200, 900);

objPrototype = FlowChart1.AddFlowShape("start", new Point(300, 50),
 Crainiate.ERM.FlowShape.FlowShapeType.Connector, new Size(100, 60));
objPrototype.Text = "Start";
objPrototype.GradientColor = Color.Beige;
objPrototype.Opacity = 80;
objPrototype.ShowGradient = true;
objPrototype.TextEdit = true;

objFlowShape = FlowChart1.AddFlowShape("details",(FlowShape)
 FlowChart1.Shapes["start"],Crainiate.ERM.FlowShape.FlowShapeType.Decision, objPrototype,
 Crainiate.ERM.Line.LineStyleEnum.Curve);
FlowChart1.NewFlowShape.Text = "Check Details";

objFlowShape = FlowChart1.AddFlowShape("reject", (FlowShape)
 FlowChart1.Shapes["details"], FlowShape.FlowShapeType.Terminator, objPrototype,
 Line.LineStyleEnum.Curve);
objFlowShape.Text = "Reject";
FlowChart1.NewFlowLine.Text = "Incomplete";

objFlowShape = FlowChart1.AddFlowShape("credit", (FlowShape)
 FlowChart1.Shapes["details"], FlowShape.FlowShapeType.Decision, objPrototype,
 Line.LineStyleEnum.Curve);
objFlowShape.Text = "Check Credit";
FlowChart1.NewFlowLine.Text = "Complete";

objFlowShape = FlowChart1.AddFlowShape("deposit", (FlowShape)
 FlowChart1.Shapes["credit"], FlowShape.FlowShapeType.Decision, objPrototype,
 Line.LineStyleEnum.Curve);
objFlowShape.Text = "Require Deposit";
FlowChart1.NewFlowLine.Text = "Fail";

objFlowShape = FlowChart1.AddFlowShape("reject2", (FlowShape)
 FlowChart1.Shapes["deposit"], FlowShape.FlowShapeType.Terminator, objPrototype,
 Line.LineStyleEnum.Curve);
objFlowShape.Text = "Reject";
FlowChart1.NewFlowLine.Text = "Declined";

objFlowShape = FlowChart1.AddFlowShape("accept", (FlowShape)
 FlowChart1.Shapes["deposit"], FlowShape.FlowShapeType.Terminator, objPrototype,
 Line.LineStyleEnum.Curve);
objFlowShape.Text = "Accept Conditions";
FlowChart1.NewFlowLine.Text = "Accepted";

objFlowShape = FlowChart1.AddFlowShape("reject3", (FlowShape)
 FlowChart1.Shapes["accept"], FlowShape.FlowShapeType.Terminator, objPrototype,
 Line.LineStyleEnum.Curve);
objFlowShape.Text = "Reject";
FlowChart1.NewFlowLine.Text = "Declined";

objFlowShape = FlowChart1.AddFlowShape("approve", (FlowShape)
 FlowChart1.Shapes["accept"], FlowShape.FlowShapeType.Terminator, objPrototype,
 Line.LineStyleEnum.Curve);
objFlowShape.Text = "Loan Approved";
FlowChart1.NewFlowLine.Text = "Accepted";

FlowChart1.AddFlowLine("creditaccept", (FlowShape) FlowChart1.Shapes["credit"],
 (FlowShape) FlowChart1.Shapes["accept"], Line.LineStyleEnum.Curve);
FlowChart1.NewFlowLine.Text = "Approved";

FlowChart1.ResumeModel();
FlowChart1.DoLayout();

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.

  E-mail: info@sys-con.com