Click here to Skip to main content
15,892,161 members
Articles / Multimedia / GDI

XPTable - .NET ListView meets Java's JTable

Rate me:
Please Sign up or sign in to vote.
4.97/5 (445 votes)
17 Sep 200512 min read 6.8M   55.3K   888  
A fully customisable ListView style control based on Java's JTable.
/*
 * Copyright � 2005, Mathew Hall
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 *    - Redistributions of source code must retain the above copyright notice, 
 *      this list of conditions and the following disclaimer.
 * 
 *    - Redistributions in binary form must reproduce the above copyright notice, 
 *      this list of conditions and the following disclaimer in the documentation 
 *      and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 */


using System;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;

using XPTable.Events;
using XPTable.Models;
using XPTable.Themes;


namespace XPTable.Renderers
{
	/// <summary>
	/// A CellRenderer that draws Cell contents as Buttons
	/// </summary>
	public class ButtonCellRenderer : CellRenderer
	{
		#region Class Data

		/// <summary>
		/// Specifies the alignment of the Image displayed on the button
		/// </summary>
		private ContentAlignment imageAlignment;

		#endregion
		
		
		#region Constructor
		
		/// <summary>
		/// Initializes a new instance of the ButtonCellRenderer class with 
		/// default settings
		/// </summary>
		public ButtonCellRenderer() : base()
		{
			this.imageAlignment = ContentAlignment.MiddleCenter;
		}

		#endregion


		#region Methods

		/// <summary>
		/// Gets the ButtonCellRenderer specific data used by the Renderer from 
		/// the specified Cell
		/// </summary>
		/// <param name="cell">The Cell to get the ButtonCellRenderer data for</param>
		/// <returns>The ButtonCellRenderer data for the specified Cell</returns>
		protected ButtonRendererData GetButtonRendererData(Cell cell)
		{
			object rendererData = this.GetRendererData(cell);

			if (rendererData == null || !(rendererData is ButtonRendererData))
			{
				rendererData = new ButtonRendererData();

				this.SetRendererData(cell, rendererData);
			}

			return (ButtonRendererData) rendererData;
		}


		/// <summary>
		/// Returns a Rectangle that specifies the size and location of the button
		/// </summary>
		/// <returns>A Rectangle that specifies the size and location of the button</returns>
		protected virtual Rectangle CalcButtonBounds()
		{
			return this.ClientRectangle;
		}


		/// <summary>
		/// Returns a Rectangle that specifies the size and location of the buttons Image
		/// </summary>
		/// <param name="image">The buttons image</param>
		/// <param name="imageAlignment">The alignment of the image</param>
		/// <returns>A Rectangle that specifies the size and location of the buttons Image</returns>
		protected Rectangle CalcImageRect(Image image, ContentAlignment imageAlignment)
		{
			Rectangle imageRect = new Rectangle(this.ClientRectangle.X, this.ClientRectangle.Y, image.Width, image.Height);

			switch (imageAlignment)
			{
				case ContentAlignment.TopCenter:
				case ContentAlignment.MiddleCenter:
				case ContentAlignment.BottomCenter:
				{
					imageRect.X += (this.ClientRectangle.Width - image.Width) / 2;

					break;
				}

				case ContentAlignment.TopRight:
				case ContentAlignment.MiddleRight:
				case ContentAlignment.BottomRight:
				{
					imageRect.X = this.ClientRectangle.Right - image.Width;

					break;
				}
			}

			switch (imageAlignment)
			{
				case ContentAlignment.TopLeft:
				case ContentAlignment.TopCenter:
				case ContentAlignment.TopRight:
				{
					imageRect.Y += 2;

					break;
				}
				
				case ContentAlignment.MiddleLeft:
				case ContentAlignment.MiddleCenter:
				case ContentAlignment.MiddleRight:
				{
					imageRect.Y += (this.ClientRectangle.Height - image.Height) / 2;

					break;
				}

				case ContentAlignment.BottomLeft:
				case ContentAlignment.BottomCenter:
				case ContentAlignment.BottomRight:
				{
					imageRect.Y = this.ClientRectangle.Bottom - image.Height - 2;

					break;
				}
			}

			return imageRect;
		}

		#endregion


		#region Properties

		/// <summary>
		/// Gets or sets the alignment of the Image displayed on the buttons
		/// </summary>
		public ContentAlignment ImageAlignment
		{
			get
			{
				return this.imageAlignment;
			}

			set
			{
				this.imageAlignment = value;
			}
		}

