|
// 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.
using System.Collections.Generic; // List<T>
using System.Collections.ObjectModel; // ObservableCollection<T>
using System.Collections.Specialized; // NotifyCollectionChangedEventHandler
using System.ComponentModel; // DesignerSerializationVisibility
using System.Diagnostics; // Debug
using System.Runtime.CompilerServices;
using System.Windows.Data; // Binding.IndexerName
using MS.Internal; // Helper
namespace System.Windows.Controls
{
/// <summary>
/// a List of GridViewColumn. Cooperating with GridViewRowPresenter, can insert and remove column.efficiently.
/// </summary>
[Serializable()]
public class GridViewColumnCollection : ObservableCollection<GridViewColumn>
{
//-------------------------------------------------------------------
//
// Constructors
//
//-------------------------------------------------------------------
//------------------------------------------------------
//
// Public Methods / Event
//
//------------------------------------------------------
//------------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
#region Protected Methods
/// <summary>
/// called by base class ObservableCollection<T> when the list is being cleared;
/// GridViewColumnCollection override this method to do some internal preprocess work
/// </summary>
protected override void ClearItems()
{
VerifyAccess();
_internalEventArg = ClearPreprocess();
base.ClearItems();
}
/// <summary>
/// called by base class ObservableCollection<T> when an column is removed from list;
/// GridViewColumnCollection override this method to do some internal preprocess work
/// </summary>
protected override void RemoveItem(int index)
{
VerifyAccess();
_internalEventArg = RemoveAtPreprocess(index);
base.RemoveItem(index);
}
/// <summary>
/// called by base class ObservableCollection<T> when an item is added to list;
/// GridViewColumnCollection override this method to do some internal preprocess work
/// </summary>
protected override void InsertItem(int index, GridViewColumn column)
{
VerifyAccess();
_internalEventArg = InsertPreprocess(index, column);
base.InsertItem(index, column);
}
/// <summary>
/// called by base class ObservableCollection<T> when an column is set in list;
/// GridViewColumnCollection override this method to do some internal preprocess work
/// </summary>
protected override void SetItem(int index, GridViewColumn column)
{
VerifyAccess();
_internalEventArg = SetPreprocess(index, column);
if (_internalEventArg != null) // the new column is equals to the old one.
{
base.SetItem(index, column);
}
}
/// <summary>
/// Move column to a different index
/// </summary>
/// <param name="oldIndex">index of the column which is being moved</param>
/// <param name="newIndex">index of the column to be move to</param>
protected override void MoveItem(int oldIndex, int newIndex)
{
if (oldIndex != newIndex)
{
VerifyAccess();
_internalEventArg = MovePreprocess(oldIndex, newIndex);
base.MoveItem(oldIndex, newIndex);
}
}
// Override OnCollectionChanged method to ensure InternalCollectionChanged event is raised before public one.
#region OnCollectionChanged
/// <summary>
/// raise CollectionChanged event to any listeners
/// </summary>
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
OnInternalCollectionChanged();
base.OnCollectionChanged(e);
}
#endregion OnCollectionChanged
#endregion
//------------------------------------------------------
//
// Internal Methods / Properties
//
//------------------------------------------------------
#region Internal Methods / Properties
internal event NotifyCollectionChangedEventHandler InternalCollectionChanged
{
add { _internalCollectionChanged += value; }
remove { _internalCollectionChanged -= value; }
}
/// <summary>
/// Make the collection not changabel.
/// Should call UnblockWrite the same times as BlockWrite to make collection writable.
/// </summary>
internal void BlockWrite()
{
Debug.Assert(IsImmutable != true, "IsImmutable is true before BlockWrite");
IsImmutable = true;
}
/// <summary>
/// Counterfact the effect of BlockWrite() - restore collection to a changable state.
/// </summary>
internal void UnblockWrite()
{
Debug.Assert(IsImmutable != false, "IsImmutable is flase before UnblockWrite");
IsImmutable = false;
}
// Column list. Columns in this list are organized in the order
// that they were inserted into this collection. So Move operation
// won't change this list.
internal List<GridViewColumn> ColumnCollection { get { return _columns; } }
// Actual index list of GridViewColumn in ColumnCollection
// this[i] == ColumnCollection[IndexList[i]]
internal List<int> IndexList { get { return _actualIndices; } }
#endregion
#region InheritanceContext
// The GridView that we're in
internal DependencyObject Owner
{
get { return _owner; }
set
{
if (value != _owner)
{
if (value == null)
{
foreach (GridViewColumn c in _columns)
{
InheritanceContextHelper.RemoveContextFromObject(_owner, c);
}
}
else
{
foreach (GridViewColumn c in _columns)
{
InheritanceContextHelper.ProvideContextForObject(value, c);
}
}
_owner = value;
}
}
}
// The GridView that we're in
[NonSerializedAttribute]
private DependencyObject _owner = null;
// the Owner is GridView
internal bool InViewMode
{
get { return _inViewMode; }
set { _inViewMode = value; }
}
private bool _inViewMode;
#endregion InheritanceContext
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
private void OnInternalCollectionChanged()
{
if (_internalCollectionChanged != null && _internalEventArg != null)
{
_internalCollectionChanged(this, _internalEventArg);
// This class member is used as parameter, so clear it after used.
// For details, see definition.
_internalEventArg = null;
}
}
private void ColumnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
GridViewColumn column = sender as GridViewColumn;
if (_internalCollectionChanged != null && column != null)
{
_internalCollectionChanged(this, new GridViewColumnCollectionChangedEventArgs(column, e.PropertyName));
}
}
private GridViewColumnCollectionChangedEventArgs MovePreprocess(int oldIndex, int newIndex)
{
Debug.Assert(oldIndex != newIndex, "oldIndex==newIndex when perform move action.");
VerifyIndexInRange(oldIndex);
VerifyIndexInRange(newIndex);
int actualIndex = _actualIndices[oldIndex];
if (oldIndex < newIndex)
{
for (int targetIndex = oldIndex; targetIndex < newIndex; targetIndex++)
{
_actualIndices[targetIndex] = _actualIndices[targetIndex + 1];
}
}
else //if (oldIndex > newIndex)
{
for (int targetIndex = oldIndex; targetIndex > newIndex; targetIndex--)
{
_actualIndices[targetIndex] = _actualIndices[targetIndex - 1];
}
}
_actualIndices[newIndex] = actualIndex;
return new GridViewColumnCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, _columns[actualIndex], newIndex, oldIndex, actualIndex);
}
private GridViewColumnCollectionChangedEventArgs ClearPreprocess()
{
GridViewColumn[] list = new GridViewColumn[Count];
if (Count > 0)
{
CopyTo(list, 0);
}
// reset columns *before* remove
foreach (GridViewColumn c in _columns)
{
c.ResetPrivateData();
((INotifyPropertyChanged)c).PropertyChanged -= new PropertyChangedEventHandler(ColumnPropertyChanged);
InheritanceContextHelper.RemoveContextFromObject(_owner, c);
}
_columns.Clear();
_actualIndices.Clear();
return new GridViewColumnCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, list);
}
private GridViewColumnCollectionChangedEventArgs RemoveAtPreprocess(int index)
{
VerifyIndexInRange(index);
int actualIndex = _actualIndices[index];
GridViewColumn column = _columns[actualIndex];
column.ResetPrivateData();
((INotifyPropertyChanged)column).PropertyChanged -= new PropertyChangedEventHandler(ColumnPropertyChanged);
_columns.RemoveAt(actualIndex);
UpdateIndexList(actualIndex, index);
UpdateActualIndexInColumn(actualIndex);
InheritanceContextHelper.RemoveContextFromObject(_owner, column);
return new GridViewColumnCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, column, index, actualIndex);
}
// Remove the index of the removed column in _actualIndices
// Correct index in _actualIndices which is bigger than actualIndex
private void UpdateIndexList(int actualIndex, int index)
{
for (int sourceIndex = 0; sourceIndex < index; sourceIndex++)
{
int i = _actualIndices[sourceIndex];
if (i > actualIndex)
{
_actualIndices[sourceIndex] = i - 1;
}
}
for (int sourceIndex = index + 1; sourceIndex < _actualIndices.Count; sourceIndex++)
{
int i = _actualIndices[sourceIndex];
if (i < actualIndex)
{
_actualIndices[sourceIndex - 1] = i;
}
else if (i > actualIndex)
{
_actualIndices[sourceIndex - 1] = i - 1;
}
}
_actualIndices.RemoveAt(_actualIndices.Count - 1);
}
// pack the actual indeices in columns from after the removed one
private void UpdateActualIndexInColumn(int iStart)
{
for (int i = iStart; i < _columns.Count; i++)
{
_columns[i].ActualIndex = i;
}
}
private GridViewColumnCollectionChangedEventArgs InsertPreprocess(int index, GridViewColumn column)
{
int count = _columns.Count;
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThan(index, count);
ValidateColumnForInsert(column);
_columns.Add(column);
column.ActualIndex = count;
_actualIndices.Insert(index, count);
//
// NOTE:
//
// To prevent a useless (if not error prone) event from being fired, we need to call
// {ProvideContextForObject()} before {column.PropertyChanged += ...}.
//
// Below is the case when a column property change event can be fired before the event
// that this column was added into the collection.
//
// 1. add a new column to the ColumnCollection, e.g.
//
// <GridViewColumn CellTemplate="{DynamicResource ...}" .../>
//
// 2. once column is connected to the collection, DynamicResource will start loading
// the template
//
// 3. #1 will trigger a collection change event which will be fired when this method
// is accomplished.
//
// 4. #2 will trigger a property change event which will be fired at the middle of this
// method.
//
// Ultimately, RowPresenter will receive both #3 and #4. But from above, #4 will come
// before #3. Therefore #4 is totally useless because #3 is unknown yet.
//
InheritanceContextHelper.ProvideContextForObject(_owner, column);
((INotifyPropertyChanged)column).PropertyChanged += new PropertyChangedEventHandler(ColumnPropertyChanged);
return new GridViewColumnCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, column, index, count /* actual index*/);
}
// This[index] = newColumn
private GridViewColumnCollectionChangedEventArgs SetPreprocess(int index, GridViewColumn newColumn)
{
VerifyIndexInRange(index);
GridViewColumn oldColumn = this[index];
if (oldColumn != newColumn)
{
int oldColumnActualIndex = _actualIndices[index];
RemoveAtPreprocess(index);
InsertPreprocess(index, newColumn);
// NOTE: Insert Preprocess already updated InheritanceContext.
// Don't update here again.
return new GridViewColumnCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newColumn, oldColumn, index, oldColumnActualIndex);
}
return null;
}
private void VerifyIndexInRange(int index, [CallerArgumentExpression(nameof(index))] string indexName = null)
{
ArgumentOutOfRangeException.ThrowIfNegative(index, indexName);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _actualIndices.Count, indexName);
}
// Throw if column is null or already existed in a GVCC
private void ValidateColumnForInsert(GridViewColumn column)
{
ArgumentNullException.ThrowIfNull(column);
if (column.ActualIndex >= 0)
{
throw new InvalidOperationException(SR.ListView_NotAllowShareColumnToTwoColumnCollection);
}
}
private void VerifyAccess()
{
if (IsImmutable)
{
throw new InvalidOperationException(SR.ListView_GridViewColumnCollectionIsReadOnly);
}
// Although CheckReentrancy() is called in base class, we still need to call it here again,
// otherwise, when Reentrancy is found and exception is thrown, our operation is done and can't be undo.
CheckReentrancy();
}
#endregion
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
// internal storage of CollumnCollection
private List<GridViewColumn> _columns = new List<GridViewColumn>();
// index of column in _columns
// GridViewColumn this[i] in public interface is _columns[_actualIndices[i]]
private List<int> _actualIndices = new List<int>();
// GridViewHeaderRowPresenter will set this field to true once reorder is started.
private bool IsImmutable
{
get { return _isImmutable; }
set { _isImmutable = value; }
}
private bool _isImmutable;
private event NotifyCollectionChangedEventHandler _internalCollectionChanged;
// EventArgs for _internalCollectionChanged event.
// We should raise internal event just before ColletonChanged event of base class,
// but can't pass this to OnCollectionChanged method as parameter. So store it as a class member.
[NonSerialized]
private GridViewColumnCollectionChangedEventArgs _internalEventArg;
#endregion
}
}
|