Windows Community Toolkit 4.0 - DataGrid - Part01

来源:https://www.cnblogs.com/shaomeng/archive/2018/08/26/9517958.html
-Advertisement-
Play Games

概述 在上面一篇 Windows Community Toolkit 4.0 - DataGrid - Overview 中,我們對 DataGrid 控制項做了一個概覽的介紹,今天開始我們會做進一步的詳細分享。 按照概述中分析代碼結構的順序,今天我們先對 CollectionViews 文件夾中的類 ...


概述

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

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

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

Windows Community Toolkit Doc - DataGrid

Windows Community Toolkit Source Code - DataGrid

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

 

開發過程

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

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

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 !!!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 集群健康檢查 取得健康狀態 GET /_cat/health?v 返回: 健康狀態分類 green:索引的primary shard和replica shard都是active狀態的 yellow:索引的primary shard都是active狀態的,但是部分replica shard不是acti ...
  • 參考來源:https://www.cnblogs.com/liwenzhou/p/8747872.html ...
  • 小編也不知道大家能不能用的到,我只是把我學到的知識分享出來,有需要的可以看一下。python本身就是一個不斷更新改進的語言,不存在抄襲,有需要就可以拿過來用,在用的過程中,你發現可以用另外一種方法把它實現,就可以把代碼做進一步的優化,然後分享出來,這樣python會變的越來越實用。今天心情不好,分享 ...
  • 前言 編程其實就是寫代碼,而寫代碼目的就是實現業務,所以,語法和框架也是為了實現業務而存在的。因此,不管多麼高大上的目標,實質上都是業務。 所以,我認為不要把寫代碼上升到科學的高度。上升到藝術就可以了,因為藝術本身也沒有高度。。。。 軟體設計存在過度設計,語法和框架的理解,也存在過度理解。比如,反編 ...
  • 在第一篇Proto.Actor博文中,HelloWorld的第一行真正代碼是: var props = Actor.FromProducer(() => new HelloActor()); 這個返回的變數props就是一個Props的對象,它是負責創Actor實例,以及配置Actor實例,並... ...
  • 一,使用工具 ①Fiddler 摘自百度百科Fiddler簡介: Fiddler是一個http協議調試代理工具,它能夠記錄並檢查所有你的電腦和互聯網之間的http通訊,設置斷點,查看所有的“進出”Fiddler的數據(指cookie,html,js,css等文件,這些都可以讓你胡亂修改的意思)。 F ...
  • 在微服務中,數據最終一致性的一個解決方案是通過有狀態的Actor模型來達到,那什麼是Actor模型呢? Actor是並行的計算模型,包含狀態,行為,並且包含一個郵箱,來非同步處理消息。 關於Actor的介紹可參考: https://www.jianshu.com/p/449850aa8e82 http... ...
  • 紙殼CMS是一個開源的可視化設計CMS,通過拖拽,線上編輯的方式來創建網站。紙殼CMS是基於插件化設計的,可以通過擴展插件來實現不同的功能。並且紙殼CMS的插件是相互獨立的,各插件的引用也相互獨立,即各插件都可引用各自需要的nuget包來達到目的。而不用把引用加到底層。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...