Single Row Horizontally Scrolling/Swipeable GridView - c#

I wanted a single-row GridView that can be scrolled horizontally both with mouse and touch swipe. The GridView is to present images through binding so that a single image will be selected from an array of images.
Everything works fine (binding, image selection, etc.) except that the horizontal scroll doesn't work. The XAML code is shown below.
What am I missing?
<GridView x:Name="IconGridView"
Grid.Row="0"
Margin="8,12"
DataContext="{x:Bind IconManagerObj}"
DoubleTapped="{x:Bind IconGridView_DoubleTapped}"
IsItemClickEnabled="True"
IsSwipeEnabled="True"
ItemsSource="{Binding Path=IconImageInfo}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollMode="Disabled"
SelectionMode="Single"
Tapped="{x:Bind IconGridView_Tapped}">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid Orientation="Horizontal" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="4,8"
HorizontalAlignment="Center"
BorderBrush="{ThemeResource SubtleBlueBrush}"
BorderThickness="1">
<Image Width="150" Source="{Binding IconImage}Stretch="Uniform"/>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>

You have everything right but the Orientation for the ItemsWrapGrid must be Vertical in order to have an Horizontal ScrollViewer.
The documentation here https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.itemswrapgrid.aspx explains that:
When the value is Vertical, the grid adds items in columns from top to bottom, then wraps from left to right. Columns of items scroll or pan horizontally.

Juan Pablo Garcia Coello's answer put me on the right track but didn't work without an additional setting. The crucial thing I found out through trial was setting the Height for GridView.
Height must be set enough for a single row elements to display but not high enough to allow a second row. For image height of 100 I set this arbitrarily to 140 and works great.
ScrollViewer.VerticalScrollMode must be Disabled
ScrollViewer.HorizontalScrollMode must be Auto or Enabled
ScrollViewer.HorizontalScrollBarVisibility must be Auto or Enabled
The most crucial, as Juan indicated, the ItemsWrapGrid Orientation must be Vertical (sounds counterintuitive but works!)
I have marked Juan's as answered as it provides a complete answer with this one, due to the fact that I couldn't have quickly figured out a complete answer without the Orientation being set Vertical -- a rather counter-intuitive setting if you ask me but now I get it.

Related

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

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.

WPF change ListView scroll speed

I have a ListView with custom-made cells (items).
This ListView represents a conversation between two persons exchanging messages with my application. Each time a message is added, the conversation auto-scrolls to the last item.
I am facing a few "strange" issues :
When a user writes a rather long message (say, 10 lines), it can then take up almost the whole screen (meaning the space allocated to the ListView) which is normal of course but then the scrolling is somewhat broken.
First, when the list auto-scrolls to this message, a big white space appears below the item all the way down to the bottom of my ListView. See picture :
And when messages are very short (single line) :
Second, and in all cases, the scroll speed is way to fast. A single mous-wheel "stroke" (the feeling in your finger as you scroll) will move the scroll bar too fast : up to 4 small messages are scrolled ! That's too much !
So question is : how to control the scroll speed ? How to slow it down ? Why is there this big white space ? Thanks for the help !
[UPDATE 1]
Requested by #CurtisHx my ListView XAML is as follow :
http://pastebin.com/FFZGhi6w
I hope it helps understanding my issue!
One way to be to set ScrollViewer.CanContentScroll="False" on the ListView. https://social.msdn.microsoft.com/Forums/vstudio/en-US/47bd6a75-7791-4c0f-93ae-931e9a6540e7/smooth-scrolling-on-listbox?forum=wpf
You will loose virtualization on the ListView, so keep the number of elements in the ListView to something reasonable. That should fix the fast scroll speed.
Text Alignment
The text is being aligned correctly. Currently, the parent container limits the width of the TextBlocks. TextBlocks will fill all of the horizontal space it can before wrapping the text. So for long messages, the TextBlock will expand horizontally until it hits the limits of the parent container.
In order to get the staggered text, the width of the message needs to be less than width of the ListView. If you widen the window, you'll see the text become staggered. Below is a snippit of code, pointing out the TextBlock that needs to be width limited.
<ListView x:Name="ConversationList" ScrollViewer.IsDeferredScrollingEnabled="False" ScrollViewer.CanContentScroll="False" ScrollViewer.VerticalScrollBarVisibility="Auto"
BorderBrush="Transparent" Grid.Row="0" ScrollViewer.HorizontalScrollBarVisibility="Disabled" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,10,10">
<!-- Big Snip /!-->
<ListView.ItemTemplate>
<DataTemplate>
<!-- Snip /!-->
<Border Padding="0, 15, 0, 15">
<Grid x:Name="ConversationBubble">
<Grid.RenderTransform>
<TranslateTransform />
</Grid.RenderTransform>
<Border Margin="70, 5, 70, 5" HorizontalAlignment="{Binding Alignment}" BorderBrush="#ECECEC" Visibility="Visible" Background="#F1F1F2" Padding="10">
<StackPanel>
I know this is too late, but:
VirtualizingPanel.ScrollUnit="Pixel"

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.

How UniformGrid work with infinite size

For example we have a ListBox with a UnidormGrid like a ListBoxPanel.
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}, Path=Items.Count}"></UniformGrid>
<!--<cntr:StackGrid Orientation="Horizontal" Direction="Normal"/>-->
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<Button>1</Button>
<Button>2</Button>
<Button>3</Button>
<Button>4</Button>
</ListBox>
ListBox will give a infinite size to UniformGrid, but there is no any errors. UniformGrid will use only a visible size. How it do that? It will help me in bulding my own panel.
P.S. I know, that i can to disable ScrollView in ListBox and my panel will get a visible size.
As far as I know, the UniformGrid derives its cell size by the largest child it is displaying. It breaks the content to a new line when either its width or height is exceeded, according to the Orientation property. By default, it is set to Horizontal.
Which leads me to your question: why do you think that it has an infinite width? Sure, the ListBox contains a ScrollViewer in its default control template, but in my opinion it only provides vertical infinite space for the panel (horizontally, it is constrained to the width of the list box), which allows breaks for the default uniform grid to happen.
If you have any further questions, please feel free to ask.

WPF RenderTransform scale questions

I am trying to find answer to my problem without success. didn't found anyone trying to do something similar.
I am simply trying to have a default scale factor on a DataGrid (object doesn't matter) and have it manually editable. Now let me explain in more detail.
I have a Slider with value range from 10 to 100 with Interval ticks of 10.
<Slider Name="sldZoom"
IsSnapToTickEnabled="True"
Height="180"
Orientation="Vertical"
TickPlacement="Both"
Minimum="10"
Maximum="100"
Ticks="10, 20, 30, 40, 50, 60, 70, 80, 90, 100"
Value="10" HorizontalAlignment="Center" />
Then i have a ScrollViewer with fix Height and Width for format purpose
<ScrollViewer Name="scrZoomPanel"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
Height="500
Width="750"
Background="White">
And at last I have in the scrollviewer a DataGrid with random columns and row quantity
<DataGrid Name="grdArrangement"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{Binding Path=ListData}"
IsHitTestVisible="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserSortColumns="False"
HeadersVisibility="None"
AutoGenerateColumns="True"
GridLinesVisibility="None"
SelectionMode="Single"
AutoGeneratingColumn="grdArrangement_AutoGeneratingColumn"
AutoGeneratedColumns="grdArrangement_AutoGeneratedColumns"
Background="White" >
<DataGrid.RenderTransform>
<ScaleTransform
ScaleX="{Binding ElementName=sldZoom, Path=Value, Converter={StaticResource ZoomRatio}}"
ScaleY="{Binding ElementName=sldZoom, Path=Value, Converter={StaticResource ZoomRatio}}" />
</DataGrid.RenderTransform>
</DataGrid>
the 2 auto generate event is because the grid get different cell styles assigned to it eveytime datasource changes
So datagrid can get different amount of rows and columns and at Zoom Value 100 the scale is 1. At Zoom value of 10 scale is 0.1.
In some case the grid get's bigger than the scrollviewer and it shows scrollbar and that's fine but what i want is :
after the binding is complete and onpaint completed (everything generated), I want to zoom to the level that allow me to see everything. If not possible because the datagrid is too big i need to stop at Zoom 10 (10%). I need the zoom factor to stop ONLY on ticks that is inside the list.
I tried iterating over the slide ticks and set the zoom and check if the scrollviewer still display scrollbars but that doesn't work the scrollviewer always show value before everything change. By that i mean if the grid was 1x1 pixel the scroll viewer scroll ba visible was false even if it zommed in enought for the 1x1 grid to be 1000 % zoom so 1000x1000 pixel and scrollbar actually shows. onve code finish running i click a button that message box scrollbar visibility and i see them as true.
Seems like changing the slider value doesn't update the scale transform of the grid right away.
So in short i simply want a Scale To Fit and that happen only once every binding update of the datagrid. I also know exactly everytime the grid datasource changes.
thank you
Edited Datagrid. Code showed LayoutTransform and not RenderTransform. I copied while i was testing LayoutTransform instead just to see if that was it. Anyhow both work on Zoom but zoom to fit fails
I started looking at the problem form another angle and try getting
scrZoomPanel.ComputedHorizontalScrollBarVisibility
and
scrZoomPanel.ComputedHorizontalScrollBarVisibility
to be anything else than original value before altering the DataGrid.
So i was looking to a way to force refresh of the scrZoomPanel and found ou there is a function for that :
scrZoomPanel.UpdateLayout();
If i call that every time just before trying to read the scrZoomPanel.ComputedHorizontalScrollBarVisibility then it finally return me the real value.
But i find that a bit invasive needing to call that every single time. Wonder if there is a way to make a binding change force refresh in XAML instead of background code.

Categories