How to optimize DataContext freeze - c#

I can't seem to solve issue with horrible UI freeze when assigning DataContext to Listbox control in WPF.
I have DataTemplate defined in Window.Resources.
When app starts I load and sort images in List, where ImageInfo holds various information about image that is loaded, including URI path or BitmapImage.
Problem does not lie here however, when I assign this List as DataContext of ListBox control, I get really huge freeze that I can't seem to be able to solve.
<DataTemplate>
<Grid HorizontalAlignment="Left" Width="260" Height="360">
<Border Padding="5" Margin="10" BorderBrush="Orange">
<Image Source="{Binding image}" Stretch="Fill" HorizontalAlignment="Center"/>
</Border>
<Border Background="Black" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Opacity="0.70" Height="50" Margin="0,10,10,0"></Border>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal" HorizontalAlignment="Center">
<!-- 3 buttons -->
</StackPanel>
</Grid>
</DataTemplate>
<ItemsPanelTemplate>
<UniformGrid Columns="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
I assign DataContext like so :
lbGallery.DataContext = lst169;
Lists contain more than 10 items.
So far I tried to solve issue by :
Trying some virtualization options on grid
Skipping loading image as BitmapImage, and instead using just URI from path
Using fixed size for grid
I'm sure that problem is not related to code regarding loading files in lst169, because it loads data only once on startup. Using URI instead of BitmapImage assured me that my method for getting image is not problem as well.
It all leads back to setting that DataContext to listbox control.
What is the proper way to do this ?
Thanks!
EDIT :
To clarify since my post is confusing many users :
Application starts
Resource data is loaded into List (3 lists with different images)
Once data is fully loaded, I set one fully loaded list as DataContext of ListBox
That is when freeze happens.
Later on user can switch between images by click on a button. I switch DataContext during this time as well. Freeze happens.
So - Freeze is not caused by loading resources on startup. It's caused by setting DataContext of Listbox to a List, when image gets binded to Image control. Regardless if I'm binding BitmapImage type, or URI with absolute path.

Have your tried setting the Image-Binding to IsAsync = true
<Image Source="{Binding image, IsAsync=True}" Stretch="Fill" HorizontalAlignment="Center"/>

Related

UWP App, Add loading page while content loads

I am building a UWP app which gets content from the internet, as I am pulling images it often takes some time to load. I would like to have a loading screen when a user navigates to one of these pages that has an image.
I have attempted to use a progress ring which is visible while the content is being loaded and collapses when content has loaded, however this has been unsuccessful.
What would be the best way to do this.
Thanks in advance.
You can create a placeholder (image with text "image is loading").
If you have a ListView or some other data control you can use attribute Phase for this purpose:
<ListView ItemsSource="{x:Bind myimages}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ImageInfo">
<StackPanel Orientation="Vertical">
<Grid>
<Image Source="Assets/placeHolderImage.jpg" x:Phase="0" />
<Image Source="{x:Bind Url}" x:Phase="3" />
</Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
As you can seein this template one image is placed over another. First is loading image with Phase="0" and when another one with Phase="3" is loaded it's placed over

Set LongListSelector

