UWP Composition API - GroupListView(一)

来源:http://www.cnblogs.com/FaDeKongJian/archive/2016/06/30/5629715.html
-Advertisement-
Play Games

需求: 光看標題大家肯定不知道是什麼東西,先上效果圖: 這不就是ListView的Group效果嗎?? 看上去是的。但是請聽完需求.1.Group中的集合需要支持增量載入ISupportIncrementalLoading 2.支持UI Virtualization oh,no。ListView 自 ...


需求:

光看標題大家肯定不知道是什麼東西,先上效果圖:

這不就是ListView的Group效果嗎?? 看上去是的。但是請聽完需求.
1.Group中的集合需要支持增量載入ISupportIncrementalLoading

2.支持UI Virtualization

oh,no。ListView 自帶的Group都不支持這2個需求。好吧,只有靠自己擼Code了。。

實現前思考:

仔細想了下,其實要解決的主要問題有2個
數據源的處理 和 GroupHeader的UI的處理

1.數據源的處理 

 因為之前在寫 UWP VirtualizedVariableSizedGridView 支持可虛擬化可變大小Item的View的時候已經做過這種處理源的工作了,所以方案出來的比較快。

不管有幾個group,其實當第1個hasMore等false的時候,我們就可以載入第2個group裡面的集合。

我為此寫了一個類GroupObservableCollection<T> 它是繼承 ObservableCollection<T>, IGroupCollection

        public class GroupObservableCollection<T> : ObservableCollection<T>, IGroupCollection
    {
        private List<IList<T>> souresList;

        private List<int> firstIndexInEachGroup = new List<int>();
        private List<IGroupHeader> groupHeaders;

        bool _isLoadingMoreItems = false;

        public GroupObservableCollection(List<IList<T>> souresList, List<IGroupHeader> groupHeaders)
        {
            this.souresList = souresList;
            this.groupHeaders = groupHeaders;
        }

        public bool HasMoreItems
        {
            get
            {
                if (CurrentGroupIndex < souresList.Count)
                {
                    var source = souresList[currentGroupIndex];
                    if (source is ISupportIncrementalLoading)
                    {
                        if (!(source as ISupportIncrementalLoading).HasMoreItems)
                        {
                            if (!_isLoadingMoreItems)
                            {
                                if (this.Count < GetSourceListTotoalCount())
                                {
                                    int count = 0;
                                    int preCount = this.Count;
                                    foreach (var item in souresList)
                                    {
                                        foreach (var item1 in item)
                                        {
                                            if (count >= preCount)
                                            {
                                                this.Add(item1);
                                                if (item == source && groupHeaders[currentGroupIndex].FirstIndex==-1)
                                                {
                                                    groupHeaders[currentGroupIndex].FirstIndex = this.Count - 1;
                                                }
                                            }
                                            count++;
                                        }
                                    }
                                }

                                groupHeaders[currentGroupIndex].LastIndex = this.Count - 1;

                                return false;
                            }
                            else
                            {
                                return true;
                            }
                        }
                        else
                        {
                            return true;
                        }
                    }
                    else
                    {
                        if (CurrentGroupIndex == source.Count - 1)
                        {
                            if (this.Count < GetSourceListTotoalCount())
                            {
                                int count = 0;
                                int preCount = this.Count;
                                foreach (var item in souresList)
                                {
                                    foreach (var item1 in item)
                                    {
                                        if (count >= preCount)
                                        {
                                            this.Add(item1);
                                            if (item == source && groupHeaders[currentGroupIndex].FirstIndex == -1)
                                            {
                                                groupHeaders[currentGroupIndex].FirstIndex = this.Count - 1;
                                            }
                                        }
                                        count++;
                                    }
                                }
                            }
                            groupHeaders[currentGroupIndex].LastIndex = this.Count - 1;
                            return false;
                        }
                        else
                        {
                            return true;
                        }
                    }
                }
                else
                {
                    return false;
                }
            }
        }

        int GetSourceListTotoalCount()
        {
            int i = 0;
            foreach (var item in souresList)
            {
                i += item.Count;
            }
            return i;
        }

        public List<int> FirstIndexInEachGroup
        {
            get
            {
                return firstIndexInEachGroup;
            }

            set
            {
                firstIndexInEachGroup = value;
            }
        }

        public List<IGroupHeader> GroupHeaders
        {
            get
            {
                return groupHeaders;
            }

            set
            {
                groupHeaders = value;
            }
        }

        public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
        {
            return FetchItems(count).AsAsyncOperation();
        }

        private int currentGroupIndex;
        public int CurrentGroupIndex
        {
            get
            {
                int count = 0;

                for (int i = 0; i < souresList.Count; i++)
                {
                    var source = souresList[i];
                    count += source.Count;
                    if (count > this.Count)
                    {
                        currentGroupIndex = i;
                        return currentGroupIndex;
                    }
                    else if (count == this.Count)
                    {
                        currentGroupIndex = i;
                        if ((source is ISupportIncrementalLoading))
                        {
                            if (!(source as ISupportIncrementalLoading).HasMoreItems)
                            {
                                if (!_isLoadingMoreItems)
                                {
                                    groupHeaders[i].LastIndex = this.Count - 1;
                                    if (currentGroupIndex + 1 < souresList.Count)
                                    {
                                        currentGroupIndex = i + 1;
                                    }
                                }
                            }
                        }
                        else
                        {
                            //next
                            if (currentGroupIndex + 1 < souresList.Count)
                            {
                                currentGroupIndex = i + 1;
                            }
                        }

                        return currentGroupIndex;
                    }
                    else
                    {
                        continue;
                    }
                }
                currentGroupIndex = 0;
                return currentGroupIndex;
            }
        }

        private async Task<LoadMoreItemsResult> FetchItems(uint count)
        {
            var source = souresList[CurrentGroupIndex];

            if (source is ISupportIncrementalLoading)
            {
                int firstIndex = 0;
                if (groupHeaders[currentGroupIndex].FirstIndex != -1)
                {
                    firstIndex = source.Count;
                }
                _isLoadingMoreItems = true;
                var result = await (source as ISupportIncrementalLoading).LoadMoreItemsAsync(count);

                for (int i = firstIndex; i < source.Count; i++)
                {
                    this.Add(source[i]);
                    if (i == 0)
                    {
                        groupHeaders[currentGroupIndex].FirstIndex = this.Count - 1;
                    }
                }
                _isLoadingMoreItems = false;
                return result;
            }
            else
            {
                int firstIndex = 0;
                if (groupHeaders[currentGroupIndex].FirstIndex != -1)
                {
                    firstIndex = source.Count;
                }
                for (int i = firstIndex; i < source.Count; i++)
                {
                    this.Add(source[i]);
                    if (i == 0)
                    {
                        groupHeaders[currentGroupIndex].FirstIndex = this.Count - 1;
                    }
                }
                groupHeaders[currentGroupIndex].LastIndex = this.Count - 1;

                return new LoadMoreItemsResult() { Count = (uint)source.Count };
            }
        }
    }
