Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

<?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>
like image 640
Alan2 Avatar asked Feb 22 '19 10:02

Alan2


2 Answers

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. Those hundreds of UI has to be rendered all at once on the screen. (even if the user is not going to look at them all)

Suggestion: How should we do this:

I highly suggest using a Listview. Or FlowListview for grids https://github.com/daniel-luberda/DLToolkit.Forms.Controls

Why?

Listview will only try to draw UI for the screen user is looking at.

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

More Information on Microsoft's Official Docs

Example I have added 1000 items in the listView's ItemSource when clicked on the button in header. 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. Use DataTemplate selecter property of ListView

<?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>();
        }
    }
like image 185
prasadsunny1 Avatar answered Nov 15 '22 08:11

prasadsunny1


You can try using BatchBegin() on stacks variable before the loop of child adds in your function AddSwitchRows and BatchCommit() after the loop. And if that works, do the same to your parent stack variable st in CreateSwitchSection.

Ie. stacks.BatchBegin(); 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.

like image 44
R. Dmytruk Avatar answered Nov 15 '22 08:11

R. Dmytruk