		#endregion


		#region Events

		#region Focus

		/// <summary>
		/// Raises the GotFocus event
		/// </summary>
		/// <param name="e">A CellFocusEventArgs that contains the event data</param>
		public override void OnGotFocus(CellFocusEventArgs e)
		{
			base.OnGotFocus(e);

			// get the table to redraw the cell
			e.Table.Invalidate(e.CellRect);
		}


		/// <summary>
		/// Raises the LostFocus event
		/// </summary>
		/// <param name="e">A CellFocusEventArgs that contains the event data</param>
		public override void OnLostFocus(CellFocusEventArgs e)
		{
			base.OnLostFocus(e);

			// get the table to redraw the cell
			e.Table.Invalidate(e.CellRect);
		}

		#endregion

		#region Keys

		/// <summary>
		/// Raises the KeyDown event
		/// </summary>
		/// <param name="e">A CellKeyEventArgs that contains the event data</param>
		public override void OnKeyDown(CellKeyEventArgs e)
		{
			base.OnKeyDown(e);

			// get the button renderer data
			ButtonRendererData rendererData = this.GetButtonRendererData(e.Cell);

			// 
			if (e.KeyData == Keys.Enter || e.KeyData == Keys.Space)
			{
				rendererData.ButtonState = PushButtonStates.Pressed;

				e.Table.Invalidate(e.CellRect);
			}
		}


		/// <summary>
		/// Raises the KeyUp event
		/// </summary>
		/// <param name="e">A CellKeyEventArgs that contains the event data</param>
		public override void OnKeyUp(CellKeyEventArgs e)
		{
			base.OnKeyUp(e);

			// get the button renderer data
			ButtonRendererData rendererData = this.GetButtonRendererData(e.Cell);

			// 
			if (e.KeyData == Keys.Enter || e.KeyData == Keys.Space)
			{
				rendererData.ButtonState = PushButtonStates.Normal;

				e.Table.Invalidate(e.CellRect);
				e.Table.OnCellButtonClicked(new CellButtonEventArgs(e.Cell, e.Column, e.Row));
			}
		}

		#endregion

		#region Mouse

		#region MouseEnter

		/// <summary>
		/// Raises the MouseEnter event
		/// </summary>
		/// <param name="e">A CellMouseEventArgs that contains the event data</param>
		public override void OnMouseEnter(CellMouseEventArgs e)
		{
			base.OnMouseEnter(e);

			// get the button renderer data
			ButtonRendererData rendererData = this.GetButtonRendererData(e.Cell);

			// if the mouse is inside the button, make sure it is "hot"
			if (this.CalcButtonBounds().Contains(e.X, e.Y))
			{
				if (rendererData.ButtonState != PushButtonStates.Hot)
				{
					rendererData.ButtonState = PushButtonStates.Hot;

					e.Table.Invalidate(e.CellRect);
				}
			}
				// the mouse isn't inside the button, so it is in its normal state
			else
			{
				if (rendererData.ButtonState != PushButtonStates.Normal)
				{
					rendererData.ButtonState = PushButtonStates.Normal;

					e.Table.Invalidate(e.CellRect);
				}
			}
		}

		#endregion

		#region MouseLeave

		/// <summary>
		/// Raises the MouseLeave event
		/// </summary>
		/// <param name="e">A CellMouseEventArgs that contains the event data</param>
		public override void OnMouseLeave(CellMouseEventArgs e)
		{
			base.OnMouseLeave(e);

			// get the button renderer data
			ButtonRendererData rendererData = this.GetButtonRendererData(e.Cell);

			// make sure the button is in its normal state
			if (rendererData.ButtonState != PushButtonStates.Normal)
			{
				rendererData.ButtonState = PushButtonStates.Normal;

				e.Table.Invalidate(e.CellRect);
			}
		}

		#endregion

		#region MouseUp

