Introduction
CFlowchartEditor
is an extension of CDiagramEditor
. CDiagramEditor
is a vector editor, with a CWnd
-derived window (CDiagramEditor
), a data container (CDiagramEntityContainer
) holding the draw-objects, undo stack and managing copy and paste, and objects derived from CDiagramEntity
representing objects drawn on screen. CDiagramEditor
lacks one feature (that is difficult to implement in both simple and general fashion) - links. Manually maintaining links in a - for example - flowchart or network topography map is unwieldy. Therefore, I've created a reference implementation for one way of adding this functionality.
The flowchart editor
The editor can be tested in the demo application, Flowcharter. In this application, you can draw basic flowchart objects, move them around with the mouse or keyboard, cut/copy/delete/paste, undo unlimited amount of times, print-preview, and print and export the flowcharts to an EMF (enhanced metafile). If you select two unlinked objects, you can also link them. A line with an arrow showing the link direction is automatically drawn. You can select two linked objects and unlink them, reverse the link direction, or set a link label (a text moving with the link line). The objects have fixed sizes, and if a linked object is moved, other linked objects will be moved with them.
Links are automatically maintained, and if you copy and paste linked objects, the pasted objects will have the same relative links. There is even a special object, a linkable line, that can be used to create more complex flows. Note that the automatic moving will not resize objects, not even lines.
The editor also contains two un-linkable objects, a dotted line that can be used to divide the flowchart, and a label that can be used to... label the chart.
Other features from CDiagramEditor
are zoom using the +/--keys, and auto-scroll when moving objects outside the visible area, as well as keyboard editing and right-click popups. You might want to download and read the CDiagramEditor
documentation (link above) for a full description of the possibilities.
CFlowchartEditor
I first made an attempt where the links were an attribute of the CDiagramEntity
object, but this was, for many reasons, too complicated. Primarily, the objects started to have to know about each other, and things like copy/paste were made very difficult indeed. Instead, I went for a solution with a separate array of links in the editor container. This necessitated some modifications to CDiagramEditor
, to simplify deriving from CDiagramEntityContainer
(mainly making stuff either virtual, or changing them from private to protected).
Another thing I also wanted to do was a reference implementation of how to use the CDiagramEditor
in a MDI-application. I soon realized a big, black blot on my architecture - it was not possible to copy and paste between several windows. Of course, as the clipboard handler was internal to the container... ripping out the clipboard handling to a separate class made this possible.
As I'm actually using the flowchart editor myself, I also needed support for enhanced metafiles, which is also added. As well as a custom right-click popup-menu for the editor (loaded from the resources, implementing link commands).
Using the package
If you want to add CFlowchartEditor
to another application, you'll have to do the following:
- Enable RTTI in your project. You do this in Project|Settings|C/C++|C++ language.
CFlowchartEditor
uses run time type information to be able to mix linkable and non-linkable objects. I could have used the object types (an attribute of CDiagramEntity
) for this, but adding new object types would have meant that changes would have to be made in many places in the code.
- Add all files of the FlowchartEditor directory, except FlowchartEditor.rc and resource.h, and all files in FlowchartEditor/DiagramEditor to your project. I do this by creating subfolders in the project - see Flowcharter.
- Open your project resources and FlowchartEditor.rc at the same time. Drag - while pressing the CTRL-key - the resources from FlowchartEditor.rc to your project.
If you have a dialog app
- Add a
CFlowchartEditor
-member to your main dialog class.
- Call
CFlowchartEditor::Create
in the OnInitDialog
of the dialog class.
If you have a SDI-app
- Add a
CFlowchartEntityContainer
-member to your CDocument
-derived class.
- Add the virtual function
SaveModified
. Add code like so: SetModifiedFlag( m_objs.IsModified() );
return CDocument::SaveModified();
where m_objs
is the name of your CFlowchartEntityContainer
.
- Modify
OnNewDocument
thus: if (!CDocument::OnNewDocument())
return FALSE;
m_objs.Clear();
return TRUE;
- Add saving and loading in
Serialize
. This is an example: if (ar.IsStoring())
{
ar.WriteString( m_objs.GetString() + _T( "\r\n" ) );
int count = 0;
CDiagramEntity* obj;
while( ( obj = m_objs.GetAt( count++ ) ) )
ar.WriteString( obj->GetString() + _T( "\r\n" ) );
int max = m_objs.GetLinks();
for( int t = 0 ; t < max ; t++ )
{
CFlowchartLink* link = m_objs.GetLinkAt( t );
if( link )
ar.WriteString( link->GetString() + _T( "\r\n" ) );
}
m_objs.SetModified( FALSE );
}
else
{
m_objs.Clear();
CString str;
while(ar.ReadString( str ) )
{
if( !m_objs.FromString( str ) )
{
CDiagramEntity* obj
= CFlowchartControlFactory::CreateFromString(str);
if( obj )
m_objs.Add( obj );
else
{
CFlowchartLink* link = new CFlowchartLink;
if( link->FromString( str ) )
m_objs.AddLink( link );
else
delete link;
}
}
}
m_objs.SetModified( FALSE );
}
Note that you might want to add:
#pragma warning( disable : 4706 )
in the top of the document class file, as well as a:
#pragma warning( default : 4706 )
to avoid the pesky warnings.
- Add a
CFlowchartEditor
-member to your CView
-derived class. I will assume the name m_editor
below.
- Add the virtual function
OnInitialUpdate
, with (at least) this code: if( !m_editor.m_hWnd )
{
CFlowcharterDoc* pDoc = GetDocument();
CRect rect;
GetClientRect( rect );
m_editor.Create(WS_CHILD | WS_VISIBLE, rect, this, pDoc->GetData());
}
else
m_editor.Clear();
- Handle
OnSize
: CView::OnSize(nType, cx, cy);
if( m_editor.m_hWnd )
m_editor.MoveWindow(0,0,cx,cy);
- And, finally,
OnEraseBkgnd
: return TRUE;
to avoid flicker.
If you have a MDI-app
In addition to the instructions for SDI-apps above, you should do the following:
- Add a
CDiagramClipboardHandler
-member somewhere central. In the Flowcharter application, I opted for the application class itself. I made it public, and added an external declaration of the application object in Flowcharter.h, like so: extern CFlowcharterApp theApp;
- Modify the
CView OnInitialUpdate
thus: CView::OnInitialUpdate();
if( !m_editor.m_hWnd )
{
CFlowcharterDoc* pDoc = GetDocument();
CRect rect;
GetClientRect( rect );
pDoc->GetData()->SetClipboardHandler( &theApp.m_clip );
m_editor.Create( WS_CHILD | WS_VISIBLE,
rect, this, pDoc->GetData() );
}
else
m_editor.Clear();
to add a shared clipboard between all the MDI-children.
Metafile export
Adding metafile export is easy. As the editor already has a function to draw the diagram contents into a CDC
, the following code can be used to draw a 8x11 inch diagram into a meta file:
CClientDC dc( this );
CMetaFileDC metaDC;
CRect rect( 0,0,
m_editor.GetVirtualSize().cx,
m_editor.GetVirtualSize().cy );
CRect r( 0, 0, 8 * 2540, 11 * 2540 );
metaDC.CreateEnhanced( &dc, dlg.GetPathName(), &r,
_T( "FlowchartEditor Drawing" ) );
m_editor.SetRedraw( FALSE );
m_editor.Print( &metaDC, rect, 1 );
m_editor.SetRedraw( TRUE );
m_editor.RedrawWindow();
::DeleteEnhMetaFile ( metaDC.CloseEnhanced() );
Adding objects
Linkable objects can be added by deriving classes from CFlowchartEntity
. You'll have to add a few functions specific to your object:
AllowLinks
, which should return the link points allowed. Initially, CFlowchartEntityTerminator
only allowed links at the top or bottom, and exposed either LINK_TOP
or LINK_BOTTOM
, for example.
GetLinkPosition
, which returns the position of the desired link point.
Link points can be either at the top, bottom, left, or right of the object. They can also be from the start or end of the object for lines.
You'll also want to add the functions necessary to derive from CDiagramEntity
, Draw
, Clone
, CreateFromString
, GetHitCode
, GetCursor
, DrawSelectionMarkers
, and BodyInRect
, as appropriate.
To allow reading of the objects from file, an entry will also have to be made into CFlowchartControlFactory::CreateFromString
.
You are, of course, completely free to use the source code in whatever way you want, I even grant the right to eradicate my name from all source files so you can claim you wrote it yourself :-). This includes using the Flowcharter code - you can base a new project on the reference application, if you like.
Points of interest
The main work was already done with CDiagramEditor
, and much of CFlowchartEditor
is just brute coding. I'm happy with CDiagramEditor
this far, the amount of work to derive new drawing objects is kept at a minimum, and it is still easy to jack in derived packages into applications of different kinds.
I realize, however, that all this might be a bunch to digest, so I've added a simplified reference implementation of links (and MDI-adaptation), CNetworkEditor
.
It doesn't have any documentation for itself, except for the commented source code, but it uses the same mechanisms as CFlowchartEditor
, with fewer features (linked objects are not moved, one single link point with unlimited links per object, for example), and my intention was that it could be used to compare the implementation with CFlowchartEditor
. It also demonstrates the use of icons as drawing objects.
History
13/6 2004
As the underlying DiagramEditor is updated, I'm also updating this article so that the framework is synchronized between the two articles.
I've also updated the network map editor demo, thanks to the feedback from Dimitris Vassiliades, changing the icons to DIBs to - hopefully - give correct printouts.
8/7 2004
Another update of the DiagramEditor. I've corrected bugs, made enhancements, and added functionality:
- Groups
A group command is added. Objects can be grouped and handled as a single object.
- Panning
Panning is added. By clicking the middle mouse button and moving the cursor, the editor will pan the diagram.
- Mouse wheel support
The editor can be scrolled using the mouse wheel.
- Zoom to fit screen
Functionality to zoom the diagram, so that all objects are visible, have been added.
The changes are documented and attributed in the diagram editor article linked above.
5/8 2004
Source code updated as the DiagramEditor is updated.
28/8 2004
Far too early, here comes nevertheless a bug correction round. Once again, a big, fat thanks to you all for the feedback!
CDiagramEditor
- Added check for non-normalized rect when hit-testing in OnLButtonDown
, as lines have them.
CDiagramEntity
- Setting m_parent
to NULL
in the ctor
(Marc G).
CTokenizer
- Changed a char
to TCHAR
to allow UNICODE builds (Enrico Detoma).
25/3 2005
A maintenance upgrade because of the update of the underlying CDiagramEditor
. Please see this article (here) for details.
25/6 2006
The CDiagramEditor
this project is ultimately based on is updated. I've not updated the source for this article, as there are no other changes. Please use the CDiagramEditor
source code - full article can be found here.