Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CFlowchartEditor - linking things in CDiagramEditor

0.00/5 (No votes)
5 Jul 2006 25  
A flowchart editor with linked objects, based on CDiagramEditor.

Sample Image - flowcharteditor.gif

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 )
        {
            // Creating the editor window
    
            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  );

    // Himetric rect

    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.

Sample Image - flowcharteditor5.gif

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here