Here's my problem.
ListBox A show all the items in an Observable Collection.
ListBox B shows only those items selected in ListBox A.
<ListBox ItemsSource="{Binding MyView}" Name="ListBoxA">
<ListBox ItemsSource="{Binding Path=SelectedItems,
ElementName=ListBoxA}" Name="ListBoxB">
When the selection is changed in ListBox A, a StoryBoard runs. The resulting UI is a cool and smooth slide in and out of ListBox B based on the user's selection.
The problem is, where my ListBox A databinds only the Name property, ListBox B databinds dozens and in some cases even hundreds of properties.
The problem continues in that databinding in WPF creates a short, 50-500 millisecond UI delay when it is rendering (specifically when it is dynamnic). The UI freezes.
This is tollerable. But my StoryBoard seems to be blocked by this DataBinding delay. As a result the UI sort of "snaps" into place and my smooth StoryBoard isn't seen.
I have resolved this by attaching to the StoryBoard.Completed event. Once the StoryBoard is complete, then I set the ItemsSource for ListBox B.
However, this is only 50% nice. The user sees the StoryBoard execute, yes. But the resulting UI of ListBox B still "snaps" into view after the animation.
It seems to me that the correct resolution is to somehow indicate to the controls rendered inside ListBox B to wait, or delay the actual databinding. This would allow the UI to be rendered and participate in the StoryBoard - but the data to "fill in" later (hopefully also delaying the delay caused by the DataBinding).
Has anyone had a similar problem?
Here's the XAML that demonstrates the problem exactly (because StackOverflow limits the size of a question, you will need to add additional TextBoxes to really see the delay significantly):
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<x:Array Type="{x:Type sys:String}" x:Key="MyData">
<sys:String>One</sys:String>
<sys:String>Two</sys:String>
<sys:String>Three</sys:String>
<sys:String>Four</sys:String>
<sys:String>Five</sys:String>
<sys:String>Six</sys:String>
</x:Array>
<Storyboard x:Key="MyGrowStoryboard">
<ParallelTimeline>
<DoubleAnimation To="1" DecelerationRatio="0.5"
Duration="00:00:00.500"
Storyboard.TargetName="MyTransform"
Storyboard.TargetProperty="ScaleX" />
<DoubleAnimation To="1" DecelerationRatio="0.5"
Duration="00:00:00.500"
Storyboard.TargetName="MyTransform"
Storyboard.TargetProperty="ScaleY" />
</ParallelTimeline>
</Storyboard>
<Storyboard x:Key="MyShrinkStoryboard">
<ParallelTimeline>
<DoubleAnimation To=".1" DecelerationRatio="0.5"
Duration="00:00:00.500"
Storyboard.TargetName="MyTransform"
Storyboard.TargetProperty="ScaleX" />
<DoubleAnimation To=".1" DecelerationRatio="0.5"
Duration="00:00:00.500"
Storyboard.TargetName="MyTransform"
Storyboard.TargetProperty="ScaleY" />
</ParallelTimeline>
</Storyboard>
</Page.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Source={StaticResource MyData}}"
Name="ListBoxA">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<DataTemplate.Triggers>
<!-- grow -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Value="True"
Binding="{Binding Path=IsSelected,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}}}" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard
Storyboard="{StaticResource MyGrowStoryboard}" />
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<BeginStoryboard
Storyboard="{StaticResource MyShrinkStoryboard}" />
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
<!-- shrink -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Value="False"
Binding="{Binding Path=IsSelected,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}}}" />
<Condition Value="1"
Binding="{Binding Path=SelectedItems.Count,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}}}" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard
Storyboard="{StaticResource MyShrinkStoryboard}" />
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<BeginStoryboard
Storyboard="{StaticResource MyGrowStoryboard}" />
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
</DataTemplate.Triggers>
<TextBlock Text="{Binding .}">
<TextBlock.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="1"
x:Name="MyTransform"/>
</TextBlock.LayoutTransform>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox ItemsSource="{Binding Path=SelectedItems,
ElementName=ListBoxA}" Name="ListBoxB">
<ListBox.ItemTemplate>
<DataTemplate>
<UniformGrid Columns="10">
<!-- repeat this part MANY times (like 3000) ! -->
<TextBox Text="{Binding .}" />
<TextBox Text="{Binding .}" />
<TextBox Text="{Binding .}" />
<TextBox Text="{Binding .}" />
<TextBox Text="{Binding .}" />
</UniformGrid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Page>
Looks like this:
Thanks!
I never had similar problem before and as per my knowledge there is no built in way to delay data binding in WPF.
I tried your code with 1000+ textbox inside ListboxB. The ListboxB renders pretty fast but if you want still want to reduce rendering time for ListboxB you can try adding below code to ListboxB.
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
This will generate only controls inside ListBoxB which are currently visible so it will improve rendering of ListBoxB.
You can checkout my article on VirtualizingStackPanel.
Hope this answer will help you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With