Windows Community Toolkit 4.0 - DataGrid - Part01

概述html

在上面一篇 Windows Community Toolkit 4.0 - DataGrid - Overview 中,咱們對 DataGrid 控件作了一個概覽的介紹,今天開始咱們會作進一步的詳細分享。git

按照概述中分析代碼結構的順序,今天咱們先對 CollectionViews 文件夾中的類作詳細的分析。github

下面是 Windows Community Toolkit Sample App 的示例截圖和 code/doc 地址:windows

Windows Community Toolkit Doc - DataGridless

Windows Community Toolkit Source Code - DataGridide

Namespace: Microsoft.Toolkit.Uwp.UI.Controls; Nuget: Microsoft.Toolkit.Uwp.UI.Controls.DataGrid;動畫

 

開發過程this

首先再來看一下 CollectionViews 文件夾的代碼結構:spa

4 個類中,CollectionView 是 EnumerableCollectionView 和 ListCollectionView 的基類,而 CollectionViewsError 是和 DataGrid 數據源中錯誤的處理類,接下來咱們來分別看一下:scala

1. CollectionView

CollectionView 類是 DataGrid 數據相關處理的基類,這個類裏的處理方法和屬性設置不少,同時還針對 FILTER,SORT 和 GROUP 特性作了處理,下面先來看看類中定義的屬性:

  • Count - 表示 DataGrid 控件數據的數量,在 OnCollectionChanged 事件處理中,非 Replace 狀況下觸發;
  • IsEmpty - 表示 DataGrid 控件中數據是否爲空,一樣在 OnCollectionChanged 事件處理中,空和非空狀態切換時觸發;
  • Culture - 表示 DataGrid 控件的區域性信息,在 Culture 變化時,包括名稱,日曆系統,字符排序等會發生變化;
  • CurrentPosition - 表示 DataGrid 控件的當前位置,在子類的 RaiseCurrencyChanges 和 LoadSnapshot 事件中被使用;
  • CurrentItem - 表示 DataGrid 控件當前選中的元素,一樣在子類的 RaiseCurrencyChanges 和 LoadSnapshot 事件中被使用;
  • IsCurrentBeforeFirst - 表示 DataGrid 控件中當前選中是否在首個元素以前;
  • IsCurrentAfterLast - 表示 DataGrid 控件中當前選中是否在最後一個元素以後;

接下來看幾個重要的方法:

1). CollectionView() 

CollectionView 類的構造方法,能夠看到方法中建立了監聽器,對時間的 Action 調用和卸載作了定義,對於集合改變事件作了綁定,並對布爾類型的屬性作了初始設置;

public CollectionView(IEnumerable collection)
{
    _sourceCollection = collection ?? throw new ArgumentNullException("collection");

    // forward collection change events from underlying collection to our listeners.
    INotifyCollectionChanged incc = collection as INotifyCollectionChanged;
    if (incc != null)
    {
        _sourceWeakEventListener =
            new WeakEventListener<CollectionView, object, NotifyCollectionChangedEventArgs>(this)
            {
                // Call the actual collection changed event
                OnEventAction = (source, changed, arg) => OnCollectionChanged(source, arg),

                // The source doesn't exist anymore
                OnDetachAction = (listener) => incc.CollectionChanged -= _sourceWeakEventListener.OnEvent
            };
        incc.CollectionChanged += _sourceWeakEventListener.OnEvent;
    }

    _currentItem = null;
    _currentPosition = -1;
    SetFlag(CollectionViewFlags.IsCurrentBeforeFirst, _currentPosition < 0);
    SetFlag(CollectionViewFlags.IsCurrentAfterLast, _currentPosition < 0);
    SetFlag(CollectionViewFlags.CachedIsEmpty, _currentPosition < 0);
}

2). OnCollectionChanged()

集合變化的處理,包括對變化動畫的判斷,當變化不是替換時,觸發 count 屬性變化;以及對於集合空的判斷,空和爲空切換時,觸發 isEmpty 屬性變化,前面在屬性說明中咱們提提到了;

protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
    if (args == null)
    {
        throw new ArgumentNullException("args");
    }

    unchecked
    {
        // invalidate enumerators because of a change
        ++_timestamp;
    }

    CollectionChanged?.Invoke(this, args);

    // Collection changes change the count unless an item is being
    // replaced or moved within the collection.
    if (args.Action != NotifyCollectionChangedAction.Replace)
    {
        OnPropertyChanged(CountPropertyName);
    }

    bool isEmpty = IsEmpty;
    if (isEmpty != CheckFlag(CollectionViewFlags.CachedIsEmpty))
    {
        SetFlag(CollectionViewFlags.CachedIsEmpty, isEmpty);
        OnPropertyChanged(IsEmptyPropertyName);
    }
}

3). SetCurrent()

根據當前選擇的元素,當前位置和元素數量設置當前選中;新元素不爲空時,設置 IsCurrentBeforeFirst 和 IsCurrentAfterLast 屬性爲 false;當集合爲空時,設置兩個屬性爲 true,設置新的選中位置爲 -1;不然,根據 newPosition 的值來設置這兩個屬性;

protected void SetCurrent(object newItem, int newPosition, int count)
{
    if (newItem != null)
    {
        // non-null item implies position is within range.
        // We ignore count - it's just a placeholder
        SetFlag(CollectionViewFlags.IsCurrentBeforeFirst, false);
        SetFlag(CollectionViewFlags.IsCurrentAfterLast, false);
    }
    else if (count == 0)
    {
        // empty collection - by convention both flags are true and position is -1
        SetFlag(CollectionViewFlags.IsCurrentBeforeFirst, true);
        SetFlag(CollectionViewFlags.IsCurrentAfterLast, true);
        newPosition = -1;
    }
    else
    {
        // null item, possibly within range.
        SetFlag(CollectionViewFlags.IsCurrentBeforeFirst, newPosition < 0);
        SetFlag(CollectionViewFlags.IsCurrentAfterLast, newPosition >= count);
    }

    _currentItem = newItem;
    _currentPosition = newPosition;
}

 

2. EnumerableCollectionView

該類是 CollectionView 類的子類,支持枚舉類型的數據集合。下面咱們主要分享它基於 CollectionView 的特殊實現部分:

1). EnumerableCollectionView()

先看看構造方法,首先根據數據源設置當前元素和位置等,綁定集合改變,屬性改變和當前的改變和改變後事件;重點說一下 OnCurrentChanging 和 OnCurrentChanged 事件,分別能夠在改變前作干預處理,改變後作對應處理;

internal EnumerableCollectionView(IEnumerable source)
    : base(source)
{
    _snapshot = new ObservableCollection<object>();

    LoadSnapshotCore(source);

    if (_snapshot.Count > 0)
    {
        SetCurrent(_snapshot[0], 0, 1);
    }
    else
    {
        SetCurrent(null, -1, 0);
    }

    // If the source doesn't raise collection change events, try to detect changes by polling the enumerator.
    _pollForChanges = !(source is INotifyCollectionChanged);

    _view = new ListCollectionView(_snapshot);

    INotifyCollectionChanged incc = _view as INotifyCollectionChanged;
    incc.CollectionChanged += new NotifyCollectionChangedEventHandler(EnumerableCollectionView_OnViewChanged);

    INotifyPropertyChanged ipc = _view as INotifyPropertyChanged;
    ipc.PropertyChanged += new PropertyChangedEventHandler(EnumerableCollectionView_OnPropertyChanged);

    _view.CurrentChanging += new CurrentChangingEventHandler(EnumerableCollectionView_OnCurrentChanging);
    _view.CurrentChanged += new EventHandler<object>(EnumerableCollectionView_OnCurrentChanged);
}

2). ProcessCollectionChanged()

