Click here to Skip to main content
15,885,910 members
Articles / Programming Languages / C#

Table Size Selector Control

Rate me:
Please Sign up or sign in to vote.
4.76/5 (23 votes)
3 Feb 20039 min read 101K   1.9K   45  
Dropdown Control that mimics the Microsoft Word Table Selector dropdown.
// *****************************************************************************
// 
//  (c) Cabot Software, Inc. 2003 
//  All rights reserved. No warranty is intended or implied. If you use this
//  software in your application please give credit where credit is due. Just
//  a quick thank you in the about box is enough. You are not licensed to 
//  sell this software in it's either it's original state or based off your
//  modifications.
//
// *****************************************************************************
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Forms;

namespace CabotSoftware.Controls
{
	#region Helper Class
	[ToolboxItem(false)]
	public class TableSizeEventArgs : EventArgs
	{
		#region Member Variables
		Size newTableSize;
		#endregion

		#region Constructors
		public TableSizeEventArgs(Size newTableSize)
		{
			this.newTableSize = newTableSize;
		}
		#endregion

		#region Properties
		public Size NewTableSize
		{
			get { return this.newTableSize; }
		}
		#endregion
	}
	#endregion

	#region Delegates
	public delegate void TableSizeChangedEventHandler(object sender, TableSizeEventArgs e);
	public delegate void TableSizeSelectedOKEventHandler(object sender, TableSizeEventArgs e);
	public delegate void TableSizeSelectedCancelEventHandler(object sender, System.EventArgs e);
	#endregion

	public class TableSizeSelectorDropDown : System.Windows.Forms.Form
	{
		#region Event Declarations
		public event TableSizeChangedEventHandler TableSizeChanged;
		public event TableSizeSelectedOKEventHandler TableSizeSelectedOK;
		public event TableSizeSelectedCancelEventHandler TableSizeSelectedCancel;
		#endregion

		#region Event Handlers
		protected virtual void OnTableSizeChanged()
		{
			if( TableSizeChanged != null )
				TableSizeChanged(this, new TableSizeEventArgs(this.tableSize));
		}
		
		protected virtual void OnTableSizeSelectedOK()
		{
			if( TableSizeSelectedOK != null )
				TableSizeSelectedOK(this, new TableSizeEventArgs(this.tableSize));
		}
		
		protected virtual void OnTableSizeSelectedCancel()
		{
			if( TableSizeSelectedCancel != null )
				TableSizeSelectedCancel(this, new System.EventArgs());
		}
		#endregion

		#region Member Variables
		// Internal variables.
		private bool DrawLeftToRight       = true;
		private bool DrawTopToBottom       = true;
		private bool SelectingByKeyboard   = false;
		private int  TextDisplayHeight     = 0;
		private Point StartLocation = new Point(0,0);
		private Size  maxDisplaySize       = new Size(-1, -1);
		private Rectangle ParentRectangle;

		// Exposed property values.
		private int   cellSpacing       = 4;
		private int   displayCellSize   = 20;
		private Size  displaySize       = new Size(4,4);
		private Size  tableSize         = new Size(0,0);
		private Size  maxTableSize      = new Size(0,0);
		private Color selectedRectColor = SystemColors.Highlight;		
		private Color textColor         = SystemColors.WindowText;
		private Color outlineRectColor  = SystemColors.WindowText;   

		private System.ComponentModel.Container components = null;
		#endregion

		#region Constructors
		public TableSizeSelectorDropDown(Control ParentControl)
		{
			ParentRectangle = ParentControl.RectangleToScreen(ParentControl.ClientRectangle);

			InitializeComponent();

			// Set appropriate styles to avoid flickering.
			SetStyle(ControlStyles.ResizeRedraw, true);
			SetStyle(ControlStyles.UserPaint, true);
			SetStyle(ControlStyles.AllPaintingInWmPaint, true);
			SetStyle(ControlStyles.DoubleBuffer, true);
			
		}
		#endregion

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			// 
			// TableSizeSelectorDropDown
			// 
			this.AutoScale = false;
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.BackColor = System.Drawing.SystemColors.Window;
			this.ClientSize = new System.Drawing.Size(50, 47);
			this.ForeColor = System.Drawing.SystemColors.Control;
			this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
			this.MaximizeBox = false;
			this.MinimizeBox = false;
			this.Name = "TableSizeSelectorDropDown";
			this.ShowInTaskbar = false;
			this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
			this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
		}
		#endregion
		
