Listbox with thousand of images in a wrappanel - c#

I'm trying to create a listbox that may contain over a thousand images in a grid like design. In term of design it would be quite similar to this:
Since I can't use a wrappanel as that would break UI virtualization and a stackpanel can't list images in such a grid(?), I'm trying to solve this issue using a modified version of https://virtualwrappanel.codeplex.com
My XAML:
<ListBox x:Name="GameWheel" ItemsSource="{Binding GameData}" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<c:VirtualizingWrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image x:Name="GameImage" Source="{Binding Path=ImagePath}" Width="{Binding ElementName=GameWheel, Path=ActualWidth, Converter={StaticResource widthConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
While this approach works, it's still quite slow and buggy, especially when using bind on the image width. Is there a better way of archiving the same result? Without the custom wrappanel preferably.

Check out my implementation of VirtualizingWrapPanel. Also available (with more included) from NuGet.
This code originally came from this CodeProject article which mostly worked and appears to be the same place you started. Along the way I fixed several bugs and improved performance so it might help you as well.
Update: The key is going to be to bind to the VirtualizingWrapPanel.ItemWidth rather than Image.Width. There was a bug in both implementations of the VirtualizingWrapPanel that would prevent the children from being resized if the ItemHeight or ItemWidth values changed. I fixed this bug in this commit (line 358).
Admittedly it's a brute force fix but I wanted to get something checked in for you and have to head out. I tested it with 10,000 images and it was very snappy. I'll work on a better solution (ie: only remeasure when we know the value has changed) and update this answer when I do.
Update 2: Quick change to improve the child.Measure with this commit.
//TODO: If either child Height or Width == PositiveInfinity and the other side == ChildSlotSize then we probably don't need to measure. Need to test this
if (!child.DesiredSize.Equals(ChildSlotSize))
{
child.Measure(ChildSlotSize);
}
Still room for improvement but no time for proper testing right now and this works.

Related

Terrible performance of MapControl with multiple children in Windows Phone 8.1 Runtime

I have a MapControl which is supposed to be filled with multiple objects (500+). These objects represent some kind of POI. When the user taps on the object (pushpin) I display more info about the POI. So, I need:
A MapControl capable of handling high amount of child objects
Intercept Tappedevent of the child object
In order to achieve the second goal I decided to define my own pushpin template:
<maps:MapControl x:Name="Map">
<maps:MapItemsControl ItemsSource="{Binding Pushpins}">
<maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<Image Width="40"
Height="40"
Source="{ ... }"
Tapped="OnPushpinTappedAsync"
maps:MapControl.Location="{Binding Location}"
maps:MapControl.NormalizedAnchorPoint="{Binding AnchorPoint}" />
</DataTemplate>
</maps:MapItemsControl.ItemTemplate>
</maps:MapItemsControl>
</maps:MapControl>
This works great except for the fact that.. the visual out of such approach is AWFULL. Every time I move a map, every Pushpin flickers a lot. It's like they are not, I don't know, bound to the position. They are also lagging. It looks really bad. Rendering of those objects is really poor.
The alternative is to add elements to the MapControl's MapElements property. It makes rendering of those objects really nice.
But then I loose binding ability and will have to workaround it - I'm not a big fan of that. There's also a second problem - from what I've read, rendering objects of the MapElements collection is a best effort deal. So it does not guarantee that it will succeed. And that is not an option for me, as in the future I plan to add clustering functionality, so I need to have a full control over what is being rendered on the map and what is not.
Do you have any idea why these MapControl's elements flickers so much? What can I do to prevent it? Thanks in advance for any hint or answer.
I'm facing the exact same problem. Seems to me, that the MapControl is completely broken. There are many more bugs besides performance :(
You cannot really solve the problem, but you can create a "oldschool" WP8.0 Silverlight application and use something like this:
<Page xmlns:map="clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps">
<map:Map Grid.Row="1" x:Name="myMap">
<tool:MapExtensions.Children>
<tool:MapItemsControl>
... and so on ...
This performs great but of course you have to change everything from Win(P)RT to Silverlight...
I too faced this problem, but I find this library to be usefull.
Hopefully Microsoft updates the MapControl for Windows 10.
Try this:
Tag="{Binding index_of_collection}"
and this:
OnPushpinTappedAsync()
int index = (int)(sender as Image).Tag; //index_of_collection

Manipulation many object by one