		/// <summary>
		/// Raises the MouseUp event
		/// </summary>
		/// <param name="e">A CellMouseEventArgs that contains the event data</param>
		public override void OnMouseUp(CellMouseEventArgs e)
		{
			base.OnMouseUp(e);

			// get the button renderer data
			ButtonRendererData rendererData = this.GetButtonRendererData(e.Cell);

			// check for the left mouse button
			if (e.Button == MouseButtons.Left)
			{
				Rectangle buttonRect = this.CalcButtonBounds();
				
				// if the mouse pointer is over the button, make sure 
				// the button is "hot"
				if (buttonRect.Contains(e.X, e.Y))
				{
					rendererData.ButtonState = PushButtonStates.Hot;

					e.Table.Invalidate(e.CellRect);

					// check if the click started inside the button.  if 
					// it did, Raise the tables CellButtonClicked event
					if (buttonRect.Contains(rendererData.ClickPoint))
					{
						e.Table.OnCellButtonClicked(new CellButtonEventArgs(e.Cell, e.Column, e.Row));
					}
				}
				else
				{
					// the mouse was released somewhere outside of the button, 
					// so make set the button back to its normal state
					if (rendererData.ButtonState != PushButtonStates.Normal)
					{
						rendererData.ButtonState = PushButtonStates.Normal;

						e.Table.Invalidate(e.CellRect);
					}
				}
			}

			// reset the click point
			rendererData.ClickPoint = Point.Empty;
		}

		#endregion

		#region MouseDown

		/// <summary>
		/// Raises the MouseDown event
		/// </summary>
		/// <param name="e">A CellMouseEventArgs that contains the event data</param>
		public override void OnMouseDown(CellMouseEventArgs e)
		{
			base.OnMouseDown(e);

			// get the button renderer data
			ButtonRendererData rendererData = this.GetButtonRendererData(e.Cell);

			// check if the left mouse button is pressed
			if (e.Button == MouseButtons.Left)
			{
				// record where the click started
				rendererData.ClickPoint = new Point(e.X, e.Y);
				
				// if the click was inside the button, set the button state to pressed
				if (this.CalcButtonBounds().Contains(rendererData.ClickPoint))
				{
					rendererData.ButtonState = PushButtonStates.Pressed;

					e.Table.Invalidate(e.CellRect);
				}
			}
		}

		#endregion

		#region MouseMove

		/// <summary>
		/// Raises the MouseMove event
		/// </summary>
		/// <param name="e">A CellMouseEventArgs that contains the event data</param>
		public override void OnMouseMove(CellMouseEventArgs e)
		{
			base.OnMouseMove(e);

			// get the button renderer data
			ButtonRendererData rendererData = this.GetButtonRendererData(e.Cell);

			Rectangle buttonRect = this.CalcButtonBounds();
			
			// check if the left mouse button is pressed
			if (e.Button == MouseButtons.Left)
			{
				// check if the mouse press originated in the button area
				if (buttonRect.Contains(rendererData.ClickPoint))
				{
					// check if the mouse is currently in the button
					if (buttonRect.Contains(e.X, e.Y))
					{
						// make sure the button is pressed
						if (rendererData.ButtonState != PushButtonStates.Pressed)
						{
							rendererData.ButtonState = PushButtonStates.Pressed;

							e.Table.Invalidate(e.CellRect);
						}
					}
					else
					{
						// the mouse isn't inside the button so make sure it is "hot"
						if (rendererData.ButtonState != PushButtonStates.Hot)
						{
							rendererData.ButtonState = PushButtonStates.Hot;

							e.Table.Invalidate(e.CellRect);
						}
					}
				}
			}
			else
			{
				// check if the mouse is currently in the button
				if (buttonRect.Contains(e.X, e.Y))
				{
					// the mouse is inside the button so make sure it is "hot"
					if (rendererData.ButtonState != PushButtonStates.Hot)
					{
						rendererData.ButtonState = PushButtonStates.Hot;

						e.Table.Invalidate(e.CellRect);
					}
				}
				else
				{
					// not inside the button so make sure it is in its normal state
					if (rendererData.ButtonState != PushButtonStates.Normal)
					{
						rendererData.ButtonState = PushButtonStates.Normal;

						e.Table.Invalidate(e.CellRect);
					}
				}
			}
		}

		#endregion

		#endregion

		#region Paint

		/// <summary>
		/// Raises the PaintCell event
		/// </summary>
		/// <param name="e">A PaintCellEventArgs that contains the event data</param>
		public override void OnPaintCell(PaintCellEventArgs e)
		{
			if (e.Table.ColumnModel.Columns[e.Column] is ButtonColumn)
			{
				this.ImageAlignment = ((ButtonColumn) e.Table.ColumnModel.Columns[e.Column]).ImageAlignment;
			}
			else
			{
				this.ImageAlignment = ContentAlignment.MiddleLeft;
			}
			
			base.OnPaintCell(e);
		}