		#region Form Override methods

		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}
		
		protected override void OnActivated(EventArgs e)
		{
			UpdateSizingInfo();
			base.OnActivated(e);
		}
		protected override void OnDeactivate(EventArgs e)
		{
			Close();
			
			if(this.DialogResult == DialogResult.OK )
				OnTableSizeSelectedOK();
			else
			{
				if( this.DialogResult == DialogResult.None )
					this.DialogResult = DialogResult.Cancel;

				OnTableSizeSelectedCancel();
			}
			
			base.OnDeactivate(e);
		}

		protected override void OnMouseLeave(System.EventArgs e)
		{	
			// Once we've started sizing our table by the keyboard, we ignore the mouse move events.
			if( SelectingByKeyboard == false )
				UpdateTableSize(new Size(0,0));
		}
		
		protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e)
		{
			// Once we've started sizing our table by the keyboard, we can ignore the mouse move event.
			if( SelectingByKeyboard == false )
			{				
				// Is it time to resize our form? (We do this if we moved 1 pixel past the
				// edge of the last rectangles.
				Size newDisplaySize = displaySize;
				if( e.Button == MouseButtons.Left || e.Button == MouseButtons.Right )
				{
					if( DrawLeftToRight )
					{
						if(e.X >= (this.displaySize.Width * (DisplayCellSize+CellSpacing)))
							newDisplaySize.Width++;
					}
					else if( e.X <= CellSpacing )
					{
						newDisplaySize.Width++;
					}

					if( DrawTopToBottom )
					{
						if(e.Y >= (this.displaySize.Height * (DisplayCellSize+CellSpacing)))
							newDisplaySize.Height++;
					}
					else if( (OverCancelRect(e.Y) == false ) && e.Y <= (TextDisplayHeight + CellSpacing) )
						newDisplaySize.Height++;
				}

				System.Drawing.Size newTableSize = new System.Drawing.Size (0,0);

				if( OverCancelRect( e.Y ) == false )
				{					
					// Determine the 'size' of our selected region.
					if( DrawLeftToRight == true )
					{
						newTableSize.Width = e.X / (CellSpacing + DisplayCellSize) +
							((((e.X % (CellSpacing + DisplayCellSize))-CellSpacing)>0) ? 1 : 0);
					}
					else
					{
						newTableSize.Width = (ClientRectangle.Width - e.X) / (CellSpacing + DisplayCellSize) +
							(((((ClientRectangle.Width - e.X ) % (CellSpacing + DisplayCellSize)) - CellSpacing)>0) ? 1 : 0);
					}

					if( DrawTopToBottom == true )
					{
						newTableSize.Height = e.Y / (CellSpacing + DisplayCellSize) +
							((((e.Y % (CellSpacing + DisplayCellSize))-CellSpacing)>0) ? 1 : 0);
					}
					else
					{
						int DivResult = (ClientRectangle.Bottom - e.Y ) / (CellSpacing + DisplayCellSize);
						int Remainder = (((((ClientRectangle.Bottom - e.Y ) % (CellSpacing + DisplayCellSize)))>0) ? 1 : 0);
						newTableSize.Height = DivResult + Remainder;
					}
				}

				UpdateDisplayAndSelectedSize(newDisplaySize, newTableSize);
			}
		}

		protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
		{	
			base.OnMouseUp(e);

			// Clicked in the 'text' area of the control?
			if( OverCancelRect(e.Y) )
			{
				this.DialogResult = (TableSize.Height == 0 || TableSize.Width == 0) ?
					DialogResult.Cancel : DialogResult.OK;

				Close();
			}
			else if( e.Button != MouseButtons.Right )
			{
				this.DialogResult = DialogResult.OK;
				Close();
			}
		}

		protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			// Create the arrays to display!
			if( displaySize.Width > 0 && displaySize.Height> 0 )
			{
				System.Drawing.Pen pen = new System.Drawing.Pen(OutlineRectColor);
				System.Drawing.SolidBrush brush = new System.Drawing.SolidBrush(SelectedRectColor);
				System.Drawing.SolidBrush brushText = new System.Drawing.SolidBrush(this.textColor);
				
				System.Drawing.Graphics g = e.Graphics;

				//create our offscreen bitmap
				Bitmap localBitmap = new Bitmap(ClientRectangle.Width,
					ClientRectangle.Height);
				Graphics bitmapGraphics = Graphics.FromImage(localBitmap);
				bitmapGraphics.Clear(this.BackColor);

				// Draw a rect around the outer edge of the control.
				Rectangle rect = new Rectangle(
					ClientRectangle.X, 
					ClientRectangle.Y, 
					ClientRectangle.Width-1,
					ClientRectangle.Height-1);

				bitmapGraphics.DrawRectangle(pen, rect);

				rect.X = 0;
				rect.Y = 0;
				rect.Height = rect.Width = DisplayCellSize;
				RectangleToClient(rect);				

				// Drawing from left to right?
				int MinXPos = DrawLeftToRight ? 0 : (displaySize.Width - TableSize.Width);
				int MaxXPos = DrawLeftToRight ? TableSize.Width : displaySize.Width;
				Debug.WriteLine(string.Format("MinXPos: {0:d} MaxXPos: {1:d}", MinXPos, MaxXPos));

				// Drawing from top to bottom?
				int MinYPos = DrawTopToBottom ? 0 : (displaySize.Height - TableSize.Height);
				int MaxYPos = DrawTopToBottom ? TableSize.Height : displaySize.Height;
				Debug.WriteLine(string.Format("MinYPos: {0:d} MaxYPos: {1:d}", MinYPos, MaxYPos));

				// Determine the initial point!
				rect.X = CellSpacing;
				rect.Y = CellSpacing + ((DrawTopToBottom) ? 0 : this.TextDisplayHeight);
				
				for( int RowIdx = 0; RowIdx < DisplaySize.Height; RowIdx++ )
				{
					for( int ColIdx = 0; ColIdx < displaySize.Width; ColIdx++ )
					{
						// Fill in the rect?
						if( (RowIdx >= MinYPos && RowIdx < MaxYPos) 
							&& ColIdx >= MinXPos && ColIdx < MaxXPos )
						{
							bitmapGraphics.FillRectangle(brush,rect);
						}

						// Outline the rectangle.
						bitmapGraphics.DrawRectangle(pen,rect);

						
						// Calculate next rect to draw.
						rect.Offset(DisplayCellSize + CellSpacing, 0);
					}			

					// Move rect back to leftmost starting position.
					rect.X = CellSpacing;
					rect.Y += (CellSpacing + DisplayCellSize);// * RowIdx + CellSpacing + TextOffset;
				}

				rect.X = 0;
				rect.Width  = ClientRectangle.Width;
				if( DrawTopToBottom )
				{
					rect.Y = (CellSpacing + DisplayCellSize) * displaySize.Height + CellSpacing;
					rect.Height = this.TextDisplayHeight;
					bitmapGraphics.DrawLine(pen, 0, rect.Top, rect.Width, rect.Top);
				
					rect.Y++;
				}
				else
				{
					rect.Y = 0;
					rect.Height = this.TextDisplayHeight;
					bitmapGraphics.DrawLine(pen,0, TextDisplayHeight, ClientRectangle.Width, TextDisplayHeight);
				}

				// output the text
				StringFormat fmt = new StringFormat();
				fmt.LineAlignment = StringAlignment.Center;
				fmt.Alignment = StringAlignment.Center;
				bitmapGraphics.DrawString(GetTablePropText(), Font, brushText, rect, fmt );

				//push our bitmap forward to the screen
				g.DrawImage(localBitmap, 0, 0);

				bitmapGraphics.Dispose();
				pen.Dispose();
				brush.Dispose();
				brushText.Dispose();
				localBitmap.Dispose();
			}
		}
		
		protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
		{
			System.Drawing.Size newSelectedSize = TableSize;
			System.Drawing.Size newDisplaySize  = displaySize;

			switch(keyData)
			{
				case Keys.Up:
				case Keys.Down:
				{
					bool Expanding = ((keyData == Keys.Down && DrawTopToBottom) || (keyData == Keys.Up && (DrawTopToBottom == false))) 
						? true : false;
					if( Expanding )
					{
						if( newSelectedSize.Height == newDisplaySize.Height )
						{
							newDisplaySize.Height = newDisplaySize.Height + 1;
							newSelectedSize.Height = newSelectedSize.Height+1;
							if( newSelectedSize.Width == 0 )
								newSelectedSize.Width = 1;
							UpdateDisplayAndSelectedSize(newDisplaySize, newSelectedSize);
						}
						else
						{
							newSelectedSize.Height = newSelectedSize.Height+1;
							if( newSelectedSize.Width == 0 )
								newSelectedSize.Width = 1;

							UpdateTableSize(newSelectedSize);
						}
					}
					else if( newSelectedSize.Height > 0 )
					{
						newSelectedSize.Height = newSelectedSize.Height-1;
						if( newSelectedSize.Height == 0 )
							newSelectedSize.Width = 0;

						UpdateTableSize(newSelectedSize);
					}

					SelectingByKeyboard = true;
				}
					break;
				case Keys.Right:
				case Keys.Left:
				{
					bool Expanding = ((keyData == Keys.Right && DrawLeftToRight) || (keyData == Keys.Left && (DrawLeftToRight == false))) 
						? true : false;
					if( Expanding )
					{
						if( newSelectedSize.Width == newDisplaySize.Width )
						{
							newDisplaySize.Width = newDisplaySize.Width + 1;
						}
						newSelectedSize.Width = newSelectedSize.Width+1;
						
						if( newSelectedSize.Height == 0 )
							newSelectedSize.Height = 1;
											
						UpdateDisplayAndSelectedSize(newDisplaySize,newSelectedSize);
					}
					else if( newSelectedSize.Width > 0 )
					{
						newSelectedSize.Width = newSelectedSize.Width - 1;
						
						// Time to zero out our selected size?
						if( newSelectedSize.Width == 0 )
							newSelectedSize.Height = 0;

						UpdateTableSize(newSelectedSize);
					}
					SelectingByKeyboard = true;
				}
					break;
				case Keys.Enter:
					this.DialogResult = (TableSize.Height == 0 || TableSize.Width == 0) ?
						DialogResult.Cancel : DialogResult.OK;
					Close();
					break;
				case Keys.Escape:
					this.DialogResult = DialogResult.Cancel;
					Close();
					break;
				default:
					break;
			}
			return base.ProcessCmdKey(ref msg,keyData);
		}
		
		protected override void SetBoundsCore(int x, int y, int w, int h, BoundsSpecified bs)
		{
			base.SetBoundsCore(
				x,
				y,
				CalcWidth(),
				CalcHeight(),
				bs);
		}

		#endregion

		#region Properties
		public int CellSpacing
		{
			get { return this.cellSpacing; }
			set { this.cellSpacing = value; }
		}
		
		public int DisplayCellSize
		{
			get { return this.displayCellSize; }
			set { this.displayCellSize = value; }
		}

		public Size DisplaySize
		{
			get { return this.displaySize; }
			set { this.displaySize = value; }
		}

		public System.Drawing.Size TableSize
		{
			get { return this.tableSize; }
		}

		public System.Drawing.Size MaxTableSize
		{
			get { return this.maxTableSize; }
			set { this.maxTableSize = value; }
		}

		public System.Drawing.Color SelectedRectColor
		{
			get { return this.selectedRectColor; }
			set { this.selectedRectColor = value; }
		}

		public System.Drawing.Color TextColor
		{
			get { return this.textColor; }
			set { this.textColor = value; }
		}

		public System.Drawing.Color OutlineRectColor
		{
			get { return this.outlineRectColor; }
			set { this.outlineRectColor = value; }
		}

		#endregion

		#region Implementation
		private void CalcMaxSize()
		{
			Screen DisplayOnScreen = Screen.FromPoint(new Point(ParentRectangle.X, ParentRectangle.Bottom));
			Rectangle rect = RectangleToScreen(ClientRectangle);

			// Get the total width we have available.
			int TotalWidthAvailable = DrawLeftToRight ? 
				Math.Abs(DisplayOnScreen.WorkingArea.Width - rect.Left)
				: Math.Abs(rect.Left + rect.Width - DisplayOnScreen.WorkingArea.X);
			this.maxDisplaySize.Width = (TotalWidthAvailable - CellSpacing) / (DisplayCellSize + CellSpacing );

			int TotalHeightAvailable = DrawTopToBottom ?
				Math.Abs(DisplayOnScreen.WorkingArea.Height - rect.Top)
				: Math.Abs(rect.Bottom - DisplayOnScreen.WorkingArea.Y );
			this.maxDisplaySize.Height = (TotalHeightAvailable - TextDisplayHeight) / (DisplayCellSize + CellSpacing);

			// Adjust if display size is greater than table size and max table size has been set.
			if( maxTableSize.IsEmpty == false )
			{
				if( maxDisplaySize.Width > this.maxTableSize.Width )
					maxDisplaySize.Width = this.MaxTableSize.Width;
				if( maxDisplaySize.Height > this.maxTableSize.Height )
					maxDisplaySize.Height = this.MaxTableSize.Height;
			}
		}

		private bool CheckDisplaySizeIs(Size sizeToCheck)
		{
			return (sizeToCheck.Width <= this.maxDisplaySize.Width
				&& sizeToCheck.Height <= this.maxDisplaySize.Height );
		}

		private int CalcHeight()
		{
			return ((CellSpacing + DisplayCellSize) * displaySize.Height) + CellSpacing + TextDisplayHeight;
		}
		private void CalcLocation()
		{
			// Determine which screen we're on and how big it is.
			Screen DisplayedOnScreen = Screen.FromPoint(new Point(ParentRectangle.X, ParentRectangle.Bottom));
			int MinScreenXPos = DisplayedOnScreen.Bounds.X;
			int MinScreenYPos = DisplayedOnScreen.Bounds.Y;
			int MaxScreenXPos = DisplayedOnScreen.Bounds.X + DisplayedOnScreen.Bounds.Width;
			int MaxScreenYPos = DisplayedOnScreen.Bounds.Y + DisplayedOnScreen.Bounds.Height;

			int DropdownWidth  = CalcWidth();
			int DropdownHeight = CalcHeight();

			// Will we bump into the right edge of the window when we first display the control?
			if((ParentRectangle.X + DropdownWidth) <= MaxScreenXPos )
			{
				if( ParentRectangle.X < MinScreenXPos )
					StartLocation.X = MinScreenXPos;
				else
					StartLocation.X = ParentRectangle.X;
			}
			else
			{
				DrawLeftToRight = false;

				// Make sure we aren't overhanging the left side of the screen.
				if( Screen.FromPoint(new Point((ParentRectangle.X + ParentRectangle.Width), 0)) == DisplayedOnScreen )
					StartLocation.X = ParentRectangle.Right-DropdownWidth;
				else
					StartLocation.X = MaxScreenXPos - DropdownWidth;
			}

			// And now check the bottom of the screen.
			if( (ParentRectangle.Bottom + DropdownHeight) <= MaxScreenYPos )
				StartLocation.Y = ParentRectangle.Bottom;
			else
			{
				DrawTopToBottom = false;
				StartLocation.Y = ParentRectangle.Y-DropdownHeight;
			}

			this.Location = StartLocation;
		}

		private int CalcWidth()
		{
			return ((CellSpacing + DisplayCellSize) * displaySize.Width) + CellSpacing + 1;
		}
		private string GetTablePropText()
		{
			string TablePropText = "Cancel";
			
			Point pt = System.Windows.Forms.Cursor.Position;
			pt = this.PointToClient(pt);

			
			if( (SelectingByKeyboard == true ||
				( pt.X >= this.ClientRectangle.Left && pt.X <= this.ClientRectangle.Right && OverCancelRect(pt.Y) == false))
				&& (TableSize.Width > 0 && TableSize.Height > 0) )
			{
				TablePropText = string.Format("{0:d} x {1:d} Table", TableSize.Height, TableSize.Width);
			}
			return TablePropText;
		}

		private bool OverCancelRect( int MousePosY )
		{
			return DrawTopToBottom ?
				MousePosY >= ((CellSpacing + DisplayCellSize) * displaySize.Height + CellSpacing + 1) 
				: MousePosY <= TextDisplayHeight;
		}

		private void UpdateDisplayAndSelectedSize(Size newDisplaySize, Size newTableSize)
		{
			bool Redraw = false;
			if( CheckDisplaySizeIs(newDisplaySize) )
			{
				Redraw = true;
				this.displaySize = newDisplaySize;
				this.Size = new Size( CalcWidth(), CalcHeight());

				if( DrawLeftToRight == false || DrawTopToBottom == false )
				{
					this.CalcLocation();
				}
			}

			if( newTableSize != TableSize 
				&& newTableSize.Width <= displaySize.Width 
				&& newTableSize.Height <= displaySize.Height )
			{
				Redraw = true;
				this.tableSize = newTableSize;
				OnTableSizeChanged();
			}

			if( Redraw )
				Invalidate();
		}

		private void UpdateSizingInfo()
		{
			// Calculate space to write out the text.
			TextDisplayHeight = (int)Font.GetHeight() + CellSpacing * 2;

			CalcLocation();
			CalcMaxSize();
		}

		private void UpdateTableSize(System.Drawing.Size newTableSize)
		{
			// Verify that table size is different and that we
			// haven't extended past our display boundaries.
			if( newTableSize != TableSize 
				&& newTableSize.Width <= displaySize.Width 
				&& newTableSize.Height <= displaySize.Height )
			{
				System.Diagnostics.Debug.WriteLine(string.Format("TableSize updated from:\n\t{0:d}\nto:\t {1:s}", 
					TableSize.ToString(), newTableSize.ToString() ));

				this.tableSize = newTableSize;
				OnTableSizeChanged();

				Invalidate();
			}
		}

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


Written By
Web Developer
United States United States
I've been writing code for over eleven years now. I started out writing C on a VAX system. I've been writing code for Microsoft Windows for nine years. I've coded everything from online streaming solutions to front end applications for the travel industry. My main focus has been on C++, ATL and COM but I've recently been doing quite a bit of work with .NET and C#.

I'm married with two kids and have a golden retriever named Rufus.

Comments and Discussions