I'm developing a Windows Phone app to practice my knowledge within the control LongListSelector. One of the pages in the app, the middle one has this code:
<!--Panorama item two-->
<phone:PanoramaItem x:Name="tasksPage" Header="Tasks">
<!--Double line list with image placeholder and text wrapping using a floating header that scrolls with the content-->
<phone:LongListSelector Margin="0,-38,-22,2" ItemsSource="{Binding Items}" LayoutMode="List">
<phone:LongListSelector.ListHeaderTemplate>
<DataTemplate>
<Grid Margin="12,0,0,38">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="second item"
Style="{StaticResource PanoramaItemHeaderTextStyle}"
Grid.Row="0"/>
</Grid>
</DataTemplate>
</phone:LongListSelector.ListHeaderTemplate>
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="12,2,0,4" Height="105" Width="432">
<!--Replace rectangle with image-->
<Border BorderThickness="1" Width="99" Height="99" BorderBrush="#FFFFC700" Background="#FFFFC700"/>
<StackPanel Width="311" Margin="8,-7,0,0">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Margin="10,0" Style="{StaticResource PhoneTextExtraLargeStyle}" FontSize="{StaticResource PhoneFontSizeLarge}" />
<TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="10,-2,10,0" Style="{StaticResource PhoneTextSubtleStyle}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</phone:PanoramaItem>
Could someone please explain briefly what the DataBindings is and how to use them (I have done some research). Could I for instance bind the LongListSelector to a list in IsolatedStorage?
I have create a ListBox before in another app, loading content from IsolatedStorage into it, but I don't know if this is the right approach. Right now the items in the LongListSelector has a yellow image right left to it - can i do the same if I'm loading the content programatically from IsolatedStorage?
I know this might be a couple or three questions, but I think they're fairly simple to answer for someone experienced.
Thanks!
Your LongListSelector has a number of items inside. They are added there through data binding by binding the ItemsSource to items which are a part of Items collection. This collection can be a List<T> or more often ObservableCollection<T> because that way, if properly implemented, the changes in ObservableCollection will reflect in your LongListSelector. The T is the type of your item - for example, a class called Book. This collection needs to be defined as a part of the DataContext object, which you set on the whole page or a part of page.
Now, as I mentioned, the Items collection is probably full of items - objects defined to have certain properties. In your case, those properties are LineOne and LineTwo, which are probably strings.
You cannot directly bind to items in isolated storage. You first need to load those items into memory. Let's assume you have a list of items serialized to JSON or XML format in your isolated storage, which is one popular way of keeping the list in isolated storage. You need to load them into a collection (deserialize) and then bind to LongListSelector. It is the right approach, yes.
The yellow image/rectangle/border defined on the left is static, but it can be there, of course. It will simply be rendered there as a part of every item you have in your LongListSelector and it will not depend on the object which you bind to.
I suggest you read the following articles/questions and answers which may explain the concept of binding to a list easier for you to understand:
MSDN - Quickstart: Data binding to controls for Windows Phone
Stack Overflow - WP8 working with XML and LongListSelector
GeekChamp - The New LongListSelector control in Windows Phone 8 SDK
in depth
Simplest explanation (overlysimplified!) is that data binding is binding a property of an object to another property a control above, there's:
<TextBlock Text="{Binding LineOne}" ... />
That is functionally equivalent to something like this:
TextBlock t = new TextBlock();
SomeObject o = new SomeObject() { LineOne = "The value of line 1" };
t.Text = o.LineOne;
// and then a propertychange listener to update t.text if o.lineone ever changes
o.PropertyChanged += (s,e) => { if (e.PropertyName == 'LineOne') t.Text = o.LineOne; };
You can't bind directly to something in isolated storage, but you can have an object load its content from isolated storage, expose those items through an Items property and then set that as the data context of the LLS.
In cases like LongListSelector (or other ItemsControl types) the itemscontrol's ItemsSource property is bound to some collection of objects (like an ObservableCollection<T>, which makes its items update whenever the collection updates. And then a template inside the ItemsControl has bindings to the properties of the individual items in the collection.

Empty LongListSelector causes infinite ScrollViewer

This is the situation:
I have a datasource that gets filtered by certain attribute (lets call it Checked), into two lists on the viewmodel. Call it New and Old.
New one needs to be displayed into one list, Old one needs to be displayed into the list right under it.
Oh and they need to scroll in unison. So if Old is currently out of screen, it will swim into visibility as the list is swiped up.
I've currently solved this with LongListSelectors like this:
<ScrollViewer VerticalAlignment="Top" VerticalScrollBarVisibility="Auto">
<StackPanel>
<phone:LongListSelector x:Name="NewList" Margin="0,0,0,0" ItemsSource="{Binding New}" SelectionChanged="NewList_SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}" Foreground="{Binding Color}" />
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
<phone:LongListSelector x:Name="OldList" Margin="0,0,0,0" ItemsSource="{Binding Path=Old}" Padding="0,20,0,0">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}" FontStyle="Italic" Foreground="{Binding Color}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</StackPanel>
</ScrollViewer>
Two longlistselectors inside a stackpanel inside a scrollviewer. Now it all works absolutely fab while there's something in both of those lists.
However, when one of them has no content whatsoever, it immediately expands to fill the entire height of its parent. In this case... the infinite scrollviewer. Which means that if there's nothing in the New list, there will be absolutely nothing visible on the screen whatsoever and if there's nothing on the New list... I can pretty much scroll infinitely after getting past the New list items.
Do I have any options here? Without programmatically creating a ton of Text fields and then trying to attach events to it, or worse, write my own list control? Standard listboxes don't work because they both scroll separately.
Any ideas?
Having two list controls under each other is a genrally a bad idea, because of ScrollViewers inside ScrolViewers.
I would advise you to use a single LongListSelector without any ScrollViewer around it.
Then create a single collection with old an new items and use an ItemTemplateSelector to style them differently.
The problem you are facing is that by the default when emty LLS is measured it's height as you see is 'infinite'. You are using StacPanel which means that second LLS is under infinite LLS.
The simples solution is to set the Height of LLS:
<phone:LongListSelector x:Name="NewList" Height="300" Margin="0,0,0,0" ItemsSource="{Binding New}" SelectionChanged="NewList_SelectionChanged">
If you can - use a Grid with defined rows instead of StacPanel. If you still want to use StackPanel, you can override the method MeasureOverride() in LLS and make extension.
It should work if you do it like this:
namespace Extensions
{
public class LongListSelectorEx : LongListSelector
{
protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
{
if (this.ItemsSource == null)
return new System.Windows.Size(this.Width, 0);
if (this.ItemsSource.Count <= 0)
return new System.Windows.Size(this.Width, 0);
return base.MeasureOverride(availableSize);
}
}
}
Watch out also if you haven't got width defined (the return value cannot be NaN - in that situation put 0 instead this.Width). Of course you will also need to check Height of LLS, bacause if you don't your controls can be pushed off the screen, when there are many items in LLS.
You can also read about this here

Get a textblock value in a listbox using GestureServices

Sorry guys, I had asked this question earlier but could not figure out the answer. Made an edit to see if that bumps it, but that did not seem to work. So here is the last try to the question
I can't seem to figure out how one can get the value of a specific textblock in a listbox. To start things off, here is the code:
<ListBox HorizontalAlignment="Left" Name="listItems" VerticalAlignment="Top" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="210" >
<Grid Height="210" Background="#75FFF8DC">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener Tap="GestureListener_Tap"
DoubleTap="GestureListener_DoubleTap"
Hold="GestureListener_Hold"
Flick="GestureListener_Flick"/>
</toolkit:GestureService.GestureListener>
...CODE...
</></></>...
The code area contains a bunch of other grids, partitions (columns and rows) and textblocks. Here is an example:
<Image Name="XXX" Source="{Binding XXXPath}" Stretch="Fill"
Grid.Column="0"/>
<TextBlock Name="YYY" Grid.Column="1" Grid.Row="0"
Text="{Binding YYYPath}" Foreground="Black"/>
<TextBlock Name="ZZZ" Grid.Column="2" Grid.Row="0"
Text="{Binding ZZZPath}" Foreground="Black"/>
So what I want, is if someone taps the grid (that means anything in the grid, including these textblocks and images), I want to first get the text of the textblock "YYY."
I could have inserted that code into a textblock and used sender as textblock, but I do not want to limit my gestures to one textblock, nor do I want to repeat that for each element in the grid (lots of issues and seems unnecessary).
Edit: If this does not work, I can also implement just one tap gesture (but again, for the whole grid) and use that to get the value of the textblock. Is there no way? Otherwise I will have to do this: Add tap for the textblock and use sender as a textblock, then get the value of the text. But I really do not want to use this approach.
I see you use bindings for your textblocks and image. So why don't you use ( if you haven't already done it) an IList instance of class which hold an information about them? Then set this instance as an ItemSource for your listbox. That way when user taps somewhere on listbox you can catch the SelectedIndex or SelectedItem of a listbox item. And this will help you to figure out which element of IList collection to extract so you could get your text or image or whatever you need.
And you don't need to use GestureServices from external Silverlight Toolkit with Mango. Tap, DoubleTap etc. are built-in.

WPF ListBox Databinding & Events

My problem is rather simple.
I have a ListBox, containing Thumnails (Image)
<ListBox Name="ListBox_Thumbnails" ItemsSource="{Binding}" DataContext="{Binding Source= {StaticResource ThumbnailListSource}}" Width="120" HorizontalAlignment="Left" Margin="-1,26,0,54">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=absolutePath}" MouseLeftButtonDown=<!--?????-->/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
I wanted to show an image, but as a new StackOverFlow user, I can't. You can find the image here:
http://www.freeimagehosting.net/uploads/61aa983cad.jpg
(For those who don't trust me, I explain here the content of the image:
On the left, there is a list of thumbnails (displayed vertically) and on the right there is a bigger image, corresponding per default to a large image of the first thumbnail).
When I click on a thumbnail (on the left), the large image on the right should be updated by the one that I clicked on.
As I am new with WPF, my approach is perhaps totally wrong with the ListBox.
Please, WPF Gurus, show me the light!
I guess, you can use events on ListBox, smth like SelectionChanged... but that's totally not the TRUE WPF-Jedi way -- remember, code-behind is the dark side! =)
Think data binding, that's the Force. Bind your large Image element's source to the SelectedItem property of the ListBox. It should look like
<Image Source="{Binding SelectedItem.absolutePath, ElementName=ListBox_Thumbnails}">
P.S. Every WPF-databinding-jedi should have this cheat sheet nearby.
P.P.S. Actually, as you're using ItemTemplate this might not work, you'll have your StackPanel as the selected item... in this case you can try the SelectedValuePath trick, set it to "absolutePath" and bind the large image to the SelectedValue property.
So your ListBox opening tag becomes:
<ListBox Name="ListBox_Thumbnails" ItemsSource="{Binding}" DataContext="{Binding Source= {StaticResource ThumbnailListSource}}" Width="120" HorizontalAlignment="Left" Margin="-1,26,0,54" SelectedValuePath="absolutePath">
And your large image tag becomes:
<Image Source="{Binding SelectedValue, ElementName=ListBox_Thumbnails}">

Categories