MultiColumnList.cs :  » Game » RealmForge » CrayzEdsGui » Base » Widgets » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » Game » RealmForge 
RealmForge » CrayzEdsGui » Base » Widgets » MultiColumnList.cs
#region LGPL License

/*************************************************************************
    Crazy Eddie's GUI System (http://crayzedsgui.sourceforge.net)
    Copyright (C)2004 Paul D Turner (crayzed@users.sourceforge.net)
  
  C# Port developed by Chris McGuirk (leedgitar@latenitegames.com)
  Compatible with the Axiom 3D Engine (http://axiomengine.sf.net)

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*************************************************************************/

#endregion LGPL License

using System;

namespace CrayzEdsGui.Base.Widgets{

  #region GridReference Structure

  /// <summary>
  ///    Simple structure used to hold grid references.
  /// </summary>
  public struct GridReference 
  {

    #region Fields

    /// <summary>
    /// The zero based row index of this grid reference.
    /// </summary>
    public int  Row;

    /// <summary>
    /// The zero based column index of this grid reference.
    /// </summary>
    public int Column;

    #endregion
    
    #region Constructor

    public GridReference(int r, int c)
    {
      Row = r;
      Column = c;
    }

    public GridReference(GridReference obj)
    {
      Row = obj.Row;
      Column = obj.Column;
    }

    #endregion

    #region Operators

    public static bool operator==(GridReference a, GridReference b)
    {
      return ((a.Row == b.Row) && (a.Column == b.Column));
    }

    public static bool operator!=(GridReference a, GridReference b)
    {
      return !(a==b);
    }

    #endregion
  }

#endregion

  #region GridRow helper / container class

  /// <summary>
  ///    Class used to wrap a grid 'row' and also ease sorting
  /// </summary>
  public class GridRow
  {
    public ListboxItemList  rowItems = new ListboxItemList();
    public int  sortColumnIndex;

    public ListboxItem this[int index] {
      get {
        return rowItems[index];
      }

      set {
        rowItems[index] = value;
      }
    }

    public static bool operator<(GridRow a, GridRow b)
    {
      ListboxItem itemA = a.rowItems[a.sortColumnIndex];
      ListboxItem itemB = b.rowItems[b.sortColumnIndex];

      // handle same object case
      if (a == b) {
        return false;
      }

      // handle empty slot cases
      if (itemB == null) {
        return true;
      }
      else if (itemA == null) {
        return false;
      }
      // both items valid, do the compare
      else {
        return itemA < itemB;
      }
    }

    public static bool operator>(GridRow a, GridRow b)
    {
      return b < a;
    }
  }

#endregion

  /// <summary>
  ///    Base class for the multi-column list / grid widget.
  /// </summary>
  public abstract class MultiColumnList: Window 
  {
    #region Constants
    #endregion

    #region Fields

    /// <summary>
    ///    vertical scroll-bar widget
    /// </summary>
    protected Scrollbar  verticalScrollbar;

    /// <summary>
    ///    horizontal scroll-bar widget
    /// </summary>
    protected Scrollbar  horizontalScrollbar;

    /// <summary>
    ///    The ListHeader widget.
    /// </summary>
    protected ListHeader  listHeader;

    /// <summary>
    ///    true if vertical scrollbar should always be displayed
    /// </summary>
    protected bool  forceVerticalScrollbar;

    /// <summary>
    ///    true if horizontal scrollbar should always be displayed
    /// </summary>
    protected bool  forceHorizontalScrollbar;

    /// <summary>
    ///    Holds selection mode (represented by settings below).
    /// </summary>
    protected GridSelectionMode  selectMode;  

    /// <summary>
    ///    Nominated column for single column selection.
    /// </summary>
    protected int  nominatedSelectColumn;  

    /// <summary>
    ///    Nominated row for single row selection.
    /// </summary>
    protected int  nominatedSelectRow;  

    /// <summary>
    ///    Allow multiple selections.
    /// </summary>
    protected bool  multiSelect;      

    /// <summary>
    ///    All items in a row should be selected.
    /// </summary>
    protected bool  fullRowSelect;

    /// <summary>
    ///    All items in a column should be selected.
    /// </summary>
    protected bool  fullColumnSelect;

    /// <summary>
    ///    true if we use a nominated row to select.
    /// </summary>
    protected bool  useNominatedRow;

    /// <summary>
    ///    true if we use a nominated col to select.
    /// </summary>
    protected bool  useNominatedColumn;

    /// <summary>
    ///    holds the last selected item (used in range selections)
    /// </summary>
    protected ListboxItem  lastSelectedItem;

    /// <summary>
    ///    Holds the grid data.
    /// </summary>
    protected GridRowList gridData;

    #endregion

    #region Constructor
    /// <summary>
    ///    Constructor.
    /// </summary>
    /// <param name="type"></param>
    /// <param name="name"></param>
    public MultiColumnList(string name) : base(name)
    {
      gridData = new GridRowList();

      forceVerticalScrollbar    = false;
      forceHorizontalScrollbar  = false;
      nominatedSelectColumn    = 0;
      nominatedSelectRow      = 0;
      lastSelectedItem      = null;

      // This is a bit of a hack to ensure all the individual settings that are affected
      // by selection mode are updated properly.  If we do not do this, the box does not
      // function properly.
      SelectionMode        = GridSelectionMode.CellSingle;
      SelectionMode        = GridSelectionMode.RowSingle;
    }
    #endregion

    #region Properties

    /// <summary>
    ///    Gets/Sets whether use control of the sort column and sort direction is enabled.
    /// </summary>
    [WidgetProperty("SortSettingEnabled")]
    public bool  UserSortControlEnabled 
    {
      get {
        return listHeader.UserSortControlEnabled;
      }

      set {
        listHeader.UserSortControlEnabled = value;
      }
    }

    /// <summary>
    ///    Gets/Sets whether the user may size column segments.
    /// </summary>    
    [WidgetProperty("ColumnsSizable")]
    public bool  UserColumnSizingEnabled 
    {
      get {
        return listHeader.SegmentSizingEnabled;
      }

      set {
        listHeader.SegmentSizingEnabled = value;
      }
    }

    /// <summary>
    ///    Gets/Sets whether the user may modify the order of the columns.
    /// </summary>
    [WidgetProperty("ColumnsMovable")]
    public bool  UserColumnDraggingEnabled 
    {
      get {
        return listHeader.SegmentDraggingEnabled;
      }

      set {
        listHeader.SegmentDraggingEnabled = value;
      }
    }

    /// <summary>
    ///    Gets the number of columns in the grid.
    /// </summary>
    public int ColumnCount {
      get {
        return listHeader.ColumnCount;
      }
    }

    /// <summary>
    ///    Gets the number of rows in the grid.
    /// </summary>
    public int RowCount {
      get {
        return gridData.Count;
      }
    }

    /// <summary>
    ///    Gets/Sets the zero based index of the current sort column.
    ///    NB: There must be at least one column to successfully read this property.
    /// </summary>
    public int  SortColumnIndex {
      get {
        return listHeader.SortColumnIndex;
      }

      set {
        if (listHeader.SortColumnIndex != value) {
          listHeader.SortColumnIndex = value;
        }
      }
    }

    /// <summary>
    /// Gets/Gets the current sort column by ID code.
    /// </summary>
    [WidgetProperty("SortColumnID")]
    public int  SortColumnID 
    {
      get {
        return listHeader.SortColumnID;
      }

      set {
        if (listHeader.SortColumnID != value) {
          listHeader.SortColumnID = value;
        }
      }
    }

    /// <summary>
    ///    Gets the total width of all column headers using the active metrics system.
    /// </summary>
    public float TotalColumnHeadersWidth {
      get {
        float width = listHeader.TotalPixelExtent;

        if (MetricsMode == MetricsMode.Relative) {
          width = AbsoluteToRelativeX(width);
        }

        return width;
      }
    }

    /// <summary>
    ///    Gets/Sets the current sort direction.
    /// </summary>
    [SortDirectionProperty("SortDirection")]
    public SortDirection SortDirection 
    {
      get {
        return listHeader.SortDirection;
      }

      set {
        if (listHeader.SortDirection != value) {
          listHeader.SortDirection = value;
        }
      }
    }

    /// <summary>
    ///    Gets the number of selected ListboxItems attached to the grid.
    /// </summary>
    public int SelectedItemCount {
      get {
        int count = 0;

        for (int row = 0; row < RowCount; ++row) {
          for (int column = 0; column < ColumnCount; ++column) {
            ListboxItem item = gridData[row][column];

            if ((item != null) && item.Selected) {
              ++count;
            }
          }
        }

        return count;
      }
    }