View Code

而IGroupCollection是個介面。

    public interface IGroupCollection: ISupportIncrementalLoading
    {
        List<IGroupHeader> GroupHeaders { get; set; }
        int CurrentGroupIndex { get; }
    }

    public interface IGroupHeader
    {
        string Name { get; set; }
        int FirstIndex { get; set; }
        int LastIndex { get; set; }
        double Height { get; set; }
    }

    public class DefaultGroupHeader : IGroupHeader
    {
        public string Name { get; set; }
        public int FirstIndex { get; set; }
        public int LastIndex { get; set; }
        public double Height { get; set; }
        public DefaultGroupHeader()
        {
            FirstIndex = -1;
            LastIndex = -1;
        }
    }

IGroupHeader 是用來描述Group header的,你可以繼承它,添加一些綁定GroupHeader的屬性(註意請給FirstIndex和LastIndex賦值-1的初始值)

比如:在效果圖中,如果只有全部評論,沒有精彩評論,那麼後面的導航的按鈕是應該不現實的,所以我加了GoToButtonVisibility屬性來控制。

    public class MyGroupHeader : IGroupHeader, INotifyPropertyChanged
    {
        public string Name { get; set; }
        public int FirstIndex { get; set; }
        public int LastIndex { get; set; }
        public double Height { get; set; }
        public string GoTo { get; set; }
        private Visibility _goToButtonVisibility = Visibility.Collapsed;

        public Visibility GoToButtonVisibility
        {
            get { return _goToButtonVisibility; }
            set
            {
                _goToButtonVisibility = value;
                OnPropertyChanged("GoToButtonVisibility");
            }
        }

        public MyGroupHeader()
        {
            FirstIndex = -1;
            LastIndex = -1;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

數據源的處理還是比較簡單的。

 2.GroupHeader的UI的處理

首先我想到的是加一個Grid,然後這些GroupHeader放在裡面,通過ScrollViewer的ViewChanged來處理它們。

比較了下ListView的Group效果,Scrollbar是會擋住GroupHeader的,所以我把這個Grid放進了ScrollViewer的模板裡面。

GroupListView的模板,這裡大家可以看到我加入了個ProgressRing,這個是後面做導航功能需要的,後面再講。

 <ControlTemplate TargetType="local:GroupListView">
                    <Grid BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
                        <ScrollViewer x:Name="ScrollViewer" Style="{StaticResource GroupListViewScrollViewer}" AutomationProperties.AccessibilityView="Raw" BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}" IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" TabNavigation="{TemplateBinding TabNavigation}" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="{TemplateBinding ScrollViewer.ZoomMode}">
                            <ItemsPresenter FooterTransitions="{TemplateBinding FooterTransitions}" FooterTemplate="{TemplateBinding FooterTemplate}" Footer="{TemplateBinding Footer}" HeaderTemplate="{TemplateBinding HeaderTemplate}" Header="{TemplateBinding Header}" HeaderTransitions="{TemplateBinding HeaderTransitions}" Padding="{TemplateBinding Padding}"/>
                        </ScrollViewer>
                        <ProgressRing x:Name="ProgressRing" Visibility="Collapsed" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    </Grid>
                </ControlTemplate>

ScrollViewer的模板

                       <Grid Background="{TemplateBinding Background}">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>
                            <ScrollContentPresenter x:Name="ScrollContentPresenter" Grid.ColumnSpan="2" ContentTemplate="{TemplateBinding ContentTemplate}" Margin="{TemplateBinding Padding}" Grid.RowSpan="2"/>
                            <Grid x:Name="GroupHeadersCanvas" Grid.RowSpan="2" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
                            <ContentControl x:Name="TopGroupHeader" Grid.RowSpan="2" Grid.ColumnSpan="2" VerticalAlignment="Top" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
                            <ScrollBar x:Name="VerticalScrollBar" Grid.Column="1" HorizontalAlignment="Right" IsTabStop="False" Maximum="{TemplateBinding ScrollableHeight}" Orientation="Vertical" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{TemplateBinding VerticalOffset}" ViewportSize="{TemplateBinding ViewportHeight}"/>
                            <ScrollBar x:Name="HorizontalScrollBar" IsTabStop="False" Maximum="{TemplateBinding ScrollableWidth}" Orientation="Horizontal" Grid.Row="1" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{TemplateBinding HorizontalOffset}" ViewportSize="{TemplateBinding ViewportWidth}"/>
                            <Border x:Name="ScrollBarSeparator" Background="{ThemeResource SystemControlPageBackgroundChromeLowBrush}" Grid.Column="1" Grid.Row="1"/>
                        </Grid>

下麵就是實現對GroupHeader顯示的控制了。

很快代碼寫好了。。運行起來效果還可以。。但是童鞋們說。。你這個跟Composition API 一毛錢關係都沒有啊。。

大家別急。。聽我說。。模擬器裡面運行還行,拿實體機器上運行的時候,當我快速向上或者向下滑動的時候,GroupHeader會出現頓一頓的感覺,卡一下,不會有慣性的感覺。

看到這個,我立馬明白了。。不管是ViewChanging或者ViewChanged事件,它們跟Manipulation都不是同步的。

看了上一盤 UWP Composition API - PullToRefresh的童鞋會說,好吧,隱藏的真深。

那我們還是用Composition API來建立GroupHeader和ScrollViewer之間的關係。

1.首先我想的是,當進入Viewport再用Composition API來建立關係,但是很快被我否決了。還是因為ViewChanged這個事件是有慣性的原因,這樣沒法讓創建GroupHeader和ScrollViewer之間的關係的初始數據完全準確。

就是說GroupHeader因為初始數據不正確的情況會造成沒放在我想要的位置,只有當慣性停止的時候獲取的位置信息才是準確的。

在PrepareContainerForItemOverride中判斷是否GroupHeader 的那個Item已經準備添加到ItemsPanel裡面。

         protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);
            ListViewItem listViewItem = element as ListViewItem;
            listViewItem.SizeChanged -= ListViewItem_SizeChanged;
            if (listViewItem.Tag == null)
            {
                defaultListViewItemMargin = listViewItem.Margin;
            }

            if (groupCollection != null)
            {
                var index = IndexFromContainer(element);
                var group = groupCollection.GroupHeaders.FirstOrDefault(x => x.FirstIndex == index || x.LastIndex == index);
                if (group != null)
                {
                    if (!groupDic.ContainsKey(group))
                    {
                        ContentControl groupheader = CreateGroupHeader(group);
                        ContentControl tempGroupheader = CreateGroupHeader(group);

                        ExpressionAnimationItem expressionAnimationItem = new ExpressionAnimationItem();
                        expressionAnimationItem.VisualElement = groupheader;
                        expressionAnimationItem.TempElement = tempGroupheader;

                        groupDic[group] = expressionAnimationItem;

                        var temp = new Dictionary<IGroupHeader, ExpressionAnimationItem>();
                        foreach (var keyValue in groupDic.OrderBy(x => x.Key.FirstIndex))
                        {
                            temp[keyValue.Key] = keyValue.Value;
                        }
                        groupDic = temp;
                        if (groupHeadersCanvas != null)
                        {
                            groupHeadersCanvas.Children.Add(groupheader);
                            groupHeadersCanvas.Children.Add(tempGroupheader);

                            groupheader.Measure(new Windows.Foundation.Size(this.ActualWidth, this.ActualHeight));

                            group.Height = groupheader.DesiredSize.Height;

                            groupheader.Height = tempGroupheader.Height = group.Height;
                            groupheader.Width = tempGroupheader.Width = this.ActualWidth;

                            if (group.FirstIndex == index)
                            {
                                listViewItem.Tag = listViewItem.Margin;
                                listViewItem.Margin = GetItemMarginBaseOnDeafult(groupheader.DesiredSize.Height);
                                listViewItem.SizeChanged += ListViewItem_SizeChanged;
                            }

                            groupheader.Visibility = Visibility.Collapsed;
                            tempGroupheader.Visibility = Visibility.Collapsed;
                            UpdateGroupHeaders();
                        }

                    }
                    else
                    {
                        if (group.FirstIndex == index)
                        {
                            listViewItem.Tag = listViewItem.Margin;
                            listViewItem.Margin = GetItemMarginBaseOnDeafult(group.Height);
                            listViewItem.SizeChanged += ListViewItem_SizeChanged;
                        }
                        else
                        {
                            listViewItem.Margin = defaultListViewItemMargin;
                        }
                    }

                }
                else
                {
                    listViewItem.Margin = defaultListViewItemMargin;
                }
            }
            else
            {
                listViewItem.Margin = defaultListViewItemMargin;
            }
        }
View Code

在UpdateGroupHeader方法裡面去設置Header的狀態

        internal void UpdateGroupHeaders(bool isIntermediate = true)
        {
            var firstVisibleItemIndex = this.GetFirstVisibleIndex();
            foreach (var item in groupDic)
            {
                //top header
                if (item.Key.FirstIndex <= firstVisibleItemIndex && (firstVisibleItemIndex <= item.Key.LastIndex || item.Key.LastIndex == -1))
                {
                    currentTopGroupHeader.Visibility = Visibility.Visible;
                    currentTopGroupHeader.Margin = new Thickness(0);
                    currentTopGroupHeader.Clip = null;
                    currentTopGroupHeader.DataContext = item.Key;

                    if (item.Key.FirstIndex == firstVisibleItemIndex)
                    {
                        if (item.Value.ScrollViewer == null)
                        {
                            item.Value.ScrollViewer = scrollViewer;
                        }

                        var isActive = item.Value.IsActive;


                        item.Value.StopAnimation();
                        item.Value.VisualElement.Clip = null;
                        item.Value.VisualElement.Visibility = Visibility.Collapsed;

                        if (!isActive)
                        {
                            if (!isIntermediate)
                            {
                                item.Value.VisualElement.Margin = new Thickness(0);
                                item.Value.StartAnimation(true);
                            }
                        }
                        else
                        {
                            item.Value.StartAnimation(false);
                        }

                    }
                    ClearTempElement(item);
                }
                //moving header
                else
                {
                    HandleGroupHeader(isIntermediate, item);
                }
            }
        }
View Code

這裡我簡單說下幾種狀態:
1. 在ItemsPanel裡面

1)全部在Viewport裡面

動畫開啟,Clip設置為Null

2)部分在Viewport裡面

動畫開啟,並且設置Clip
3)沒有在viewport裡面

動畫開啟,Visible 設置為Collapsed
2. 沒有在ItemsPanel裡面

動畫停止。

關於GroupHeader初始狀態的設置,這裡是最坑的,遇到很多問題。

        public void StartAnimation(bool update = false)
        {

            if (update || expression == null || visual == null)
            {
                visual = ElementCompositionPreview.GetElementVisual(VisualElement);
                //if (0 <= VisualElement.Margin.Top && VisualElement.Margin.Top <= ScrollViewer.ActualHeight)
                //{
                //    min = (float)-VisualElement.Margin.Top;
                //    max = (float)ScrollViewer.ActualHeight + min;
                //}
                //else if (VisualElement.Margin.Top < 0)
                //{

                //}
                //else if (VisualElement.Margin.Top > ScrollViewer.ActualHeight)
                //{

                //}
                if (scrollViewerManipProps == null)
                {
                    scrollViewerManipProps = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(ScrollViewer);
                }
                Compositor compositor = scrollViewerManipProps.Compositor;

                // Create the expression
                //expression = compositor.CreateExpressionAnimation("min(max((ScrollViewerManipProps.Translation.Y + VerticalOffset), MinValue), MaxValue)");
                ////Expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y +VerticalOffset");

                //expression.SetScalarParameter("MinValue", min);
                //expression.SetScalarParameter("MaxValue", max);
                //expression.SetScalarParameter("VerticalOffset", (float)ScrollViewer.VerticalOffset);

                expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y + VerticalOffset");
                ////Expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y +VerticalOffset");

                //expression.SetScalarParameter("MinValue", min);
                //expression.SetScalarParameter("MaxValue", max);
                VerticalOffset = ScrollViewer.VerticalOffset;
                expression.SetScalarParameter("VerticalOffset", (float)ScrollViewer.VerticalOffset);

                // set "dynamic" reference parameter that will be used to evaluate the current position of the scrollbar every frame
                expression.SetReferenceParameter("ScrollViewerManipProps", scrollViewerManipProps);

            }



            visual.StartAnimation("Offset.Y", expression);

            IsActive = true;
            //Windows.UI.Xaml.Media.CompositionTarget.Rendering -= OnCompositionTargetRendering;

            //Windows.UI.Xaml.Media.CompositionTarget.Rendering += OnCompositionTargetRendering;
        }