		/// <summary>
		/// Raises the PaintBackground event
		/// </summary>
		/// <param name="e">A PaintCellEventArgs that contains the event data</param>
		protected override void OnPaintBackground(PaintCellEventArgs e)
		{
			base.OnPaintBackground(e);

			// don't bother going any further if the Cell is null 
			if (e.Cell == null)
			{
				return;
			}

			// get the button state
			ButtonRendererData rendererData = this.GetButtonRendererData(e.Cell);
			PushButtonStates state = rendererData.ButtonState;

			// if the cell has focus and is in its normal state, 
			// make the button look like a default button
			if (state == PushButtonStates.Normal && e.Focused)
			{
				state = PushButtonStates.Default;
			}

			// if the table is not enabled, make sure the button is disabled
			if (!e.Enabled)
			{
				state = PushButtonStates.Disabled;
			}

			// draw the button
			ThemeManager.DrawButton(e.Graphics, this.CalcButtonBounds(), state);
		}


		/// <summary>
		/// Raises the Paint event
		/// </summary>
		/// <param name="e">A PaintCellEventArgs that contains the event data</param>
		protected override void OnPaint(PaintCellEventArgs e)
		{
			base.OnPaint(e);

			// don't bother going any further if the Cell is null 
			if (e.Cell == null)
			{
				return;
			}

			Rectangle textRect = this.CalcButtonBounds();
			textRect.Inflate(-4, -2);

			if (e.Cell.Image != null)
			{
				Rectangle imageRect = this.CalcImageRect(e.Cell.Image, this.ImageAlignment);

				if (this.GetButtonRendererData(e.Cell).ButtonState == PushButtonStates.Pressed && !ThemeManager.VisualStylesEnabled)
				{
					imageRect.X += 1;
					imageRect.Y += 1;
				}
				
				this.DrawImage(e.Graphics, e.Cell.Image, imageRect, e.Enabled);
			}

			// draw the text
			if (e.Cell.Text != null && e.Cell.Text.Length != 0)
			{
				if (e.Enabled)
				{
					if (!ThemeManager.VisualStylesEnabled && this.GetButtonRendererData(e.Cell).ButtonState == PushButtonStates.Pressed)
					{
						textRect.X += 1;
						textRect.Y += 1;
					}

					// if the cell or the row it is in is selected 
					// our forecolor will be the selection forecolor.
					// we'll ignore this and reset our forecolor to 
					// that of the cell being rendered
					if (e.Selected)
					{
						this.ForeColor = e.Cell.ForeColor;
					}
					
					e.Graphics.DrawString(e.Cell.Text, this.Font, this.ForeBrush, textRect, this.StringFormat);
				}
				else
				{
					e.Graphics.DrawString(e.Cell.Text, this.Font, this.GrayTextBrush, textRect, this.StringFormat);
				}
			}

			// draw focus
			if (e.Focused && e.Enabled)
			{
				Rectangle focusRect = this.CalcButtonBounds();
				
				if (ThemeManager.VisualStylesEnabled)
				{
					focusRect.Inflate(-3, -3);

					if (this.GetButtonRendererData(e.Cell).ButtonState != PushButtonStates.Pressed)
					{
						ControlPaint.DrawFocusRectangle(e.Graphics, focusRect);
					}
				}
				else
				{
					focusRect.Inflate(-4, -4);

					ControlPaint.DrawFocusRectangle(e.Graphics, focusRect);
				}
			}
		}
		
		
		/// <summary>
		/// Draws the Image displayed on the button
		/// </summary>
		/// <param name="g">The Graphics to draw on</param>
		/// <param name="image">The Image to draw</param>
		/// <param name="imageRect">A Rectangle that specifies the location 
		/// of the Image</param>
		/// <param name="enabled">Specifies whether the Image should be drawn 
		/// in an enabled state</param>
		protected void DrawImage(Graphics g, Image image, Rectangle imageRect, bool enabled)
		{
			if (enabled)
			{
				g.DrawImageUnscaled(image, imageRect);
			}
			else
			{
				ControlPaint.DrawImageDisabled(g, image, imageRect.X, imageRect.Y, this.BackColor);
			}
		}

		#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 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
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions