ListView with columns and bindings performance - c#

I have performance issue with ListView:
Single item takes 13-30 ms to create (50 items is over 1 second). Virtualization (recyling mode) is on, but scrolling even 100 items is already very uncomfortable.
At first I thought it's layout problem. But the reason seems to be - bindings.
There are multiple columns and each column cell has different template with different bindings, as an example:
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Value}"
Visibility="{Binding IsEditable, Converter={local:TrueToCollapsedConverter}}" />
<Grid local:GridBehavior.Columns=",auto"
Visibility="{Binding IsEditable, Converter={local:FalseToCollapsedConverter}}">
<TextBox Text="{local:ExceptionBinding Path=Value, Converter={local:DoubleToStringConverter}}"
local:TextBoxBehavior.SelectAllOnFocus="True"
local:ListBoxBehavior.SelectListBoxItemOnFocus="True" />
<TextBlock Grid.Column="1" Text="{local:Lng Key=Unit.mm}" />
</Grid>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
Any single binding add something like 0.1 ms. There are 20 columns, each cell has from 1 to 20 bindings, so it leads to this:
Binding take majority of time, e.g. 2.83 of 3.07 ms for the first column on screenshot.
Is there a way to gain some performance? Am I doing some obvious mistake?

Related

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

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.

How to make ListViewItem share the same height and scale automatically

I have a ListView in a Grid inside a MainWindow, and it has defined a DateTemplate inside of the ItemTemplate, which includes a TextBlock, each TextBlock has a different length of text, some are long, and some short.
The application needs to fit different screen resolutions, that is, the FontSize of each TextBlock needs to be autoscaled accordingly.
So I wrap the TextBlock with a ViewBox, but the problem is, the short text will get a bigger FontSize than the long text, I want all TextBlock's can share the same FontSize and can autoscale to fit different screen size and resolution, any idea?
My ListView is as below:
<ListView x:Name="specs" Margin="10 15 10 15" VerticalAlignment="Center"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" Background="Transparent" BorderThickness="0">
<ListView.ItemTemplate>
<DataTemplate>
<Viewbox>
<TextBlock Text="{Binding}" TextWrapping="Wrap" FontFamily="Kaiti Regular"/>
</Viewbox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You can use a shared size group to force the textblocks all to have the same width as the widest one, which in turn will force the Viewboxes to scale identically.
The key things here are Grid.IsSharedSizeScope="True" on the ListView, and putting the content of the ViewBox in a grid column with SharedSizeGroup="SomeArbitraryString". Under a given parent that's a shared size scope, all the grid columns in a particular named shared size group will have the same width as the widest one (and the same for grid rows and height).
<ListView
x:Name="specs"
Margin="10 15 10 15"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Words}"
Grid.IsSharedSizeScope="True"
>
<ListView.ItemTemplate>
<DataTemplate>
<Viewbox>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" SharedSizeGroup="Text" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Text="{Binding}"
TextWrapping="Wrap"
FontFamily="Kaiti Regular"
/>
</Grid>
</Viewbox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I'm not convinced this will resolve the entire issue, but it'll get you another step along. And in general, it's a wonderfully useful tool for DataTemplate layout in ItemsControls.

ListView: Enable text trimming instead of HorisontalScrollBar

I tried to search Google, but i cannot formulate it clearly enough.
So idea is: if text cannot be displayed fully in a TextBlock, end of text should be removed and '...' should be added. For fixed size i can write something like this:
DetailsListView.Items.Add(string.Format("{0}.{1} - {2}...", i + 1, j + 1, somestring.Remove(15)));
but for dynamic WPF layout that looks ugly.
What the best way to do it?
i'm trying this one, but it doesn't work
<ListView Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Margin="5" Name="TaskListView">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock TextTrimming="CharacterEllipsis" Text="{Binding}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
just a scrollbar appears
No need to do it yourself, TextBlock can already do this for you:
<TextBlock TextTrimming="CharacterEllipsis"/>
It seems your text is in a list of some sort you need to set this on the TextBlock in a DataTemplate for each item in the list, e.g.:
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Of course if your TextBlock is in a pane that grows as it needs then there is nothing to trim - you can set a fixed size on your TextBlock, or your ListView and disable horizontal scrolling.
<TextBlock
TextTrimming="WordEllipsis" TextWrapping="NoWrap"
FontSize="14">
One<LineBreak/>
two two<LineBreak/>
Three Three Three<LineBreak/>
four four four four<LineBreak/>
Five Five Five Five Five<LineBreak/>
six six six six six six<LineBreak/>
Seven Seven Seven Seven Seven Seven Seven
</TextBlock>
TextBlock.TextTrimming

ListBox Value Display in Windows Phone 8

I am trying to bind the observable collection with a ListBox and displaying the data on UI (Windows Phone 8.0).
My Listbox have four textblock for four property,
<ListBox x:Name="allListBox" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Style="{StaticResource txtBlockStyleDate}" Text="{Binding Date}"></TextBlock>
<TextBlock Style="{StaticResource txtBlockStyle1}" Text="{Binding TypeOfApproval}"></TextBlock>
<TextBlock Style="{StaticResource txtBlockStyle2}"
Text="{Binding TypeOfRequest}" />
<TextBlock Style="{StaticResource txtBlockStyle3}" Text="{Binding Status}"/>
<TextBlock Height="30"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
all the objects in observable collection are showing fine, but when any of the property does not have any value, its text block is still there, and its space is kind of visible, which gives the bad impression over UI.
Can you suggest what should i do, when any property is blank, textblock related to that should not eat any height and next textblock should take space of it.
I am attaching an image, see after testing blank space is visible, coz its property is null, i want to remove this space.
You are going to need an IValueConverter, basically the idea is you do this:
... insert ...
<ListBox.Resources>
<VisibilityConverter x:Key="VisibilityConverter"/>
</ListBox.Resources>
... change ...
<TextBlock Style="{StaticResource txtBlockStyleDate}" Visibility="{Binding Date, Converter={StaticResource VisibilityConverter}}" Text="{Binding Date}"/>
Within your IValueConverter implementation, you just see if the property is null or empty. If it is you just return Visibility.Collapsed
You need to use a Visibility converter for this!
If your bound data is text, a StringToVisibilityConverter would be fine.
An example on how to do this can be found here: http://www.smallandmighty.net/blog/using-value-converters-to-change-the-visibility-of-a-control

Adding a row of controls in a Silverlight Datagrid

In the application I'm developing, I'm using a datagrid to display data fetch from a database. It is declared like so in my XAML file:
<sdk:DataGrid x:Name="dg" AutoGenerateColumns="False" ItemSource={Binding etc.} >
<sdk:DataGrid.Columns>
<sdk:DataGridTextBoxColumn Header="Col1"
Binding="{Binding Col1Data}" />
<sdk:DataGridTextBoxColumn Header="Col2"
Binding="{Binding Col2Data}" />
<sdk:DataGridTextBoxColumn Header="Col3"
Binding="{Binding Col3Data}" />
<sdk:DataGridCheckBoxColumn Header="Col4"
Binding="{Binding Col4Data}" />
<sdk:DataGrid.Columns>
<sdk:DataGrid>
What I want to do, is to add a row, containing 5 combo boxes (1 for each column) between the header and the first row of my data. It is pretty easy to do for a column, using DataGridTemplateColumn, but how can I do this for a row?
Thanks in advance for your suggestions!
Fake edit: Oh by the way I'm not a fan of code behind (trying to go full MVVM), so I'm looking for a way to do this in XAML, if possible.
You may be able to get away with providing a template for the header, but failing that you'll need to re-template the DataGrid in order to do this.
Ok, so I kinda found a work around. I declared a template for my cells that contains a button and a textblock bound to the data. i bind the visibility property of the button on a boolean that will be true only for the elements of the first row.
<sdk:DataGridTemplateColumn Header="Col1" Width="60">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Height="30">
<Button Content="Boutton" Visibility="{Binding Path=IsFirstElement, Converter={StaticResource visibilityConverter}}" />
<TextBlock Text="{Binding Path=Col1Data}" />
</StackPanel>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
It's a bit hack-ish. But it works. My only concern is performance since a button is declared for each cell. So with thousands of row, I guess there could be a performance hit.

Categories