您的位置:首页 > 其它

sourcegrid 应用实例(全部来自官网下载的例子)_GridVirtual

2011-06-26 22:53 585 查看
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

namespace SourceGrid
{
///
/// A abstract Grid control to support large virtual data. You must override: GetCell, CreateRowsObject, CreateColumnsObject
///
[System.ComponentModel.ToolboxItem(false)]
public abstract class GridVirtual : CustomScrollControl
{
private static log4net.ILog m_log = log4net.LogManager.GetLogger(typeof(GridVirtual));
#region Constructor
///
/// Grid constructor
///
public GridVirtual()
{
SetStyle(ControlStyles.Selectable, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.ContainerControl, true);

TabStop = true;

m_Rows = CreateRowsObject();
m_Columns = CreateColumnsObject();

SelectionMode = GridSelectionMode.Cell;

//Create the Controller list for the Cells (the order is important because is the same order that the events will be dispatched)
Controller.AddController(Cells.Controllers.StandardBehavior.Default);
Controller.AddController(Cells.Controllers.MouseSelection.Default);
Controller.AddController(Cells.Controllers.CellEventDispatcher.Default);

m_LinkedControls = new LinkedControlsList(this);

//ToolTip
toolTip = new System.Windows.Forms.ToolTip();
ToolTipText = "";
}

///
/// Abstract method used to create the rows object.
///
protected abstract RowsBase CreateRowsObject();

///
/// Abstract method used to create the columns object.
///
protected abstract ColumnsBase CreateColumnsObject();

#endregion

#region Dispose
///
/// Clean up any resources being used.
///
protected override void Dispose( bool disposing )
{
if( disposing )
{
}
base.Dispose( disposing );
}
#endregion

#region Default Cell Width/Height
private int mDefaultHeight = 20;
///
/// Indicates the default height of new row
///
[DefaultValue(20)]
public int DefaultHeight
{
get{return mDefaultHeight;}
set{mDefaultHeight = value;}
}

private int mDefaultWidth = 50;
///
/// Indicates the default width of new column
///
[DefaultValue(50)]
public int DefaultWidth
{
get{return mDefaultWidth;}
set{mDefaultWidth = value;}
}

private int mMinimumHeight = 0;
///
/// Indicates the minimum height of the rows
///
[DefaultValue(0)]
public int MinimumHeight
{
get{return mMinimumHeight;}
set{mMinimumHeight = value;}
}

private int mMinimumWidth = 0;
///
/// Indicates the minimum width of the columns
///
[DefaultValue(0)]
public int MinimumWidth
{
get{return mMinimumWidth;}
set{mMinimumWidth = value;}
}
#endregion

#region AutoSize
///
/// Auto size the columns and the rows speified
///
///
public virtual void AutoSizeCells(Range p_RangeToAutoSize)
{
SuspendLayout();
if (p_RangeToAutoSize.IsEmpty() == false)
{
Rows.SuspendLayout();
Columns.SuspendLayout();
try
{
for (int c = p_RangeToAutoSize.End.Column; c >= p_RangeToAutoSize.Start.Column ; c--)
Columns.AutoSizeColumn(c,false, p_RangeToAutoSize.Start.Row, p_RangeToAutoSize.End.Row);
for (int r = p_RangeToAutoSize.End.Row; r >= p_RangeToAutoSize.Start.Row ; r--)
Rows.AutoSizeRow(r, false, p_RangeToAutoSize.Start.Column, p_RangeToAutoSize.End.Column);
}
finally
{
//aggiorno top e left
Rows.ResumeLayout();
Columns.ResumeLayout();
}

//Call this method after calculated Bottom and Right
if (AutoStretchColumnsToFitWidth)
Columns.StretchToFit();
if (AutoStretchRowsToFitHeight)
Rows.StretchToFit();
}
ResumeLayout(false);
}

///
/// Auto size all the columns and all the rows with the required width and height
///
public virtual void AutoSizeCells()
{
AutoSizeCells(CompleteRange);
}

private bool m_bAutoStretchColumnsToFitWidth = false;
///
/// True to auto stretch the columns width to always fit the available space, also when the contents of the cell is smaller.
/// False to leave the original width of the columns
///
[DefaultValue(false)]
public bool AutoStretchColumnsToFitWidth
{
get{return m_bAutoStretchColumnsToFitWidth;}
set{m_bAutoStretchColumnsToFitWidth = value;}
}
private bool m_bAutoStretchRowsToFitHeight = false;
///
/// True to auto stretch the rows height to always fit the available space, also when the contents of the cell is smaller.
/// False to leave the original height of the rows
///
[DefaultValue(false)]
public bool AutoStretchRowsToFitHeight
{
get{return m_bAutoStretchRowsToFitHeight;}
set{m_bAutoStretchRowsToFitHeight = value;}
}
#endregion

#region CheckPositions
///
/// Check if the positions saved are still valid, for example if all the selected cells are still valid positions, if not the selection are removed without calling any other methods.
///
public virtual void CheckPositions()
{
Range complete = CompleteRange;

if (m_MouseCellPosition.IsEmpty() == false &&
CompleteRange.Contains(m_MouseCellPosition) == false)
m_MouseCellPosition = Position.Empty;

if (m_MouseDownPosition.IsEmpty() == false &&
CompleteRange.Contains(m_MouseDownPosition) == false)
m_MouseDownPosition = Position.Empty;

if (mDragCellPosition.IsEmpty() == false &&
CompleteRange.Contains(mDragCellPosition) == false)
mDragCellPosition = Position.Empty;

//If the selection contains some invalid cells reset the selection state.
RangeRegion completeRegion = new RangeRegion(complete);
if (
(Selection.ActivePosition.IsEmpty() == false && complete.Contains(Selection.ActivePosition) == false) ||
(Selection.IsEmpty() == false && completeRegion.Contains(Selection.GetSelectionRegion()) == false)
)
Selection.ResetSelection(false);
}
#endregion

#region Position methods
///
/// Get the rectangle of the cell respect to the client area visible, the grid DisplayRectangle.
/// Returns Rectangle.Empty if the Position is empty or if is not valid.
///
///
///
public Rectangle PositionToRectangle(Position position)
{
return RangeToRectangle(PositionToCellRange(position));
}

///
/// Returns the cell at the specified grid view relative point (the point must be relative to the grid display region), SearchInFixedCells = true. Return Position.Empty if no valid cells are found.
///
/// Point relative to the DisplayRectangle area.
///
public virtual Position PositionAtPoint(Point point)
{
int? row = Rows.RowAtPoint(point.Y);
if (row == null)
return Position.Empty;

int? col = Columns.ColumnAtPoint(point.X);
if (col == null)
return Position.Empty;

Position realPos = new Position(row.Value, col.Value);

//Returns the logical position (using row/col span if present)
return PositionToStartPosition(realPos);
}

#endregion

#region Range methods

public Size RangeToSize(Range range)
{
if (range.IsEmpty())
return Size.Empty;

int width = 0;
for (int c = range.Start.Column; c <= range.End.Column; c++)
width += Columns.GetWidth(c);

int height = 0;
for (int r = range.Start.Row; r <= range.End.Row; r++)
height += Rows.GetHeight(r);

return new Size(width, height);
}

///
/// Returns the relative rectangle to the current scrollable area of the specified Range.
/// Returns a Rectangle.Empty if the Range is not valid.
///
public Rectangle RangeToRectangle(Range range)
{
if (range.IsEmpty())
return Rectangle.Empty;

int x = Columns.GetLeft(range.Start.Column);
int y = Rows.GetTop(range.Start.Row);

Size size = RangeToSize(range);
if (size.IsEmpty)
return Rectangle.Empty;

return new Rectangle(new Point(x, y), size);
}

///
/// Get the range of cells at the specified dispaly area.
/// This method consider only the visible cells using the current scroll position.
/// Returns a single Range for the specified grid area (scrollable, fixedtop, fixedleft, fixedtopleft).
/// Returns Range.Empty if there isn't a valid range in the specified area.
///
public Range RangeAtArea(CellPositionType areaType)
{
if (areaType == CellPositionType.FixedTopLeft)
{
if (FixedRows > 0 && Rows.Count >= FixedRows &&
FixedColumns > 0 && Columns.Count >= FixedColumns)
return new Range(0, 0, FixedRows - 1, FixedColumns - 1);
else
return Range.Empty;
}
else if (areaType == CellPositionType.FixedLeft)
{
int actualFixed = FixedColumns;
if (actualFixed > Columns.Count)
actualFixed = Columns.Count;

if (actualFixed <= 0)
return Range.Empty;

int? firstRow = Rows.FirstVisibleScrollableRow;
int? lastRow = Rows.LastVisibleScrollableRow;

if (firstRow == null || lastRow == null)
return Range.Empty;

return new Range(firstRow.Value, 0, lastRow.Value, actualFixed - 1);
}
else if (areaType == CellPositionType.FixedTop)
{
int actualFixed = FixedRows;
if (actualFixed > Rows.Count)
actualFixed = Rows.Count;

if (actualFixed <= 0)
return Range.Empty;

int? firstCol = Columns.FirstVisibleScrollableColumn;
int? lastCol = Columns.LastVisibleScrollableColumn;

if (firstCol == null || lastCol == null)
return Range.Empty;

return new Range(0, firstCol.Value, actualFixed - 1, lastCol.Value);
}
else if (areaType == CellPositionType.Scrollable)
{
int? firstRow = Rows.FirstVisibleScrollableRow;
int? lastRow = Rows.LastVisibleScrollableRow;

int? firstCol = Columns.FirstVisibleScrollableColumn;
int? lastCol = Columns.LastVisibleScrollableColumn;

if (firstRow == null || firstCol == null ||
lastRow == null || lastCol == null)
return Range.Empty;

return new Range(firstRow.Value, firstCol.Value,
lastRow.Value, lastCol.Value);
}
else
throw new SourceGridException("Invalid areaType");
}

/////
///// Get the range for the specific area type and rectangle
/////
//public Range RangeAtRectangle(CellPositionType areaType, Rectangle clipRectangle)
//{
// if (areaType == CellPositionType.FixedTopLeft)
// {
// }
// else if (areaType == CellPositionType.FixedLeft)
// {
// }
// else if (areaType == CellPositionType.FixedTop)
// {
// }
// else if (areaType == CellPositionType.Scrollable)
// {
// }
// else
// throw new SourceGridException("Invalid areaType");
//}

///
/// Get the visible ranges. Returns a list of Range, one for each area.
///
///
private IEnumerable GetVisibleRegion()
{
Range rng;

rng = RangeAtArea(CellPositionType.FixedTopLeft);
if (rng.IsEmpty() == false)
yield return rng;
rng = RangeAtArea(CellPositionType.FixedTop);
if (rng.IsEmpty() == false)
yield return rng;
rng = RangeAtArea(CellPositionType.FixedLeft);
if (rng.IsEmpty() == false)
yield return rng;
rng = RangeAtArea(CellPositionType.Scrollable);
if (rng.IsEmpty() == false)
yield return rng;
}

#endregion

#region Rectangle, range and scrollable areas methods

public List GetVisibleRows(bool returnsPartial)
{
return GetVisibleRows(DisplayRectangle, returnsPartial);
}

public List GetVisibleRows(Rectangle displayRectangle, bool returnsPartial)
{
return Rows.RowsInsideRegion(displayRectangle.Y, displayRectangle.Height, returnsPartial, true);
}

public List GetVisibleColumns(bool returnsPartial)
{
return GetVisibleColumns(DisplayRectangle, returnsPartial);
}

public List GetVisibleColumns(Rectangle displayRectangle, bool returnsPartial)
{
return Columns.ColumnsInsideRegion(displayRectangle.X, displayRectangle.Width, returnsPartial, true);
}

/////
///// Returns the logical scroll size (usually Rows and Columns) for the specified display area.
/////
/////
/////
//protected override Size GetVisibleScrollArea(Rectangle displayRectangle)
//{
// List rows = GetVisibleRows(displayRectangle, false);
// List columns = GetVisibleColumns(displayRectangle, false);

// return new Size(columns.Count, rows.Count);
//}

/////
///// Returns true if the vertical scrollbar is required
/////
/////
//protected override bool RequireVerticalScroll(Size displaySize)
//{
// int currentHeight = 0;

// for (int r = 0; r < Rows.Count; r++)
// {
// currentHeight += Rows.GetHeight(r);

// if (currentHeight > displaySize.Height)
// return true;
// }

// return false;
//}

/////
///// Returns true if the horizontal scrollbar is required
/////
/////
//protected override bool RequireHorizontalScroll(Size displaySize)
//{
// int currentWidth = 0;

// for (int c = 0; c < Columns.Count; c++)
// {
// currentWidth += Columns.GetWidth(c);

// if (currentWidth > displaySize.Width)
// return true;
// }

// return false;
//}

///
/// Calculate the number of rows to scroll. 0 to disable the scrollbar.
/// The returned value is independent from the current scrolling position, must be a fixed value
/// calculated based on the total number of rows and the available area.
///
protected override int GetScrollRows(int displayHeight)
{
int currentHeight = 0;
int scrollRows = 0;

//Remove the fixed rows from the scrollable area
for (int f = 0; f < ActualFixedRows; f++)
displayHeight -= Rows.GetHeight(f);

//Calculate the rows to be scrolled
for (int r = Rows.Count - 1; r >= ActualFixedRows; r--)
{
currentHeight += Rows.GetHeight(r);

if (currentHeight > displayHeight)
return Rows.Count - scrollRows;

scrollRows++;
}

return 0;
}

//MICK(13)
protected override int GetActualFixedRows()
{
return ActualFixedRows;
}

/*
//MICK(14)
protected int GetHiddenRows()
{
int HiddenRows=0;

for (int r = 0; r < Rows.Count; r++)
{
//IsRowVisible - this function name is confusing - is it visible on the screen? - no.
//Also FirstVisibleScrollableRow - is it a bug - it does not consider hidden rows?
if (!Rows.IsRowVisible(r)) HiddenRows++;
}

return HiddenRows;
}
*/

///
/// Calculate the number of columns to scroll. 0 to disable the scrollbar.
/// The returned value is independent from the current scrolling position, must be a fixed value
/// calculated based on the total number of columns and the available area.
///
protected override int GetScrollColumns(int displayWidth)
{
int currentWidth = 0;
int scrollCols = 0;

//Remove the fixed columns from the scrollable area
for (int f = 0; f < ActualFixedColumns; f++)
displayWidth -= Columns.GetWidth(f);

//Calculate the columns to be scrolled
for (int c = Columns.Count - 1; c >= ActualFixedColumns; c--)
{
currentWidth += Columns.GetWidth(c);

if (currentWidth > displayWidth)
return Columns.Count - scrollCols;

scrollCols++;
}

return 0;
}

///
/// Indicates if the specified cell is visible.
///
///
/// True to returns also partial visible cells
///
public bool IsCellVisible(Position position, bool partial)
{
Point scrollPosition;
return !(GetScrollPositionToShowCell(position, partial, out scrollPosition));
}

/////
///// Indicates if the specified range is visible
/////
/////
///// True to return also partial visible cells
/////
//public bool IsRangeVisible(Range range, bool partial)
//{
// List rows = GetVisibleRows(partial);
// List columns = GetVisibleColumns(partial);

// if (rows.Count == 0 || columns.Count == 0)
// return false;

// //All the fixed rows are considered to be visible
// bool isRowVisible = false;
// if (range.Start.Row < FixedRows)
// isRowVisible = true;
// else if (range.Start.Row <= rows[rows.Count - 1] &&
// range.End.Row >= rows[0])
// isRowVisible = true;

// bool isColVisible = false;
// if (range.Start.Column < FixedColumns)
// isColVisible = true;
// else if (range.Start.Column <= columns[columns.Count - 1] &&
// range.End.Column >= columns[0])
// isColVisible = true;

// return isColVisible && isRowVisible;
//}

///
/// Return the scroll position that must be set to show a specific cell.
///
///
/// True to consider also partial visible cells
///
/// Return false if the cell is already visible, return true is the cell is not currently visible.
protected virtual bool GetScrollPositionToShowCell(Position position, bool partial, out Point newScrollPosition)
{
Rectangle displayRectangle = DisplayRectangle;

List rows = GetVisibleRows(partial);
List columns = GetVisibleColumns(partial);

if (rows.Contains(position.Row) && columns.Contains(position.Column))
{
newScrollPosition = CustomScrollPosition;
return false;
}
else
{
CellPositionType posType = GetPositionType(position);
bool isFixedTop = false;
if (posType == CellPositionType.FixedTop || posType == CellPositionType.FixedTopLeft)
isFixedTop = true;
bool isFixedLeft = false;
if (posType == CellPositionType.FixedLeft || posType == CellPositionType.FixedTopLeft)
isFixedLeft = true;

int x;
if (columns.Contains(position.Column)) //Is x visible
{
x = CustomScrollPosition.X;
}
else
{
if (isFixedLeft)
x = 0;
else
x = position.Column - FixedColumns;

//Check if the scrollable positioin if not outside the valid area
int maxX = GetScrollColumns(displayRectangle.Width);
if (x > maxX)
x = maxX;
}

int y;
if (rows.Contains(position.Row)) //Is y visible
{
y = CustomScrollPosition.Y;
y = ExtendTopRowByHiddenRows(y);
}
else
{
//MICK(15): add direction dependent procedure of finding y (and x should be too).
//It means that when you go from bottom to top, the selected item will be at top,
// (this already happens, so this part is not changed)
// when you go from top to bottom, the selected item will be at bottom,
// y (and x) value is adjusted accordingly
if (CustomScrollPosition.Y + ActualFixedRows > position.Row)
{
//MICK(16): direction bottom to top
if (isFixedTop)
y = 0;
else
y = position.Row - FixedRows;

//Check if the scrollable positioin if not outside the valid area
int maxY = GetScrollRows(displayRectangle.Height);
if (y > maxY)
y = maxY;
//MICK(21)
y=ExtendTopRowByHiddenRows(y);

}
else
{
//MICK(17): direction from top to bottom
y = GetTopVisibleRowFromBottomRow(position.Row) - ActualFixedRows;
y = ExtendTopRowByHiddenRows(y);
}
}

newScrollPosition = new Point(x, y);

return true;
}
}

///
/// Scroll the view to show the cell passed. Ensure that if the cell if invisible or partial visible it will be totally visible
///
///
/// true to ignore and consider already visible partial visible cells
/// Returns true if the Cell passed was already visible, otherwise false
public bool ShowCell(Position p_Position, bool ignorePartial)
{
Point l_newCustomScrollPosition;
if (GetScrollPositionToShowCell(p_Position, ignorePartial, out l_newCustomScrollPosition))
{
CustomScrollPosition = l_newCustomScrollPosition;
//il problema di refresh si verifica solo in caso di FixedRows e ColumnsCount maggiori di 0
if (FixedRows > 0 || FixedColumns > 0)
Invalidate();

return false;
}
return true;
}

///
/// Force a cell to redraw.
///
///
public virtual void InvalidateCell(Position position)
{
InvalidateRange(new Range(position));
}

///
/// Force a range of cells to redraw.
///
///
public void InvalidateRange(Range range)
{
range = Range.Intersect(range, CompleteRange); //to ensure the range is valid
if (range.IsEmpty() == false)
{
Rectangle gridRectangle = RangeToRectangle(range);
if (gridRectangle.IsEmpty == false)
Invalidate(gridRectangle, true);
}
}

///
/// Move the scrollbars to the direction specified by the point specified.
/// Method used by the Mouse multi selection (MouseSelection.cs).
/// Scroll the grid only if the specified location is outside the visible area.
///
///
public void ScrollOnPoint(Point mousePoint)
{
//Scroll if necesary
Rectangle scrollRect = GetScrollableArea();

if (mousePoint.X > scrollRect.Right)
CustomScrollLineRight();
if (mousePoint.Y > scrollRect.Bottom)
CustomScrollLineDown();
if (mousePoint.X < scrollRect.Left)
CustomScrollLineLeft();
if (mousePoint.Y < scrollRect.Top)
CustomScrollLineUp();
}

///
/// Invalidate the cells
///
protected override void InvalidateScrollableArea()
{
Invalidate(true);
}

public Rectangle GetScrollableArea()
{
Rectangle rect = DisplayRectangle;

int fixedRows = ActualFixedRows;

int fixedColumns = ActualFixedColumns;

if (fixedRows > 0)
rect.Y = Rows.GetAbsoluteBottom(fixedRows - 1);
else
rect.Y = 0;
rect.Height -= rect.Y;

if (fixedColumns > 0)
rect.X = Columns.GetAbsoluteRight(fixedColumns - 1);
else
rect.X = 0;
rect.Width -= rect.X;

return rect;
}
public Rectangle GetFixedTopLeftArea()
{
Rectangle rect = GetScrollableArea();

return new Rectangle(0, 0, rect.Left, rect.Top);
}

public Rectangle GetFixedTopArea()
{
Rectangle rect = GetScrollableArea();

return new Rectangle(rect.Left, 0, rect.Width, rect.Top);
}
public Rectangle GetFixedLeftArea()
{
Rectangle rect = GetScrollableArea();

return new Rectangle(0, rect.Top, rect.Left, rect.Height);
}
#endregion

#region Row/Column Span
///
/// This method converts a Position to the real start position of cell. This is usefull when RowSpan or ColumnSPan is greater than 1.
/// For example suppose to have at grid[0,0] a cell with ColumnSpan equal to 2. If you call this method with the position 0,0 returns 0,0 and if you call this method with 0,1 return again 0,0.
/// Get the real position for the specified position. For example when p_Position is a merged cell this method returns the starting position of the merged cells.
/// Usually this method returns the same cell specified as parameter. This method is used for processing arrow keys, to find a valid cell when the focus is in a merged cell.
/// For this class returns always p_Position.
///
///
///
public Position PositionToStartPosition(Position p_Position)
{
return PositionToCellRange(p_Position).Start;
}

///
/// This method converts a Position to the real range of the cell. This is usefull when RowSpan or ColumnSpan is greater than 1.
/// For example suppose to have at grid[0,0] a cell with ColumnSpan equal to 2. If you call this method with the position 0,0 returns 0,0-0,1 and if you call this method with 0,1 return again 0,0-0,1.
///
///
///
public virtual Range PositionToCellRange(Position pPosition)
{
return new Range(pPosition);
}
#endregion

#region Drag Fields
///
/// Indica la cella che ha subito l'ultimo evento di DragEnter
///
private Position mDragCellPosition = Position.Empty;

///
/// The last cell that has received a DragEnter event.
///
[Browsable(false),DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Position DragCellPosition
{
get{return mDragCellPosition;}
}

///
/// Fired when the cell in the drag events change. For internal use only.
///
///
///
public virtual void ChangeDragCell(CellContext cell, DragEventArgs pDragEventArgs)
{
if (cell.Position != mDragCellPosition)
{
if (mDragCellPosition.IsEmpty() == false)
Controller.OnDragLeave(new CellContext(this, mDragCellPosition, GetCell(mDragCellPosition)), pDragEventArgs);

if (cell.Position.IsEmpty() == false)
Controller.OnDragEnter(cell, pDragEventArgs);

mDragCellPosition = cell.Position;
}
}
#endregion

#region Selection
///
/// Virtual factory method used to create the SelectionBase derived object.
/// The base method create a different object based on the value of SelectionMode property.
///
///
protected virtual Selection.SelectionBase CreateSelectionObject()
{
switch (SelectionMode)
{
case GridSelectionMode.Cell:
return new Selection.FreeSelection();
case GridSelectionMode.Column:
return new Selection.ColumnSelection();
case GridSelectionMode.Row:
return new Selection.RowSelection();
default:
throw new ArgumentException("SelectionMode not valid", "SelectionMode");
}
}

private Selection.SelectionBase mSelection;
///
/// Gets or sets the Selection object that represents the selected cells and the active cell.
/// Use the SelectionMode property to set the type of selection (free, by row, by column).
/// Override the CreateSelectionObject method to create a custom Selection class for special needs.
///
[Browsable(false),DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Selection.SelectionBase Selection
{
get{return mSelection;}
protected set
{
if (mSelection != null)
mSelection.UnBindToGrid();

mSelection = value;

if (mSelection != null)
mSelection.BindToGrid(this);
}
}

private GridSelectionMode mSelectionMode;
///
/// Gets or sets the selection mode. Changing this property cause the Selection object to be recreated, for this reason remember to set this property at the beginning of your code.
///
public GridSelectionMode SelectionMode
{
get { return mSelectionMode; }
set
{
mSelectionMode = value;
Selection = CreateSelectionObject();
Invalidate(true);
}
}
#endregion

#region Mouse cell positions and mouse selection
///
/// Represents the cell that receive the mouse down event
///
protected Position m_MouseDownPosition = Position.Empty;

///
/// Represents the cell that have received the MouseDown event. You can use this cell for contextmenu logic. Can be null.
///
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Position MouseDownPosition
{
get { return m_MouseDownPosition; }
}

protected Position m_MouseCellPosition = Position.Empty;
///
/// The cell position currently under the mouse cursor (row, col).
/// If you MouseDown on a cell this cell is the MouseCellPosition until an MouseUp is fired
///
[Browsable(false),DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Position MouseCellPosition
{
get{return m_MouseCellPosition;}
}

///
/// Fired when the cell under the mouse change. For internal use only.
///
///
public virtual void ChangeMouseCell(Position p_Cell)
{
if (m_MouseCellPosition != p_Cell)
{
//se la cella che sta perdento il mouse ?anche quella che ha ricevuto un eventuale evento di MouseDown non scateno il MouseLeave (che invece verr?scatenato dopo il MouseUp)
if (m_MouseCellPosition.IsEmpty() == false &&
m_MouseCellPosition != m_MouseDownPosition)
{
Controller.OnMouseLeave(new CellContext(this, m_MouseCellPosition), EventArgs.Empty);
}

m_MouseCellPosition = p_Cell;
if (m_MouseCellPosition.IsEmpty() == false)
{
Controller.OnMouseEnter(new CellContext(this, m_MouseCellPosition), EventArgs.Empty);
}
}
}

///
/// Change the cell currently under the mouse
///
///
///
public virtual void ChangeMouseDownCell(Position p_MouseDownCell, Position p_MouseCell)
{
m_MouseDownPosition = p_MouseDownCell;

ChangeMouseCell(p_MouseCell);
}

///
/// Fired when the selection eith the mouse is finished
///
///
protected virtual void OnMouseSelectionFinish(RangeEventArgs e)
{
m_OldMouseSelectionRange = Range.Empty;
}

private Range m_OldMouseSelectionRange = Range.Empty;
private Range m_MouseSelectionRange = Range.Empty;
///
/// Returns the cells that are selected with the mouse. Range.Empty if no cells are selected. Consider that this method returns valid cells only during the mouse down operations, when release the mouse the cells are selected and you can read them using Grid.Selection object.
///
[Browsable(false),DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public virtual Range MouseSelectionRange
{
get{return m_MouseSelectionRange;}
}

///
/// Fired when the mouse selection must be canceled. See also MouseSelectionRange.
///
///
protected virtual void OnUndoMouseSelection(RangeEventArgs e)
{
Selection.SelectRange(e.Range, false);
}

///
/// Fired when the mouse selection is succesfully finished. See also MouseSelectionRange.
///
///
protected virtual void OnApplyMouseSelection(RangeEventArgs e)
{
Selection.SelectRange(e.Range, true);
}

///
/// Fired when the mouse selection change. See also MouseSelectionRange.
///
///
protected virtual void OnMouseSelectionChange(EventArgs e)
{
Range l_MouseRange = MouseSelectionRange;

OnUndoMouseSelection(new RangeEventArgs(m_OldMouseSelectionRange));

OnApplyMouseSelection(new RangeEventArgs(l_MouseRange));

m_OldMouseSelectionRange = l_MouseRange;
}

///
/// Fired when the mouse selection finish. See also MouseSelectionRange.
///
public void MouseSelectionFinish()
{
if (m_MouseSelectionRange != Range.Empty)
OnMouseSelectionFinish(new RangeEventArgs(m_OldMouseSelectionRange));

m_MouseSelectionRange = Range.Empty;
}

///
/// Fired when the corner of the mouse selection change. For internal use only.
///
///
public virtual void ChangeMouseSelectionCorner(Position p_Corner)
{
Range newMouseSelection = new Range(Selection.ActivePosition, p_Corner);

bool l_bChange = false;
if (m_MouseSelectionRange != newMouseSelection)
{
m_MouseSelectionRange = newMouseSelection;
l_bChange = true;
}

if (l_bChange)
OnMouseSelectionChange(EventArgs.Empty);
}
#endregion

#region Special Keys and command keys
///
/// Processes a command key.
///
///
///
///
protected override bool ProcessCmdKey(
ref Message msg,
Keys keyData
)
{
if ((keyData == Keys.Enter ||
keyData == Keys.Escape ||
keyData == Keys.Tab ||
keyData == (Keys.Tab | Keys.Shift)) &&
OverrideCommonCmdKey)
{
KeyEventArgs args = new KeyEventArgs(keyData);
OnKeyDown(args);
if (args.Handled)
return true;
else
return base.ProcessCmdKey(ref msg, keyData);
}
else
return base.ProcessCmdKey(ref msg, keyData);
}

private bool m_OverrideCommonCmdKey = true;
///
/// True to override with the ProcessCmdKey the common Command Key: Enter, Escape, Tab
///
[DefaultValue(true)]
public bool OverrideCommonCmdKey
{
get { return m_OverrideCommonCmdKey; }
set { m_OverrideCommonCmdKey = value; }
}

private GridSpecialKeys m_GridSpecialKeys = GridSpecialKeys.Default;

///
/// Special keys that the grid can handle. You can change this enum to block or allow some special keys function. For example to disable Ctrl+C Copy operation remove from this enum the GridSpecialKeys.Ctrl_C.
///
[DefaultValue(GridSpecialKeys.Default)]
public GridSpecialKeys SpecialKeys
{
get{return m_GridSpecialKeys;}
set{m_GridSpecialKeys = value;}
}

private bool mAcceptsInputChar = true;
///
/// True accepts input char when the grid has the focus. Used for example to start the edit operation without processing the char.
/// If you set this property to false when the character is sended to the windows forms handler and can be used for example to execute directly some access key (shortcut keys).
/// Default is true.
///
[DefaultValue(true)]
public bool AcceptsInputChar
{
get{return mAcceptsInputChar;}
set{mAcceptsInputChar = value;}
}

///
/// Process Delete, Ctrl+C, Ctrl+V, Up, Down, Left, Right, Tab keys
///
///
public virtual void ProcessSpecialGridKey(KeyEventArgs e)
{
if (e.Handled)
return;

bool enableArrows,enableTab,enablePageDownUp;
enableArrows = enableTab = enablePageDownUp = false;

if ( (SpecialKeys & GridSpecialKeys.Arrows) == GridSpecialKeys.Arrows)
enableArrows = true;
if ( (SpecialKeys & GridSpecialKeys.PageDownUp) == GridSpecialKeys.PageDownUp)
enablePageDownUp = true;
if ( (SpecialKeys & GridSpecialKeys.Tab) == GridSpecialKeys.Tab)
enableTab = true;

bool enableEscape = false;
if ( (SpecialKeys & GridSpecialKeys.Escape) == GridSpecialKeys.Escape)
enableEscape = true;
bool enableEnter = false;
if ( (SpecialKeys & GridSpecialKeys.Enter) == GridSpecialKeys.Enter)
enableEnter = true;

#region Processing keys
//Escape
if (e.KeyCode == Keys.Escape && enableEscape)
{
CellContext focusCellContext = new CellContext(this, Selection.ActivePosition);
if (focusCellContext.Cell != null && focusCellContext.IsEditing())
{
if (focusCellContext.EndEdit(true))
e.Handled = true;
}
}

//Enter
if (e.KeyCode == Keys.Enter && enableEnter)
{
CellContext focusCellContext = new CellContext(this, Selection.ActivePosition);
if (focusCellContext.Cell != null && focusCellContext.IsEditing())
{
focusCellContext.EndEdit(false);

e.Handled = true;
}
}

//Tab
if (e.KeyCode == Keys.Tab && enableTab)
{
CellContext focusCellContext = new CellContext(this, Selection.ActivePosition);
if (focusCellContext.Cell != null && focusCellContext.IsEditing())
{
//se l'editing non riesce considero il tasto processato
// altrimenti no, in questo modo il tab ha effetto anche per lo spostamento
if (focusCellContext.EndEdit(false) == false)
{
e.Handled = true;
return;
}
}
}
#endregion

#region Navigate keys: arrows, tab and PgDown/Up
if (e.KeyCode == Keys.Down && enableArrows)
{
Selection.MoveActiveCell(1, 0);
e.Handled = true;
}
else if (e.KeyCode == Keys.Up && enableArrows)
{
Selection.MoveActiveCell(-1, 0);
e.Handled = true;
}
else if (e.KeyCode == Keys.Right && enableArrows)
{
Selection.MoveActiveCell(0, 1);
e.Handled = true;
}
else if (e.KeyCode == Keys.Left && enableArrows)
{
Selection.MoveActiveCell(0, -1);
e.Handled = true;
}
else if (e.KeyCode == Keys.Tab && enableTab)
{
//If the tab failed I automatically select the next control in the form (SelectNextControl)

if (e.Modifiers == Keys.Shift) // backward
{
if (Selection.MoveActiveCell(0, -1, -1, int.MaxValue) == false)
FindForm().SelectNextControl(this, false, true, true, true);
e.Handled = true;
}
else //forward
{
if (Selection.MoveActiveCell(0, 1, 1, int.MinValue) == false)
FindForm().SelectNextControl(this, true, true, true, true);
e.Handled = true;
}
}
else if ( (e.KeyCode == Keys.PageUp || e.KeyCode == Keys.PageDown)
&& enablePageDownUp)
{
//MICK(18): I commented out this part - focusing is done within my CustomScrollPageDown() and CustomScrollPageUp()
//Point focusPoint = PositionToRectangle(Selection.ActivePosition).Location;
//focusPoint.Offset(1, 1); //in modo da entrare nella cella

if (e.KeyCode == Keys.PageDown)
CustomScrollPageDown();
else if (e.KeyCode == Keys.PageUp)
CustomScrollPageUp();

//MICK(18): I commented out this part - focusing is done within my CustomScrollPageDown() and CustomScrollPageUp()
//Position newPosition = PositionAtPoint(focusPoint);
//if (Selection.CanReceiveFocus(newPosition))
// Selection.Focus(newPosition, true);

e.Handled = true;
}
#endregion

#region Clipboard
RangeRegion selRegion = Selection.GetSelectionRegion();

//Paste
if (e.Control && e.KeyCode == Keys.V)
{
PerformPaste(selRegion);
e.Handled = true;
}
//Copy
else if (e.Control && e.KeyCode == Keys.C)
{
PerformCopy(selRegion);
e.Handled = true;
}
//Cut
else if (e.Control && e.KeyCode == Keys.X)
{
PerformCut(selRegion);
e.Handled = true;
}
//Delete
else if (e.KeyCode == Keys.Delete)
{
PerformDelete(selRegion);
e.Handled = true;
}
#endregion
}

///
/// Checks if cut mode is enabled, and copies
/// data into clipaborad
///
///
public void PerformCut(RangeRegion selRegion)
{
bool cutEnabled = (ClipboardMode & ClipboardMode.Cut) == ClipboardMode.Cut;
if (cutEnabled == false || selRegion.IsEmpty() == true)
return;
Range rng = selRegion[0];

RangeData data = new RangeData();
data.LoadData(this, rng, rng.Start, CutMode.CutImmediately);
RangeData.ClipboardSetData(data);
}

///
/// Checks if paste mode is enabled and writes
/// data into grid from clipboard
///
///
public void PerformPaste(RangeRegion selRegion)
{
bool pasteEnabled = (ClipboardMode & ClipboardMode.Paste) == ClipboardMode.Paste;
if (pasteEnabled == false || selRegion.IsEmpty() == true)
return;
RangeData rngData = RangeData.ClipboardGetData();

if (rngData != null)
{
Range rng = selRegion[0];

Range destinationRange = rngData.FindDestinationRange(this, rng.Start);

rngData.WriteData(this, destinationRange);

Selection.ResetSelection(true);
Selection.SelectRange(destinationRange, true);
}
}

///
/// Checks if copy mode is enabled and copies data into clipboard
///
///
public void PerformCopy(RangeRegion selRegion)
{
bool copyEnabled = (ClipboardMode & ClipboardMode.Copy) == ClipboardMode.Copy;
if (copyEnabled == false || selRegion.IsEmpty() == true)
return;
Range rng = selRegion[0];

RangeData data = new RangeData();
data.LoadData(this, rng, rng.Start, CutMode.None);
RangeData.ClipboardSetData(data);
}

///
/// Checks if delete is enabled, and clears values from
/// given destination by invoking ClearValues(selRegion)
///
///
public void PerformDelete(RangeRegion selRegion)
{
bool deleteEnabled = (ClipboardMode & ClipboardMode.Delete) == ClipboardMode.Delete;
if (deleteEnabled == false || selRegion.IsEmpty() == true)
return;
ClearValues(selRegion);
}
///
/// Allow the grid to handle specials keys like Arrows and Tab. See also Grid.SpecialKeys
///
///
///
protected override bool IsInputKey(Keys keyData)
{
//Handle arrows and tab keys only if OverrideCommonCmdKey
if (OverrideCommonCmdKey)
{
if ((SpecialKeys & GridSpecialKeys.Arrows) == GridSpecialKeys.Arrows)
{
switch (keyData)
{
case Keys.Up:
case Keys.Down:
case Keys.Left:
case Keys.Right:
//altrimenti venivano (per qualche strana ragione) selezionate le scrollbars
case (Keys.Up | Keys.Shift):
case (Keys.Down | Keys.Shift):
case (Keys.Left | Keys.Shift):
case (Keys.Right | Keys.Shift):
return true;
}
}

if ((SpecialKeys & GridSpecialKeys.Tab) == GridSpecialKeys.Tab)
{
switch (keyData)
{
case Keys.Tab:
case (Keys.Tab | Keys.Shift):
return true;
}
}
}

return base.IsInputKey(keyData);
}

///
/// IsInputChar method.
///
///
///
protected override bool IsInputChar(char charCode)
{
return AcceptsInputChar;
}
#endregion

#region Paint

protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);

//NOTE: For now I draw all the visible cells (not only the invalidated cells).
using (DevAge.Drawing.GraphicsCache grCache = new DevAge.Drawing.GraphicsCache(e.Graphics, e.ClipRectangle))
{
foreach (Range rng in GetVisibleRegion())
{
OnRangePaint(new RangePaintEventArgs(this, grCache, rng));
}
}
}

public event RangePaintEventHandler RangePaint;
protected virtual void OnRangePaint(RangePaintEventArgs e)
{
Rectangle drawRectangle = RangeToRectangle(e.DrawingRange);

System.Drawing.Drawing2D.GraphicsState state = e.GraphicsCache.Graphics.Save();

try
{
e.GraphicsCache.Graphics.SetClip(drawRectangle);

int top = drawRectangle.Top;
int width;
int height;
for (int r = e.DrawingRange.Start.Row; r <= e.DrawingRange.End.Row; r++)
{
height = Rows.GetHeight(r);

int left = drawRectangle.Left;
for (int c = e.DrawingRange.Start.Column; c <= e.DrawingRange.End.Column; c++)
{
width = Columns.GetWidth(c);

Position position = new Position(r, c);

Cells.ICellVirtual cell = GetCell(position);
if (cell != null)
{
Rectangle drawRect = new Rectangle(left, top, width, height);

//Draw the cell only if inside the ClipRectangle
if (e.GraphicsCache.ClipRectangle.IsEmpty ||
e.GraphicsCache.ClipRectangle.IntersectsWith(drawRect))
{
CellContext cellContext = new CellContext(this, position, cell);
PaintCell(e.GraphicsCache, cellContext, drawRect);
}
}

left += width;
}

top += height;
}

//Draw the decorators
foreach (SourceGrid.Decorators.DecoratorBase dec in Decorators)
{
if (dec.IntersectWith(e.DrawingRange))
dec.Draw(e);
}

if (RangePaint != null)
RangePaint(this, e);
}
finally
{
e.GraphicsCache.Graphics.Restore(state);
}
}

///
/// Draw the specified Cell
///
protected virtual void PaintCell(DevAge.Drawing.GraphicsCache graphics,
CellContext cellContext,
RectangleF drawRectangle)
{
if (drawRectangle.Width > 0 && drawRectangle.Height > 0 &&
cellContext.CanBeDrawn() )
{
cellContext.Cell.View.DrawCell(cellContext, graphics, drawRectangle);
}
}
#endregion

#region Mouse
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);

Position mousePosition = PositionAtPoint(new Point(e.X, e.Y));
Cells.ICellVirtual mouseCell = GetCell(mousePosition);

//Call MouseMove on the cell that receive tha MouseDown event
if (MouseDownPosition.IsEmpty() == false)
{
Cells.ICellVirtual mouseDownCell = GetCell(MouseDownPosition);
if (mouseDownCell != null)
Controller.OnMouseMove(new CellContext(this, MouseDownPosition, mouseDownCell), e);
}
else //se non ho nessuna cella attualmente che ha ricevuto un mousedown, l'evento di MouseMove viene segnalato sulla cella correntemente sotto il Mouse
{
// se non c'?nessuna cella MouseDown cambio la cella corrente sotto il Mouse
ChangeMouseCell(mousePosition);//in ogni caso cambio la cella corrente

if (mousePosition.IsEmpty() == false && mouseCell != null)
{
// I call MouseMove on the current cell only if there aren't any cells under the mouse
Controller.OnMouseMove(new CellContext(this, mousePosition, mouseCell), e);
}
}
}

protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);

ChangeMouseCell(Position.Empty);
}

protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);

//verifico che l'eventuale edit sia terminato altrimenti esco
if (Selection.ActivePosition.IsEmpty() == false)
{
CellContext focusCell = new CellContext(this, Selection.ActivePosition);
if (focusCell.Cell != null && focusCell.IsEditing())
{
if (focusCell.EndEdit(false) == false)
return;
}
}

//scateno eventi di MouseDown
Position position = PositionAtPoint(new Point(e.X, e.Y));
if (position.IsEmpty() == false)
{
m_log.DebugFormat("Processing OnMouseDown event, because clickPosition was not empty");
Cells.ICellVirtual cellMouseDown = GetCell(position);
if (cellMouseDown != null)
{
ChangeMouseDownCell(position, position);

//Cell.OnMouseDown
CellContext cellContext = new CellContext(this, position, cellMouseDown);
Controller.OnMouseDown(cellContext, e);
}
}
else
ChangeMouseDownCell(Position.Empty, Position.Empty);
}

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

m_log.DebugFormat("Processing OnMouseUp event, because clickPosition was not empty");
if (MouseDownPosition.IsEmpty() == false)
{
Cells.ICellVirtual mouseDownCell = GetCell(MouseDownPosition);
if (mouseDownCell != null)
Controller.OnMouseUp(new CellContext(this, MouseDownPosition, mouseDownCell), e);

ChangeMouseDownCell(Position.Empty, PositionAtPoint(new Point(e.X, e.Y)));
}
}

protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);

Position clickPosition = PositionAtPoint(PointToClient(Control.MousePosition));

//Se ho precedentemente scatenato un MouseDown su una cella
// e se questa corrisponde alla cella sotto il puntatore del mouse (non posso usare MouseCellPosition perch?questa viene aggiornata solo quando non si ha una cella come MouseDownPosition
if (MouseDownPosition.IsEmpty() == false &&
MouseDownPosition == clickPosition)
{
Cells.ICellVirtual mouseDownCell = GetCell(MouseDownPosition);
if (mouseDownCell != null)
Controller.OnClick(new CellContext(this, MouseDownPosition, mouseDownCell), e);
}
}

protected override void OnDoubleClick(EventArgs e)
{
Position clickPosition = PositionAtPoint(PointToClient(Control.MousePosition));
HandleMouseDoubleClick(clickPosition, e);
base.OnDoubleClick(e);
}

private void HandleMouseDoubleClick(Position clickPosition, EventArgs e)
{
if (clickPosition.IsEmpty() == true)
return;
m_log.DebugFormat("Processing OnMouseDoubleClick event, because clickPosition was not empty");
Cells.ICellVirtual mouseDownCell = GetCell(clickPosition);
if (mouseDownCell != null)
Controller.OnDoubleClick(new CellContext(this, clickPosition, mouseDownCell), e);
}

protected override void OnMouseDoubleClick(MouseEventArgs e)
{
Position clickPosition = PositionAtPoint(PointToClient(Control.MousePosition));
HandleMouseDoubleClick(clickPosition, e);
base.OnMouseDoubleClick(e);
}

///
/// Fired when a user scroll with the mouse wheel
///
///
protected override void OnMouseWheel(MouseEventArgs e)
{
base.OnMouseWheel(e);

CustomScrollWheel(e.Delta);
}

#endregion

#region Keyboard

protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);

if (Selection.ActivePosition.IsEmpty() == false)
{
Cells.ICellVirtual focusCell = GetCell(Selection.ActivePosition);
if (focusCell != null)
Controller.OnKeyDown(new CellContext(this, Selection.ActivePosition, focusCell), e);
}

if (e.Handled == false)
ProcessSpecialGridKey(e);
}

protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);

if (Selection.ActivePosition.IsEmpty() == false)
{
Cells.ICellVirtual focusCell = GetCell(Selection.ActivePosition);
if (focusCell != null)
Controller.OnKeyUp(new CellContext(this, Selection.ActivePosition, focusCell), e);
}
}

protected override void OnKeyPress(KeyPressEventArgs e)
{
base.OnKeyPress(e);

//Che if is not Tab, Enter or Copy/Paste
if (Selection.ActivePosition.IsEmpty() || e.KeyChar == '/t' || e.KeyChar == 13 ||
e.KeyChar == 3 || e.KeyChar == 22 || e.KeyChar == 24)
{
}
else
{
Cells.ICellVirtual focusCell = GetCell(Selection.ActivePosition);
if (focusCell != null)
Controller.OnKeyPress(new CellContext(this, Selection.ActivePosition, focusCell), e);
}
}

#endregion

#region Focus
private bool mAutoFocusFirstCellOnEnter = true;
///
/// Set the Focus on the grid with an optional behaviour.
/// If selectFirstCell is true and the FocusStyle is equal to FocusStyle.FocusFirstCellOnEnter then
/// if there isn't a cell selected the first cell of the grid receive became the active cell.
/// Otherwise the focus is not changed.
/// The Default is true.
///
///
public virtual bool Focus(bool selectFirstCell)
{
bool ret;
try
{
mAutoFocusFirstCellOnEnter = selectFirstCell;
ret = Focus();
}
finally
{
mAutoFocusFirstCellOnEnter = true;
}

return ret;
}
protected override void OnEnter(EventArgs e)
{
base.OnEnter(e);

//Move the focus on the first cell if there isn't an active cell and the FocusStyle FocusFirstCellOnEnter is on.
if ((Selection.FocusStyle & FocusStyle.FocusFirstCellOnEnter) == FocusStyle.FocusFirstCellOnEnter &&
mAutoFocusFirstCellOnEnter && Selection.ActivePosition.IsEmpty())
{
Selection.FocusFirstCell(false);
}
}
protected override void OnValidated(EventArgs e)
{
base.OnValidated(e);

//NOTE: I use OnValidated and not OnLostFocus because is not called when the focus is on another child control (for example an editor control) or OnLeave because before Validating event and so the validation can still be stopped

if ((Selection.FocusStyle & FocusStyle.RemoveFocusCellOnLeave) == FocusStyle.RemoveFocusCellOnLeave)
{
Selection.Focus(Position.Empty, false);
}

if ((Selection.FocusStyle & FocusStyle.RemoveSelectionOnLeave) == FocusStyle.RemoveSelectionOnLeave)
{
Selection.ResetSelection(true);
}
}

#endregion

#region Controls linked
private LinkedControlsList m_LinkedControls;

///
/// List of controls that are linked to a specific cell position. For example is used for editors controls. Key=Control, Value=Position. The controls are automatically removed from the list when they are removed from the Grid.Controls collection
///
[Browsable(false),DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public LinkedControlsList LinkedControls
{
get{return m_LinkedControls;}
}

///
/// OnHScrollPositionChanged
///
///
protected override void OnHScrollPositionChanged(ScrollPositionChangedEventArgs e)
{
base.OnHScrollPositionChanged (e);

ArrangeLinkedControls();
}

///
/// OnVScrollPositionChanged
///
///
protected override void OnVScrollPositionChanged(ScrollPositionChangedEventArgs e)
{
base.OnVScrollPositionChanged (e);

ArrangeLinkedControls();
}

///
/// Refresh the linked controls bounds
///
public virtual void ArrangeLinkedControls()
{
SuspendLayout();
foreach (LinkedControlValue linked in m_LinkedControls)
{
if (linked.Position.IsEmpty())
continue;

Control control = linked.Control;
Cells.ICellVirtual cell = GetCell(linked.Position);

Rectangle rect = PositionToRectangle(linked.Position);

if (cell != null && linked.UseCellBorder)
rect = Rectangle.Round(cell.View.Border.GetContentRectangle(rect));

control.Bounds = rect;
}
ResumeLayout(false);
}

#endregion

#region Layout
///
/// Recalculate the scrollbar position and value based on the current cells, scroll client area, linked controls and more. If redraw == false this method has not effect.
///
private void PerformStretch()
{
if (AutoStretchColumnsToFitWidth || AutoStretchRowsToFitHeight)
{
if (AutoStretchColumnsToFitWidth && AutoStretchRowsToFitHeight)
{
Rows.AutoSize(false);
Columns.AutoSize(false);
Columns.StretchToFit();
Rows.StretchToFit();
}
else if (AutoStretchColumnsToFitWidth)
{
Columns.AutoSize(true);
Columns.StretchToFit();
}
else if (AutoStretchRowsToFitHeight)
{
Rows.AutoSize(true);
Rows.StretchToFit();
}
}
}

protected override void OnResize(EventArgs e)
{
base.OnResize (e);

SuspendLayout();
SetScrollArea();
PerformStretch();
ResumeLayout(true);
}

///
/// Force to recalculate scrollbars and panels location. Used usually after changing width and height of the columns / rows, or after adding or removing rows and columns.
///
public virtual void OnCellsAreaChanged()
{
SuspendLayout();
SetScrollArea();
CheckPositions();
ArrangeLinkedControls();
ResumeLayout(true);
}

private void SetScrollArea()
{
//MICK(19): original version is obviously a bug - when vertPage is calculated in this manner
//the thumb height is not right
//and scrolling is not continuous (not all items are displayed when scrolling)
//ORIG: int vertPage = Math.Max(Rows.Count / 10, 1);
//ORIG: int horPage = Math.Max(Columns.Count / 10, 1);

//MICK(20)
int vertPage = GetVisibleRows(false).Count - ActualFixedRows;
int horPage = GetVisibleColumns(false).Count - ActualFixedColumns;

LoadScrollArea(vertPage, horPage);
}
#endregion

#region Sort Range
///
/// Sort a range of the grid
///
/// Range to sort
/// Index of the column relative to the grid to use as sort keys, must be between start and end col of the range
/// Ascending true, Descending false
/// CellComparer, if null the default comparer will be used
public void SortRangeRows(IRangeLoader p_RangeToSort,
int keyColumn,
bool p_bAsc,
IComparer p_CellComparer)
{
Range l_Range = p_RangeToSort.GetRange(this);
SortRangeRows(l_Range, keyColumn, p_bAsc, p_CellComparer);
}

///
/// Sort a range of the grid.
///
///
/// Index of the column relative to the grid to use as sort keys, must be between start and end col
/// Ascending true, Descending false
/// CellComparer, if null the default ValueCellComparer comparer will be used
public void SortRangeRows(Range p_Range,
int keyColumn,
bool p_bAscending,
IComparer p_CellComparer)
{
SortRangeRowsEventArgs eventArgs = new SortRangeRowsEventArgs(p_Range, keyColumn, p_bAscending, p_CellComparer);

if (SortingRangeRows!=null)
SortingRangeRows(this, eventArgs);

OnSortingRangeRows(eventArgs);

if (SortedRangeRows!=null)
SortedRangeRows(this, eventArgs);

OnSortedRangeRows(eventArgs);
}

///
/// Fired when calling SortRangeRows method
///
[Browsable(true)]
public event SortRangeRowsEventHandler SortingRangeRows;

///
/// Fired after calling SortRangeRows method
///
[Browsable(true)]
public event SortRangeRowsEventHandler SortedRangeRows;

///
/// Fired when calling SortRangeRows method
///
///
protected virtual void OnSortingRangeRows(SortRangeRowsEventArgs e)
{
}
///
/// Fired after calling SortRangeRows method
///
///
protected virtual void OnSortedRangeRows(SortRangeRowsEventArgs e)
{
}
#endregion

#region GetCell methods

///
/// Return the Cell at the specified Row and Col position. Abstract, must be implemented in the derived class.
///
///
///
///
public abstract Cells.ICellVirtual GetCell(int p_iRow, int p_iCol);

///
/// Return the Cell at the specified Row and Col position. This method is called for sort operations and for Move operations. If position is Empty return null. This method calls GetCell(int p_iRow, int p_iCol)
///
///
///
public Cells.ICellVirtual GetCell(Position p_Position)
{
if (p_Position.IsEmpty())
return null;
else
return GetCell(p_Position.Row, p_Position.Column);
}

///
/// Returns all the cells at specified row position
///
///
///
public virtual Cells.ICellVirtual[] GetCellsAtRow(int p_RowIndex)
{
Cells.ICellVirtual[] l_Cells = new Cells.ICellVirtual[Columns.Count];
for (int c = 0; c < Columns.Count; c++)
l_Cells[c] = GetCell(p_RowIndex, c);

return l_Cells;
}

///
/// Returns all the cells at specified column position
///
///
///
public virtual Cells.ICellVirtual[] GetCellsAtColumn(int p_ColumnIndex)
{
Cells.ICellVirtual[] l_Cells = new Cells.ICellVirtual[Rows.Count];
for (int r = 0; r < Rows.Count; r++)
l_Cells[r] = GetCell(r, p_ColumnIndex);

return l_Cells;
}

#endregion

#region Rows, Columns
private int m_FixedRows = 0;
///
/// Gets or Sets how many rows are not scrollable
///
[DefaultValue(0)]
public int FixedRows
{
get{return m_FixedRows;}
set
{
if (m_FixedRows != value)
{
m_FixedRows = value;
OnCellsAreaChanged();
}
}
}
public int ActualFixedRows
{
get
{
if (FixedRows > Rows.Count)
return Rows.Count;
else
return FixedRows;
}
}

private int m_FixedCols = 0;
///
/// Gets or Sets how many cols are not scrollable
///
[DefaultValue(0)]
public int FixedColumns
{
get{return m_FixedCols;}
set
{
if (m_FixedCols != value)
{
m_FixedCols = value;
OnCellsAreaChanged();
}
}
}
public int ActualFixedColumns
{
get
{
if (FixedColumns > Columns.Count)
return Columns.Count;
else
return FixedColumns;
}
}

private RowsBase m_Rows;

///
/// RowsCount informations
///
[Browsable(false),DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public RowsBase Rows
{
get{return m_Rows;}
}

private ColumnsBase m_Columns;

///
/// Columns informations
///
[Browsable(false),DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ColumnsBase Columns
{
get{return m_Columns;}
}

///
/// Returns the type of a cell position
///
///
///
public CellPositionType GetPositionType(Position position)
{
if (position.IsEmpty())
return CellPositionType.Empty;
else if (position.Row < FixedRows && position.Column < FixedColumns)
return CellPositionType.FixedTopLeft;
else if (position.Row < FixedRows)
return CellPositionType.FixedTop;
else if (position.Column < FixedColumns)
return CellPositionType.FixedLeft;
else
return CellPositionType.Scrollable;
}

///
/// Returns a Range that represents the complete cells of the grid
///
[Browsable(false),DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Range CompleteRange
{
get
{
if (Rows.Count > 0 && Columns.Count > 0)
return new Range(0, 0, Rows.Count - 1, Columns.Count - 1);
else
return Range.Empty;
}
}
#endregion

#region Exception
///
/// Event fired when an exception is throw in some method that require a notification to the user.
///
public event ExceptionEventHandler UserException;

///
/// Event fired when an exception is throw in some method that require a notification to the user.
/// If not handled by the user (Handled property = false) a MessageBox is used to display the exception.
///
///
public virtual void OnUserException(ExceptionEventArgs e)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("Exception on editing cell: " + e.Exception.ToString());
#endif

if (UserException!=null)
UserException(this, e);

if (e.Handled == false)
{
DevAge.Windows.Forms.ErrorDialog.Show(this, e.Exception, "Error");
e.Handled = true;
}
}
#endregion

#region Right To Left Support
//With Visual Studio 2005 probably I can use the new RightToLeftLayout and TextRenderer class
// se also
// http://www.microsoft.com/middleeast/msdn/visualstudio2005.aspx // http://www.microsoft.com/middleeast/msdn/mirror.aspx // http://msdn2.microsoft.com/en-us/library/fh376txk.aspx // http://msdn2.microsoft.com/en-us/library/7d3337xw.aspx // "How to: Create Mirrored Windows Forms and Controls" http://msdn2.microsoft.com/en-us/library/xwbz5ws0.aspx
const int WS_EX_LAYOUTRTL = 0x400000;
const int WS_EX_NOINHERITLAYOUT = 0x100000;
private bool m_Mirrored = false;

[Description("Change the right-to-left layout."), DefaultValue(false),
Localizable(true), Category("Appearance"), Browsable(true)]
public bool Mirrored
{
get
{
return m_Mirrored;
}
set
{
if (m_Mirrored != value)
{
m_Mirrored = value;
base.OnRightToLeftChanged(EventArgs.Empty);
}
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams CP;
CP = base.CreateParams;
if (Mirrored)
CP.ExStyle = CP.ExStyle | WS_EX_LAYOUTRTL;
return CP;
}
}

///
/// Hide the RightToLeft property and returns always No.
///
[Browsable(false)]
[DefaultValue(RightToLeft.No)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override RightToLeft RightToLeft
{
get { return RightToLeft.No; }
set { }
}
#endregion

#region Clear
///
/// Clear all the selected cells with a valid Model.
///
public virtual void ClearValues(RangeRegion region)
{
PositionCollection positions = region.GetCellsPositions();
foreach (Position pos in positions)
{
CellContext cellContext = new CellContext(this, pos);
if (cellContext.Cell != null)
{
if (cellContext.Cell.Editor != null)
cellContext.Cell.Editor.ClearCell(cellContext);
}
}
}
#endregion

#region ToolTip
private System.Windows.Forms.ToolTip toolTip;
///
/// ToolTip text
///
public string ToolTipText
{
get { return toolTip.GetToolTip(this); }
set { toolTip.SetToolTip(this, value); }
}

///
/// Gets the tooltip control used when showing tooltip text.
///
public ToolTip ToolTip
{
get { return toolTip; }
}
#endregion

#region Decorator List
private Decorators.DecoratorList mDecorators = new Decorators.DecoratorList();
///
/// Grid decorators, used to draw additiona contents on the drawing surface
///
public Decorators.DecoratorList Decorators
{
get { return mDecorators; }
}
#endregion

#region Controllers
private Cells.Controllers.ControllerContainer mController = new SourceGrid.Cells.Controllers.ControllerContainer();
public Cells.Controllers.ControllerContainer Controller
{
get { return mController; }
}
#endregion

#region Clipboard
private ClipboardMode mClipboardMode = ClipboardMode.None;

///
/// Gets or sets the clipboard operation mode. Default is none
///
[DefaultValue(ClipboardMode.None)]
public ClipboardMode ClipboardMode
{
get { return mClipboardMode; }
set { mClipboardMode = value; }
}
#endregion

#region MICK(1)
#region Scroll PageDown/Up/Right/Left/LineUp/Down/Right/Left

#region NewScrollDown
public override void CustomScrollPageDown()
{
bool NeedActualScrolling=false;
int NewProposedTopRow=0;
int NewProposedFocusedRow=0;

Range rngFocusedCell = PositionToCellRange(Selection.ActivePosition);

//if there is no selection here (ActiveRow is undefined),
//put selection in the first possible cell
//confusing NAMING: Rows.IsRowVisible -> Rows.NotHidden - because it is not hidden,
// may be confused with visible on screen within visible page

if (rngFocusedCell.IsEmpty())
{
Selection.MoveActiveCell(1, 0);
return;
}

List rows = GetVisibleRows(false);

if (rows.Count <= ActualFixedRows + 1)
{
Selection.MoveActiveCell(1, 0);
return;
};

int firstVisibleRow = rows[ActualFixedRows];
int lastVisibleRow = rows[rows.Count - 1];

if (IsThisRowVisibleOnScreen(rngFocusedCell.Start.Row))
{ //focus is on the screen

int BottommostFocusableRow = GetBottommostFocusableRowFromRange(firstVisibleRow, lastVisibleRow);
if (rngFocusedCell.ContainsRow(BottommostFocusableRow))
{ //need actual scrolling, ie. displaying new page
int FindFirstFocusableRow = FindFirstFocusableRowInDownDirection(lastVisibleRow);
if (FindFirstFocusableRow != -1)
{
NeedActualScrolling = true;
NewProposedTopRow = lastVisibleRow;
int NewProposedBottomRow = GetBottomVisibleRowFromTopRow(NewProposedTopRow);
NewProposedFocusedRow = GetBottommostFocusableRowFromRange(NewProposedTopRow, NewProposedBottomRow);

if (NewProposedFocusedRow == -1)
{//There is no Focusable Row in the proposed page, so there need to be new page and focus found
NewProposedFocusedRow = FindFirstFocusableRowInDownDirection(NewProposedBottomRow);
NewProposedTopRow = GetTopVisibleRowFromBottomRow(NewProposedFocusedRow);
}
}
else
{ //no focusable cells below -
//in this case we might scroll down page, leaving focus unchanged
//or move focus to the side like in arrow down handler

//this is only temporary
//MICK(23)
Selection.MoveActiveCell(1, 0);
return;
}
}
else
{ //this time only move focus, wihout actual scrolling

Position newPosition = new Position(BottommostFocusableRow, Selection.ActivePosition.Column);
Selection.Focus(newPosition, true);
}

}
else
{// focus is not on the screen, so actual scrolling is done always
NeedActualScrolling = true;
NewProposedTopRow = rngFocusedCell.Start.Row;
int NewProposedBottomRow = GetBottomVisibleRowFromTopRow(NewProposedTopRow);
NewProposedFocusedRow = GetBottommostFocusableRowFromRange(NewProposedTopRow, NewProposedBottomRow);
}

if (NeedActualScrolling)
{
int NewTopRow=0;
int NewFocusedRow=0;

GetValidScrollDown(NewProposedTopRow, NewProposedFocusedRow, out NewTopRow, out NewFocusedRow);
NewTopRow = ExtendTopRowByHiddenRows(NewTopRow);

base.CustomScrollPageToLine(NewTopRow - ActualFixedRows);

if (rngFocusedCell.ContainsRow(NewFocusedRow) == false)
{
Position newPosition = new Position(NewFocusedRow, Selection.ActivePosition.Column);
Selection.Focus(newPosition, true);
}

}

}

int ExtendTopRowByHiddenRows(int TopRow)
{//Simple (though not the best) way to handle hidden rows

if (TopRow == 0) return 0;
for (int k = TopRow; k >= ActualFixedRows; k--)
{
if (Rows.IsRowVisible(k)) return k;
}
return ActualFixedRows;
}
int FindFirstFocusableRowInDownDirection(int TopRow)
{
for (int k = TopRow; k < Rows.Count; k++)
{
Position newPosition = new Position(k, Selection.ActivePosition.Column);
if (Selection.CanReceiveFocus(newPosition)) return k;
}
return -1;
}
int GetBottommostFocusableRowFromRange(int firstRow, int lastRow)
{
for (int k = lastRow; k >= firstRow; k--)
{
Position newPosition = new Position(k, Selection.ActivePosition.Column);
if (Selection.CanReceiveFocus(newPosition)) return k;
}
return -1;
}
bool IsThisRowVisibleOnScreen(int Row)
{
List rows = GetVisibleRows(false);
int firstVisibleRow = rows[ActualFixedRows];
int lastVisibleRow = rows[rows.Count - 1];
if ((Row <= lastVisibleRow) && (Row >= firstVisibleRow)) return true;
else return false;
}
int GetTopVisibleRowFromBottomRow(int BottomRow)
{
int displayHeight = DisplayRectangle.Height;
int currentHeight = 0;
//int CurrentRows = 0;

//Remove the fixed rows from the scrollable area
for (int f = 0; f < ActualFixedRows; f++)
displayHeight -= Rows.GetHeight(f);

int r = 0;
for (r = BottomRow; r >= ActualFixedRows; r--)
{
currentHeight += Rows.GetHeight(r);

if (currentHeight == displayHeight)
{
return r;
}
if (currentHeight > displayHeight)
{
if (r == BottomRow) return r; else return r + 1;
}

}
return 0;
}
int GetBottomVisibleRowFromTopRow(int TopRow)
{
int displayHeight = DisplayRectangle.Height;
int currentHeight = 0;
//int CurrentRows = 0;

//Remove the fixed rows from the scrollable area
for (int f = 0; f < ActualFixedRows; f++)
displayHeight -= Rows.GetHeight(f);

int r = 0;
for (r = TopRow; r < Rows.Count; r++)
{
currentHeight += Rows.GetHeight(r);

if (currentHeight == displayHeight)
{
return r;
}
if (currentHeight > displayHeight)
{
if (r == TopRow) return r; else return r - 1;
}

}
return Rows.Count - 1;
}
void GetValidScrollDown(int NewProposedTopRow, int NewProposedFocusedRow, out int NewTopRow, out int NewFocusedRow)
{ //if scrool down is too far down, it needs to be corrected
NewTopRow=NewProposedTopRow;
NewFocusedRow = NewProposedFocusedRow;

int ValidLastTopRow = GetTopVisibleRowFromBottomRow(Rows.Count - 1);

if (NewProposedTopRow > ValidLastTopRow)
NewTopRow = ValidLastTopRow;

if (NewProposedFocusedRow > Rows.Count - 1)
NewFocusedRow = Rows.Count - 1;
}
#endregion

#region NewScrollUp
public override void CustomScrollPageUp()
{
bool NeedActualScrolling=false;
int NewProposedTopRow=0;
int NewProposedFocusedRow=0;

//confusing NAMING: ActivePosition -> FocusedPosition - it would be better
int FocusedRow = Selection.ActivePosition.Row;
//TODO: if there is no selection here (ActiveRow is undefined),
//put selection in the first possible cell
//confusing NAMING: Rows.IsRowVisible -> Rows.NotHidden - because it is not hidden,
// may be confused with visible on screen within visible page

if (FocusedRow == -1)
{
Selection.MoveActiveCell(-1, 0);
return;
}

List rows = GetVisibleRows(false);

//TODO: if Rows.heightActualFixedRows)
{
int displayHeight = DisplayRectangle.Height;

//Remove the fixed rows from the scrollable area
for (int f = 0; f < ActualFixedRows; f++)
displayHeight -= Rows.GetHeight(f);

if (displayHeight <= Rows.GetHeight(FocusedRow - 1))
{
Selection.MoveActiveCell(-1, 0);
return;
}
}

if (IsThisRowVisibleOnScreen(FocusedRow))
{ //focus is on the screen

int TopmostFocusableRow = GetTopmostFocusableRowFromRange(firstVisibleRow, lastVisibleRow);
if (TopmostFocusableRow == FocusedRow)
{ //need actual scrolling, ie. displaying new page
int FindFirstFocusableRow = FindFirstFocusableRowInUpDirection(firstVisibleRow);
if (FindFirstFocusableRow != -1)
{
NeedActualScrolling = true;
int NewProposedBottomRow = firstVisibleRow;
NewProposedTopRow = GetTopVisibleRowFromBottomRow(NewProposedBottomRow);
NewProposedFocusedRow = GetTopmostFocusableRowFromRange(NewProposedTopRow, NewProposedBottomRow);

if (NewProposedFocusedRow == -1)
{//There is no Focusable Row in the proposed page, so there need to be new page and focus found
NewProposedFocusedRow = FindFirstFocusableRowInUpDirection(NewProposedTopRow);
NewProposedTopRow = NewProposedFocusedRow;
}
}
else
{ //no focusable cells below -
//in this case we might scroll down page, leaving focus unchanged
//or move focus to the side like in arrow down handler

//this is only temporary
//MICK(24)
Selection.MoveActiveCell(-1, 0);
return;
}
}
else
{ //this time only move focus, wihout actual scrolling

Position newPosition = new Position(TopmostFocusableRow, Selection.ActivePosition.Column);
Selection.Focus(newPosition, true);
}

}
else
{// focus is not on the screen, so actual scrolling is done always
NeedActualScrolling = true;

int NewProposedBottomRow = FocusedRow;
NewProposedTopRow = GetTopVisibleRowFromBottomRow(NewProposedBottomRow);
NewProposedFocusedRow = GetTopmostFocusableRowFromRange(NewProposedTopRow, NewProposedBottomRow);

//NewProposedTopRow = FocusedRow;
//NewProposedFocusedRow = GetBottomVisibleRowFromTopRow(NewProposedTopRow);
}

if (NeedActualScrolling)
{
int NewTopRow=0;
int NewFocusedRow=0;

GetValidScrollUp(NewProposedTopRow, NewProposedFocusedRow, out NewTopRow, out NewFocusedRow);
NewTopRow = ExtendTopRowByHiddenRows(NewTopRow);

base.CustomScrollPageToLine(NewTopRow - ActualFixedRows);

if (NewFocusedRow != FocusedRow)
{
Position newPosition = new Position(NewFocusedRow, Selection.ActivePosition.Column);
Selection.Focus(newPosition, true);
}

}

}

int FindFirstFocusableRowInUpDirection(int BottomRow)
{
for (int k = BottomRow; k > ActualFixedRows; k--)
{
Position newPosition = new Position(k, Selection.ActivePosition.Column);
if (Selection.CanReceiveFocus(newPosition)) return k;
}
return -1;
}
int GetTopmostFocusableRowFromRange(int firstRow, int lastRow)
{
for (int k = firstRow; k <= lastRow; k++)
{
Position newPosition = new Position(k, Selection.ActivePosition.Column);
if (Selection.CanReceiveFocus(newPosition)) return k;
}
return -1;
}
void GetValidScrollUp(int NewProposedTopRow, int NewProposedFocusedRow, out int NewTopRow, out int NewFocusedRow)
{ //if scrool down is too far down, it needs to be corrected
NewTopRow = NewProposedTopRow;
NewFocusedRow = NewProposedFocusedRow;

int ValidFirstTopRow = ActualFixedRows;

if (NewProposedTopRow < ValidFirstTopRow)
NewTopRow = ValidFirstTopRow;

if (NewProposedFocusedRow < ValidFirstTopRow)
NewFocusedRow = ValidFirstTopRow;
}

#endregion

#endregion

protected override int GetTopRowPositionForLargeIncr()
{
List rows = GetVisibleRows(false);
if (rows.Count != 0)
{
int NextPage = rows[rows.Count - 1] + 1;
int LastTopPosition = GetTopVisibleRowFromBottomRow(Rows.Count - 1);
if (NextPage > LastTopPosition) NextPage = LastTopPosition;
return NextPage;
}
else
{
rows = GetVisibleRows(true);
if (rows.Count != 0)
{
int NextPage = rows[rows.Count - 1] + 1;
int LastTopPosition = GetTopVisibleRowFromBottomRow(Rows.Count - 1);
if (NextPage > LastTopPosition) NextPage = LastTopPosition;
return NextPage;
}

}
//we have to finally give up and scroll without any optimization
return -1;
}

protected override int GetTopRowPositionForLargeDecr()
{
List rows = GetVisibleRows(false);
if (rows.Count != 0)
{
int NextPage = GetTopVisibleRowFromBottomRow(rows[ActualFixedRows] - 1);
int FirstTopPosition = ActualFixedRows;
if (NextPage < FirstTopPosition) NextPage = FirstTopPosition;
return NextPage;
}
else
{
rows = GetVisibleRows(true);
if (rows.Count != 0)
{
int NextPage = GetTopVisibleRowFromBottomRow(rows[ActualFixedRows] - 1);
int FirstTopPosition = ActualFixedRows;
if (NextPage < FirstTopPosition) NextPage = FirstTopPosition;
return NextPage;
}

}
//we have to finally give up and scroll without any optimization
return -1;
}
#endregion
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