註釋掉了的代碼是處理:

當GroupHeader進入Viewport的時候才啟動動畫,離開之後就關閉動畫,表達式就是一個限制,這個就不講了。

expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y + VerticalOffset");

可以看到我給表達式加了一個VericalOffset。。嗯。其實Visual的Offset是表示 Visual 相對於其父 Visual 的位置偏移量。

舉2個例子,整個Viewport的高度是500,現在滾動條的VericalOffset是100。

1.如果我想把Header(header高度為50)放到Viewport的最下麵(Header剛好全部進入Viewport),那麼初始的參數應該是哪些呢?

Header.Margin = new Thickness(450);

Header.Clip=null;

expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y +100");

這樣向上滾ScrollViewerManipProps.Translation.Y(-450),Header 就會滾Viewport的頂部。


2.如果我想把Header(header高度為50)放到Viewport的最下麵(Header剛好一半全部進入Viewport),那麼初始的參數應該是哪些呢?

Header.Margin = new Thickness(475);

Header.Clip=new RectangleGeometry() { Rect = new Rect(0, 0, this.ActualWidth, 25) };

expression = compositor.CreateExpressionAnimation("ScrollViewerManipProps.Translation.Y +100");

當向上或者向下滾動的時候,記得更新Clip值就可以了。

說到為什麼要加Clip,因為如果你的控制項不是整個Page大小的時候,這個Header會顯示到控制項外部去,大家應該都是懂得。

