|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// Description: Table implementation
//
// See spec at WPP TableOM.doc
//
using MS.Internal;
using MS.Internal.PtsHost;
using MS.Internal.PtsTable;
using MS.Utility;
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Threading;
using System.Windows;
using System.Windows.Automation.Peers;
using System.Windows.Media;
using System.Windows.Markup;
using MS.Internal.PtsHost.UnsafeNativeMethods;
using MS.Internal.Documents;
#pragma warning disable 1634, 1691 // suppressing PreSharp warnings
namespace System.Windows.Documents
{
/// <summary>
/// Table implements
/// </summary>
[ContentProperty("RowGroups")]
public class Table : Block, IAddChild, IAcceptInsertion
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
/// <summary>
/// Static ctor. Initializes property metadata.
/// </summary>
static Table()
{
MarginProperty.OverrideMetadata(typeof(Table), new FrameworkPropertyMetadata(new Thickness(Double.NaN)));
}
/// <summary>
/// Table constructor.
/// </summary>
public Table()
{
PrivateInitialize();
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
#region Public Methods
/// <summary>
/// <see cref="IAddChild.AddChild"/>
/// </summary>
void IAddChild.AddChild(object value)
{
ArgumentNullException.ThrowIfNull(value);
TableRowGroup rowGroup = value as TableRowGroup;
if (rowGroup != null)
{
RowGroups.Add(rowGroup);
return;
}
throw (new ArgumentException(SR.Format(SR.UnexpectedParameterType, value.GetType(), typeof(TableRowGroup)), "value"));
}
/// <summary>
/// <see cref="IAddChild.AddText"/>
/// </summary>
void IAddChild.AddText(string text)
{
XamlSerializerUtil.ThrowIfNonWhiteSpaceInAddText(text, this);
}
/// <summary>
/// Initialization of this element is about to begin
/// </summary>
public override void BeginInit()
{
base.BeginInit();
_initializing = true;
}
/// <summary>
/// Initialization of this element has completed
/// </summary>
public override void EndInit()
{
_initializing = false;
OnStructureChanged();
base.EndInit();
}
/// <summary>
/// <see cref="FrameworkContentElement.LogicalChildren"/>
/// </summary>
/// <remarks>
/// children enumerated in the following order:
/// * columns
/// * rowgroups
/// </remarks>
protected internal override IEnumerator LogicalChildren
{
get { return (new TableChildrenCollectionEnumeratorSimple(this)); }
}
#endregion Public Methods
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
/// <summary>
/// Returns table column collection.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public TableColumnCollection Columns { get { return (_columns); } }
/// <summary>
/// This method is used by TypeDescriptor to determine if this property should
/// be serialized.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool ShouldSerializeColumns()
{
return (Columns.Count > 0);
}
/// <summary>
/// Returns table row group.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public TableRowGroupCollection RowGroups
{
get { return (_rowGroups); }
}
/// <summary>
/// Cell spacing property.
/// </summary>
[TypeConverter(typeof(LengthConverter))]
public double CellSpacing
{
get { return (double) GetValue(CellSpacingProperty); }
set { SetValue(CellSpacingProperty, value); }
}
#endregion Public Properties
//------------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
#region Protected Methods
/// <summary>
/// Creates AutomationPeer (<see cref="ContentElement.OnCreateAutomationPeer"/>)
/// </summary>
protected override AutomationPeer OnCreateAutomationPeer()
{
return new TableAutomationPeer(this);
}
#endregion Protected Methods
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
/// <summary>
/// Internal cell spacing getter
/// </summary>
internal double InternalCellSpacing
{
get { return Math.Max(CellSpacing, 0); }
}
int IAcceptInsertion.InsertionIndex
{
get { return this.InsertionIndex; }
set { this.InsertionIndex = value; }
}
/// <summary>
/// Stores temporary data for where to insert a new row group
/// </summary>
internal int InsertionIndex
{
get { return _rowGroupInsertionIndex; }
set { _rowGroupInsertionIndex = value; }
}
/// <summary>
/// Count of columns in the table
/// </summary>
internal int ColumnCount
{
get
{
return (_columnCount);
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
#region Internal Methods
/// <summary>
/// Updates table actual column count
/// </summary>
/// <param name="columnCount">Count of column to account for</param>
internal void EnsureColumnCount(int columnCount)
{
if (_columnCount < columnCount)
_columnCount = columnCount;
}
/// <summary>
/// OnStructureChanged - Called to rebuild structure.
/// </summary>
internal void OnStructureChanged()
{
if (!_initializing)
{
if (TableStructureChanged != null)
{
TableStructureChanged(this, EventArgs.Empty);
}
ValidateStructure();
// Table structure changes affect number of rows and colums. Need to notify peer about it.
TableAutomationPeer peer = ContentElementAutomationPeer.FromElement(this) as TableAutomationPeer;
if (peer != null)
{
peer.OnStructureInvalidated();
}
}
}
/// <summary>
/// ValidateStructure
/// </summary>
internal void ValidateStructure()
{
if (!_initializing)
{
//
// validate row groups structural cache
//
_columnCount = 0;
for (int i = 0; i < _rowGroups.Count; ++i)
{
_rowGroups[i].ValidateStructure();
}
_version++;
}
}
/// <summary>
/// Notifies the text container that some property change has occurred, requiring a revalidation of table.
/// </summary>
internal void InvalidateColumns()
{
NotifyTypographicPropertyChanged(true /* affectsMeasureOrArrange */, true /* localValueChanged */, null);
}
/// <summary>
/// Returns true if the given rowGroupIndex is the first non-empty one
/// </summary>
internal bool IsFirstNonEmptyRowGroup(int rowGroupIndex)
{
rowGroupIndex--;
while(rowGroupIndex >= 0)
{
if(RowGroups[rowGroupIndex].Rows.Count > 0)
{
return false;
}
rowGroupIndex--;
}
return true;
}
/// <summary>
/// Returns true if the given rowGroupIndex is the last non-empty one
/// </summary>
internal bool IsLastNonEmptyRowGroup(int rowGroupIndex)
{
rowGroupIndex++;
while(rowGroupIndex < RowGroups.Count)
{
if(RowGroups[rowGroupIndex].Rows.Count > 0)
{
return false;
}
rowGroupIndex++;
}
return true;
}
#endregion Internal Methods
//------------------------------------------------------
//
// Internal Events
//
//------------------------------------------------------
#region Internal Events
/// <summary>
/// Fired when the table changes structurally
/// </summary>
internal event EventHandler TableStructureChanged;
#endregion
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
/// <summary>
/// Private ctor time initialization.
/// </summary>
private void PrivateInitialize()
{
// Acquire new PTS Context.
_columns = new TableColumnCollection(this);
_rowGroups = new TableRowGroupCollection(this);
_rowGroupInsertionIndex = -1;
}
private static bool IsValidCellSpacing(object o)
{
double spacing = (double)o;
double maxSpacing = Math.Min(1000000, PTS.MaxPageSize);
if (Double.IsNaN(spacing))
{
return false;
}
if (spacing < 0 || spacing > maxSpacing)
{
return false;
}
return true;
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
private TableColumnCollection _columns; // collection of columns
private TableRowGroupCollection _rowGroups; // collection of row groups
private int _rowGroupInsertionIndex; // insertion index used by row group collection
private const double c_defaultCellSpacing = 2; // default value of cell spacing
private int _columnCount;
private int _version = 0;
private bool _initializing; // True if the table is being initialized
#endregion Private Fields
//------------------------------------------------------
//
// Private Structures / Classes
//
//------------------------------------------------------
#region Private Structures Classes
/// <summary>
/// Implementation of a simple enumerator of table's children
/// </summary>
private class TableChildrenCollectionEnumeratorSimple : IEnumerator, ICloneable
{
internal TableChildrenCollectionEnumeratorSimple(Table table)
{
Debug.Assert(table != null);
_table = table;
_version = _table._version;
_columns = ((IEnumerable)_table._columns).GetEnumerator();
_rowGroups = ((IEnumerable)_table._rowGroups).GetEnumerator();
}
public Object Clone()
{
return (MemberwiseClone());
}
public bool MoveNext()
{
if (_version != _table._version)
{
throw new InvalidOperationException(SR.EnumeratorVersionChanged);
}
// Strange design, but iterator must spin on contained column iterator
if ((_currentChildType != ChildrenTypes.Columns) && (_currentChildType != ChildrenTypes.RowGroups))
_currentChildType++;
Object currentChild = null;
while (_currentChildType < ChildrenTypes.AfterLast)
{
switch (_currentChildType)
{
case (ChildrenTypes.Columns):
if (_columns.MoveNext())
{
currentChild = _columns.Current;
}
break;
case (ChildrenTypes.RowGroups):
if (_rowGroups.MoveNext())
{
currentChild = _rowGroups.Current;
}
break;
}
if (currentChild != null)
{
_currentChild = currentChild;
break;
}
_currentChildType++;
}
Debug.Assert(_currentChildType != ChildrenTypes.BeforeFirst);
return (_currentChildType != ChildrenTypes.AfterLast);
}
public Object Current
{
get
{
if (_currentChildType == ChildrenTypes.BeforeFirst)
{
#pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception
throw new InvalidOperationException(SR.EnumeratorNotStarted);
}
if (_currentChildType == ChildrenTypes.AfterLast)
{
#pragma warning suppress 6503 // IEnumerator.Current is documented to throw this exception
throw new InvalidOperationException(SR.EnumeratorReachedEnd);
}
return (_currentChild);
}
}
public void Reset()
{
if (_version != _table._version)
{
throw new InvalidOperationException(SR.EnumeratorVersionChanged);
}
_columns.Reset();
_rowGroups.Reset();
_currentChildType = ChildrenTypes.BeforeFirst;
_currentChild = null;
}
private Table _table;
private int _version;
private IEnumerator _columns;
private IEnumerator _rowGroups;
private ChildrenTypes _currentChildType;
private Object _currentChild;
private enum ChildrenTypes : int
{
BeforeFirst = 0,
Columns = 1,
RowGroups = 2,
AfterLast = 3,
}
}
#endregion Private Structures Classes
//------------------------------------------------------
//
// Properties
//
//------------------------------------------------------
#region Properties
/// <summary>
/// Cell spacing property.
/// </summary>
public static readonly DependencyProperty CellSpacingProperty =
DependencyProperty.Register(
"CellSpacing",
typeof(double),
typeof(Table),
new FrameworkPropertyMetadata(
c_defaultCellSpacing,
FrameworkPropertyMetadataOptions.AffectsMeasure),
new ValidateValueCallback(IsValidCellSpacing));
#endregion Properties
//------------------------------------------------------
//
// Debug / Performance
//
//------------------------------------------------------
#if TABLEPARANOIA
internal int ParanoiaVersion = 0;
#endif // TABLEPARANOIA
}
}
|