    /// <summary>
    ///    Gets/Sets the ID of the column to be used when one of the 'NominatedColumn' selection modes is used.
    /// </summary>
    [WidgetProperty("NominatedSelectionColumnID")]
    public int NominatedSelectionColumnID 
    {
      get {
        return listHeader[nominatedSelectColumn].ID;
      }

      set {
        NominatedSelectionColumn = GetColumnIndexWithID(value);
      }
    }

    /// <summary>
    ///    Gets/Sets the index of the column to be used when one of the 'NominatedColumn' selection modes is used. 
    /// </summary>
    public int NominatedSelectionColumn 
    {
      get {
        return nominatedSelectColumn;
      }

      set {
        if (nominatedSelectColumn != value) {
          nominatedSelectColumn = value;
          ClearAllSelections();

          OnNominatedSelectColumnChanged(new WindowEventArgs(this));
        }
      }
    }

    /// <summary>
    ///    Gets/Sets the index of the row to be used when one of the 'NominatedRow' selection modes is used.
    /// </summary>
    [WidgetProperty("NominatedSelectionRow")]
    public int NominatedSelectionRow {
      get {
        return nominatedSelectRow;
      }

      set {
        if (nominatedSelectRow != value) {
          nominatedSelectRow = value;
          ClearAllSelections();

          OnNominatedSelectRowChanged(new WindowEventArgs(this));
        }
      }
    }

    /// <summary>
    ///    Gets/Sets the selection mode to be used with the grid.
    /// </summary>
    [GridSelectionModeProperty("SelectionMode")]
    public GridSelectionMode SelectionMode {
      get {
        return selectMode;
      }

      set {
        if (selectMode != value) {
          selectMode = value;

          ClearAllSelections();

          switch(selectMode) {
            case GridSelectionMode.RowSingle:
              multiSelect      = false;
              fullRowSelect    = true;
              fullColumnSelect  = false;
              useNominatedColumn  = false;
              useNominatedRow    = false;
              break;

            case GridSelectionMode.RowMultiple:
              multiSelect      = true;
              fullRowSelect    = true;
              fullColumnSelect  = false;
              useNominatedColumn  = false;
              useNominatedRow    = false;
              break;

            case GridSelectionMode.CellSingle:
              multiSelect      = false;
              fullRowSelect    = false;
              fullColumnSelect  = false;
              useNominatedColumn  = false;
              useNominatedRow    = false;
              break;

            case GridSelectionMode.CellMultiple:
              multiSelect      = true;
              fullRowSelect    = false;
              fullColumnSelect  = false;
              useNominatedColumn  = false;
              useNominatedRow    = false;
              break;

            case GridSelectionMode.NominatedColumnSingle:
              multiSelect      = false;
              fullRowSelect    = false;
              fullColumnSelect  = false;
              useNominatedColumn  = true;
              useNominatedRow    = false;
              break;

            case GridSelectionMode.NominatedColumnMultiple:
              multiSelect      = true;
              fullRowSelect    = false;
              fullColumnSelect  = false;
              useNominatedColumn  = true;
              useNominatedRow    = false;
              break;

            case GridSelectionMode.ColumnSingle:
              multiSelect      = false;
              fullRowSelect    = false;
              fullColumnSelect  = true;
              useNominatedColumn  = false;
              useNominatedRow    = false;
              break;

            case GridSelectionMode.ColumnMultiple:
              multiSelect      = true;
              fullRowSelect    = false;
              fullColumnSelect  = true;
              useNominatedColumn  = false;
              useNominatedRow    = false;
              break;

            case GridSelectionMode.NominatedRowSingle:
              multiSelect      = false;
              fullRowSelect    = false;
              fullColumnSelect  = false;
              useNominatedColumn  = false;
              useNominatedRow    = true;
              break;

            case GridSelectionMode.NominatedRowMultiple:
              multiSelect      = true;
              fullRowSelect    = false;
              fullColumnSelect  = false;
              useNominatedColumn  = false;
              useNominatedRow    = true;
              break;

            default:
              throw new InvalidRequestException("Invalid or unknown SelectionMode value supplied.");
          }

          // Fire event.
          OnSelectionModeChanged(new WindowEventArgs(this));
        }
      }
    }

    /// <summary>
    ///    Gets/Sets whether the vertical scroll bar will be shown even if it is not required.
    /// </summary>
    [WidgetProperty("ForceVertScrollbar")]
    public bool AlwaysShowVerticalScrollbar 
    {
      get {
        return forceVerticalScrollbar;
      }

      set {
        if (forceVerticalScrollbar != value) {
          forceVerticalScrollbar = value;

          ConfigureScrollbars();

          OnVerticalScrollbarModeChanged(new WindowEventArgs(this));
        }
      }
    }

    /// <summary>
    ///    Gets/Sets whether the horizontal scroll bar will be shown even if it is not required.
    /// </summary>
    [WidgetProperty("ForceHorzScrollbar")]
    public bool AlwaysShowHorizontalScrollbar 
    {
      get {
        return forceHorizontalScrollbar;
      }

      set {
        if (forceHorizontalScrollbar != value) {
          forceHorizontalScrollbar = value;

          ConfigureScrollbars();

          OnHorizontalScrollbarModeChanged(new WindowEventArgs(this));
        }
      }
    }

    #endregion

    #region Methods

    #region Accessor type methods

    /// <summary>
    ///    Return the zero based column index of the column with the specified ID.
    /// </summary>
    /// <param name="id">ID code of the column whos index is to be returned.</param>
    /// <returns>Zero based column index of the first column whos ID matches 'id'.</returns>
    /// <exception cref="InvalidRequestException">thrown if no attached column has the requested ID.</exception>
    public int GetColumnIndexWithID(int id)
    {
      return listHeader.GetColumnIndexFromSegmentID(id);
    }

    /// <summary>
    ///    Return the zero based index of the column whos header text matches the specified text.
    /// </summary>
    /// <param name="text">string containing the text to be searched for.</param>
    /// <returns>Zero based column index of the column whos header has the specified text.</returns>
    /// <exception cref="InvalidRequestException">thrown if no columns header has the requested text.</exception>"
    public int GetColumnIndexWithHeaderText(string text)
    {
      return listHeader.GetColumnIndexFromSegmentText(text);
    }

    /// <summary>
    ///    Return the width of the column at the specified zero based index.
    /// </summary>
    /// <param name="columnIndex">Zero based index of the column whos width is to be returned.</param>
    /// <returns>Width of the column at the zero based column index specified by 'columnIndex', in absolute pixels.</returns>
    /// <exception cref="InvalidRequestException">thrown if columnIndex is out of range.</exception>
    public float GetColumnPixelWidth(int columnIndex)
    {
      return listHeader.GetPixelWidthOfColumn(columnIndex);
    }

    /// <summary>
    ///    Return the ListHeaderSegment object for the column at the specified index.
    /// </summary>
    /// <param name="columnIndex">Zero based index of the column whos ListHeaderSegment is to be returned.</param>
    /// <returns>ListHeaderSegment object for the column at the requested index.</returns>
    /// <exception cref="InvalidRequestException">thrown if columnIndex is out of range.</exception>
    public ListHeaderSegment GetColumnHeaderSegment(int columnIndex)
    {
      return listHeader[columnIndex];
    }

    /// <summary>
    ///    Return the zero based index of the Row that contains the given ListboxItem.
    /// </summary>
    /// <param name="item">ListboxItem for which the row index is to be returned.</param>
    /// <returns>Zero based index of the row that contains ListboxItem 'item'.</returns>
    /// <exception cref="InvalidRequestException">thrown if 'item' is not attached to the grid.</exception>
    public int GetRowIndexOfItem(ListboxItem item)
    {
      for (int i = 0; i < RowCount; ++i) {
        if (IsItemInRow(item, i)) {
          return i;
        }
      }

      throw new InvalidRequestException("The specified ListboxItem is not attached to the grid.");
    }

    /// <summary>
    ///    Return the zero based index of the column that contains 'item'.
    /// </summary>
    /// <param name="item">ListboxItem for which the column index is to returned.</param>
    /// <returns>Zero based index of the column that contains ListboxItem 'item'.</returns>
    /// <exception cref="InvalidRequestException">thrown if 'item' is not attached to the grid.</exception>
    public int  GetColumnIndexOfItem(ListboxItem item)
    {
      for (int i = 0; i < ColumnCount; ++i) {
        if (IsItemInColumn(item, i)) {
          return i;
        }
      }

      throw new InvalidRequestException("The specified ListboxItem is not attached to the grid.");
    }

