//######################################################################################### //★★★★★★★ http://www.cnpopsoft.com [华普软件] ★★★★★★★ //★★★★★★★ 华普软件 - VB & C#.NET 专业论文与源码荟萃! ★★★★★★★ //######################################################################################### /* * 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.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.Drawing; using System.Drawing.Design; using XPTable.Events; using XPTable.Models.Design; namespace XPTable.Models { /// /// Represents a collection of Rows and Cells displayed in a Table. /// [DesignTimeVisible(true), ToolboxItem(true), ToolboxBitmap(typeof(TableModel))] public class TableModel : Component { #region Event Handlers /// /// Occurs when a Row is added to the TableModel /// public event TableModelEventHandler RowAdded; /// /// Occurs when a Row is removed from the TableModel /// public event TableModelEventHandler RowRemoved; /// /// Occurs when the value of the TableModel Selection property changes /// public event SelectionEventHandler SelectionChanged; /// /// Occurs when the value of the RowHeight property changes /// public event EventHandler RowHeightChanged; #endregion #region Class Data /// /// The default height of a Row /// public static readonly int DefaultRowHeight = 15; /// /// The minimum height of a Row /// public static readonly int MinimumRowHeight = 14; /// /// The maximum height of a Row /// public static readonly int MaximumRowHeight = 1024; /// /// The collection of Rows's contained in the TableModel /// private RowCollection rows; /// /// The Table that the TableModel belongs to /// private Table table; /// /// The currently selected Rows and Cells /// private Selection selection; /// /// The height of each Row in the TableModel /// private int rowHeight; #endregion #region Constructor /// /// Initializes a new instance of the TableModel class with default settings /// public TableModel() { this.Init(); } /// /// Initializes a new instance of the TableModel class with an array of Row objects /// /// An array of Row objects that represent the Rows /// of the TableModel public TableModel(Row[] rows) { if (rows == null) { throw new ArgumentNullException("rows", "Row[] cannot be null"); } this.Init(); if (rows.Length > 0) { this.Rows.AddRange(rows); } } /// /// Initialise default settings /// private void Init() { this.rows = null; this.selection = new Selection(this); this.table = null; this.rowHeight = TableModel.DefaultRowHeight; } #endregion #region Methods /// /// Releases the unmanaged resources used by the TableModel and optionally /// releases the managed resources /// protected override void Dispose(bool disposing) { if (disposing) { } base.Dispose(disposing); } /// /// Returns the index of the Row that lies on the specified position /// /// The y-coordinate to check /// The index of the Row at the specified position or -1 if /// no Row is found public int RowIndexAt(int yPosition) { int row = yPosition / this.RowHeight; if (row < 0 || row > this.Rows.Count - 1) { return -1; } return row; } #endregion #region Properties /// /// Gets the Cell located at the specified row index and column index /// /// The row index of the Cell /// The column index of the Cell [Browsable(false)] public Cell this[int row, int column] { get { if (row < 0 || row >= this.Rows.Count) { return null; } if (column < 0 || column >= this.Rows[row].Cells.Count) { return null; } return this.Rows[row].Cells[column]; } } /// /// Gets the Cell located at the specified cell position /// /// The position of the Cell [Browsable(false)] public Cell this[CellPos cellPos] { get { return this[cellPos.Row, cellPos.Column]; } } /// /// A TableModel.RowCollection representing the collection of /// Rows contained within the TableModel /// [Category("Behavior"), Description("Row Collection"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Editor(typeof(RowCollectionEditor), typeof(UITypeEditor))] public RowCollection Rows { get { if (this.rows == null) { this.rows = new RowCollection(this); } return this.rows; } } /// /// A TableModel.Selection representing the collection of selected /// Rows and Cells contained within the TableModel /// [Browsable(false)] public Selection Selections { get { if (this.selection == null) { this.selection = new Selection(this); } return this.selection; } } /// /// Gets or sets the height of each Row in the TableModel /// [Category("Appearance"), Description("The height of each row")] public int RowHeight { get { return this.rowHeight; } set { if (value < TableModel.MinimumRowHeight) { value = TableModel.MinimumRowHeight; } else if (value > TableModel.MaximumRowHeight) { value = TableModel.MaximumRowHeight; } if (this.rowHeight != value) { this.rowHeight = value; this.OnRowHeightChanged(EventArgs.Empty); } } } /// /// Specifies whether the RowHeight property should be serialized at /// design time /// /// true if the RowHeight property should be serialized, /// false otherwise private bool ShouldSerializeRowHeight() { return this.rowHeight != TableModel.DefaultRowHeight; } /// /// Gets the total height of all the Rows in the TableModel /// [Browsable(false)] public int TotalRowHeight { get { return this.Rows.Count * this.RowHeight; } } /// /// Gets the Table the TableModel belongs to /// [Browsable(false)] public Table Table { get { return this.table; } } /// /// Gets or sets the Table the TableModel belongs to /// internal Table InternalTable { get { return this.table; } set { this.table = value; } } /// /// Gets whether the TableModel is able to raise events /// protected internal bool CanRaiseEvents { get { // check if the Table that the TableModel belongs to is able to // raise events (if it can't, the TableModel shouldn't raise // events either) if (this.Table != null) { return this.Table.CanRaiseEvents; } return true; } } /// /// Gets whether the TableModel is enabled /// internal bool Enabled { get { if (this.Table == null) { return true; } return this.Table.Enabled; } } /// /// Updates the Row's Index property so that it matches the Rows /// position in the RowCollection /// /// The index to start updating from internal void UpdateRowIndicies(int start) { if (start == -1) { start = 0; } for (int i=start; i /// Raises the RowAdded event /// /// A TableModelEventArgs that contains the event data protected internal virtual void OnRowAdded(TableModelEventArgs e) { e.Row.InternalTableModel = this; e.Row.InternalIndex = e.RowFromIndex; e.Row.ClearSelection(); this.UpdateRowIndicies(e.RowFromIndex); if (this.CanRaiseEvents) { if (this.Table != null) { this.Table.OnRowAdded(e); } if (RowAdded != null) { RowAdded(this, e); } } } /// /// Raises the RowRemoved event /// /// A TableModelEventArgs that contains the event data protected internal virtual void OnRowRemoved(TableModelEventArgs e) { if (e.Row != null && e.Row.TableModel == this) { e.Row.InternalTableModel = null; e.Row.InternalIndex = -1; if (e.Row.AnyCellsSelected) { e.Row.ClearSelection(); this.Selections.RemoveRow(e.Row); } } this.UpdateRowIndicies(e.RowFromIndex); if (this.CanRaiseEvents) { if (this.Table != null) { this.Table.OnRowRemoved(e); } if (RowRemoved != null) { RowRemoved(this, e); } } } /// /// Raises the SelectionChanged event /// /// A SelectionEventArgs that contains the event data protected virtual void OnSelectionChanged(SelectionEventArgs e) { if (this.CanRaiseEvents) { if (this.Table != null) { this.Table.OnSelectionChanged(e); } if (SelectionChanged != null) { SelectionChanged(this, e); } } } /// /// Raises the RowHeightChanged event /// /// An EventArgs that contains the event data protected virtual void OnRowHeightChanged(EventArgs e) { if (this.CanRaiseEvents) { if (this.Table != null) { this.Table.OnRowHeightChanged(e); } if (RowHeightChanged != null) { RowHeightChanged(this, e); } } } /// /// Raises the RowPropertyChanged event /// /// A RowEventArgs that contains the event data internal void OnRowPropertyChanged(RowEventArgs e) { if (this.Table != null) { this.Table.OnRowPropertyChanged(e); } } /// /// Raises the CellAdded event /// /// A RowEventArgs that contains the event data internal void OnCellAdded(RowEventArgs e) { if (this.Table != null) { this.Table.OnCellAdded(e); } } /// /// Raises the CellRemoved event /// /// A RowEventArgs that contains the event data internal void OnCellRemoved(RowEventArgs e) { if (this.Table != null) { this.Table.OnCellRemoved(e); } } /// /// Raises the CellPropertyChanged event /// /// A CellEventArgs that contains the event data internal void OnCellPropertyChanged(CellEventArgs e) { if (this.Table != null) { this.Table.OnCellPropertyChanged(e); } } #endregion #region Selection /// /// Represents the collection of selected Rows and Cells in a TableModel. /// public class Selection { #region Class Data /// /// The TableModel that owns the Selection /// private TableModel owner; /// /// The list of Rows that have selected Cells /// private ArrayList rows; /// /// The starting cell of a selection that uses the shift key /// private CellPos shiftSelectStart; /// /// The ending cell of a selection that uses the shift key /// private CellPos shiftSelectEnd; #endregion #region Constructor /// /// Initializes a new instance of the TableModel.Selection class /// that belongs to the specified TableModel /// /// A TableModel representing the tableModel that owns /// the Selection public Selection(TableModel owner) { if (owner == null) { throw new ArgumentNullException("owner", "owner cannot be null"); } this.owner = owner; this.rows = new ArrayList(); this.shiftSelectStart = CellPos.Empty; this.shiftSelectEnd = CellPos.Empty; } #endregion #region Methods #region Add /// /// Replaces the currently selected Cells with the Cell at the specified /// row and column indexes /// /// The row index of the Cell to be selected /// The column index of the Cell to be selected public void SelectCell(int row, int column) { // don't bother going any further if the cell // is already selected if (this.rows.Count == 1) { Row r = (Row) this.rows[0]; if (r.InternalIndex == row && r.SelectedCellCount == 1) { if (column >= 0 && column < r.Cells.Count) { if (r.Cells[column].Selected) { return; } } } } this.SelectCells(row, column, row, column); } /// /// Replaces the currently selected Cells with the Cell at the specified CellPos /// /// A CellPos thst specifies the row and column indicies of /// the Cell to be selected public void SelectCell(CellPos cellPos) { this.SelectCell(cellPos.Row, cellPos.Column); } /// /// Replaces the currently selected Cells with the Cells located between the specified /// start and end row/column indicies /// /// The row index of the start Cell /// The column index of the start Cell /// The row index of the end Cell /// The column index of the end Cell public void SelectCells(int startRow, int startColumn, int endRow, int endColumn) { int[] oldSelectedIndicies = this.SelectedIndicies; this.InternalClear(); if (this.InternalAddCells(startRow, startColumn, endRow, endColumn)) { this.owner.OnSelectionChanged(new SelectionEventArgs(this.owner, oldSelectedIndicies, this.SelectedIndicies)); } this.shiftSelectStart = new CellPos(startRow, startColumn); this.shiftSelectEnd = new CellPos(endRow, endColumn); } /// /// Replaces the currently selected Cells with the Cells located between the specified /// start and end CellPos /// /// A CellPos that specifies the start Cell /// A CellPos that specifies the end Cell public void SelectCells(CellPos start, CellPos end) { this.SelectCells(start.Row, start.Column, end.Row, end.Column); } /// /// Adds the Cell at the specified row and column indicies to the current selection /// /// The row index of the Cell to add to the selection /// The column index of the Cell to add to the selection public void AddCell(int row, int column) { this.AddCells(row, column, row, column); } /// /// Adds the Cell at the specified row and column indicies to the current selection /// /// A CellPos that specifies the Cell to add to the selection public void AddCell(CellPos cellPos) { this.AddCell(cellPos.Row, cellPos.Column); } /// /// Adds the Cells located between the specified start and end row/column indicies /// to the current selection /// /// The row index of the start Cell /// The column index of the start Cell /// The row index of the end Cell /// The column index of the end Cell public void AddCells(int startRow, int startColumn, int endRow, int endColumn) { int[] oldSelectedIndicies = this.SelectedIndicies; if (InternalAddCells(startRow, startColumn, endRow, endColumn)) { this.owner.OnSelectionChanged(new SelectionEventArgs(this.owner, oldSelectedIndicies, this.SelectedIndicies)); } this.shiftSelectStart = new CellPos(startRow, startColumn); this.shiftSelectEnd = new CellPos(endRow, endColumn); } /// /// Adds the Cells located between the specified start and end CellPos to the /// current selection /// /// A CellPos that specifies the start Cell /// A CellPos that specifies the end Cell public void AddCells(CellPos start, CellPos end) { this.AddCells(start.Row, start.Column, end.Row, end.Column); } /// /// Adds the Cells located between the specified start and end CellPos to the /// current selection without raising an event /// /// A CellPos that specifies the start Cell /// A CellPos that specifies the end Cell /// true if any Cells were added, false otherwise private bool InternalAddCells(CellPos start, CellPos end) { return this.InternalAddCells(start.Row, start.Column, end.Row, end.Column); } /// /// Adds the Cells located between the specified start and end row/column indicies /// to the current selection without raising an event /// /// The row index of the start Cell /// The column index of the start Cell /// The row index of the end Cell /// The column index of the end Cell /// true if any Cells were added, false otherwise private bool InternalAddCells(int startRow, int startColumn, int endRow, int endColumn) { this.Normalise(ref startRow, ref endRow); this.Normalise(ref startColumn, ref endColumn); bool anyAdded = false; bool anyAddedInRow = false; for (int i=startRow; i<=endRow; i++) { if (i >= this.owner.Rows.Count) { break; } Row r = this.owner.Rows[i]; for (int j=startColumn; j<=endColumn; j++) { if (j >= r.Cells.Count) { break; } if (!r.Cells[j].Selected && r.Cells[j].Enabled) { if (this.owner.Table != null && !this.owner.Table.IsCellEnabled(i, j)) { continue; } r.Cells[j].SetSelected(true); r.InternalSelectedCellCount++; anyAdded = true; anyAddedInRow = true; } } if (anyAddedInRow && !this.rows.Contains(r)) { this.rows.Add(r); } anyAddedInRow = false; } return anyAdded; } /// /// Adds the Cells between the last selection start Cell and the Cell at the /// specified row/column indicies to the current selection. Any Cells that are /// between the last start and end Cells that are not in the new area are /// removed from the current selection /// /// The row index of the shift selected Cell /// The column index of the shift selected Cell public void AddShiftSelectedCell(int row, int column) { int[] oldSelectedIndicies = this.SelectedIndicies; if (this.shiftSelectStart == CellPos.Empty) { this.shiftSelectStart = new CellPos(0, 0); } bool changed = false; if (this.shiftSelectEnd != CellPos.Empty) { changed = this.InternalRemoveCells(this.shiftSelectStart, this.shiftSelectEnd); changed |= this.InternalAddCells(this.shiftSelectStart, new CellPos(row, column)); } else { changed = this.InternalAddCells(0, 0, row, column); } if (changed) { this.owner.OnSelectionChanged(new SelectionEventArgs(this.owner, oldSelectedIndicies, this.SelectedIndicies)); } this.shiftSelectEnd = new CellPos(row, column); } /// /// Adds the Cells between the last selection start Cell and the Cell at the /// specified CellPas to the current selection. Any Cells that are /// between the last start and end Cells that are not in the new area are /// removed from the current selection /// /// A CellPos that specifies the shift selected Cell public void AddShiftSelectedCell(CellPos cellPos) { this.AddShiftSelectedCell(cellPos.Row, cellPos.Column); } /// /// Ensures that the first index is smaller than the second index, /// performing a swap if necessary /// /// The first index /// The second index private void Normalise(ref int a, ref int b) { if (a < 0) { a = 0; } if (b < 0) { b = 0; } if (b < a) { int temp = a; a = b; b = temp; } } #endregion #region Clear /// /// Removes all selected Rows and Cells from the selection /// public void Clear() { if (this.rows.Count > 0) { int[] oldSelectedIndicies = this.SelectedIndicies; this.InternalClear(); this.shiftSelectStart = CellPos.Empty; this.shiftSelectEnd = CellPos.Empty; this.owner.OnSelectionChanged(new SelectionEventArgs(this.owner, oldSelectedIndicies, this.SelectedIndicies)); } } /// /// Removes all selected Rows and Cells from the selection without raising an event /// private void InternalClear() { if (this.rows.Count > 0) { for (int i=0; i /// Removes the Cell at the specified row and column indicies from the current selection /// /// The row index of the Cell to remove from the selection /// The column index of the Cell to remove from the selection public void RemoveCell(int row, int column) { this.RemoveCells(row, column, row, column); } /// /// Removes the Cell at the specified row and column indicies from the current selection /// /// A CellPos that specifies the Cell to remove from the selection public void RemoveCell(CellPos cellPos) { this.RemoveCell(cellPos.Row, cellPos.Column); } /// /// Removes the Cells located between the specified start and end row/column indicies /// from the current selection /// /// The row index of the start Cell /// The column index of the start Cell /// The row index of the end Cell /// The column index of the end Cell public void RemoveCells(int startRow, int startColumn, int endRow, int endColumn) { if (this.rows.Count > 0) { int[] oldSelectedIndicies = this.SelectedIndicies; if (this.InternalRemoveCells(startRow, startColumn, endRow, endColumn)) { this.owner.OnSelectionChanged(new SelectionEventArgs(this.owner, oldSelectedIndicies, this.SelectedIndicies)); } this.shiftSelectStart = new CellPos(startRow, startColumn); this.shiftSelectEnd = new CellPos(endRow, endColumn); } } /// /// Removes the Cells located between the specified start and end CellPos from the /// current selection /// /// A CellPos that specifies the start Cell /// A CellPos that specifies the end Cell public void RemoveCells(CellPos start, CellPos end) { this.RemoveCells(start.Row, start.Column, end.Row, end.Column); } /// /// Removes the Cells located between the specified start and end CellPos from the /// current selection without raising an event /// /// A CellPos that specifies the start Cell /// A CellPos that specifies the end Cell /// true if any Cells were added, false otherwise private bool InternalRemoveCells(CellPos start, CellPos end) { return this.InternalRemoveCells(start.Row, start.Column, end.Row, end.Column); } /// /// Removes the Cells located between the specified start and end row/column indicies /// from the current selection without raising an event /// /// The row index of the start Cell /// The column index of the start Cell /// The row index of the end Cell /// The column index of the end Cell /// true if any Cells were added, false otherwise private bool InternalRemoveCells(int startRow, int startColumn, int endRow, int endColumn) { this.Normalise(ref startRow, ref endRow); this.Normalise(ref startColumn, ref endColumn); bool anyRemoved = false; for (int i=startRow; i<=endRow; i++) { if (i >= this.owner.Rows.Count) { break; } Row r = this.owner.Rows[i]; for (int j=startColumn; j<=endColumn; j++) { if (j >= r.Cells.Count) { break; } if (r.Cells[j].Selected) { r.Cells[j].SetSelected(false); r.InternalSelectedCellCount--; anyRemoved = true; } } if (!r.AnyCellsSelected) { if (this.rows.Contains(r)) { this.rows.Remove(r); } } } return anyRemoved; } /// /// Removes the specified Row from the selection /// /// The Row to be removed from the selection internal void RemoveRow(Row row) { if (this.rows.Contains(row)) { int[] oldSelectedIndicies = this.SelectedIndicies; this.rows.Remove(row); this.owner.OnSelectionChanged(new SelectionEventArgs(this.owner, oldSelectedIndicies, this.SelectedIndicies)); } } #endregion #region Queries /// /// Returns whether the Cell at the specified row and column indicies is /// currently selected /// /// The row index of the specified Cell /// The column index of the specified Cell /// true if the Cell at the specified row and column indicies is /// selected, false otherwise public bool IsCellSelected(int row, int column) { if (row < 0 || row >= this.owner.Rows.Count) { return false; } return this.owner.Rows[row].IsCellSelected(column); } /// /// Returns whether the Cell at the specified CellPos is currently selected /// /// A CellPos the represents the row and column indicies /// of the Cell to check /// true if the Cell at the specified CellPos is currently selected, /// false otherwise public bool IsCellSelected(CellPos cellPos) { return this.IsCellSelected(cellPos.Row, cellPos.Column); } /// /// Returns whether the Row at the specified index in th TableModel is /// currently selected /// /// The index of the Row to check /// true if the Row at the specified index is currently selected, /// false otherwise public bool IsRowSelected(int index) { if (index < 0 || index >= this.owner.Rows.Count) { return false; } return this.owner.Rows[index].AnyCellsSelected; } #endregion #endregion #region Properties /// /// Gets an array that contains the currently selected Rows /// public Row[] SelectedItems { get { if (this.rows.Count == 0) { return new Row[0]; } this.rows.Sort(new RowComparer()); return (Row[]) this.rows.ToArray(typeof(Row)); } } /// /// Gets an array that contains the indexes of the currently selected Rows /// public int[] SelectedIndicies { get { if (this.rows.Count == 0) { return new int[0]; } this.rows.Sort(new RowComparer()); int[] indicies = new int[this.rows.Count]; for (int i=0; i /// Returns a Rectange that bounds the currently selected Rows /// public Rectangle SelectionBounds { get { if (this.rows.Count == 0) { return Rectangle.Empty; } int[] indicies = this.SelectedIndicies; return this.CalcSelectionBounds(indicies[0], indicies[indicies.Length-1]); } } /// /// /// /// /// /// internal Rectangle CalcSelectionBounds(int start, int end) { this.Normalise(ref start, ref end); Rectangle bounds = new Rectangle(); if (this.owner.Table != null && this.owner.Table.ColumnModel != null) { bounds.Width = this.owner.Table.ColumnModel.VisibleColumnsWidth; } bounds.Y = start * this.owner.RowHeight; if (start == end) { bounds.Height = this.owner.RowHeight; } else { bounds.Height = ((end + 1) * this.owner.RowHeight) - bounds.Y; } return bounds; } #endregion } #endregion } }