How can I optimize listview data binding in .NET MAUI? - c#

I am working on a MAUI app, which has a page with two ListViews. Only one should be visible at a time, which i do by setting their IsVisible property to true or false. Upon switching from one to the other, the app freezes or becomes extremely slow. I suppose this is because the listview's itemssource is rather large. I bound both listviews to an observablecollection, which should be the fastest option. Both ObservableCollections have data that updates rather often, and both should get quite big (1000+ items).
How can I optimize the listviews to load quickly?
I have tried to limit the itemssource to a certain amount of objects, but this only makes it slower. I guess this is because removing items from the itemssource in a first-in-first-out way basically updates every single object within the obervablecollection.
My current code:
`
<Grid RowDefinitions="*,5,40" ColumnDefinitions="75,75,75,105,*">
<ScrollView x:Name="SerialLogScrollViewASCII"
Grid.ColumnSpan="6"
VerticalScrollBarVisibility="Always"
Grid.Row="0"
IsVisible="{Binding IsASCIILogVisible}"
BackgroundColor="Transparent">
<ListView x:Name="LogStackASCII"
ItemsSource="{Binding SerialLogASCII}"
BackgroundColor="Transparent"
ItemTapped="LogMessageTapped"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate >
<ViewCell>
<Grid PropertyChanged="SerialLogChanged" ColumnDefinitions="160,*" ColumnSpacing="5" BackgroundColor="Transparent">
<Label Text="{Binding Time}" Margin="0,0,10,0" Grid.Column="0" FontSize="12" BackgroundColor="Transparent"/>
<Label Text="{Binding Message}" LineBreakMode="CharacterWrap" Grid.Column="1" FontSize="12" BackgroundColor="Transparent"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollView>
<ScrollView x:Name="SerialLogScrollViewHEX"
Grid.ColumnSpan="6"
VerticalScrollBarVisibility="Always"
Grid.Row="0"
IsVisible="{Binding IsHEXLogVisible}"
BackgroundColor="Transparent">
<ListView x:Name="LogStackHEX"
ItemsSource="{Binding SerialLogHEX}"
BackgroundColor="Transparent"
ItemTapped="LogMessageTapped"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate >
<ViewCell>
<Grid PropertyChanged="SerialLogChanged" Padding="1" ColumnDefinitions="160,*" ColumnSpacing="5" BackgroundColor="Transparent">
<Label Text="{Binding Time}" Margin="0,0,10,0" Grid.Column="0" FontSize="12" BackgroundColor="Transparent"/>
<Label Text="{Binding Message}" LineBreakMode="CharacterWrap" Grid.Column="1" FontSize="12" BackgroundColor="Transparent"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollView>
</Grid>
`

When you put Vertical ListView, inside Vertical ScrollView, the performance is pretty much non-existent. The more and more items you add, the worse it will get.
You should not be doing this. ListView has its own scroll.
Consider CollectionView or DataGrid.
First remove the scolls. Then if you are not happy, try CollectionView or DataGrid.
And then if you have questions, please ask.
Edit: Last attempt to explain this.
Putting a ListView or CollectionView or ANY container that has variable height, inside Vertical ScrollView or Vertical StackLayout or ANY container that is not limiting the height, makes the first container GROW in height until all the content is loaded.
This means, if you have 100 000 items, all of them will be loaded in the inner container.
This "programmatically scrolling works better" is actually the result of pre-loading everything, and it is not better for the performance, it is plain and simple killing it.
Try adding 100, 1000, 10000, 100000 items, and you will see how it gets progressively worse.
Containers of other VisualElements (Like ListView or CollectionView) have built-in design patterns to handle displaying them in portions. When and if displaying of that item is needed.

You could try Caching strategy in ListView. Try the following code
<ListView x:Name="LogStackASCII" CachingStrategy="RecycleElement"
...
RecycleElement should be used in the following circumstances:
Each cell has a small to moderate number of bindings.
Each cell's BindingContext defines all of the cell data.
Each cell is largely similar, with the cell template unchanging.
I found the Xamarin official docs ListView performance for you. It also works for Maui. Besides the Caching strategy, it also gives some other ListView performance suggestions to enhance performance of Listview.
By the way, the two ListView almost have the same template, so why not just use one ListView? And when switching, just change the itemsSource it binds to.
Hope it works for you.

Related

MAUI: Header in Colectionview not displayed when layout is inserted

I am trying to show a header in my collectionview with this code (which im sure would work on xamarin)
<CollectionView SelectionMode="Multiple"
SelectionChanged="collectionview_coll_skills_SelectionChanged"
Margin="5,0,0,0"
EmptyView="{x:Static res:Strings.LaedtSKills}"
Grid.Row="0"
VerticalScrollBarVisibility="Never" x:Name="collectionview_coll_skills">
<CollectionView.HeaderTemplate>
<DataTemplate>
<ViewCell>
<ContentView>
<Label Text="Hi mom"/>
</ContentView>
</ViewCell>
</DataTemplate>
</CollectionView.HeaderTemplate>
The text "hi mom" is just not shown at all,
if I however inserted the text directly into the property (eg Header="hi mom")
It would be displayed but then no layoutting is possible.
Anyone already found a fix or workaround for this?
Thanks
As posted in my comment you can just use the Header of the CollectionView
You can style it as needed. More Info Microsoft Header/Footer resource
<CollectionView.Header>
<Label Text="Hi mom" />
</CollectionView.Header>

strings randomly cut off in xamarin listview

I'm making a basic Android master/detail app in VS 2019, which was started with the "Blank" mobile template.
I'm just using a grid for the layout. I have the ListView in a frame, which is in a row of the grid. The ItemsSource of the ListView is bound to a generic List:
List<MyObject>
The MyObject class has a Name property. The ListView contains a simple Label in its ViewCell/DataTemplate/ItemTemplate. The Text of the Label is bound to the Name property of the MyObject class.
The problem is that some of the strings displayed are cutoff. Which strings are cutoff seems random. When I tap a row, a Details page is displayed for the item in the tapped row. When I do that and then hit Back, some of the strings that were cutoff are now fully displayed, while other entries either remain cutoff and/or there are entries that were not cutoff, but are now cutoff.
The pic at the link below shows 2 screen shots. The one on the left is before opening the Details page, and the other is after returning from the Details page:
2 screen shots; left is before Details page displayed, the right after
I'm fairly new to Xamarin Forms, so I'm probably missing something basic. The randomness of which entries are cutoff really has me puzzled. Any help would be appreciated.
Here's the XAML for the Frame and ListView:
<!-- items list -->
<Frame x:Name="ListFrame"
Grid.Row="3"
VerticalOptions="Fill"
HorizontalOptions="Fill"
OutlineColor="Black"
Padding="5"
Margin="4"
>
<ListView x:Name="ItemsListView"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedCryptoItem}"
IsRefreshing="{Binding IsRefreshingList}"
RefreshCommand="{Binding RefreshListCommand}"
ItemSelected="OnItemSelected"
CachingStrategy="RecycleElement"
VerticalScrollBarVisibility="Always"
RowHeight="24"
IsPullToRefreshEnabled="True"
>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Name}"
LineBreakMode="NoWrap"
FontSize="Medium"
FontAttributes="Bold"
HorizontalOptions="StartAndExpand"
VerticalOptions="CenterAndExpand"
/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Frame>
Looking to your code, VerticalOptions and HorizontalOptions are not optimized and can be buggy (regarding cells recycling with different texts ?).
You should use HorizontalTextAlignment and VerticalTextAlignment like advised here https://kent-boogaart.com/blog/jason-smith%27s-xamarin-forms-performance-tips. The Label Control will then use the entire space and text will be aligned as you want.
Later, you will probably embedd this Label in a Grid or another Layout if you want a more complex cell (with several controls)

How do I keep a specific xaml element within the window (so it doesn't overflow through the bottom)

I'm trying to make a simple invoicing application as part of an assessment. I have made a interface with a listbox that contains all the items that have been requested. However when I add too many items, the listbox then flows through the bottom of the window and I have to resize the window to fit.
I've tried a dockpanel, and I've assigned the stackpanel to the grid itself. If I set a fixed height it seems to work as expected.
Here is the xaml of the listbox:
<StackPanel Grid.Column="1" Grid.Row="0" ClipToBounds="True">
<ListBox Name="Shirts" HorizontalContentAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Visible" Margin="5" ClipToBounds="True">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding ShirtSize}" />
<Run Text="{Binding ShirtStyle}" />
<Run Text="{Binding ShirtColour}" />
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Print Invoice" Margin="5" ClipToBounds="True"/>
</StackPanel>
I expected the code to work like this (it's not meant to be of fix height but it works as an example).
How it actually works.
Use a DockPanel instead of a StackPanel and it should work better. StackPanel will grow with its content regardless of its container size. A DockPanel will respect the container size and fill the available space. Put the button first in order with Dock="Bottom" and then the ListBox with Dock="Fill" (the list will arrange itself above the button even though it is declared after it, which is a bit unintuitive)

Custom viewcell like imagecell xaml?

How would i go about creating an identical "imagecell" but custom? The problem i am having with regular "imagecell" is that the image is hardcoded to 60x60 ..
I am pulling favicon from url's which means that a favicon from 16x16 to 60x60 will have super low quality. The alternative i have here is to change the size of the image but yet again, not doable in "imagecell" as it is hard coded.
Is there any website with different custom cells that i can look on or do anyone have the code for an identical imagecell view.
this is the code i am currently using.
<ListView x:Name="listView">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell Text="{Binding mainSite}" TextColor="Black" Detail="{Binding link}" ImageSource="{Binding image}"></ImageCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
you could easily build a similar UI with a ViewCell using nested StackLayouts, or alternatively a Grid
<StackLayout Orientation="Horizontal">
<Image />
<StackLayout>
<Label />
<Label />
</StackLayout>
</StackLayout>

Display data from two levels down

I have the following classes, which contain ObservableCollections of the next level down:
Draw
ObservableCollection<Round>();
Round
ObservableCollection<Formation>();
Formation
So a Draw is made up of Rounds, Rounds are made up of Formations.
I have a page which has a button to create a random draw, I currently have it calling another class which returns a draw:
this.defaultViewModel[DrawName] = RandomDraw.generate();
I am having no problem binding a ListView to Rounds and displaying round information, but how do I display the individual formations? This is what I am currently doing, I was not expecting to be able to just display things by binding to Formations but how do I access it?
<ListView
ItemsSource="{Binding Rounds}"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick"
ContinuumNavigationTransitionInfo.ExitElementContainer="True">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,9.5">
<TextBlock
Text="{Binding RoundNumber}"
TextWrapping="Wrap"
Pivot.SlideInAnimationGroup="1"
CommonNavigationTransitionInfo.IsStaggerElement="True"
Style="{ThemeResource ListViewItemTextBlockStyle}"
Margin="0,0,19,0"/>
<TextBlock
Text="{Binding Formations}"
TextWrapping="WrapWholeWords"
Pivot.SlideInAnimationGroup="2"
CommonNavigationTransitionInfo.IsStaggerElement="True"
Style="{ThemeResource ListViewItemContentTextBlockStyle}"
Margin="0,0,19,0"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You should take a look at Hierarchical Data Templates, which are used by the WPF TreeView control rather than ListViews. They are a natural fit to show hierarchical data. Of course, like any WPF control, you can completely customize their appearance using styling and templates. Here are some good references:
MSDN How to: Use a TreeView to Display Hierarchical Data
Hierarchical Databinding in WPF
However, if you would like to keep using ListViews, then one way to do this is to nest another container control inside the parent ListVIew. ObservableCollections are processed automatically by specific WPF elements, such as Panels. In your example, you can replace the second TextBlock with another ListView, with an ItemTemplate similar to the first. It can also be any Collection-like Panel element, such as StackPanel.
<ListView
ItemsSource="{Binding Rounds}"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick"
ContinuumNavigationTransitionInfo.ExitElementContainer="True">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,9.5">
<TextBlock
Text="{Binding RoundNumber}"
TextWrapping="Wrap"
Pivot.SlideInAnimationGroup="1"
CommonNavigationTransitionInfo.IsStaggerElement="True"
Style="{ThemeResource ListViewItemTextBlockStyle}"
Margin="0,0,19,0"/>
<!-- CHANGED CODE HERE -->
<ListView
ItemsSource="{Binding Formations}"
...>
<ListView.ItemTemplate>...</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Categories