简体   繁体   English

有什么办法可以在使用C#后端时加快向页面添加新元素的速度吗?

[英]Is there any way that I can speed up the adding of new elements to a page when using the C# back end?

I have code that works but I notice it's rather slow to create the page elements. 我有可行的代码,但我注意到创建页面元素的速度相当慢。

Here's what I have so far. 这是我到目前为止所拥有的。 Note that I'm not adding everything at once as I found that when I did the page creation was even slower. 请注意,我没有立即添加所有内容,因为我发现创建页面时速度更慢。

    public void CreateSwitchSection(bool? selected)
    {
        Application.Current.Resources.TryGetValue("FrameBorder", out object frameBorder);
        var st = new StackLayout { Orientation = StackOrientation.Vertical, Spacing = 0 };
        st.Children.Add(AddSwitchRows(selected, App.cardSetWithWordCount.Take(20)));
        st.Children.Add(AddSwitchRows(selected, App.cardSetWithWordCount.Skip(20).Take(20)));
        st.Children.Add(AddSwitchRows(selected, App.cardSetWithWordCount.Skip(40).Take(20)));
        st.Children.Add(AddSwitchRows(selected, App.cardSetWithWordCount.Skip(60).Take(20)));
        st.Children.Add(AddSwitchRows(selected, App.cardSetWithWordCount.Skip(80).Take(20)));
        var fr = new Frame { Style = (Style)frameBorder };
        var fs = new FrameStack { };
        var ht = new HeaderTemplate()
        {
            Text = "CHOOSE CARD SETS FOR THE DECK"
        };
        fs.Children.Add(ht);
        fs.Children.Add(st);
        fs.Children.Add(new LineTemplate());
        fr.Content = fs;
        details.Children.Clear();
        details.Children.Add(fr);
    }

    private StackLayout AddSwitchRows(bool? selected, IEnumerable<CardSetWithWordCount> data)
    {
        var stack = new StackLayout
        {
            Orientation = StackOrientation.Vertical,
            Spacing = 0
        };

        foreach (var x in data)
        {
            var cell = new BadgeGridTemplate
            {
                BindingContext = x,
                Text = x.Name,
                State = selected == true ? "E" : "D",
                Message = x.TotalWordCount.ToString(),
                TapCommand = (Command)vm.SelectCardSetCmd,
                RowId = x.Id,
                Separator = true
            };
            stack.Children.Add(cell);
        }
        return stack;
    }

For reference here is the BadgeGridTemplate I coded: 这里参考的是我编写的BadgeGridTemplate:

<?xml version="1.0" encoding="UTF-8"?>
<t:BaseGridTemplate xmlns="http://xamarin.com/schemas/2014/forms" 
                    xmlns:t="clr-namespace:Japanese.Templates" 
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
                    xmlns:local="clr-namespace:Japanese;assembly=Japanese" 
                    xmlns:b="clr-namespace:Behaviors;assembly=Behaviors" 
                    xmlns:converters="clr-namespace:Japanese.Converters;assembly=Japanese" 
                    x:Class="Japanese.Templates.BadgeGridTemplate" 
                    x:Name="this" 
                    HeightRequest="{DynamicResource GridHeight}" Margin="0"
    Orientation="Vertical" Spacing="0">
    <BoxView HeightRequest="1" HorizontalOptions="FillAndExpand" IsVisible="{Binding Separator, Source={x:Reference this}}" BackgroundColor="{DynamicResource LineColor}" Margin="0" />
    <Grid Padding="20,0" VerticalOptions="CenterAndExpand">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Label Grid.Column="0" Text="{Binding Text,  Source={x:Reference this}}" TextColor="{DynamicResource LabelColor}" Style="{StaticResource LabelText}" VerticalTextAlignment="Center" WidthRequest="30" />
        <t:Button Grid.Column="1" Meta="GsT" RowId="{Binding RowId, Source={x:Reference this}}" State="{Binding State, Source={x:Reference this}}" TapCommand="{Binding TapCommand, Source={x:Reference this}}" Text="{Binding Message, Source={x:Reference this}}" Theme="{Binding Theme}" WidthRequest="30" />
    </Grid>
</t:BaseGridTemplate>

What are we trying to do: 我们想做什么:

Take a list of data from some source, Put them in a list 从一些来源获取数据列表,将它们放入列表中

What your code is doing: 你的代码在做什么:

taking all the data building(all 100 AddSwitchRows ) UI right then. 然后立即获取所有数据构建(所有100个AddSwitchRows)UI。 Those hundreds of UI has to be rendered all at once on the screen. 必须在屏幕上一次渲染数百个UI。 (even if the user is not going to look at them all) (即使用户不打算全部查看)

Suggestion: How should we do this: 建议:我们该怎么做:

I highly suggest using a Listview. 我强烈建议使用Listview。 Or FlowListview for grids https://github.com/daniel-luberda/DLToolkit.Forms.Controls 或者用于网格的FlowListview https://github.com/daniel-luberda/DLToolkit.Forms.Controls

Why? 为什么?

Listview will only try to draw UI for the screen user is looking at. Listview只会尝试为用户正在查看的屏幕绘制UI。

If there are a thousand more items needed. 如果还有一千件物品需要。 It will be built only when the user scrolls down to that portion. 仅当用户向下滚动到该部分时才会构建它。

If you want to make multiple kinds of cells which depends on the data you receive we should use DataTemplate with ListView 如果你想根据你收到的数据制作多种单元格,我们应该使用DataTemplate和ListView

More Information on Microsoft's Official Docs 有关Microsoft官方文档的更多信息

Example I have added 1000 items in the listView's ItemSource when clicked on the button in header. 示例在单击标题中的按钮时,我在listView的ItemSource中添加了1000个项目。 You can add your template in the Listview.datatemplate tag and bind your ViewModel to this view View And if you want to change item's view as per a property value. 您可以在Listview.datatemplate标记中添加模板,并将ViewModel绑定到此视图View如果要根据属性值更改项目视图。 Use DataTemplate selecter property of ListView 使用ListView的DataTemplate选择器属性

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:stackAnswerApp"
             x:Class="stackAnswerApp.MainPage"
             x:Name="Root">
    <ContentPage.BindingContext>
        <local:MainPageViewModel />
    </ContentPage.BindingContext>
    <ListView ItemsSource="{Binding ListItems}" RowHeight="100">
        <ListView.Header>
            <Button Text="Start Adding" Command="{Binding StartAddingItems}" />
        </ListView.Header>
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <!-- whatever is your template -->
                    <StackLayout BackgroundColor="Aqua">
                        <Label Text="{Binding Text1}" />
                        <Button BackgroundColor="Blue"
                                Text="Go" BindingContext="{Binding Source={x:Reference Root},Path=BindingContext}"
                                Command="{Binding  ButtonTapped}" />
                    </StackLayout>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

ViewModel 视图模型

class AModel
    {
        public string Text1 { get; set; }
        public string Text2 { get; set; }
    }

    class MainPageViewModel
    {
        public ObservableCollection<AModel> ListItems { get; set; }
        private const int TotalRows = 1000;

        public ICommand StartAddingItems
        {
            get
            {
                return new Command(async () =>
                {
                    //add too many items in the list
                    await Task.Run(async () => { await AddItemsToList(); });
                });
            }
        }

        private async Task AddItemsToList()
        {
            for (var i = 0; i < TotalRows; i++)
            {
                ListItems.Add(new AModel() {Text1 = $"index {i}", Text2 = $"tap {i}"});
            }
        }

        public ICommand ButtonTapped
        {
            get
            {
                return new Command((() =>
                {
                    //button in the list was tapped
                }));
            }
        }

        public MainPageViewModel()
        {
            ListItems = new ObservableCollection<AModel>();
        }
    }

If the order matters, I haven't seen another way to do this. 如果订单很重要,我还没有看到另一种方法。 If the order doesn't matter, You could split the StackLayout in to multiple StackLayouts and just add the individual elements inside asynchronous threads using Task.WhenAll . 如果顺序无关紧要,您可以将StackLayout拆分为多个StackLayouts,并使用Task.WhenAll将各个元素添加到异步线程中。

Task.WhenAll is like having multiple people do work for you at the same time, instead of just 1 person. Task.WhenAll就好像有多个人同时为你工作,而不只是一个人。

You can try using BatchBegin() on stacks variable before the loop of child adds in your function AddSwitchRows and BatchCommit() after the loop. 您可以尝试在stacks变量上使用BatchBegin(),然后在循环之后在您的函数AddSwitchRows和BatchCommit()中添加子循环。 And if that works, do the same to your parent stack variable st in CreateSwitchSection. 如果可行,请在CreateSwitchSection中对父堆栈变量st执行相同操作。

Ie. IE浏览器。 stacks.BatchBegin(); stacks.BatchBegin(); foreach var x in data { … } stacks.BatchCommit(); foreach var x in data {...} stacks.BatchCommit();

This may resolve it as many languages displaying forms like to recalculate layout every time (expensive) the collection/list/children are added/removed/updated. 这可以解决它,因为许多语言显示形式,例如每次(昂贵)添加/删除/更新集合/列表/子项时重新计算布局。 Batching allows the layout recalculations to be done once instead of every time the list changes. 批处理允许布局重新计算一次,而不是每次列表更改。

I think Problem is in App.cardSetWithWordCount It may be a linq query I think this goes to db every time .Just run it once and save it on variable or property 我认为问题在App.cardSetWithWordCount中它可能是一个linq查询我认为每次都会转到db。只运行一次并保存在变量或属性上

  var globalApp_cardSetWithWordCount = App.cardSetWithWordCount.ToList()

Linq is executed when to list or toarray is called. Linq在列表或toarray被调用时被执行。

 List<int> ints = new List<int>();

        foreach(var y in query)
            ints.Add(y.Count());

        return ints.ToArray();

then use same take and skip format 然后使用相同的take和skip格式

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM