|
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.
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