UWP GridView - How to maintain visible items upon resizing (Column count changes) - c#

I have a GridView that I use to display thousands of images/thumbnails (using data binding). I used ImplicitAnimations to add transitions, so that when the control is resized and the number of columns changes - every item smoothly goes into the new position after the resize.
Problem
This all works fine when the user is at the top of the GridView but becomes a problem the further the user scrolls. The further you scroll, the more items are moved around when the number of columns changes. More rows are added/removed, but the Scrollbar is kept on the same position/offset which causes previously visible items to go far away from the view - and the user gets lost.
What I tried so far...
I tried working around this issue by tracking the first visible item on every scroll change event and then scrolling to it on SizeChanged event. The problem of this solution, however, is that it's really rough. Animations are no longer noticable and the whole experience is laggy.
Is there any better solution to this problem?
Illustration of the problem
Video example
I have also made a video to better illustrate the issue.
(The images have been blurred because some were NSFW)
Video Link
What I am looking for is a way for the scrollbar to adjust it's position on resize and stay on the same items the User was looking at - without messing up the animations.
Edit
So apparently this can't be solved any other way but manually scrolling to the item I wish to have visible on resize, which I already tried doing and had problems with the animations being really rough again because the ScrollToItem was happening AFTER the animations were being triggered.
So my question now is - can I scroll to an item on reorder/resize - BEFORE the animations get triggered? That way I think I could keep the smoothness. Using the resize event, however, doesn't work for this.
Edit 2 - Code sample
<GridView x:Name="mylist" animations:ReorderGridAnimation.Duration="600" ItemsSource="{Binding Source={StaticResource viewSource}}">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid Orientation="Horizontal" HorizontalAlignment="Center" Loaded="ItemsWrapGrid_Loaded"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate>
<Grid Width="300" Height="300" Background="Gainsboro">
<Image Source="{Binding ThumbnailImage}" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
GridView ItemSource is binded to the following CollectionView
<UserControl.Resources>
<controls:AdvancedCollectionView x:Key="viewSource" Source="{x:Bind Collection}" />
</UserControl.Resources>
I switched to the Community Toolkit for the animations and the AdvancedCollectionView. The custom panel is being used for 2 reasons - to get the Panel and apply implicit animations to it - and to keep the scrollbar on the rightmost side when HorizontalAlignment is Center.
The CollectionView is binded to an ObservableCollection.
To reproduce the issue - just add a lot of items into the ItemSource and try resizing the GridView at different scroll offsets (check video sample)
.

For your this issue, it may be caused by the ListView and GridView UI virtualization, you can try to use VariableSizedWrapGrid as the GridView's ItemsPanel,
<GridView>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
But it will cost much device memory and performance without virtualization. So you should also take into consideration displaying many images in once.
As for displaying the Item that the User is looking at, since there are many items displayed in the screen, the GridView does not know to track witch one.So also as #Pratyay's suggestion, you need to keep track of the item then make the ScrollView witch is in the GridView to scroll to the item.

Related

C#/UWP OutOfMemory when change ListView to horizontal

I have a ListView with about 700 entries (one Image per entry). The ListView works just fine in vertical scroll mode. But when I change it to Horizontal it crashes on the phone with an OutOfMemory Exception.
I change the scroll direction with code from Microsoft:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
It seems like Lord Windows is trying to load the complete list at once when the Orienation is changed. Anyone else experience this issue, maybe even provide a solution?
By using StackPanel you loose virtualization. So all the 700 entries are in memory at once when you scroll the list. Use VirtualizingStackPanel or better, use GridView instead of ListView.

WP8: ListBox last row won't remain scrolled into view

When vertically scrolling into view, I can pull the last row into view, but on releasing the drag, the last row springs back (partially) out of view. I think inaccuracy of measure can be attributed to a grid that I'm using as the listbox's header but I'm currently unable to fix this to that they work together correctly.
<Border>
<StackPanel>
<ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Auto">
<StackPanel>
<Grid x:Name="_headers"/>
<ListBox x:Name="_dataGrid"/>
</StackPanel>
</ScrollViewer>
</StackPanel>
</Border>
**Update
This example removes the scroll and also suffers from the same truncated row problem as the example above. The header grid row also doesn't scroll horizontally with the listbox rows which is an even bigger problem for my solution.
<Border>
<StackPanel>
<Grid x:Name="_headers"/>
<ListBox x:Name="_dataGrid"/>
</StackPanel>
</Border>
It's because you put ScrollViewer inside StackPanel. StackPanel doesn't limit control's size so the ScrollViewer can grow forever and never scroll properly. Its size must be limited. Grid can do it perfectly (if only the height of the row is set to *, not Auto).
Also, in my opinion you shouldn't put ListBox inside ScrollViewer, because ListBox already has its own scroll feature.
Never ever use a ListBox inside a ScrollViewer, you will run into scrolling conflicts.
As far as I can see, you need to add a header to your list. LongListSeelctor might be the best option, since it has got a Header property (also Footer):
See: http://www.geekchamp.com/articles/the-new-longlistselector-control-in-windows-phone-8-sdk-in-depth

WPF ListBox Auto Width Changes Incorrectly When DataTemplate Has Visibility Bindings

I have a ListBox docked to the left of a window whose width should size to it's contents. When I scroll the listbox, it's width wobbles and becomes wider than it should be. Essentially what I think is happening is that the ListBox calculates the size of all it's children to determine it's own size, but only seems to evaluate visibility bindings for controls that are actually on the screen (presumably for performance reasons). The result is that it calculates the size of it's off-screen children incorrectly, reserving space for hidden controls.
If there are fewer items in the list than the window height (and thus no scrollbar), the list is always the correct width, and it always snaps to the correct width when you scroll to the top or bottom. It's only whilst the scrollbar is positioned somewhere inbetween that the width is wrong.
I've been wrestling with this problem all evening, trying various refactorings like using a DataTemplateSelector to abstract away the need for visibility bindings but I've ended up with really dirty workarounds I'm not happy with. I'd like to solve the actual problem at hand rather than dodge it. Any pointers would be much appreciated.
In the following example, the list appears to size itself to the width of both Foo and Bar. Changing the StackPanel orientation causes it to size to the width of whichever is longer out of Foo and Bar. Hope that makes sense.
<ListBox ItemsSource="{Binding Widgets}" DockPanel.Dock="Left">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Visibility="{Binding ShowFoo, Converter={StaticResource BoolToVisConverter}}" Text="{Binding Foo}" />
<TextBlock Visibility="{Binding ShowBar, Converter={StaticResource InverseBoolToVisConverter}}" Text="{Binding Bar}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This effect what you're describing does not have to do with the visibility bindings.
Essentially what I think is happening is that the ListBox calculates
the size of all it's children to determine it's own size, but only
seems to evaluate visibility bindings for controls that are actually
on the screen (presumably for performance reasons). The result is that
it calculates the size of it's off-screen children incorrectly,
reserving space for hidden controls.
This sounds correct to me. It's done for performance reasons. Also known as virtualization of ListBox. The other controls inherit this too, such as DataGrid.
In order to get rid of it, you can switch out VirtualizingStackPanel with the usual stackpanel:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
You might, or might not suffer the performance penalty, if you have massive amount of items, but that's the life. If you do not like this, I suggest you not to have this requirement in the first place.

WPF ScrollViewer for ListBox -> Performance issues

I'm trying to create grid with listbox with Scrollbar.
It's done somehow like that:
<Grid>
<ListBox Name="xxx" ItemsSource="{Binding}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Visible">
....
</ListBox>
</Grid>
The problem is that if I use scrollbar, then the size of bar-button jumps back and forward within scrolling. If I remove ScrollViewer from properties and instead put ListBox in ScrollViewer tag, then everything is working perfect, except of terrible performance of rerendering UI (resize, move window, resource consuming). According to google it does "disable virtualization". This sounds crazy that there's no simple solution to get properly working scrollbar and usable UI without issues.
Is there compromise for both of these things? Virtualization + properly working scrollviewer with fixed size of scrollbar button.

Sync two listboxes

I have a layout where there are two listboxes, I was trying to make them sync and found some tutorials on the net like http://www.software-architects.com/TechnicalArticles/ScrollSync/tabid/101/Default.aspx or Listboxes, scrolling in sync but they dont seem to work under WP7 SDK because there are missing events or properties. Does anybody out there solved the problem of syncing two or more listboxes under windows phone 7?
Thanks in advance
See my answer in this question: WP7 ScrollViewer programatically scroll a background ScrollViewer in sync with front ScrollViewer
You will be able to keep the scrollviewers in sync, but it may not be smooth, as the WP7 scroll viewer does not have a Scroll event.
To get the ScrollViewer's generated by the ListBox's, use this solution by ColinE WP7 - Scrolling ListBox in external ScrollViewer.
I came up with a solution using the WarpPanel available from the Silverlight Toolkit for Wp7
<ListBox Height="350" HorizontalAlignment="Left" Margin="102,72,0,0" Name="lsScore" VerticalAlignment="Top" Width="450" HorizontalContentAlignment="Center">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel ItemWidth="220" ItemHeight="50"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
This solution uses one listbox which is ofc always on sync and in order to seperate the data and align them I use the ItemWidth thats why it has such a high value. If you know any other way to seperate the data without using the ItemWidth property feel free to add an answer. Thanks in advance.

Categories