這裡說下這個裡面碰到一個問題。當GroupHeader Viewport之外的時候(在Grid之外的,Margin大於Grid的高度)創建動畫,會發現你怎麼修改Header屬性都是沒有效果的。

最終結果的是不會在屏幕上顯示任何東西。

實驗了下用Canvas發現就可以了,但是Grid卻不行,是不是可以認為Visual在創建的時候如果對象不在它父容器的Size範圍之內,創建出來都是看不見的??

這個希望懂得童鞋能留言告訴一下。

把ScrollViewer模板裡面的Grid換成Canvas就好了。。

剩下的都是一些計算,計算位置,計算大小變化。

最後就是GoToGroup方法,當跳轉的Group沒有load出來的時候(也就是FirstIndex還沒有值得時候),我們就Load,Load,Load,直到

它有值,這個可能是個長的時間過程,所以加了ProgressRing,找到Index,最後用ListView的API來跳轉就好了。

        public async Task GoToGroupAsync(int groupIndex, ScrollIntoViewAlignment scrollIntoViewAlignment = ScrollIntoViewAlignment.Leading)
        {
            if (groupCollection != null)
            {
                var gc = groupCollection;
                if (groupIndex < gc.GroupHeaders.Count && groupIndex >= 0 && !isGotoGrouping)
                {
                    isGotoGrouping = true;
                    //load more so that ScrollIntoViewAlignment.Leading can go to top
                    var loadcount = this.GetVisibleItemsCount() + 	   

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

-Advertisement-
Play Games
更多相關文章
  • 1. IIS7中應用程式池隊列長度調整為65535(預設為1000) 打開IIS7管理器,選擇應用程式池,右鍵選擇應用程式池,選擇高級設置,把1000改為65535 2. IIS7的appConcurrentRequestLimit調正為100000(預設5000) 方法: 命令行:C:\windo ...
  • 使用軟體的一個重要原因,是因為軟體可以幫我們重覆處理很多事情。在前面我們已經講到了迴圈。迴圈就是為了重覆處理一個事情。那麼我們有沒有想過,我們要重覆處理的一批數據怎麼在程式里存放呢? 舉個例子吧。 我們有一個通訊錄的程式,需要保存通訊錄中的所有名字。我們可以這樣做。 string name1=”Da ...
  • 題記:之前一直用timer,突然用了次ThreadPool來註冊線程,發現在註銷的時候不會了(%>_<%),於是簡單記錄幾句: private AutoResetEvent autoReset = new AutoResetEvent(false); private RegisteredWaitHa ...
  • 寫這篇文章的目的是我這段時間關於ANYCAD的一些學習感悟,並且附上具體的操作,使新手少走一些彎路。 本人是C#的新手,從來沒接觸過編程,暑期跟著老師做學習,第一個任務就是用C#打開DXF文件,我以為任務很簡單,BUT 在網上搜索一大堆,都是沒用的(對於我這個新手來講),偶然一次看到一篇《基於Any ...
  • 在C#的迴圈語句中,有的時候我們希望跳過其中某個迴圈,有時我們希望當某個條件滿足時,直接終止整個迴圈。C#為我們提供了 continue;和break;語句。 continue和break的用法一樣,直接寫上這個單詞,後面加一個分號就行 比如: continue; break; continue和b ...
  • Microsoft Visual Studio(簡稱VS)是美國微軟公司的開發工具包系列產品。 Visual Studio 2015 是一個豐富的集成開發環境,可用於創建出色的 Windows、Android 和 iOS 應用程式以及新式 Web 應用程式和雲服務。 主要特點: ●適用於各種規模和復 ...
  • jQuery dataTables 插件是一個優秀的表格插件,是後臺工程師的福音!它提供了針對數據表格的排序、瀏覽器分頁、伺服器分頁、查詢、格式化等功能。dataTables 官網也提供了大量的演示和詳細的文檔進行說明,為了方便使用,這裡進行詳細說明。 去官網:https://www.datatab ...
  • (此文章同時發表在本人微信公眾號“dotNET每日精華文章”,歡迎右邊二維碼來關註。) 題記:ABP框架對多租戶場景提供了很好的支持,內建了多租戶的處理機制,今天我們來深入解析一下這一特性。 最近在基於ABP框架(ASP.NET Boilerplate)開發了一個SaaS。所以接下來可能會時不時分享... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...