    /// <summary>
    ///    Return the grid reference for 'item'.
    /// </summary>
    /// <param name="item">ListboxItem for which the grid reference is to be returned.</param>
    /// <returns>GridReference describing the current grid reference of ListboxItem 'item'.</returns>
    /// <exception cref="InvalidRequestException">thrown if 'item' is not attached to the grid.</exception>
    public GridReference GetGridReferenceOfItem(ListboxItem item)
    {
      return new GridReference(GetRowIndexOfItem(item), GetColumnIndexOfItem(item));
    }
  
    /// <summary>
    ///    Return the ListboxItem at the specified grid reference.
    /// </summary>
    /// <param name="gridRef">GridReference that describes the position of the ListboxItem to be returned.</param>
    /// <returns>ListboxItem at grid reference 'gridRef'</returns>
    /// <exception cref="InvalidRequestException">thrown if 'gridRef' is invalid.</exception>
    public ListboxItem  GetItemAtGridReference(GridReference gridRef)
    {
      // check for invalid grid ref
      if (gridRef.Column >= ColumnCount) {
        throw new InvalidRequestException("The column index {0} is invalid for this grid.", gridRef.Column);
      }
      else if (gridRef.Row >= RowCount) {
        throw new InvalidRequestException("The row index {0} is invalid for this grid.", gridRef.Row);
      }
      else {
        return gridData[gridRef.Row][gridRef.Column];
      }
    }

    /// <summary>
    ///    Return whether ListboxItem 'item' is attached to the column at index 'columnIndex'.
    /// </summary>
    /// <param name="item">ListboxItem to look for.</param>
    /// <param name="columnIndex">Zero based index of the column that is to be searched.</param>
    /// <returns>
    ///    - true if 'item' is attached to column 'columnIndex'.
    ///    - false if 'item' is not attached to column 'columnIndex'.
    /// </returns>
    /// <exception cref="InvalidRequestException">thrown if 'columnIndex' is out of range.</exception>
    public bool  IsItemInColumn(ListboxItem item, int columnIndex)
    {
      // check for invalid index
      if (columnIndex >= ColumnCount) {
        throw new InvalidRequestException("Column index {0} is invalid for this grid.", columnIndex);
      }
      else {
        for (int i = 0; i < RowCount; ++i) {
          if (gridData[i][columnIndex] == item) {
            return true;
          }
        }

        // item was not in search column
        return false;
      }
    }

    /// <summary>
    ///    Return whether ListboxItem 'item' is attached to the row at index 'rowIndex'.
    /// </summary>
    /// <param name="item">ListboxItem to look for.</param>
    /// <param name="rowIndex">Zero based index of the row that is to be searched.</param>
    /// <returns>
    ///    - true if 'item' is attached to row 'rowIndex'.
    ///    - false if 'item' is not attached row 'rowIndex'.
    /// </returns>
    /// <exception cref="InvalidRequestException">thrown if 'rowIndex' is out of range.</exception>
    public bool IsItemInRow(ListboxItem item, int rowIndex)
    {
      // check for invalid index
      if (rowIndex >= RowCount) {
        throw new InvalidRequestException("Row index {0} is invalid for this grid.", rowIndex);
      }
      else {
        for (int i = 0; i < ColumnCount; ++i) {
          if (gridData[rowIndex][i] == item) {
            return true;
          }
        }

        // item was not in search row
        return false;
      }
    }

    /// <summary>
    ///    Return whether ListboxItem 'item' is attached to the grid.
    /// </summary>
    /// <param name="item">ListboxItem to look for.</param>
    /// <returns>
    ///    - true if 'item' is attached to the grid.
    ///    - false if 'item' is not attached to the grid.
    /// </returns>
    public bool  IsItemInGrid(ListboxItem item)
    {
      for (int row = 0; row < RowCount; ++row) {
        for (int column = 0; column < ColumnCount; ++column) {
          if (gridData[row][column] == item) {
            return true;
          }
        }
      }

      // item was not in grid.
      return false;
    }

    /// <summary>
    ///    Return the ListboxItem in column 'columnIndex' that has the text string 'text'.
    /// </summary>
    /// <param name="text">string containing the text to be searched for.</param>
    /// <param name="columnIndex">Zero based index of the column to be searched.</param>
    /// <param name="startItem">ListboxItem where the exclusive search is to start, or null to search from the top of the column.</param>
    /// <returns>The first ListboxItem in column 'columnIndex', after 'startItem', that has the string 'text'.</returns>
    /// <exception cref="InvalidRequestException">thrown if 'startItem' is not attached to the grid, or if 'columnIndex' is out of range.</exception>
    public ListboxItem GetColumnItemWithText(string text, int columnIndex, ListboxItem startItem)
    {
      // check for valid index
      if (columnIndex >= ColumnCount) {
        throw new InvalidRequestException("Column index {0} is invalid for this grid.", columnIndex);
      }
      else {
        int i = (startItem == null) ? 0 : GetRowIndexOfItem(startItem);

        for ( ; i < RowCount; ++i) {
          // does item text match?
          if (gridData[i][columnIndex].Text == text) {
            return gridData[i][columnIndex];
          }
        }

        // no matching items
        return null;
      }
    }

    /// <summary>
    ///    Return the ListboxItem in row 'rowIndex' that has the text string 'text'.
    /// </summary>
    /// <param name="text">string containing the text to be searched for.</param>
    /// <param name="rowIndex">Zero based index of the row to be searched.</param>
    /// <param name="startItem">ListboxItem where the exclusive search is to start, or null to search from the start of the row.</param>
    /// <returns>The first ListboxItem in row 'rowIndex', after 'startItem', that has the string 'text'.</returns>
    /// <exception cref="InvalidRequestException">thrown if 'startItem' is not attached to the grid, or if 'rowIndex' is out of range.</exception>
    public ListboxItem  GetRowItemWithText(string text, int rowIndex, ListboxItem startItem)
    {
      // check for valid index
      if (rowIndex >= RowCount) {
        throw new InvalidRequestException("Row index {0} is invalid for this grid.", rowIndex);
      }
      else {
        int i = (startItem == null) ? 0 : GetColumnIndexOfItem(startItem);

        for ( ; i < ColumnCount; ++i) {
          // does item text match?
          if (gridData[rowIndex][i].Text == text) {
            return gridData[rowIndex][i];
          }
        }

        // no matching items
        return null;
      }
    }

    /// <summary>
    ///    Return the ListboxItem that has the text string 'text'.
    /// </summary>
    /// <remarks>Searching progresses across the columns in each row.</remarks>
    /// <param name="text">string containing the text to be searched for.</param>
    /// <param name="startItem">ListboxItem where the exclusive search is to start, or null to search the whole grid.</param>
    /// <returns>The first ListboxItem, after 'startItem', that has the string 'text'.</returns>
    /// <exception cref="InvalidRequestException">thrown if 'startItem' is not attached to the grid.</exception>
    public ListboxItem  GetItemWithText(string text, ListboxItem startItem)
    {
      GridReference searchRef;

      // obtain starting location
      if (startItem == null) {
        searchRef = new GridReference(0, 0);
      }
      else {
        searchRef = GetGridReferenceOfItem(startItem);
      }

      // perform search
      for ( ; searchRef.Row < RowCount; ++searchRef.Row) {
        for ( ; searchRef.Column < ColumnCount; ++searchRef.Column) {
          // is this a match?
          if (gridData[searchRef.Row][searchRef.Column].Text == text) {
            return gridData[searchRef.Row][searchRef.Column];
          }
        }
      }

      // no match
      return null;
    }

    /// <summary>
    ///    Return a the first selected ListboxItem attached to the grid.
    /// </summary>
    /// <remarks>Searching progresses across the columns in each row.</remarks>
    /// <returns>The first ListboxItem attached to the grid that is selected, or null if no item is selected.</returns>
    public ListboxItem GetFirstSelectedItem()
    {
      return GetNextSelectedItem(null);
    }

    /// <summary>
    ///    Return the next selected ListboxItem after 'startItem'.
    /// </summary>
    /// <remarks>Searching progresses across the columns in each row.</remarks>
    /// <param name="startItem">ListboxItem where the exclusive search is to start, or null to search the whole grid.</param>
    /// <returns>The first selected ListboxItem attached to the grid, after 'startItem', or null if no such item is selected.</returns>
    /// <exception cref="InvalidRequestException">thrown if 'startItem' is not attached to the grid.</exception>
    public ListboxItem GetNextSelectedItem(ListboxItem startItem)
    {
      GridReference searchRef;

      // obtain starting location
      if (startItem == null) {
        searchRef = new GridReference(0, 0);
      }
      else {
        searchRef = GetGridReferenceOfItem(startItem);
      }

      // perform search
      for ( ; searchRef.Row < RowCount; ++searchRef.Row) {
        for ( ; searchRef.Column < ColumnCount; ++searchRef.Column) {
          // is this a match?
          ListboxItem item = gridData[searchRef.Row][searchRef.Column];
          if ((item != null) && item.Selected) {
            return item;
          }
        }
      }

      // no match
      return null;
    }

    /// <summary>
    ///    Return whether the ListboxItem at 'gridRef' is selected.
    /// </summary>
    /// <param name="gridRef">GridReference describing the grid reference that is to be examined.</param>
    /// <returns>
    ///    - true if there is a ListboxItem at 'gridRef' and it is selected.
    ///    - false if there is no ListboxItem at 'gridRef', or if the item is not selected.
    /// </returns>
    /// <exception cref="InvalidRequestException">thrown if 'gridRef' contains an invalid grid position.</exception>
    public bool IsItemSelected(GridReference gridRef)
    {
      ListboxItem item = GetItemAtGridReference(gridRef);

      if (item != null) {
        return item.Selected;
      }
      else {
        return false;
      }
    }

    /// <summary>
    ///    Return the ID code assigned to the specified column.
    /// </summary>
    /// <param name="columnIndex">Zero based index of the column whos ID code is to be returned.</param>
    /// <returns>Current ID code assigned to the column at the requested index.</returns>
    /// <exception cref="InvalidRequestException">thrown if 'columnIndex' is out of range</exception>
    public int GetColumnID(int columnIndex)
    {
      return listHeader[columnIndex].ID;
    }

    #endregion

    #region Manipulator type methods.

    /// <summary>
    ///    Initialise the Window based object ready for use.
    /// </summary>
    public override void Initialize()
    {
      base.Initialize();

      // create component widgets
      verticalScrollbar  = CreateVerticalScrollbar();
      horizontalScrollbar  = CreateHorizontalScrollbar();
      listHeader      = CreateListHeader();

      // add component widgets
      AddChild(verticalScrollbar);
      AddChild(horizontalScrollbar);
      AddChild(listHeader);

      // subscribe to event notifications
      listHeader.ScrollOffsetChanged    += new WindowEventHandler(ScrollOffsetChanged_handler);
      listHeader.SegmentSequenceChanged  += new HeaderSequenceEventHandler(SegmentSequenceChanged_handler);
      listHeader.SegmentSized        += new WindowEventHandler(SegmentSized_handler);
      listHeader.SortColumnChanged    += new WindowEventHandler(SortColumnChanged_handler);
      listHeader.SortDirectionChanged    += new WindowEventHandler(SortDirectionChanged_handler);
      listHeader.SplitterDoubleClicked  += new WindowEventHandler(SplitterDoubleClicked_handler);
      horizontalScrollbar.ScrollPositionChanged += new WindowEventHandler(ScrollPositionChanged_handler);

      // do some final initialisation now we are complete
      SortDirection = SortDirection.None;

      // do initial layout
      LayoutComponentWidgets();
    }

    /// <summary>
    ///    Remove all items from the grid.
    /// </summary>
    public void  ResetList()
    {
      if (ResetListImpl()) {
        OnContentsChanged(new WindowEventArgs(this));
      }
    }

    /// <summary>
    ///    Add a column to the grid.
    /// </summary>
    /// <param name="heading">string containing the text label for the column header.</param>
    /// <param name="columnID">ID code to be assigned to the column header.</param>
    /// <param name="width">Initial width to be set for the column using the active metrics mode for this window.</param>
    public void  AddColumn(string heading, int columnID, float width)
    {
      InsertColumn(heading, columnID, width, ColumnCount);
    }

    /// <summary>
    /// Insert a new column in the grid.
    /// </summary>
    /// <param name="heading">string containing the text label for the column header.</param>
    /// <param name="columnID">ID code to be assigned to the column header.</param>
    /// <param name="width">Initial width to be set for the column using the active metrics mode for this window.</param>
    /// <param name="positionIndex">Zero based index where the column is to be inserted.  If this is greater than the current number of columns, the new column is inserted at the end.</param>
    public void  InsertColumn(string heading, int columnID, float width, int positionIndex)
    {
      // if position is out of range, insert at end
      if (positionIndex > ColumnCount) {
        positionIndex = ColumnCount;
      }

      // get desired width as pixels
      if (MetricsMode == MetricsMode.Relative) {
        width = RelativeToAbsoluteX(width);
      }

      // insert new column into header
      listHeader.InsertColumn(heading, columnID, listHeader.AbsoluteToRelativeX(width), positionIndex);

      // insert a blank entry for new column in each row
      for (int i = 0; i < RowCount; ++i) {
        gridData[i].rowItems.Insert(positionIndex, null);
      }

      // update nominated column index if that has now changed
      if ((nominatedSelectColumn >= positionIndex) && (ColumnCount > 1)) {
        ++nominatedSelectColumn;
      }

      // signal that the grid content has changed
      OnContentsChanged(new WindowEventArgs(this));
    }

    /// <summary>
    ///    Removes a column from the grid.
    /// </summary>
    /// <param name="columnIndex">Zero based index of the column to be removed.</param>
    /// <exception cref="InvalidRequestException">thrown if 'columnIndex' is invalid.</exception>
    public void  RemoveColumn(int columnIndex)
    {
      // check for invalid index
      if (columnIndex >= ColumnCount) {
        throw new InvalidRequestException("Column index {0} is invalid for this grid.", columnIndex);
      }
      else {
        // if we're removing the nominated selection column, reset that to 0.
        if (nominatedSelectColumn == columnIndex) {
          nominatedSelectColumn = 0;
        }

        // remove the column entry in each row
        for (int i = 0; i < RowCount; ++i) {
          gridData[i].rowItems.RemoveAt(columnIndex);
        }

        // remove the header segment
        listHeader.RemoveColumn(columnIndex);

        // signal that the grid content has changed
        OnContentsChanged(new WindowEventArgs(this));
      }
    }

    /// <summary>
    ///    Removes a column from the grid.
    /// </summary>
    /// <param name="columnID">ID code of the column to be deleted.</param>
    /// <exception cref="InvalidRequestException">thrown if no column with 'columnID' is in the grid.</exception>
    public void  RemoveColumnWithID(int columnID)
    {
      RemoveColumn(GetColumnIndexWithID(columnID));
    }

    /// <summary>
    ///    Move the column at index 'columnIndex' so it is at index 'positionIndex'.
    /// </summary>
    /// <param name="columnIndex">Zero based index of the column to be moved.</param>
    /// <param name="positionIndex">Zero based index of the new position for the column.</param>
    /// <exception cref="InvalidRequestException">thrown if 'columnIndex' is invalid.</exception>
    public void  MoveColumn(int columnIndex, int positionIndex)
    {
      // all we have to do here is move the header segment.  Events will ensure that
      // the data gets moved.
      listHeader.MoveColumn(columnIndex, positionIndex);
    }

    /// <summary>
    ///    Move the column with ID 'columnID' so it is at index 'positionIndex'.
    /// </summary>
    /// <param name="columnID">ID code of the column to be moved.</param>
    /// <param name="positionIndex">Zero based index of the new position for the column.</param>
    /// <exception cref="InvalidRequestException">thrown if no column with ID 'columnID' is present.</exception>
    public void  MoveColumnWithID(int columnID, int positionIndex)
    {
      // all we have to do here is move the header segment.  Events will ensure that
      // the data gets moved.
      listHeader.MoveColumn(GetColumnIndexWithID(columnID), positionIndex);
    }

    /// <summary>
    ///    Add an empty row to the grid.
    /// </summary>
    /// <remarks>
    ///    If the list is being sorted, the new row will appear at an appropriate position according to the sorting being
    ///    applied.  If no sorting is being done, the new row will appear at the bottom of the list.
    /// </remarks>
    /// <returns>Initial zero based index of the new row.</returns>
    public int  AddRow()
    {
      return AddRow(null, 0);
    }

    /// <summary>
    ///    Add a row to the grid, and set the item in the column with ID 'columnID' to 'item'.
    /// </summary>
    /// <remarks>
    ///    If the list is being sorted, the new row will appear at an appropriate position according to the sorting being
    ///    applied.  If no sorting is being done, the new row will appear at the bottom of the list.
    /// </remarks>
    /// <param name="item">ListboxItem to be used as the initial contents for the column with ID 'columnID'.</param>
    /// <param name="columnID">ID code of the column whos initial item is to be set to 'item'.</param>
    /// <returns>Initial zero based index of the new row.</returns>
    /// <exception cref="InvalidRequestException">thrown if no column with the specified ID is present.</exception>
    public int  AddRow(ListboxItem item, int columnID)
    {
      int columnIndex = 0;

      // build new row
      GridRow row = new GridRow();
      row.sortColumnIndex = SortColumnIndex;
      row.rowItems.Resize(ColumnCount);

      if (item != null) {
        // discover which column to assign item to
        columnIndex = GetColumnIndexWithID(columnID);

        // establish ownership an enter item into new row.
        item.OwnerWindow = this;
        row[columnIndex] = item;
      }

      int positionIndex;
      gridData.Add(row);
      ResortGrid();

      if (SortDirection != SortDirection.None) {
        positionIndex = gridData.Find(row);        
      }
      else {
        positionIndex = RowCount - 1;
      }

      // signal that the grid content has changed
      OnContentsChanged(new WindowEventArgs(this));

      return positionIndex;
    }

    /// <summary>
    ///    Insert an empty row into the grid.
    /// </summary>
    /// <remarks>
    ///    If the list is being sorted, the new row will appear at an appropriate position according to the sorting being
    ///    applied.  If no sorting is being done, the new row will appear at the specified index.
    /// </remarks>
    /// <param name="rowIndex">Zero based index where the row should be inserted.  If this is greater than the current number of rows, the row is appended to the list.</param>
    /// <returns>Zero based index where the row was actually inserted.</returns>
    public int InsertRow(int rowIndex)
    {
      return InsertRow(null, 0, rowIndex);
    }

    /// <summary>
    ///    Insert a row into the grid, and set the item in the column with ID 'columnID' to 'item'.
    /// </summary>
    /// <remarks>
    ///    If the list is being sorted, the new row will appear at an appropriate position according to the sorting being
    ///    applied.  If no sorting is being done, the new row will appear at the specified index.
    /// </remarks>
    /// <param name="item">ListboxItem to be used as the initial contents for the column with ID 'columnID'.</param>
    /// <param name="columnID">ID code of the column whos initial item is to be set to 'item'.</param>
    /// <param name="rowIndex">Zero based index where the row should be inserted.  If this is greater than the current number of rows, the row is appended to the list.</param>
    /// <returns>Zero based index where the row was actually inserted.</returns>
    /// <exception cref="InvalidRequestException">thrown if no column with the specified ID is present.</exception>
    public int InsertRow(ListboxItem item, int columnID, int rowIndex)
    {
      // if list is sorted, just add the row instead
      if (SortDirection != SortDirection.None) {
        return AddRow(item, columnID);
      }
      else {
        // if insert index is too big, insert at end
        if (rowIndex > RowCount) {
          rowIndex = RowCount;
        }

        // build empty row
        GridRow row = new GridRow();
        row.sortColumnIndex = SortColumnIndex;
        row.rowItems.Resize(ColumnCount);

        // insert the row into the grid
        gridData.Insert(rowIndex, row);

        // set initial item in the new row
        SetGridItem(columnID, rowIndex, item);

        // signal that the grid content has changed
        OnContentsChanged(new WindowEventArgs(this));

        return rowIndex;
      }
    }

    /// <summary>
    ///    Remove the grid row at index 'rowIndex'.
    /// </summary>
    /// <param name="rowIndex">Zero based index of the row to be removed.</param>
    /// <exception cref="InvalidRequestException">thrown if 'rowIndex' is invalid.</exception>
    public void  RemoveRow(int rowIndex)
    {
      // check index
      if (rowIndex >= RowCount) {
        throw new InvalidRequestException("The row index {0} is invalid for this grid.", rowIndex);
      }
      else {
        // remove row from the grid
        gridData.RemoveAt(rowIndex);

        // re-set nominated row if we have just deleted it
        if (nominatedSelectRow == rowIndex) {
          nominatedSelectRow = 0;
        }

        // signal that the grid content has changed
        OnContentsChanged(new WindowEventArgs(this));
      }
    }

    /// <summary>
    ///    Set the ListboxItem for grid reference 'gridRef'.
    /// </summary>
    /// <param name="gridRef">GridReference describing the location of the item to be set.</param>
    /// <param name="item">ListboxItem to be set at 'gridRef'.</param>
    /// <exception cref="InvalidRequestException">thrown if 'gridRef' contains an invalid grid reference.</exception>
    public void  SetGridItem(GridReference gridRef, ListboxItem item)
    {
      // check for invalid grid ref
      if (gridRef.Column >= ColumnCount) 
      {
        throw new InvalidRequestException("The column index {0} is invalid for this grid.", gridRef.Column);
      }
      else if (gridRef.Row >= RowCount) 
      {
        throw new InvalidRequestException("The row index {0} is invalid for this grid.", gridRef.Row);
      }
      else 
      {
        // set new item into grid position
        item.OwnerWindow = this;
        gridData[gridRef.Row][gridRef.Column] = item;

        // if item was put into sort column, resort the grid

        // signal that the grid content has changed
        OnContentsChanged(new WindowEventArgs(this));
      }
    }

    /// <summary>
    ///    Set the ListboxItem for the column with ID 'columnID' in row 'rowIndex'.
    /// </summary>
    /// <param name="columnID">ID code of the column to receive 'item'.</param>
    /// <param name="rowIndex">Zero based index of the row to receive 'item'.</param>
    /// <param name="item">ListboxItem to be put into the grid.</param>
    /// <exception cref="InvalidRequestException">thrown if no column with ID 'columnID' exists, or if 'rowIndex' is out of range.</exception>
    public void  SetGridItem(int columnID, int rowIndex, ListboxItem item)
    {
      SetGridItem(new GridReference(rowIndex, GetColumnIndexWithID(columnID)), item);
    }

    /// <summary>
    ///    Remove the selected state from any currently selected ListboxItem attached to the grid.
    /// </summary>
    public void  ClearAllSelections()
    {
      if (ClearAllSelectionsImpl()) {
        OnSelectionChanged(new WindowEventArgs(this));
      }
    }

    /// <summary>
    ///    Sets or clears the selected state of the given ListboxItem (which must be already attached to the grid).
    /// </summary>
    /// <remarks>
    ///    Depending upon the current selection mode, this may cause other items to be selected, other
    ///    items to be deselected, or for nothing to actually happen at all.
    /// </remarks>
    /// <param name="item">ListboxItem to be affected.</param>
    /// <param name="state">
    ///    - true to put the ListboxItem into the selected state.
    ///    - false to put the ListboxItem into the de-selected state.
    /// </param>
    /// <exception cref="InvalidRequestException">thrown if 'item' is not attached to the grid.</exception>
    public void  SetItemSelectState(ListboxItem item, bool state)
    {
      SetItemSelectState(GetGridReferenceOfItem(item), state);
    }

    /// <summary>
    ///    Sets or clears the selected state of the ListboxItem at the given grid reference.
    /// </summary>
    /// <remarks>
    ///    Depending upon the current selection mode, this may cause other items to be selected, other
    ///    items to be deselected, or for nothing to actually happen at all.
    /// </remarks>
    /// <param name="gridRef">GridReference describing the position of the item to be affected.</param>
    /// <param name="state">
    ///    - true to put the ListboxItem into the selected state.
    ///    - false to put the ListboxItem into the de-selected state.
    /// </param>
    /// <exception cref="InvalidRequestException">thrown if 'gridRef' is invalid.</exception>
    public void  SetItemSelectState(GridReference gridRef, bool state)
    {
      if (SetItemSelectStateImpl(gridRef, state)) {
        OnSelectionChanged(new WindowEventArgs(this));
      }
    }

    /// <summary>
    ///    Inform the grid that one or more attached ListboxItems have been externally modified, and
    ///    the list should re-sync its internal state and refresh the display as needed.
    /// </summary>
    public void  HandleUpdatedItemData()
    {
      ConfigureScrollbars();
      RequestRedraw();
    }

    /// <summary>
    ///    Set the width of the specified column.
    /// </summary>
    /// <param name="columnIndex">Zero based column index of the column whos width is to be set.</param>
    /// <param name="width">float value specifying the new width for the column using the active metrics system.</param>
    /// <exception cref="InvalidRequestException">thrown if 'columnIndex' is out of range.</exception>
    public void  SetColumnWidth(int columnIndex, float width)
    {
      if (MetricsMode == MetricsMode.Relative) {
        width = RelativeToAbsoluteX(width);
      }

      listHeader.SetColumnPixelWidth(columnIndex, width);
    }

    #endregion

    #endregion

    #region Implementation Members

    #region Abstract Properties

    /// <summary>
    ///    Gets the area in un-clipped pixels, the window relative area
    ///    that is to be used for rendering grid items.
    /// </summary>
    protected abstract Rect ListRenderArea 
    {
      get;
    }

    #endregion
    
    #region Abstract Methods

    /// <summary>
    ///    Create and return a ListHeaer widget for use as the column headers.
    /// </summary>
    /// <returns>ListHeader based object.</returns>
    protected abstract ListHeader CreateListHeader();

    /// <summary>
    ///    Create and return a Scrollbar widget for use as vertical scroll bar.
    /// </summary>
    /// <returns>Scrollbar to be used for scrolling the list vertically.</returns>
    protected abstract Scrollbar CreateVerticalScrollbar(); 

    /// <summary>
    ///    Create and return a Scrollbar widget for use as horizontal scroll bar.
    /// </summary>
    /// <returns>Scrollbar to be used for scrolling the list horizontally.</returns>
    protected abstract Scrollbar CreateHorizontalScrollbar();

    /// <summary>
    ///    Setup size and position for the component widgets.
    /// </summary>
    protected abstract void  LayoutComponentWidgets();

    /// <summary>
    ///    Perform rendering of the widget control frame and other 'static' areas.  This
    ///    method should not render the actual items.  Note that the items are typically
    ///    rendered to layer 3, other layers can be used for rendering imagery behind and
    ///    infront of the items.
    /// </summary>
    /// <param name="z">Z co-ordinate for layer 0.</param>
    protected abstract void  RenderBaseImagery(float z);

    #endregion

    #region Properties
    /// <summary>
    ///    Gets the sum of all row heights
    /// </summary>
    protected float TotalRowsHeight {
      get {
        float height = 0.0f;

        for (int i = 0; i < RowCount; ++i) {
          height += GetHighestRowItemHeight(i);
        }

        return height;
      }
    }

    #endregion

    #region Methods

    /// <summary>
    ///    Perform the actual rendering for this Window.
    /// </summary>
    /// <param name="z">float value specifying the base Z co-ordinate that should be used when rendering.</param>
    protected override void DrawSelf(float z)
    {
      // make sub-class draw box imagery
      RenderBaseImagery(z);

      //
      // Render items
      //
      Vector3 itemPosition = new Vector3();
      Size  itemSize   = new Size();
      Rect  itemClipper   = new Rect();

      // calculate screen area that we are to draw items in to.
      Rect absArea = ListRenderArea;
      absArea.Offset(UnclippedPixelRect.Position);

      // calculate main clipper for rendering area
      Rect clipper = absArea.GetIntersection(PixelRect);

      // initialise item position
      itemPosition.y = absArea.top - verticalScrollbar.ScrollPosition;
      itemPosition.z = GuiSystem.Instance.Renderer.GetZLayer(3);  // TODO: Magic number?

      float alpha = EffectiveAlpha;

      // loop through items
      for (int row = 0; row < RowCount; ++row) {
        // set initial x position for this row
        itemPosition.x = absArea.left - horizontalScrollbar.ScrollPosition;

        // calculate height to be used for this row
        itemSize.height = GetHighestRowItemHeight(row);

        // loop through columns in the row
        for (int column = 0; column < ColumnCount; ++column) {
          // allow item to use full width of column
          itemSize.width = listHeader.GetPixelWidthOfColumn(column);

          ListboxItem item = gridData[row][column];

          // if there is an item here to be drawn
          if (item != null) {
            // calculate clipper for this item
            itemClipper.left  = itemPosition.x;
            itemClipper.top    = itemPosition.y;
            itemClipper.Size  = itemSize;
            itemClipper = itemClipper.GetIntersection(clipper);

            // draw item if not totally clipped
            if (itemClipper.Width != 0) {
              item.Draw(itemPosition, alpha, itemClipper);
            }
          }

          // update position for next column
          itemPosition.x += itemSize.width;
        }

        // update position for next row
        itemPosition.y += itemSize.height;
      }
    }

    /// <summary>
    ///    display required integrated scroll bars according to current state of the list box and update their values.
    /// </summary>
    protected void ConfigureScrollbars()
    {
      float totalHeight  = TotalRowsHeight;
      float totalWidth  = listHeader.TotalPixelExtent;

      //
      // First show or hide the scroll bars as needed (or requested)
      //
      // show or hide vertical scroll bar as required (or as specified by option)
      if ((totalHeight > ListRenderArea.Height) || forceVerticalScrollbar) {
        verticalScrollbar.Show();

        // show or hide horizontal scroll bar as required (or as specified by option)
        if ((totalWidth > ListRenderArea.Width) || forceHorizontalScrollbar) {
          horizontalScrollbar.Show();
        }
        else {
          horizontalScrollbar.Hide();
        }
      }
      else {
        // show or hide horizontal scroll bar as required (or as specified by option)
        if ((totalWidth > ListRenderArea.Width) || forceHorizontalScrollbar) {
          horizontalScrollbar.Show();

          // show or hide vertical scroll bar as required (or as specified by option)
          if ((totalHeight > ListRenderArea.Height) || forceVerticalScrollbar) {
            verticalScrollbar.Show();
          }
          else {
            verticalScrollbar.Hide();
          }
        }
        else {
          verticalScrollbar.Hide();
          horizontalScrollbar.Hide();
        }
      }

      //
      // Set up scroll bar values
      //
      Rect renderArea = ListRenderArea;

      verticalScrollbar.DocumentSize = totalHeight;
      verticalScrollbar.PageSize = renderArea.Height;
      verticalScrollbar.StepSize = Math.Max(1.0f, renderArea.Height / 10.0f);
      verticalScrollbar.ScrollPosition = verticalScrollbar.ScrollPosition;

      horizontalScrollbar.DocumentSize = totalWidth;
      horizontalScrollbar.PageSize = renderArea.Width;
      horizontalScrollbar.StepSize = Math.Max(1.0f, renderArea.Width / 10.0f);
      horizontalScrollbar.ScrollPosition = horizontalScrollbar.ScrollPosition;
    }

    /// <summary>
    ///    select all items between positions 'start' and 'end' (inclusive).
    /// </summary>
    /// <param name="start"></param>
    /// <param name="end"></param>
    /// <returns>true if something was modified.</returns>
    protected bool SelectRange(GridReference start, GridReference end)
    {
      GridReference tmpStart  = new GridReference(start);
      GridReference tmpEnd  = new GridReference(end);

      // ensure start is before end
      if (tmpStart.Row > tmpEnd.Row) {
        tmpStart.Row = tmpEnd.Row;
        tmpEnd.Row = start.Row;
      }
      
      if (tmpStart.Column > tmpEnd.Column) {
        tmpStart.Column = tmpEnd.Column;
        tmpEnd.Column = start.Column;
      }
      
      bool modified = false;

      // loop through range, selecting items
      for (int row = tmpStart.Row; row <= tmpEnd.Row; ++row) {
        for (int column = tmpStart.Column; column <= tmpEnd.Column; ++column) {
          ListboxItem item = gridData[row][column];

          if (item != null) {
            modified |= SetItemSelectStateImpl(GetGridReferenceOfItem(item), true);
          }
        }
      }

      return modified;
    }

    /// <summary>
    ///    Return the width of the widest item in the given column
    /// </summary>
    /// <param name="columnIndex"></param>
    /// <returns>width of the widest item in the given column</returns>
    protected float GetWidestColumnItemWidth(int columnIndex)
    {
      if (columnIndex >= ColumnCount) {
        throw new InvalidRequestException("Column index {0} is invalid for the grid.", columnIndex);
      }
      else {
        float widest = 0.0f;

        for (int i = 0; i < RowCount; ++i) {
          ListboxItem item = gridData[i][columnIndex];

          // if slot has an item
          if (item != null) {
            Size size = item.Size;

            // is this item widest so far?
            if (size.width > widest) {
              widest = size.width;
            }
          }
        }

        return widest;
      }
    }

    /// <summary>
    ///    Return the height of the highest item in the given row.
    /// </summary>
    /// <param name="rowIndex"></param>
    /// <returns>height of the highest item in the given row.</returns>
    protected float GetHighestRowItemHeight(int rowIndex)
    {
      if (rowIndex >= RowCount) {
        throw new InvalidRequestException("Row index {0} is invalid for the grid.", rowIndex);
      }
      else {
        float tallest = 0.0f;

        for (int i = 0; i < ColumnCount; ++i) {
          ListboxItem item = gridData[rowIndex][i];

          // if slot has an item
          if (item != null) {
            Size size = item.Size;

            // is this item tallest so far?
            if (size.height > tallest) {
              tallest = size.height;
            }
          }
        }

        return tallest;
      }
    }

    /// <summary>
    ///    Clear the selected state for all items (implementation)
    /// </summary>
    /// <returns>true if some selections were cleared, false if nothing was changed.</returns>
    protected bool ClearAllSelectionsImpl()
    {
      bool modified = false;

      for (int row = 0; row < RowCount; ++row) {
        for (int column = 0; column < ColumnCount; ++column) {
          ListboxItem item = gridData[row][column];

          if ((item != null) && item.Selected) {
            item.Selected = false;
            modified = true;
          }
        }
      }

      return modified;
    }

    /// <summary>
    ///    Return the ListboxItem under the given window local pixel co-ordinate.
    /// </summary>
    /// <param name="point"></param>
    /// <returns>
    ///    ListboxItem that is under window pixel co-ordinate 'point', or null if no
    ///    item is under that position.
    /// </returns>
    protected ListboxItem GetItemAtPoint(Point point)
    {
      Rect listArea = ListRenderArea;

      float y = listArea.top - verticalScrollbar.ScrollPosition;
      float x = listArea.left - horizontalScrollbar.ScrollPosition;

      for (int row = 0; row < RowCount; ++row) {
        y += GetHighestRowItemHeight(row);

        // is this the row?
        if (point.y < y) {
          // scan across to find the column
          for (int column = 0; column < ColumnCount; ++column) {
            x += GetColumnPixelWidth(column);

            // is this the column?
            if (point.x < x) {
              // return item at this grid cell
              return gridData[row][column];
            }
          }
        }
      }

      // no item was clicked
      return null;
    }

    /// <summary>
    ///    Set select state for the given item.
    ///    This appropriately selects other  items depending upon the select mode.
    /// </summary>
    /// <param name="gridRef"></param>
    /// <param name="state"></param>
    /// <returns>true if something is changed, else false.</returns>
    protected bool SetItemSelectStateImpl(GridReference gridRef, bool state)
    {
      // check for invalid grid ref
      if (gridRef.Column >= ColumnCount) {
        throw new InvalidRequestException("The column index {0} is invalid for this grid.", gridRef.Column);
      }
      else if (gridRef.Row >= RowCount) {
        throw new InvalidRequestException("The row index {0} is invalid for this grid.", gridRef.Row);
      }

      // only continue if state is changing
      if (gridData[gridRef.Row][gridRef.Column].Selected != state) {
        // if using nominated selection row and/ or column, check that they match.
        if ((!useNominatedColumn || (nominatedSelectColumn == gridRef.Column)) &&
          (!useNominatedRow || (nominatedSelectRow == gridRef.Row)))
        {
          // clear current selection if not multi-select box
          if (state && !multiSelect) {
            ClearAllSelectionsImpl();
          }

          // full row?
          if (fullRowSelect) {
            // set selection on all items in the row
            SetSelectForItemsInRow(gridRef.Row, state);
          }
          // full column?
          else if (fullColumnSelect) {
            // set selection on all items in the column
            SetSelectForItemsInColumn(gridRef.Column, state);
          }
          // single item to be affected
          else {
            gridData[gridRef.Row][gridRef.Column].Selected = state;
          }

          return true;
        }

      }

      return false;
    }

    /// <summary>
    ///    Set select state for all items in the given row
    /// </summary>
    /// <param name="rowIndex"></param>
    /// <param name="state"></param>
    protected void SetSelectForItemsInRow(int rowIndex, bool state)
    {
      for (int i = 0; i < ColumnCount; ++i) {
        ListboxItem item = gridData[rowIndex][i];
        
        if (item != null) {
          item.Selected = state;
        }
      }
    }

    /// <summary>
    ///    Set select state for all items in the given column
    /// </summary>
    /// <param name="columnIndex"></param>
    /// <param name="state"></param>
    protected void SetSelectForItemsInColumn(int columnIndex, bool state)
    {
      for (int i = 0; i < RowCount; ++i) {
        ListboxItem item = gridData[i][columnIndex];
        
        if (item != null) {
          item.Selected = state;
        }
      }
    }

    /// <summary>
    ///    Move the column at index 'columnIndex' so it is at index 'positionIndex'.
    ///    Implementation version which does not move the header segment (since that may have already happned).
    /// </summary>
    /// <param name="columnIndex"></param>
    /// <param name="positionIndex"></param>
    /// <exception cref="InvalidRequestException">thrown if 'columnIndex' is invalid.</exception>
    protected void MoveColumnImpl(int columnIndex, int positionIndex)
    {
      // ensure column is valid
      if (columnIndex >= ColumnCount) {
        throw new InvalidRequestException("Column index {0} is out of range for this grid.", columnIndex);
      }
      
      // if new position is too big, move to the end
      if (positionIndex > ColumnCount) {
        positionIndex = ColumnCount;
      }

      // update select column and index value if needed
      if (nominatedSelectColumn == columnIndex) {
        nominatedSelectColumn = positionIndex;
      }
      else if ((columnIndex < nominatedSelectColumn) && (positionIndex >= nominatedSelectColumn)) {
        --nominatedSelectColumn;
      }
      else if ((columnIndex > nominatedSelectColumn) && (positionIndex <= nominatedSelectColumn)) {
        ++nominatedSelectColumn;
      }

      // move entry for this column in each row.
      for (int i = 0; i < RowCount; ++i) {
        ListboxItem item = gridData[i][columnIndex];

        // remove old entry
        gridData[i].rowItems.RemoveAt(columnIndex);

        // insert item at new position
        gridData[i].rowItems.Insert(positionIndex, item);
      }
    }

    /// <summary>
    ///    Remove all items from the grid.
    /// </summary>
    /// <returns>
    ///    - true if the grid contents were changed.
    ///    - false if the grid contents were not changed (already empty?).
    /// </returns>
    protected bool ResetListImpl()
    {
      // return false if the list is empty
      if (RowCount == 0) {
        return false;
      }
      else {
        gridData.Clear();

        nominatedSelectRow = 0;
        lastSelectedItem = null;

        return true;
      }
    }

    /// <summary>
    ///    Causes a re-sort of the grid data in the appropriate direction
    /// </summary>
    protected void ResortGrid()
    {
      if (SortDirection == SortDirection.Descending) 
      {
        gridData.SortDescending();
      }
      else if (SortDirection == SortDirection.Ascending) 
      {
        gridData.SortAscending();
      }

      // else no sorting, so do nothing
    }

    #endregion

    #endregion

    #region Events

    #region Event Declarations

    /// <summary>
    ///    Event fired when the selection mode for the grid changes.
    /// </summary>
    public event WindowEventHandler  SelectionModeChanged;

    /// <summary>
    ///    Event fired when the nominated select column changes.
    /// </summary>
    public event WindowEventHandler  NominatedSelectColumnChanged;

    /// <summary>
    ///    Event fired when the nominated select row changes.
    /// </summary>
    public event WindowEventHandler  NominatedSelectRowChanged;

    /// <summary>
    ///    Event fired when the vertical scroll bar 'force' setting changes.
    /// </summary>
    public event WindowEventHandler  VerticalScrollbarModeChanged;

    /// <summary>
    ///    Event fired when the horizontal scroll bar 'force' setting changes.
    /// </summary>
    public event WindowEventHandler  HorizontalScrollbarModeChanged;

    /// <summary>
    ///    Event fired when the current selection(s) within the grid changes.
    /// </summary>
    public event WindowEventHandler  SelectionChanged;

    /// <summary>
    ///    Event fired when the contents of the grid changes.
    /// </summary>
    public event WindowEventHandler  ContentsChanged;

    /// <summary>
    ///    Event fired when the sort column changes.
    /// </summary>
    public event WindowEventHandler  SortColumnChanged;

    /// <summary>
    ///    Event fired when the sort direction changes.
    /// </summary>
    public event WindowEventHandler  SortDirectionChanged;

    /// <summary>
    ///    Event fired when the width of a column in the grid changes.
    /// </summary>
    public event WindowEventHandler  ColumnSized;

    /// <summary>
    ///    Event fired when the column order changes.
    /// </summary>
    public event WindowEventHandler  ColumnMoved;

    #endregion
    
    #region Trigger Methods

    /// <summary>
    /// Internal handler triggered when the selection mode changes
    /// </summary>
    /// <param name="e"></param>
    protected internal void OnSelectionModeChanged(WindowEventArgs e) 
    {
      if(SelectionModeChanged != null) {
        SelectionModeChanged(this, e);
      }
    }

    /// <summary>
    /// Internal handler triggered when the nominated selection column is changed.
    /// </summary>
    /// <param name="e"></param>
    protected internal void OnNominatedSelectColumnChanged(WindowEventArgs e) 
    {
      if(NominatedSelectColumnChanged != null) {
        NominatedSelectColumnChanged(this, e);
      }
    }
    
    /// <summary>
    /// Internal handler triggered when the nominated selection row is changed.
    /// </summary>
    /// <param name="e"></param>
    protected internal void OnNominatedSelectRowChanged(WindowEventArgs e) 
    {
      if(NominatedSelectRowChanged != null) {
        NominatedSelectRowChanged(this, e);
      }
    }
    
    /// <summary>
    /// Internal handler triggered when the vertical scroll bar 'force' setting is changed.
    /// </summary>
    /// <param name="e"></param>
    protected internal void OnVerticalScrollbarModeChanged(WindowEventArgs e) 
    {
      if(VerticalScrollbarModeChanged != null) {
        VerticalScrollbarModeChanged(this, e);
      }
    }

    /// <summary>
    /// Internal handler triggered when the horizontal scroll bar 'force' setting is changed.
    /// </summary>
    /// <param name="e"></param>
    protected internal void OnHorizontalScrollbarModeChanged(WindowEventArgs e) 
    {
      if(HorizontalScrollbarModeChanged != null) {
        HorizontalScrollbarModeChanged(this, e);
      }
    }
    
    /// <summary>
    /// Internal handler triggered when the grid selection changes.
    /// </summary>
    /// <param name="e"></param>
    protected internal void OnSelectionChanged(WindowEventArgs e) 
    {
      RequestRedraw();

      if(SelectionChanged != null) {
        SelectionChanged(this, e);
      }
    }
    
    /// <summary>
    /// Internal handler triggered when the grid content changes.
    /// </summary>
    /// <param name="e"></param>
    protected internal void OnContentsChanged(WindowEventArgs e) 
    {
      ConfigureScrollbars();
      RequestRedraw();

      if(ContentsChanged != null) {
        ContentsChanged(this, e);
      }
    }
    
    /// <summary>
    /// Internal handler triggered when the sort column changes.
    /// </summary>
    /// <param name="e"></param>
    protected internal void OnSortColumnChanged(WindowEventArgs e) 
    {
      RequestRedraw();

      if(SortColumnChanged != null) {
        SortColumnChanged(this, e);
      }
    }
    
    /// <summary>
    /// Internal handler triggered when the sort direction changes.
    /// </summary>
    /// <param name="e"></param>
    protected internal void OnSortDirectionChanged(WindowEventArgs e) 
    {
      RequestRedraw();

      if(SortDirectionChanged != null) {
        SortDirectionChanged(this, e);
      }
    }
    
    /// <summary>
    /// Internal handler triggered when a column size changes.
    /// </summary>
    /// <param name="e"></param>
    protected internal void OnColumnSized(WindowEventArgs e) 
    {
      ConfigureScrollbars();
      RequestRedraw();

      if(ColumnSized != null) {
        ColumnSized(this, e);
      }
    }
    
    /// <summary>
    /// Internal handler triggered when the column sequence changes.
    /// </summary>
    /// <param name="e"></param>
    protected internal void OnColumnMoved(WindowEventArgs e) 
    {
      RequestRedraw();

      if(ColumnMoved != null) {
        ColumnMoved(this, e);
      }
    }
    #endregion
    
    #endregion

    #region Window Members

    #region Overridden Event Triggers

    protected internal override void OnSized(GuiEventArgs e)
    {
      // base class processing
      base.OnSized(e);

      ConfigureScrollbars();
      LayoutComponentWidgets();

      e.Handled = true;
    }

    protected internal override void OnMouseButtonDown(MouseEventArgs e)
    {
      // base class processing
      base.OnMouseButtonDown(e);

      if (e.Button == MouseButton.Left) {
        bool modified = false;

        // clear old selections if no control key is pressed or if not multi-select
        if (((e.SysKeys & SystemKey.Control) == 0) || !multiSelect) {
          modified = ClearAllSelectionsImpl();
        }

        // get mouse position as a local pixel value
        Point localMousePosition = ScreenToWindow(e.Position);

        if (MetricsMode == MetricsMode.Relative) {
          localMousePosition = RelativeToAbsolute(localMousePosition);
        }

        ListboxItem item = GetItemAtPoint(localMousePosition);

        if (item != null) {
          modified = true;

          // select range or item depending upon system keys and last selected item
          if ((((e.SysKeys & SystemKey.Shift) != 0) && (lastSelectedItem != null)) && multiSelect) {
            modified |= SelectRange(GetGridReferenceOfItem(item), GetGridReferenceOfItem(lastSelectedItem));
          }
          else {
            modified |= SetItemSelectStateImpl(GetGridReferenceOfItem(item), item.Selected ^ true);
          }

          // update last selected item
          lastSelectedItem = item.Selected ? item : null;
        }

        // fire event if something was changed.
        if (modified) {
          OnSelectionChanged(new WindowEventArgs(this));
        }

        e.Handled = true;
      }
    }

    protected internal override void OnMouseWheel(MouseEventArgs e)
    {
      // base class processing
      base.OnMouseWheel(e);

      if (verticalScrollbar.Visible && (verticalScrollbar.DocumentSize > verticalScrollbar.PageSize)) {
        verticalScrollbar.ScrollPosition = verticalScrollbar.ScrollPosition + verticalScrollbar.StepSize * -e.WheelDelta;
      }
      else if (horizontalScrollbar.Visible && (horizontalScrollbar.DocumentSize > horizontalScrollbar.PageSize)) {
        horizontalScrollbar.ScrollPosition = horizontalScrollbar.ScrollPosition + horizontalScrollbar.StepSize * -e.WheelDelta;
      }

      e.Handled = true;
    }

    #endregion

    #endregion

    #region Event Handling methods
    private void ScrollOffsetChanged_handler(object sender, WindowEventArgs e)
    {
      // grab the header scroll value, convert to pixels, and set the scroll bar to match.
      horizontalScrollbar.ScrollPosition = listHeader.RelativeToAbsoluteX(listHeader.ScrollOffset);
    }

    private void SegmentSequenceChanged_handler(object sender, HeaderSequenceEventArgs e)
    {
      MoveColumnImpl(e.OldIndex, e.NewIndex);

      // signal change
      OnColumnMoved(new WindowEventArgs(this));
    }

    private void SegmentSized_handler(object sender, WindowEventArgs e)
    {
      ConfigureScrollbars();

      // signal change
      OnColumnSized(new WindowEventArgs(this));
    }

    private void SortColumnChanged_handler(object sender, WindowEventArgs e)
    {
      int sortColumn = SortColumnIndex;

      // set new sort column on all rows
      for (int i = 0; i < RowCount; ++i) {
        gridData[i].sortColumnIndex = sortColumn;
      }

      ResortGrid();

      // signal change
      OnSortColumnChanged(new WindowEventArgs(this));
    }

    private void SortDirectionChanged_handler(object sender, WindowEventArgs e)
    {
      ResortGrid();

      // signal change
      OnSortDirectionChanged(new WindowEventArgs(this));
    }

    private void SplitterDoubleClicked_handler(object sender, WindowEventArgs e)
    {
      // get the column index for the segment that was double-clicked
      int column = listHeader.GetColumnIndexFromSegment((ListHeaderSegment)e.Window);

      // get the width of the widest item in the column.
      float width = Math.Max(GetWidestColumnItemWidth(column), ListHeader.MinimumSegmentPixelWidth);

      // perform metrics conversion if needed
      if (MetricsMode == MetricsMode.Relative) {
        width = AbsoluteToRelativeX(width);
      }

      // set new column width
      SetColumnWidth(column, width);
    }

    private void ScrollPositionChanged_handler(object sender, WindowEventArgs e)
    {
      // set header offset to match scroll position
      listHeader.ScrollOffset = listHeader.AbsoluteToRelativeX(horizontalScrollbar.ScrollPosition);
    }

    #endregion
  }

}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.