Removing a UWP Grid is too slow - performance

I have a Grid inside ListBox in my UWP C# application.
There is no problem with small grid.
However, when a Grid has over 50 cells with multiple rows and columns, removing the grid from its parent is very slow. It takes over 1 minute or about 2 minutes.
I tried to hide it by changing its Visibility to Collapsed or Opacity to 0 or building a release executable but still too slow as the same.
ToList().Clear() works fast in some case, but not enough.
ListBox rootBox = new ListBox();
rootBox.Items.Add(grid); // adding a complex Grid with over 50 cells with inner UI elements like TextBlock, TextBox and so on.
rootBox.Items.Remove(grid); <--- takes about 2 minutes with CPU utilization under 15% in my modern PC
There's no APIs to suspend and resume layout update in UWP.
Dynamically manipulating a UWP Grid element seems unpractically slow to me.
I tried to find a way to optimize performance for Grid UI, but failed.
Profiling showed me that layout task takes about 50% of CPU of the process but not intensive even in 1 core. It means the slowness is not from CPU-intensive calculation.
Oh, I tried to simplify the problem and found the case.
A grid inside multiple nested ListBoxes!
Nesting more ListBox makes the program slower.
You could reproduce the case by clicking the bottom 'Clear' button of the program below.
Reproduction code:
------------------ MainPage.xaml -----------------
<Page
x:Class="GridSlow.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:GridSlow"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Loaded="OnLoaded_Page"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Name="rootGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Visible" Grid.Row="0">
<ListBox Name="innerList">
</ListBox>
</ScrollViewer>
<Button Content="Clear" Click="Button_Click" Grid.Row="1"/>
</Grid>
</Page>
------------------------ MainPage.xaml.cs -----------------------
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace GridSlow
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void OnLoaded_Page(object sender, RoutedEventArgs args)
{
Grid grid = new Grid();
for (int row = 0; row < 50; ++row)
{
RowDefinition rowDef = new RowDefinition();
rowDef.Height = new GridLength(0, GridUnitType.Auto);
rowDef.MinHeight = 10;
grid.RowDefinitions.Add(rowDef);
for (int col = 0; col < 2; ++col)
{
ColumnDefinition colDef = new ColumnDefinition();
colDef.Width = new GridLength(0, GridUnitType.Auto);
colDef.MinWidth = 10;
grid.ColumnDefinitions.Add(colDef);
Border border = new Border() { BorderBrush = new SolidColorBrush(Colors.DarkGray), BorderThickness = new Thickness(1) };
TextBox textBox = new TextBox();
textBox.Text = "aaa";
border.Child = textBox;
grid.Children.Add(border);
Grid.SetRow(border, row);
Grid.SetColumn(border, col);
}
}
ListBox list2 = new ListBox();
ListBox list3 = new ListBox();
ListBox list4 = new ListBox();
ListBox list5 = new ListBox();
ListBox list6 = new ListBox();
TextBox box2 = new TextBox();
box2.Margin = new Thickness(1);
list2.Items.Add(box2);
list2.Items.Add(list3);
list3.Items.Add(new TextBox());
list3.Items.Add(list4);
list4.Items.Add(list5);
list5.Items.Add(list6);
list6.Items.Add(grid);
innerList.Items.Add(list2);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
innerList.Items.Clear();
}
}
}

Your layout has a ListBox named innerList with a complex item, Listbox2 is nested in the item, and a textbox and ListBox3 are nested in the ListBox2, which is another complex layout. This is meaningless control nesting.
This multi-level nesting causes that the designer spends a long time on rendering. So when you clear the Items, the layout will load very slowly.
Please remove unnecessary control nesting like the following.
private void OnLoaded_Page(object sender, RoutedEventArgs args)
{
Grid grid = new Grid();
for (int row = 0; row < 50; ++row)
{
RowDefinition rowDef = new RowDefinition();
rowDef.Height = new GridLength(0, GridUnitType.Auto);
rowDef.MinHeight = 10;
grid.RowDefinitions.Add(rowDef);
for (int col = 0; col < 2; ++col)
{
ColumnDefinition colDef = new ColumnDefinition();
colDef.Width = new GridLength(0, GridUnitType.Auto);
colDef.MinWidth = 10;
grid.ColumnDefinitions.Add(colDef);
Border border = new Border() { BorderBrush = new SolidColorBrush(Colors.DarkGray), BorderThickness = new Thickness(1) };
TextBox textBox = new TextBox();
textBox.Text = "aaa";
border.Child = textBox;
grid.Children.Add(border);
Grid.SetRow(border, row);
Grid.SetColumn(border, col);
}
}
innerList.Items.Add(grid);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
innerList.Items.Clear();
}

Related

Expanding the size of ListView cell on tap

I'm trying to implement a solution to increase the size of a ListView Cell when tapped using Xamarin Forms (and custom renderers if required).
I'm still pretty new to C#, and the idea of data binding is still a little unclear to me, however, it seems like that is the way to go to solve this problem (perhaps something along the lines of binding the Height / HeightRequest properties of the cell?).
My attempts thus far have been unsuccessful.
If anyone could give me a push in the right direction it would be much appreciated.
Thank you!
ViewCell does not expose Height as a BindableProperty in Xamarin.Forms 1.4.2x
However if you create your own BindableProperty in your Model you can achieve changing the height still as shown below:-
Model:-
public class MenuItem2 : BindableObject
{
public static readonly BindableProperty TextProperty = BindableProperty.Create<MenuItem2, string>(p => p.Text, default(string));
public static readonly BindableProperty CellHeightProperty = BindableProperty.Create<MenuItem2, int>(p => p.CellHeight, default(int));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public int CellHeight
{
get { return (int)GetValue(CellHeightProperty); }
set { SetValue(CellHeightProperty, value); }
}
}
XAML:-
<StackLayout>
<Button x:Name="cmdButton1" Text="Change Cell Heights" Clicked="cmdButton1_Clicked"/>
<ListView x:Name="lstItems" />
</StackLayout>
XAML Code-Behind:-
lstItems.HasUnevenRows = true;
lstItems.ItemTemplate = new DataTemplate(typeof(Classes.MenuCell2));
//
lstItems.ItemsSource = new List<MenuItem2>
{
new MenuItem2(),
new MenuItem2(),
new MenuItem2(),
new MenuItem2(),
};
If you don't set .HasUnevenRows you will not be able to change the cell height.
void cmdButton1_Clicked(object sender, EventArgs e)
{
Random objRandom = new Random();
//
var objItems = lstItems.ItemsSource;
//
foreach (MenuItem2 objMenuItem in objItems)
{
int intNewCellHeight = objRandom.Next(80, 160);
objMenuItem.CellHeight = intNewCellHeight;
objMenuItem.Text = "Cell Height = " + intNewCellHeight.ToString();
}
}
Custom ViewCell:-
public class MenuCell2 : ViewCell
{
public MenuCell2()
{
Label objLabel = new Label
{
YAlign = TextAlignment.Center,
TextColor = Color.Yellow,
};
objLabel.SetBinding(Label.TextProperty, new Binding("Text"));
StackLayout objLayout = new StackLayout
{
Padding = new Thickness(20, 0, 0, 0),
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.StartAndExpand,
Children = { objLabel }
};
Frame objFrame_Inner = new Frame
{
Padding = new Thickness(15, 15, 15, 15),
HeightRequest = 36,
OutlineColor = Color.Accent,
BackgroundColor = Color.Blue,
Content = objLayout,
};
Frame objFrame_Outer = new Frame
{
Padding = new Thickness(0, 0, 0, 10),
Content = objFrame_Inner
};
View = objFrame_Outer;
this.BindingContextChanged += MenuCell2_BindingContextChanged;
}
void MenuCell2_BindingContextChanged(object sender, EventArgs e)
{
MenuItem2 objMenuItem = (MenuItem2)this.BindingContext;
objMenuItem.PropertyChanged += objMenuItem_PropertyChanged;
}
void objMenuItem_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "CellHeight":
this.Height = (this.BindingContext as MenuItem2).CellHeight;
(this.View as Frame).ForceLayout();
break;
}
}
Remember to call ForceLayout on the root element of the ViewCell's View property, so it can redraw correctly.
This will give you a result something similar to the following (tested only on WindowsPhone at present):-
In order to do it on a ViewCell being tapped, on the XAML Page add:-
lstItems.ItemTapped += lstItems_ItemTapped;
and then change the model for the item to something like this:-
void lstItems_ItemTapped(object sender, ItemTappedEventArgs e)
{
(e.Item as MenuItem2).CellHeight = 200;
}
Xamarin now has an official example of doing this right within xaml and xaml code behind:
Overview:
https://developer.xamarin.com/samples/xamarin-forms/UserInterface/ListView/DynamicUnevenListCells/
Code:
https://github.com/xamarin/xamarin-forms-samples/tree/master/UserInterface/ListView/DynamicUnevenListCells

windows phone 7.. play or pause the music

i am developing game in windows phone 7. i want to add a button on homepage which allow to toggle between images.
i wrote following code but doesnt work
int key = 1;
switch (key)
{
case 1:
var brush = new ImageBrush();
BitmapImage image = new BitmapImage(new Uri(#"Assets/small/misc/music pause button.png", UriKind.Relative));
brush.ImageSource = image;
music.Background = brush;
key=0;
break;
case 0:
var brush2 = new ImageBrush();
BitmapImage image2 = new BitmapImage(new Uri(#"Assets/small/misc/music pause button.png", UriKind.Relative));
brush2.ImageSource = image2;
music.Background = brush2;
key = 1;
break;
}
solved this by using togglebutton
in xaml there is toggle button control
<ToggleButton Name="tog" Margin="555,358,0,7" IsChecked="{x:Null}" Checked="tog_Checked" Unchecked="tog_Unchecked" Background="{x:Null}" BorderBrush="{x:Null}" BorderThickness="0" IsThreeState="False" HorizontalAlignment="Left" Width="123"></ToggleButton>
now add code on event handlers :
private void tog_Checked(object sender, RoutedEventArgs e)
{
tog.Background = brush;
togkey = 1;
System.Diagnostics.Debug.WriteLine("1");
}
private void tog_Unchecked(object sender, RoutedEventArgs e)
{
tog.Background = null;
togkey = 0;
System.Diagnostics.Debug.WriteLine("0");
}

Not playing animation in Storyboard (using ObjectAnimationUsingKeyFrames)

I can not play animation storyboard. Displayed only last picture animation.
What could be wrong?
My code create animation:
public static class AnimationHelper
{
private const string PathImageAnimation = "/LeSommet.ZooSnap.UI.WindowsPhone;component/Resources/Images/Animation/ButtonState{0}.png";
public static void StartAnimation(UIElement target)
{
Storyboard storyboard = new Storyboard();
ObjectAnimationUsingKeyFrames objectAnimation = new ObjectAnimationUsingKeyFrames();
objectAnimation.AutoReverse = false;
objectAnimation.SpeedRatio = 2;
Storyboard.SetTargetProperty(objectAnimation, new PropertyPath("Source"));
Storyboard.SetTarget(objectAnimation, target);
for (int i = 1; i <= 4; i++)
{
DiscreteObjectKeyFrame discreteObject = new DiscreteObjectKeyFrame()
{
KeyTime = TimeSpan.FromMilliseconds(1000),
Value = new BitmapImage(new Uri(string.Format(PathImageAnimation, i), UriKind.Relative))
};
objectAnimation.KeyFrames.Add(discreteObject);
}
storyboard.Children.Add(objectAnimation);
storyboard.Begin();
}
}
My code Image xaml:
<Image Source="/LeSommet.ZooSnap.UI.WindowsPhone;component/Resources/Images/Animation/ButtonState5.png"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Width="110" Height="110" Stretch="Fill"
x:Name="imageSquareAnimation">
Are all of your keyframes at exactly the same time?
KeyTime = TimeSpan.FromMilliseconds(1000),
You could try:
KeyTime = TimeSpan.FromMilliseconds(1000 * i),
Try learning how to use Blend - it has an excellent editor to help you create animations.

WP7 - VisualTreeHelper to loop through all ListBox Items

I need to create a new ListBox based on items that are selected (checked). The following code actually worked if I only had like 20 items on the ListBox, but adding more items make it crash. Can anybody know how to make it work, or have a different aproach? Is there a limite for looping through a listBox?
// worked fine for 20 items,
// but my actual list contems 95 items...
private void btnCreateNewList_Click(object sender, RoutedEventArgs e)
{
int totalItemsCB = ListCheckBoxVocabulary.Items.Count;
for (int ii = 0; ii < totalItemsCB-1; ii++)
{
ListBoxItem item = this.ListCheckBoxVocabulary.ItemContainerGenerator.ContainerFromIndex(ii) as ListBoxItem;
CheckBox thisCheckBox = FindFirstElementInVisualTree<CheckBox>(item);
if (thisCheckBox.IsChecked == true)
{
dataPlayListSource.Add(new SampleData() { Text = thisCheckBox.Content.ToString() + " | " + ii });
// this.PlayListCheckBoxVocabulary.UpdateLayout();
this.PlayListCheckBoxVocabulary.ItemsSource = dataPlayListSource;
}
}
}
private T FindFirstElementInVisualTree<T>(DependencyObject parentElement) where T : DependencyObject
{
var count = VisualTreeHelper.GetChildrenCount(parentElement);
if (count == 0)
return null;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(parentElement, i);
if (child != null && child is T)
{
return (T)child;
}
else
{
var result = FindFirstElementInVisualTree<T>(child);
if (result != null)
return result;
}
}
return null;
}
and xaml:
<controls:PivotItem Header="Vocabulary" >
<ListBox x:Name="ListCheckBoxVocabulary" Margin="0,0,-12,0" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<!--<StackPanel Margin="0,0,0,17" Width="432">-->
<CheckBox x:Name="cbVocabulary" Content="{Binding Text}" Checked="CheckBox_Checked" Unchecked="UncheckBox" />
<!--</StackPanel>-->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PivotItem>
The list is virtual - controls are created as they are needed and potentially reused (I think).
Your options are to turn the ListBox to not be virtualized (override the template, and for the container, instead of a SerializedStackPanel, choose a regular StackPanel).
Your other (and preferable) option is to do the checking via Data Binding. Way easier and faster in most situations.

ListBox offset in WP7

It is possible set the offset for a ListBox? All that I can find is scroll to an element, but I need to scroll the ListBox to any position.
As an alternative, there are any other component that can make virtualize their items, and that I can control the offset?
You can get the ListBox's ScrollViewer and use its ScrollToVerticalOffset-method. To get the ScrollViewer, you can for example hook up to the ListBox's Loaded-event like the following:
XAML:
<ListBox Loaded="HookScrollViewer">
Code-behind:
private void HookScrollViewer(object sender, RoutedEventArgs e)
{
var element = (FrameworkElement)sender;
var scrollViewer = ControlHelpers.FindChildOfType<ScrollViewer>(element);
if (scrollViewer == null)
return;
this.myScrollViewer = scrollViewer;
}
The ControlHerlpers.FindChildOfType-method is implement this way:
public static T FindChildOfType<T>(DependencyObject root) where T : class
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(root);
while (queue.Count > 0)
{
var current = queue.Dequeue();
for (int i = VisualTreeHelper.GetChildrenCount(current) - 1; 0 <= i; i--)
{
var child = VisualTreeHelper.GetChild(current, i);
var typedChild = child as T;
if (typedChild != null)
{
return typedChild;
}
queue.Enqueue(child);
}
}
return null;
}
Now you have the ListBox's ScrollViewer in the myScrollViewer member and you can directly access its methods. For example, to scroll bottom you can call:
this.myScrollViewer.ScrollToVerticalOffset(double.MaxValue);

Resources