處理集合變化事件的方法,主要對改變作了 Add,Remove,Replace 和 Reset 四種狀況的處理;分別看一下處理內容:

  • Add - Add 操做後,對 snapshot 集合作對應變化,當新增索引 < 0 或小於當前開始索引時,加到集合開始位置,不然插入對應位置;
  • Remove - Remove 操做後,在 snapshot 集合中刪除對應位置的元素;
  • Replace - Replace 操做後,在 snapshot 集合中替換對應位置的元素;
  • Reset - Reset 操做後,對應重置 snapshot 集合;
protected override void ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
{
    // Apply the change to the snapshot
    switch (args.Action)
    {
        case NotifyCollectionChangedAction.Add:
            if (args.NewStartingIndex < 0 || _snapshot.Count <= args.NewStartingIndex)
            { // Append
                for (int i = 0; i < args.NewItems.Count; ++i)
                {
                    _snapshot.Add(args.NewItems[i]);
                }
            }
            else
            { // Insert
                for (int i = args.NewItems.Count - 1; i >= 0; --i)
                {
                    _snapshot.Insert(args.NewStartingIndex, args.NewItems[i]);
                }
            }

            break;

        case NotifyCollectionChangedAction.Remove:
            if (args.OldStartingIndex < 0)
            {
                throw CollectionViewsError.EnumerableCollectionView.RemovedItemNotFound();
            }

            for (int i = args.OldItems.Count - 1, index = args.OldStartingIndex + i; i >= 0; --i, --index)
            {
                if (!object.Equals(args.OldItems[i], _snapshot[index]))
                {
                    throw CollectionViewsError.CollectionView.ItemNotAtIndex("removed");
                }

                _snapshot.RemoveAt(index);
            }

            break;

        case NotifyCollectionChangedAction.Replace:
            for (int i = args.NewItems.Count - 1, index = args.NewStartingIndex + i; i >= 0; --i, --index)
            {
                if (!object.Equals(args.OldItems[i], _snapshot[index]))
                {
                    throw CollectionViewsError.CollectionView.ItemNotAtIndex("replaced");
                }

                _snapshot[index] = args.NewItems[i];
            }

            break;

        case NotifyCollectionChangedAction.Reset:
            LoadSnapshot(SourceCollection);
            break;
    }
}

3). LoadSnapshot() 

加載 snapshot 方法,根據從新加載的元素集合,判斷如下屬性是否須要響應變化:IsCurrentAfterLast,IsCurrentBeforeFirst,CurrentPosition 和 CurrentItem。

private void LoadSnapshot(IEnumerable source)
{
    // Force currency off the collection (gives user a chance to save dirty information).
    OnCurrentChanging();

    // Remember the values of the scalar properties, so that we can restore
    // them and raise events after reloading the data
    object oldCurrentItem = CurrentItem;
    int oldCurrentPosition = CurrentPosition;
    bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
    bool oldIsCurrentAfterLast = IsCurrentAfterLast;

    // Reload the data
    LoadSnapshotCore(source);

    // Tell listeners everything has changed
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));

    OnCurrentChanged();

    if (IsCurrentAfterLast != oldIsCurrentAfterLast)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(IsCurrentAfterLastPropertyName));
    }

    if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(IsCurrentBeforeFirstPropertyName));
    }

    if (oldCurrentPosition != CurrentPosition)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(CurrentPositionPropertyName));
    }

    if (oldCurrentItem != CurrentItem)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(CurrentItemPropertyName));
    }
}

 

3. ListCollectionView

該類是 CollectionView 類的子類,支持列表類型的數據集合。下面咱們也會主要分享它基於 CollectionView 的特殊實現部分:

1). ListCollectionView()

ListCollectionView 類的構造方法,當支持編輯行爲時,須要刷新可增長,可刪除,可取消編輯的判斷;而後設置當前位置和元素;當支持分組時,註冊分組描述,分組改變和分組依據的變化處理事件;

public ListCollectionView(IList list) : base(list)
{
    _internalList = list;

    #if FEATURE_IEDITABLECOLLECTIONVIEW
                RefreshCanAddNew();
                RefreshCanRemove();
                RefreshCanCancelEdit();
    #endif
    if (InternalList.Count == 0)
    {
        // don't call virtual IsEmpty in ctor
        SetCurrent(null, -1, 0);
    }
    else
    {
        SetCurrent(InternalList[0], 0, 1);
    }

    #if FEATURE_ICOLLECTIONVIEW_GROUP
        _group = new CollectionViewGroupRoot(this);
        _group.GroupDescriptionChanged += new EventHandler(OnGroupDescriptionChanged);
        ((INotifyCollectionChanged)_group).CollectionChanged += new NotifyCollectionChangedEventHandler(OnGroupChanged);
        ((INotifyCollectionChanged)_group.GroupDescriptions).CollectionChanged += new NotifyCollectionChangedEventHandler(OnGroupByChanged);
    #endif
}

2). ProcessCollectionChangedWithAdjustedIndex()

處於集合變化和索引調整的方法,首先判斷當前動做的類型:Add,Remove 或 Replace,並針對每種不一樣類型的操做,進行分別的處理;再對 afterLastHasChanged,beforeFirstHasChanged,currentPositionHasChanged 和 currentItemHasChanged 屬性進行設置;

private void ProcessCollectionChangedWithAdjustedIndex(EffectiveNotifyCollectionChangedAction action, object oldItem, object newItem, int adjustedOldIndex, int adjustedNewIndex)
{
    EffectiveNotifyCollectionChangedAction effectiveAction = action;
    if (adjustedOldIndex == adjustedNewIndex && adjustedOldIndex >= 0)
    {
        effectiveAction = EffectiveNotifyCollectionChangedAction.Replace;
    }
    else if (adjustedOldIndex == -1)
    {
        if (adjustedNewIndex < 0)
        {
            if (action == EffectiveNotifyCollectionChangedAction.Add)
            {
                return;
            }

            effectiveAction = EffectiveNotifyCollectionChangedAction.Remove;
        }
    }
    else if (adjustedOldIndex < -1)
    { ... }
    else
    { ... }

    int originalCurrentPosition = CurrentPosition;
    int oldCurrentPosition = CurrentPosition;
    object oldCurrentItem = CurrentItem;
    bool oldIsCurrentAfterLast = IsCurrentAfterLast;
    bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;

    NotifyCollectionChangedEventArgs args = null, args2 = null;

    switch (effectiveAction)
    {
        case EffectiveNotifyCollectionChangedAction.Add:
            // insert into private view
#if FEATURE_ICOLLECTIONVIEW_SORT_OR_FILTER
#if FEATURE_IEDITABLECOLLECTIONVIEW
            // (unless it's a special item (i.e. new item))
            if (UsesLocalArray && (!IsAddingNew || !object.Equals(_newItem, newItem)))
#else
            if (UsesLocalArray)
#endif
            {
                InternalList.Insert(adjustedNewIndex, newItem);
            }
#endif
            if (!IsGrouping)
            {
                AdjustCurrencyForAdd(adjustedNewIndex);
                args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItem, adjustedNewIndex);
            }
#if FEATURE_ICOLLECTIONVIEW_GROUP
            else
            {
                AddItemToGroups(newItem);
            }
#endif
            break;

        case EffectiveNotifyCollectionChangedAction.Remove:
            ...
        case EffectiveNotifyCollectionChangedAction.Replace:
            ...
        case EffectiveNotifyCollectionChangedAction.Move:
            ...
        default:
            Debug.Assert(false, "Unexpected Effective Collection Change Action");
            break;
    }

    bool afterLastHasChanged = IsCurrentAfterLast != oldIsCurrentAfterLast;
    bool beforeFirstHasChanged = IsCurrentBeforeFirst != oldIsCurrentBeforeFirst;
    bool currentPositionHasChanged = CurrentPosition != oldCurrentPosition;
    bool currentItemHasChanged = CurrentItem != oldCurrentItem;

    oldIsCurrentAfterLast = IsCurrentAfterLast;
    oldIsCurrentBeforeFirst = IsCurrentBeforeFirst;
    oldCurrentPosition = CurrentPosition;
    oldCurrentItem = CurrentItem;

    // base class will raise an event to our listeners
    if (!IsGrouping)
    {
        Debug.Assert(!CurrentChangedMonitor.Busy, "Expected _currentChangedMonitor.Busy is false.");

        CurrentChangedMonitor.Enter();
        using (CurrentChangedMonitor)
        {
            OnCollectionChanged(args);
            if (args2 != null)
            {
                OnCollectionChanged(args2);
            }
        }

        // Any scalar properties that changed don't need a further notification,
        // but do need a new snapshot
        ...
    }

    // currency has to change after firing the deletion event,
    // so event handlers have the right picture
    if (_currentElementWasRemoved)
    {
        int oldCurPos = originalCurrentPosition;

#if FEATURE_ICOLLECTIONVIEW_GROUP
        if (_newGroupedItem != null)
        {
            oldCurPos = IndexOf(_newGroupedItem);
        }
#endif
        MoveCurrencyOffDeletedElement(oldCurPos);

        // changes to the scalar properties need notification
        afterLastHasChanged = afterLastHasChanged || (IsCurrentAfterLast != oldIsCurrentAfterLast);
        beforeFirstHasChanged = beforeFirstHasChanged || (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst);
        currentPositionHasChanged = currentPositionHasChanged || (CurrentPosition != oldCurrentPosition);
        currentItemHasChanged = currentItemHasChanged || (CurrentItem != oldCurrentItem);
    }

    RaiseCurrencyChanges(false, currentItemHasChanged, currentPositionHasChanged, beforeFirstHasChanged, afterLastHasChanged);
}

 

4. CollectionViewsError 

CollectionViewsError 類中主要定義了 DataGrid 控件數據,就是 CollectionView 中的錯誤,咱們來看一下都定義了哪些錯誤:

  • EnumeratorVersionChanged - InvalidOperationException,「Collection was modified; enumeration operation cannot execute.」
  • MemberNotAllowedDuringAddOrEdit - InvalidOperationException,"'{0}' is not allowed during an AddNew or EditItem transaction."
  • NoAccessWhileChangesAreDeferred - InvalidOperationException,"This value cannot be accessed while changes are deferred."
  • ItemNotAtIndex - InvalidOperationException,"The {0} item is not in the collection."
  • RemovedItemNotFound - InvalidOperationException,"The removed item is not found in the source collection."
  • CollectionChangedOutOfRange - InvalidOperationException,"The collection change is out of bounds of the original collection."
  • AddedItemNotInCollection - InvalidOperationException,"The added item is not in the collection."
  • CancelEditNotSupported - InvalidOperationException,"CancelEdit is not supported for the current edit item."
  • MemberNotAllowedDuringTransaction - InvalidOperationException,"'{0}' is not allowed during a transaction started by '{1}'."
  • MemberNotAllowedForView - InvalidOperationException,"'{0}' is not allowed for this view."

 

總結

這裏咱們把 DataGrid 的 CollectionView 相關類介紹完成了,做爲 DataGrid 相關分享的第一篇,後面咱們會繼續分享 Utilities 和最重要的 DataGrid 的相關重點。

最後,再跟你們安利一下 WindowsCommunityToolkit 的官方微博:https://weibo.com/u/6506046490你們能夠經過微博關注最新動態。

衷心感謝 WindowsCommunityToolkit 的做者們傑出的工做,感謝每一位貢獻者,Thank you so much, ALL WindowsCommunityToolkit AUTHORS !!!

相關文章
相關標籤/搜索