Performance with XAML binding one value to many elements in GridView (Windows 8) - xaml

I have a Windows 8 XAML page with a grid in it that shows sample text in 100+ different fonts. The sample text is the same in each grid view item and can be changed using a textbox at the top of the page.
Every time you type a character all the grid view items are updated. The problem is that this is noticeably slow. Particularly if you type quickly.
I'm not sure what is making it so slow. Is it updating all the grid view items including the ones not on screen? Is something else causing the problem and this particular binding is just a red herring?
Here is my binding code (I've removed some xaml from my data template to make it clearer):
<ScrollViewer>
<GridView x:Name="FontGridView" ItemsSource="{Binding Fonts}" SelectionMode="Multiple" Margin="116,0,40,46">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Width="600" MinHeight="100" MaxHeight="120">
<TextBox Text="{Binding ElementName=pageRoot, Path=DataContext.SampleText, Mode=OneWay}"
FontFamily="{Binding FamilyName}" FontSize="32" Background="Transparent" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</ScrollViewer>
Is there a better way to do this, or are there any other performance tuning things I can turn on?
UPDATE: You're less likely to make this mistake since Visual Studio 2012 RC came out. The templates for WinRT apps no longer use a ScrollViewer in this way. I'm keeping the question here for those that have ported apps they created using Visual Studio 2011.

Try removing the ScrollViewer.
Because the GridView is inside a scrollviewer, it is effectively given an infinite width. That might kill the virtualization of the WrapGrid in the GridView.
Furthermore, the GridView internally contains a ScrollViewer already. Obviously, you don't need two of them. But the inner ScrollViewer will also eat mouse wheel events, leaving no events for the outer ScrollViewer to react to. As a result scrolling with the mouse will not work.
Visual Studio 11 (bèta 1) templates contain code like this, to set a margin on the GridView. However, it's better to set a margin on the GridView's ItemsPanel, like this:
<GridView x:Name="FontGridView" ItemsSource="{Binding Fonts}" SelectionMode="Multiple">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Width="600" MinHeight="100" MaxHeight="120">
<TextBox Text="{Binding ElementName=pageRoot, Path=DataContext.SampleText, Mode=OneWay}"
FontFamily="{Binding FamilyName}" FontSize="32" Background="Transparent" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid x:Name="itemGridViewPanel" Margin="116,53,116,46"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
Your original code did not include the Margin that you set on the GridView. But whatever it was, set the same Margin on the WrapGrid.
Notice also that I gave the WrapGrid a name. You can use that name to change the Margin when the screen orientation changes, typically as follows:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="FullScreenLandscape"/>
<VisualState x:Name="Filled"/>
<VisualState x:Name="FullScreenPortrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridViewPanel"
Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0"
Value="96,53,96,46"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Snapped"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
UPDATE:
I had done this before, but with a VariableSizedWrapGrid. That produces the correct layout, but unfortunately, I'm afraid that VariableSizedWrapGrid is not a virtualizing panel.
WrapGrid is virtualizing, but the behavior at the left margin seems wrong when scrolling. It is correct at the right margin though.
Oh well, in two weeks time we should have the release candidate available. Hopefully things will have improved...
UPDATE:
It seems that Microsoft is aware of this. See http://social.msdn.microsoft.com/Forums/en-US/winappswithcsharp/thread/17385f7d-aadc-4edc-bbff-8738a2f0c917.
UPDATE:
It seems that the WrapGrid has not been fixed in the Release Candidate.

Simplify your data template.
<DataTemplate>
<TextBlock Text="{Binding ElementName=pageRoot, Path=DataContext.SampleText}"
Width="600" MinHeight="100" MaxHeight="120"
FontFamily="{Binding FamilyName}" FontSize="32" />
</DataTemplate>
You don't need the Grid. And it looks like you don't need a (complex) TextBox either, just a (simple) TextBlock.
Also, fixing the height of those text blocks (Height="120") might help (optimize) the layout process.
UPDATE:
I understand you're code is simplified from "the real thing". But that might hide the real problem for us. Remember, the data template is intantiated for every item that is on screen. The more complex it is, the more objects need to be instantiated (CPU and memory usage), possibly need to databind (CPU), need to be measured and laid out (CPU), and need to be rendered (CPU and GPU).
Just do a test with a very simple data template. If your program is faster now, or not, you know whether the data template is the performance problem or not.
Note that the complexity of the data template is not measured by the number of xaml elements you need to describe it, as those elements may have very different internal complexities. For example, do prefer a TextBlock over a TextBox if all you need to do is display text, not allow editing.
Also, if some of these elements have a lot of properties set, do use styles to set them. Using a style on many objects is more efficient than setting many properties on many objects, especially when it come to memory usage. And the less memory you use, the faster your program will be (due to modern CPU multilevel caching architectures).
For example:
<GridView x:Name="FontGridView" ItemsSource="{Binding Fonts}" SelectionMode="Multiple">
<GridView.Resources>
<Style TargetType="TextBlock">
<Setter Property="Width" Value="600" />
<Setter Property="MinHeight" Value="100" />
<Setter Property="MaxHeight" Value="120" />
<Setter Property="FontSize" Value="32" />
</Style>
</GridView.Resources>
<GridView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ElementName=pageRoot, Path=DataContext.SampleText}"
FontFamily="{Binding FamilyName}" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>

Try using a VirtualizingStackPanel
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>

Related

Scrolling a ListView - screen height detection needed

I am working on a WinRT app which contains a listview. The Listview has grown recently and I need to put a vertical scrollbar around it.
So far I have hardcoded the height in the Grid to 500.
However I want to know how to set the height to detect how much space is available. This may vary depending on the device being used. How do I do that?
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="500"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Text="*" FontSize="40" FontWeight="Bold" Foreground="Red"/>
<TextBlock Text=" = Required " FontSize="20"/>
</StackPanel>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" >
<ListView
ItemsSource="{Binding Path=Survey.SelectedSection.Questions, Mode=TwoWay}"
IsSwipeEnabled="False"
SelectionMode="None"
Background="White"
ItemTemplateSelector="{StaticResource ResourceKey=QuestionDisplay}"
ItemContainerStyle=
"{StaticResource ResourceKey=QuestionListViewItemContainerStyle}" />
</ScrollViewer>
</Grid>
So like we discussed. ListView has a ScrollViewer alread embedded in its template. The reason it would invoke scrolling with a fixed height as opposed to naturally was a fixed boundary was provided to invoke it.
By adjusting your layout so that it's parent panel (and up the parent tree) didn't include StackPanel and using Grid instead with star * sizing it allowed a boundary to invoke it as desired. The reason for this is whereas StackPanel will only consume the space required, regardless of the space available. A Grid will consume whatever space is provided while restricting its children in its layout providing that boundary necessary to invoke the scrolling of the embedded ScrollViewer which has the attached property of ScrollViewer.VerticalScrollBarVisibility set to Auto by default.
Glad you found your fix, welcome to the wonderful world of XAML which once you get used to it, you'll generally find is a lot easier to work with than it's cousin HTML. Cheers :)

CommandBar overflow in UWP app - can it be scrollable?

Background
I am developing a UWP app, which is being designed for mobile, desktop etc.
I am having a bit of difficulty with the CommandBar on certain pages - specifically on a phone (but this may also apply to a smaller desktop window).
Issue
Where I require quite a few AppBarButtons on the CommandBar, sometimes it overflows and I am unable to see/access the hidden buttons.
Here is a sample screenshot where you can see that the "Documents" label is being cut off. There is also another button (which cannot be seen/accessed) which I guess you would say is "underneath" the ... button:
XAML
Shortened for brevity (nothing special/out of the ordinary here)
<CommandBar>
<!-- Secondary Commands -->
<CommandBar.SecondaryCommands>
<AppBarButton Command="{Binding RefreshCommand}"
Icon="Refresh" Label="Refresh"/>
<AppBarButton Command="{Binding AddToLibraryCommand}"
Icon="Save" Label="Add to Library"/>
</CommandBar.SecondaryCommands>
<!-- Primary Commands -->
<CommandBar.PrimaryCommands>
<AppBarButton Command="{Binding CompleteFormCommand}"
Icon="Paste" Label="Complete a Form" />
<AppBarSeparator />
<AppBarButton Command="{Binding ViewPeopleCommand}"
Icon="People" Label="People" />
<AppBarButton Command="{Binding ViewPropertiesCommand}"
Icon="Home" Label="Properties" />
<AppBarButton Command="{Binding ViewDocumentsCommand}"
Icon="Folder" Label="Documents" />
<AppBarSeparator />
<AppBarButton Command="{Binding ViewMapCommand}"
Icon="Map" Label="Map" />
</CommandBar.PrimaryCommands>
</CommandBar>
What I need
I still need the other AppBarButtons to be accessible by the user.
The amount of buttons on each CommandBar can vary - sometimes they fit fine, some have 1 (like the example) hidden, others have 2 or 3 hidden.
I am thinking that (surely, somehow) it might be possible somehow to allow horizontal scrolling in order to access the other buttons?
What I have tried
I have resorted to moving some of the non-essential commands to the CommandBar.SecondaryCommands, to free up some space; as can be seen in the screenshot/XAML above - however, the main problem is still apparent.
There is no ScrollViewer attached property, and trying to nest the AppBarButtons inside one naturally throws me a compiler error:
Invalid type: expected types are IObservableVector<ICommandBarElement> or ICommandBarElement, actual type is ScrollViewer.
I have searched the Web far and wide for answers to see if it is possible, and it doesn't appear there is much literature (or any similar StackOverflow answers) on how I can go about solving this issue.
All advice is greatly appreciated. Thank you in advance.
As for me, it is not pretty good UX behavior, but you can override Style for CommandBar and wrap primary ItemsControl into ScrollViewer.
Find the default style and find the PrimaryItemsControl and replace to:
<ScrollViewer VerticalScrollMode="Disabled"
VerticalScrollBarVisibility="Disabled"
HorizontalScrollMode="Enabled"
HorizontalScrollBarVisibility="Visible">
<ItemsControl x:Name="PrimaryItemsControl"
HorizontalAlignment="Right"
MinHeight="{ThemeResource AppBarThemeMinHeight}"
IsTabStop="False"
Grid.Column="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
After that don't forget apply new style for CommandBar:
<CommandBar Style="{StaticResource ScrollablePrimaryComamndCommandBar}">
P.S. full xaml on pastebin

Strange behaviour of combobox in WinRT

Comboboxes in WinRT have a default ItemsPanel of type CarouselPanel. This gives Windows 8.1 apps an "infinite loop" when scrolling combobox items.
If you don't want this behaviour, there are a lot of blog posts explaining how to "fix it".
For example this: Cancel WinRT ComboBox infinte scroll effect
or: http://netitude.bc3tech.net/2013/04/12/windows-8s-combobox-and-the-carouselpanel/
The problem with this solution is that you get a weird behaviour on the first item in the combobox.
How to reproduce:
Create a new blank Windows 8.1 app
In mainpage.xaml put:
<TimePicker Time="0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
Create a style.xaml resource dictionary like this:
<Style TargetType="ComboBox">
<Style.Setters>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
Now start the app, select an item down the list (for example '05' minutes), then select the first item in the same dropdown (for example '00' minutes). The text in the dropdown control will now disappear.
Anyone know how to fix this? If I change the style of combobox itemspanel back to CarouselPanel it works (but with the infinite loop of course).
Just corrected this issue using a VirtualizingStackPanel in place of a StackPanel.
We had to set a size cause otherthise it take all the width of the screen.
<VirtualizingStackPanel HorizontalAlignment="Center" Width="150"/>
We didn't try to get a more flexible solution because we don't need it yet
Hope it will help you
StackPanel just not working with ComoboBox the possible solution is changing it to VirtualizingStackPanel, but you must bind with to the parent, otherthise it will stretch to screen width.
<ComboBox Name="ReasonComboBox"">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Width="{Binding ActualWidth, ElementName=ReasonComboBox}"/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>

How to rearrange WrapGrid contents when zoom level changes?

I am developing a Windows 8 Metro application whose layout is pretty simple. It consists of a single page with a WrapGrid enclosed in an ItemsControl, which is in turn enclosed in a ScrollViewer. This is the XAML code of the application main page:
<Page ...>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid x:Name="MainGrid" Margin="120,140,32,0">
<ScrollViewer x:Name="ScrollView"
VerticalScrollBarVisibility="Auto"
HorizontalAlignment="Stretch">
<ItemsControl x:Name="itemsControl" HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Horizontal"
HorizontalChildrenAlignment="Stretch"
Margin="0"
HorizontalAlignment="Center">
<WrapGrid.ChildrenTransitions>
<TransitionCollection>
<EntranceThemeTransition />
<RepositionThemeTransition />
</TransitionCollection>
</WrapGrid.ChildrenTransitions>
</WrapGrid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
<Page.BottomAppBar>
...
</Page.BottomAppBar>
</Page>
There is also an user control of which new instances are created and added to the ItemsControl programmatically when the user clicks on a certain button in the application bar. As expected by the fact of using a WrapGrid, the control instances are stacked sequentially in a single row until there is no more room in the screen, at which point they appear in a new row and it is necessary to scroll down in order to see them. So far so good.
Now I want to implement a feature and I don't know how to achieve it. What I want is the following: when the user zooms out in the application, causing the controls to appear smaller, I want the new available space to be used so that more controls can be displayed per row; instead, the current behavior is that the ItemsControl itself is reduced and the extra surrounding space is unused.
For example, imagine that the user adds 10 controls. There is room for 4 controls in one row, so that 3 rows of controls are displayed, with 4, 4 and 2 controls. If the user zooms out and now there is room for 7 controls in a row, I want the ItemsControl to rearrange itself so that now there are only two rows with 7 and 3 controls. How could I achieve this?
I hope I have explained myself properly. Please don't hesitate to ask if my question is not clear enough. Thank you very much!

Using Blend to set a Timer on an ActionState/Transition with Silverlight 4

I'm a developer who has recently downloaded the trial of Blend and I am trying to get to grips with not using CodeBehind to do stuff - it's very cool but it has quite a learning curve!
I started off with these tuts here and implemented some simple animation on the menu as per the example on my poker blind timer. What I want to do now is to make the menu transition only start after 20 seconds - ie. so that the menu on the left that disappears on MouseLeave (see link above) - only does so 20 seconds after the mouse has left (and cancels if they MouseOver again). This will make the menu stay longer in case they mouse off by accident.
I am sure it is really simple in Blend but I am battling to find any decent documentation - I'll happily RTFM - I just need to know where to start looking (I googled "Blend timer stateaction" with no joy).
Thanks for any tips!
If I understand your problem correctly:
When you get a Mouse Enter event over your side menu, it animates out (e.g. a "ShowMenuStoryboard") .
You then want a "HideMenuStoryboard" to slide the menu back off, but only commence it's changes 20 seconds after it is triggered (by the MouseLeave event) but it needs to be cancelled if a subsequent Mouse Enter event fires.
you want to do all this with no code-behind logic.
There are 2 things to do.
Make sure your storyboards only specify the end state values (no starting states) and
You just need to set BeginTime="0:0:20" in the XAML for the HideStoryboard e.g.
<Storyboard x:Name="HideMenuStoryboard" BeginTime="0:0:20">
I have not found a property anywhere for BeginTime in the Expression blend editor, so this has to be done in the XAML view. The properties shows only AutoReverse and RepeatBehavior.
There is an inherent problem with this kind of animation, but should be OK for your example. The time duration is fixed, so if you trigger the opposite animation while one is commencing it will actually animate more slowly to it's final position as it takes a fixed time to go "from where it currently is" to the final position.
Hope this helps. The complete sample MainPage.XAML with memu menu is below. It only requires the 2 storyboards and Storyboard control behaviors:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
x:Class="SilverlightApplication1.MainPage"
mc:Ignorable="d">
<UserControl.Resources>
<Storyboard x:Name="ShowMenuStoryboard">
<DoubleAnimation Duration="0:0:0.5" To="0" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="stackPanel" d:IsOptimized="True"/>
</Storyboard>
<Storyboard x:Name="HideMenuStoryboard" BeginTime="0:0:20">
<DoubleAnimation Duration="0:0:0.5" To="-100" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="stackPanel" d:IsOptimized="True"/>
</Storyboard>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel x:Name="stackPanel" HorizontalAlignment="Left" Orientation="Vertical" Width="150" d:LayoutOverrides="Height" RenderTransformOrigin="0.5,0.5" Background="#FF646CE7">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeave">
<ei:ControlStoryboardAction Storyboard="{StaticResource HideMenuStoryboard}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseEnter">
<ei:ControlStoryboardAction Storyboard="{StaticResource ShowMenuStoryboard}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<StackPanel.RenderTransform>
<CompositeTransform TranslateX="-100"/>
</StackPanel.RenderTransform>
<StackPanel.Projection>
<PlaneProjection/>
</StackPanel.Projection>
<TextBlock TextWrapping="Wrap" Text="TextBlock"/>
<TextBlock TextWrapping="Wrap" Text="TextBlock"/>
<TextBlock TextWrapping="Wrap" Text="TextBlock"/>
<TextBlock TextWrapping="Wrap" Text="TextBlock"/>
<TextBlock TextWrapping="Wrap" Text="TextBlock"/>
</StackPanel>
</Grid>
</UserControl>
You can add a 'fake' story board, that serves as a trigger for the second animation.
You will need two story boards. Fake and HideMenu.
You need to ControlStoryboardActions to start each of them.
The first one will have an event trigger (the Mouse out).
The first one will have a StoryboardCompleterTrigger linked to the 'fake' animation.