I have situation
<Grid>
<FlipView x:Name="flip1">
...
</FlipView>
<FlipView x:Name="flip2">
...
</FlipView>
</Grid>
How I can synchronize manipulations on these two FlipView controls?
If user makes 'swipe' gest on grid, booth of FlipViews should change page.
You could try binding SelectedItem property of these two - e.g. set SelectedItem={Binding SelectedItem, ElementName=flip2, Mode=TwoWay} on flip1. The results with touch manipulation feedback might not be ideal though and you can't make it perfect even if you try to manually synchronize the offset on the ScrollViewer in the template of each of the FlipViews.

VirtualizingStackPanel and TextWrapping bug? Windows Phone

I have a strange behaviour with VirtualizingStackPanel. I have a list with items that contains TextBlock with TextWrap="Wrap". Here is the code:
<ListBox x:Name="messagesList" ItemsSource="{Binding Messages}" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
...
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<CheckBox Style="{Binding Own, Converter={StaticResource MsgTypeToStyle}}"
Tag="{Binding TimeString}"
IsEnabled="True">
<TextBlock Text="{Binding Content}" TextWrapping="Wrap"/>
</CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It works pretty good, but if I try to scroll very fast (using mouse on emulator, not prommatically) there is some lagging in scroll, probably HorizontallOffset sometimes calculates wrong, and in the bottom in ends with very strange result (see image, right image demostrates normal behaviour).
After research I figured out that problem in combination VirtualizingStackPanel and TextBlock.TextWrap="Wrap", if I remove one element from this couple all works correctly.
But I need virtualization because of big items count, and TextWrap for correct text displaying.
So I think about making my own implementation of Virtualizing Panel, can you please guide me, how to do this, or how to fix current problem?
UPD: The problem:
on the first two images ListBox is already(!) scrolled to the bottom (it can't be scrolled down any more), but elements placed incorrectly, correct placing shown on the right image. This happens only if you will scroll very fast.
UPD2: Thanks to Milan Aggarwal. He provided a good case of my problem here. Seems that is really a bug in ListBox. Workaround, provided doesn't fit in my scenario, because I need to interact with controls inside ListBox item.
Now I'm trying to catch ManipulationCompleted event and check if it is Inertial, if so that means scroll and I set focus to page:
void messagesList_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
if (e.IsInertial)
this.Focus();
}
P.S. thanks for good luck wishes;)
Inorder to overcome the black occurrence on scrolling you need to virtualize your scroll control. For that you should inherit IList and create a Collection of your own similar to ObservableCollection in which you will have to override the default indexer depending on your caching requirement and simultaneously maintain a cache for your items. I feel this might be what you are looking for: http://blogs.msdn.com/b/ptorr/archive/2010/08/16/virtualizing-data-in-windows-phone-7-silverlight-applications.aspx
There is a sample project on that page. Try that out.
I also feel that you are facing this problem http://blog.rsuter.com/?p=258. I guess this will be solved using virtualization itself. Hope it helps
What is the problem on 2nd screen? You mean large empty space after last message? Last message not being put at the bottom of page? Am I getting this right?
Actually i didn't try to reproduce your code and bug but my point is that since you are using 3rd party libs (Silverlight Toolkit) in your app, why not to use Coding4Fun Tools as well?
I wrote this dummy class:
public class Message
{
public string Time { get; private set; }
public string Content { get; private set; }
public Message(string time, string content)
{
Time = time;
Content = content;
}
}
Then i put this dummy code in main page constructor:
var _messages = new ObservableCollection<Message>();
for (int i = 0; i < 50; i++)
{
_messages.Add(new Message("12:40", "Teh very long string which should be wrapped. Pavel Durov gives WP7 Contest winners less money than he did for Android/iOS winners. FFFUUUUUUUUUUUUUU "));
}
this.ListBox.ItemsSource = _messages;
And in xaml i put a listbox with chat bubble control of toolkit:
<ListBox x:Name="ListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<c4fToolkit:ChatBubble>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding Content}"
TextWrapping="Wrap"/>
<TextBlock Grid.Row="1"
Text="{Binding Time}"
HorizontalAlignment="Right"/>
</Grid>
</c4fToolkit:ChatBubble>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I ran it and saw no strange behaviours, maybe little bit of lagging.
There is the result:
Hope i did some help.
Удачи с конкурсом, я б сам поучаствовал, но не верю, что за 1,5 месяца удастся сделать приличный клиент.
What is the problem at the second image? Is it that the BlackArea showed up at all, or is it that the ScrollView did not "bounceback" after the scrolling ended to fill the black area with content?
It is crucial to know this, because ... this effect is simply natural to WPF/WP7..
BTW. the scroller should "bounceback" after the over-scrolling, but it seems it has a bug and sometimes forgets to do it.
I'm asking because black/white areas are quite often not only on WP7, but and anywhere where there's a XAMLish ScrollViewer with inertial scrolling. You see, when scrolling faster than the platform can provide new items, you just scroll outside of the pre-calculated items and once you "over"scroll very far, the slowly-incoming items abruptly discover that there are no more items, hence - back empty area..
This happens mostly when some conditions happen at once: you scroll fast (yay), your bindings are slow (overly complicated expressions), your template-rendering is slow (complex non-reusable UI templates), the bindings does not know how many items there are (bound to IEnumerable not IList -> no .Count information!), or the renderer doesnt know how much height to reserve for an item (the item is not constant-height, but eveyr item has to be calculated to determine its own exact height).
If you want to correct it, you must fight with those causes first or you must implement your own Scrolling or hack into virtualizing stack panel and synchronize the feeding of items from bindingsource with the scrolling to let the scroller guess the total height better: for example, display only a small items at once, listen to scrolling events and add/remove next pack of items from head/tail of the list dynamically.. This will solve the problem by not allowing to scroll fast :)))))
I believe that constraining the item height is the easiest way. Can't you do something smart to make the items actually constant-sized?
I suggest you create the simplest possible function that computes a height for the wrapped textblock and bind to that. This way, you get the benefit of variable height items in the listbox, but get to keep using the virtualizing stack panel.

Can I implement ScrollToHorizontalOffset() functionality in XAML? (for a dynamic list)

Here's the problem: I have a data-bound list of items, basically a way for users to map a request to a response. The response is an xml-based file. I'm letting them queue these up, so I've used a combobox for responses. The responses will include the full path, so they get a bit long. I want the displayed text of the combobox to be right-justified so the user can see the file name. For my static controls, I just use ScrollToHorizontalOffset() when a file is loaded and I'm done. For this dynamic list, I'd like to do it in xaml.
The "somewhat ugly" solution would be to store all the ComboBox objects as they load... then I can call ScrollToHorizontalOffset() directly, but I'd really prefer to do it a cleaner way than that! EDIT: (Actually, this may not be reasonable. A quick look at trying to hack around this issue gets into some really awkward situations trying to map my datasource items to the controls)
I've tried HorizontalContentAlignment, that only impacts the "dropped-down" portion of the ComboBox.
I've also tried to hook other various loading events, but haven't found one that works.
Using an Item template you can decide what will be shown.
You can set tooltip. You can then also use converters to add the dots.
<ComboBox x:Name="ConfigurationComboBox" VerticalContentAlignment="Center" ToolTip="saved configuration" SelectionChanged="ConfigurationComboBox_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate >
<StackPanel>
<TextBlock Text="{Binding}" ToolTip="{Binding Path}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
To measure the text, see Measuring text in WPF.

AutoCompleteBox in silverlight toolkit for windows phone wrong display

I'm having a problem with the autocompletebox from the toolkit for windows phone. I bind it to some data, then when i press it and start typing, it discovers some items but they are displayed wrong (the list is shown separated from the box, and also if i click on any item, nothing happens. If i click where the item would be supposed to be (for example, right on the top of the box), then it gets selected. It looks like a rendering problem (bug?)) but perhaps i'm doing something wrong. Here's the code for the box :
<DataTemplate x:Key="DataTemplate1">
<ContentControl Content="{Binding Name}" Margin="8,7"/>
</DataTemplate>
<toolkit:AutoCompleteBox ItemsSource="{Binding}" x:Name="txtSelectValues" MinWidth="250" Margin="0,0,0,0" ItemTemplate="{StaticResource DataTemplate1}" VerticalAlignment="Top" />
Found it. It's a bug with the AutoCompleteBox. When inside a scrollviewer control, the dropdown gets messed up and displayed in an incorrect position
Its not just that is also to do with being placed inside of a Pivot/Panaroma as well as the scrollviewer, the silverlight gurus have stated they haven't a timeline for the fix for the Pivot control, and there is a nasty hack
http://silverlight.codeplex.com/workitem/7574
I think the answer might just be that you shouldn't be using a ContentControl directly used like this. Try using something like a TextBlock instead - e.g.:
<DataTemplate x:Key="DataTemplate1">
<TextBlock Text="{Binding Name}" Margin="8,7"/>
</DataTemplate>
If that's not the answer, then try pulling back to a simple example - especially removing all the Margin's, Width's, Alignment's, etc - then put them back in one-by-one to work out and understand what is causing the effect you are seeing.

Categories