Click here to Skip to main content
15,881,803 members
Articles / Multimedia / GDI+

100% Reflective Class Diagram Creation Tool

Rate me:
Please Sign up or sign in to vote.
4.98/5 (498 votes)
14 Jun 2011CPOL28 min read 1.8M   39.6K   1.2K  
100% Reflective Class Diagram Creation Tool
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace AutoDiagramer
{
    #region ClassDrawerContainerPanel CLASS
    /// <summary>
    /// This class is responsible for laying out the internal
    /// list of <see cref="DrawableClass"> classes </see> and
    /// also for drawing the associations for each available 
    /// association.
    /// </summary>
    public class ClassDrawerContainerPanel : TableLayoutPanel
    {
        #region Instance Fields
        //instance fields
        private List<DrawableClass> _classesToDraw;
        private int _GenericSpace = 20;
        private int _CurrRow = 0;
        //layout helper fields
        private int[] _colMaxWidths;
        private int _maxRows;
        private int[] _rowMaxHeights;
        private Size _MaxSize;
        private bool _LatchIn = false;
        #endregion
        #region Constructor
        /// <summary>
        /// Creates a new ClassDrawerContainerPanel object
        /// </summary>
        public ClassDrawerContainerPanel()
        {
            //set to flickerless style
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw |
                    ControlStyles.DoubleBuffer | ControlStyles.UserPaint, true);
        }
        #endregion
        #region Protected Methods
        /// <summary>
        /// Do a custom paint job. Basically iterate thrigh all the contained
        /// <see cref="DrawableClass">classes</see> and get the valid
        /// associations, and for each valid association get the destination
        /// <see cref="DrawableClass"> class </see> and then create a new
        /// <see cref="AssociationDrawer"> AssociationDrawer </see>to draw the 
        /// association line and arrow
        /// </summary>
        /// <param name="e">the event args</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            AssociationDrawer ac = null;
            //check that the _classesToDraw contains a list, will be null
            //whrn this control is 1st drawn
            if (_classesToDraw != null)
            {
                //get the graphics
                Graphics g = e.Graphics;

                //loop through each DrawableClass control and draw its associations
                foreach (DrawableClass ucdSrc in _classesToDraw)
                {
                    foreach (string associationDest in ucdSrc.Associations)
                    {
                        DrawableClass ucdDest = getDestForAssociation(associationDest);
                        if (ucdDest != null)
                        {
                            int _XLeft = getLeftWidestXPos(ucdDest.ContainerColumn);
                            int _XThis = getLeftWidestXPos(ucdSrc.ContainerColumn);
                            ac = new AssociationDrawer(g, ucdSrc, ucdDest, _GenericSpace,_XLeft,_XThis);
                        }
                    }
                }
            }
        }
        #endregion
        #region Private Methods

        private int getLeftWidestXPos(int columnToExamine)
        {
            int widestXPos=0;
            foreach(DrawableClass dc in _classesToDraw)
            {
                if (dc.ContainerColumn==columnToExamine)
                {
                    int rightEdge = dc.Location.X + dc.Width;
                    if (rightEdge > widestXPos) 
                    {
                        widestXPos = rightEdge;
                    }
                }
            }
            return widestXPos;
        }



        /// <summary>
        /// Returns a <see cref="DrawableClass">class </see> whos
        /// name is equal to the destName input parameter string
        /// </summary>
        /// <param name="destName">Destination string for association</param>
        /// <returns>A <see cref="DrawableClass">class </see> whos
        /// name is equal to the destName input parameter string</returns>
        private DrawableClass getDestForAssociation(string destName)
        {
            //if the current DrawableClass is the association destination
            //return it, so that the association line may be drawn
            foreach (DrawableClass ucdDest in _classesToDraw)
            {
                if (destName.ToLower().Equals(ucdDest.Name.ToLower()))
                {
                    return ucdDest;
                }
            }
            return null;
        }

        /// <summary>
        /// Lays out the contained <see cref="DrawableClass">classes</see>
        /// within a grid manner. (rows/columns)
        /// </summary>
        /// <param name="initialLayout">true means this is the 1st time layout 
        /// has been performed</param>
        private void DoPositioningOfControls(bool initialLayout)
        {
            //is this is the 1st time layout has been performed
            if (initialLayout)
            {
                //find the widest column widths based on the widest control in that column
                for (int c = 0; c < _colMaxWidths.Length; c++)
                {
                    _colMaxWidths[c] = GetWidthOfColumn(c);
                }
                //loop columns and set column widths
                for (int c = 0; c < _colMaxWidths.Length; c++)
                {
                    float colWidth = (float)_colMaxWidths[c] + (2 * _GenericSpace);
                    this.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, colWidth));
                }
                //find the highest row height based on the highest control in that row
                for (int r = 0; r < _rowMaxHeights.Length; r++)
                {
                    _rowMaxHeights[r] = GetHeightOfRow(r);
                }
                //loop rows and set row heights
                for (int r = 0; r < _rowMaxHeights.Length; r++)
                {
                    float rowHeight = (float)_rowMaxHeights[r] + (2 * _GenericSpace);
                    this.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, rowHeight));
                }
                //now add each of the DrawableClass contained in the _classesToDraw List
                //to the Controls collection of this inherited Panel
                //NOTE : We are trying to create a fluid grid type layout (different column widths etc)
                this.SuspendLayout();
                //now add the controls
                foreach (DrawableClass ucd in _classesToDraw)
                {
                    ucd.Location = new System.Drawing.Point(0, 0);
                    ucd.Dock = System.Windows.Forms.DockStyle.None;
                    ucd.Margin = new System.Windows.Forms.Padding(_GenericSpace);
                    this.Controls.Add(ucd, ucd.ContainerColumn, ucd.ContainerRow);
                }
                this.ResumeLayout();
            }
            //not initial layout, so find the highest row height based on the highest control in that row
            else
            {
                _rowMaxHeights[_CurrRow] = GetHeightOfRow(_CurrRow);
                this.RowStyles[_CurrRow].Height = _rowMaxHeights[_CurrRow] + (2 * _GenericSpace);

                //work out new MaxSize based on new Row/Column sizes
                int height = 0;
                for (int r = 0; r < _rowMaxHeights.Length; r++)
                {
                    height += (int)this.RowStyles[r].Height;
                }
                //keep width the same there should be no change for width
                int width = MaxSize.Width;
                MaxSize = new Size(width, height);
                this.Size = MaxSize;
            }
            //redraw
            this.Invalidate();
            Application.DoEvents();
        }

        /// <summary>
        /// returns a int which represents the height of the highest 
        /// <see cref="DrawableClass">class</see> with the same 
        /// ContainerRow as the row input parameter
        /// </summary>
        /// <param name="row">the row to get max height of</param>
        /// <returns>a int which represents the height of the highest 
        /// <see cref="DrawableClass">class</see> with the same 
        /// ContainerRow as the row input parameter</returns>
        private int GetHeightOfRow(int row)
        {
            int MaxHeight = 0;
            //loop through all DrawableClass which have the same row number 
            //and return the height of the highest control
            foreach (DrawableClass ucd in _classesToDraw)
            {
                if (ucd.ContainerRow == row)
                {
                    if (ucd.CurrentState == DrawableClass.States.Expanded)
                    {
                        if (ucd.FullExpandedHeight > MaxHeight)
                        {
                            MaxHeight = ucd.FullExpandedHeight;
                        }
                    }
                    else
                    {
                        if (ucd.Height > MaxHeight)
                        {
                            MaxHeight = ucd.Height;
                        }
                    }
                }
            }
            return MaxHeight;
        }

        /// <summary>
        /// returns a int which represents the width of the widest 
        /// <see cref="DrawableClass">class</see> with the same 
        /// ContainerColumn as the col input parameter
        /// </summary>
        /// <param name="row">the col to get max width of</param>
        /// <returns>a int which represents the width of the widest 
        /// <see cref="DrawableClass">class</see> with the same 
        /// ContainerColumn as the col input parameter</returns>
        private int GetWidthOfColumn(int col)
        {
            int MaxWidth = 0;
            //loop through all DrawableClass which have the same column number 
            //and return the width of the widest control
            foreach (DrawableClass ucd in _classesToDraw)
            {
                if (ucd.ContainerColumn == col)
                {
                    if (ucd.Width > MaxWidth)
                    {
                        MaxWidth = ucd.Width;
                    }
                }
            }
            return MaxWidth;
        }

        /// <summary>
        /// Deletes all Associations from the internal list of
        /// <see cref="DrawableClass">classes </see>, where the
        /// associated destination is not available with the 
        /// internal list. If its not there, no association line
        /// can be drawn. The user may have not selected the destination
        /// class as one to include on the diagram
        /// </summary>
        private void TrimAssociationsNotAvailable()
        {
            List<string> _AllowedAssociations = new List<string>();
            //loop through the classes we have available in the current
            //diagram
            foreach (DrawableClass ucdSrc in _classesToDraw)
            {
                //loop through its assocaitions
                foreach (string sAss in ucdSrc.Associations)
                {
                    //now check to see if this class is one of the ones that is present
                    //on the diagram, if so we have a valid association to draw
                    foreach (DrawableClass ucdDest in _classesToDraw)
                    {
                        //if the association is one that can be drawn on the current
                        //diagram, include it in the new list for the source DrawableClass
                        if (sAss.ToLower().Equals(ucdDest.Name.ToLower()))
                        {
                            _AllowedAssociations.Add(ucdDest.Name);
                        }
                    }
                }
                //no update the associations list for the source DrawableClass to be
                //the trimmed list of associations that are available
                ucdSrc.Associations = _AllowedAssociations;
                //create a new list for the next source DrawableClass to be checked
                _AllowedAssociations = new List<string>();
            }
        }


        /// <summary>
        /// event raised when one of the <see cref="DrawableClass">classes</see>
        /// changed state. This event is then used to determine of a row
        /// height should be changed when calling the internal DoPositioningOfControls()
        /// method
        /// </summary>
        /// <param name="sender">a <see cref="DrawableClass">class</see></param>
        /// <param name="e">the event args</param>
        private void ucd_SizeChanged(object sender, SizeEventArgs e)
        {

            this.SuspendLayout();
            //store current row for layout change
            _CurrRow = (sender as DrawableClass).ContainerRow;
            try
            {
                //now do the positioning of the controls based on the new 
                //row/col widths / heights
                DoPositioningOfControls(false);
            }
            catch (Exception)
            {

            }
            this.ResumeLayout();
        }
        #endregion
        #region Public Methods
        /// <summary>
        /// Lais out the contained <see cref="DrawableClass">classes </see> 
        /// in rows and columns (basically a grid) with spaxing between them
        /// </summary>
        public void LayoutControls()
        {
            this.Controls.Clear();
            //1st assign row and columns to all DrawableClass object so we can work
            //out which is the widest row / column so that the layout can be arrange
            //in a correctly sized grid
            int row=0;
            int col=0;
            //loop through all DrawableClass objects and assign them to the correct
            //row / column according to the _MAX_Columns value
            for (int i = 0; i < _classesToDraw.Count; i++)
            {
                _classesToDraw[i].ContainerRow = row;
                _classesToDraw[i].ContainerColumn = col;
                //should we begin a new row
                if (col == Program._MAX_Columns-1)
                {
                    col = 0;
                    row++;
                }
                else
                {
                    col++;
                }
            }
            //layout helper fields
            _colMaxWidths = new int[Program._MAX_Columns];
            _maxRows = _classesToDraw[_classesToDraw.Count - 1].ContainerRow;
            _rowMaxHeights = new int[_maxRows + 1];
            //now do the positioning of the controls based on row/col widths / heights
            try
            {
                DoPositioningOfControls(true);
                this.Refresh();
                if (!_LatchIn)
                {
                    _MaxSize = this.PreferredSize;
                    _LatchIn = true;
                }
            }
            catch (Exception)
            {
                //cant do much about this, just catch it
            }
        }

        /// <summary>
        /// gets /set the MaxSize for the entire control, to allow the saving
        /// of this object to done. As this control is docked so, we need
        /// to maintain the MaxSize to allow the <see cref="frmMain">main
        /// form </see> to create an image of the correct size to hold the
        /// entire control contents
        /// </summary>
        public Size MaxSize
        {
            get { return _MaxSize; }
            set { _MaxSize =value; }
        }

        /// <summary>
        /// Sets the internal list of <see cref="DrawableClass">classes</see>.
        /// And also calls the internal method TrimAssociationsNotAvailable()
        /// of the list provided is not null
        /// </summary>
        public List<DrawableClass> ClassesToDraw
        {
            set 
            {
                if (value != null)
                {
                    _classesToDraw = value;
                    foreach (DrawableClass ucd in _classesToDraw)
                    {
                        ucd.SizeChanged += new DrawableClass.SizeStateChangedEventHandler(ucd_SizeChanged);
                    }
                    //trim un-available associations. remember user may not have selected
                    //to view all classes from main form treeview, so an assocaition might
                    //not be able to be drawn
                    TrimAssociationsNotAvailable();
                }
                
            }
            get 
            {
                return _classesToDraw;
            }

        }
        #endregion
    }
    #endregion
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions