Partager via


Never do today what you can put off till tomorrow [DeferredLoadListBox (and StackPanel) help Windows Phone 7 lists scroll smoothly and consistently]

**

This blog has moved to a new location and comments have been disabled.

All old posts, new posts, and comments can be found on The blog of dlaa.me.

See you there!

Comments

  • Anonymous
    September 08, 2010
    Is there a version for WPF? This would be great! Thanks Jus

  • Anonymous
    September 09, 2010
    jus, My experience is that WPF doesn't suffer from the underlying performance problem - so you can do things the "default" way there and they'll perform just fine. :) However, if you're seeing similar problems on WPF and want to try DeferredLoadListBox out there, it should compile and work for that platform pretty much as-is. There might be a tweak or two that I'm not thinking of right now, but everything I used and did seems like it will apply equally well to WPF (or Silverlight 4). Hope this helps!

  • Anonymous
    September 09, 2010
    Delay, thanks for your answer. If you use WPF on a slower machine, scrolling feels a bit "sluggish". Even on faster machines this could happen, when your ListBoxItems are quite complex. I think this lies in the nature of the VirtualizingStackPanel. I will try to implement your DeferredLoadListBox in my WPF app and let you know how it works :-) Thanks again for your great work! Jus

  • Anonymous
    September 17, 2010
    The comment has been removed

  • Anonymous
    September 17, 2010
    David Burela, That's really great to hear - thanks a lot for sharing your success story! :)

  • Anonymous
    September 19, 2010
    Hey, great work, it's bene really helpful with my program! One problem I've noticed though with the LowProfileImageLoader is when you navigate back to a page using the back button, it reloads all the images, which considering these programs will be running on mobile devices with limited/slow conenctivity, this isn't ideal. With the normal image control this doesn't happen - is there any solution?? Thanks, John

  • Anonymous
    September 20, 2010
    The comment has been removed

  • Anonymous
    November 03, 2010
    Is it possible to tweak LowProfileImageLoader to always cache the image in isolated storage?

  • Anonymous
    November 04, 2010
    The comment has been removed

  • Anonymous
    January 09, 2011
    I found that in some rare cases, when you are checking for "overlap", the child may not belong to the parent. The following is the exception I captured. I am unsure how to trigger it, I just keep adding/removing stuff on my list. The parameter is incorrect.   at MS.Internal.XcpImports.MethodEx(IntPtr ptr, String name, CValue[] cvData)   at MS.Internal.XcpImports.MethodPack(IntPtr objectPtr, String methodName, Object[] rawData)   at MS.Internal.XcpImports.UIElement_TransformToVisual(UIElement element, UIElement visual)   at Delay.DeferredLoadListBox.Overlap(FrameworkElement parent, FrameworkElement child, Double padding)   at Delay.DeferredLoadListBox.UnmaskVisibleContent()   at Delay.DeferredLoadListBox.<PrepareContainerForItemOverride>b__1()   at System.Reflection.RuntimeMethodInfo.InternalInvoke(RuntimeMethodInfo rtmi, Object obj, BindingFlags invokeAttr, Binder binder, Object parameters, CultureInfo culture, Boolean isBinderDefault, Assembly caller, Boolean verifyAccess, StackCrawlMark& stackMark)   at System.Reflection.RuntimeMethodInfo.InternalInvoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, StackCrawlMark& stackMark)   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)   at System.Delegate.DynamicInvokeOne(Object[] args)   at System.MulticastDelegate.DynamicInvokeImpl(Object[] args)   at System.Windows.Threading.DispatcherOperation.Invoke()   at System.Windows.Threading.Dispatcher.Dispatch(DispatcherPriority priority)   at System.Windows.Threading.Dispatcher.OnInvoke(Object context)   at System.Windows.Hosting.CallbackCookie.Invoke(Object[] args)   at System.Windows.RuntimeHost.ManagedHost.InvokeDelegate(IntPtr pHandle, Int32 nParamCount, ScriptParam[] pParams, ScriptParam& pResult)

  • Anonymous
    January 09, 2011
    William Wong, The Windows Phone 7 implementation of TransformToVisual seems to have some kind of quirk that leads to issues like this in seemingly unexpected situations. :( I've successfully fixed something very similar for ContextMenu by switching to the LayoutInformation.GetLayoutSlot method (instead of TransformToVisual), so that should work here, too. In fact, I've heard from a customer who did just that with DeferredLoadListBox to avoid an exception with a similar-sounding call stack to yours. If you're able to find a reliable way to reproduce the problem, that would be a great way to be sure the fix was good. I've got a note on my TODO list to look into this, but I haven't had a chance to do so yet - so if you give this a try and have good luck, I'd love to hear it! Thanks for the report - hope this helps!

  • Anonymous
    February 06, 2011
    David - I was getting a very similar stack trace to William when using Peter Torr's LazyListBox inside a Pivot on a Page that I was Navigate()ing to from the Application.Current.RootVisual but I'm not sure if this is 100% reproducible (I can try and create a simple test case, if you need). As per your advice I changed I changed the call to TransformToVisual in ExtensionMethod TestVisibility (included in Torr's LazyListBox project) to GetLayoutSlot and it fixed the issue.

  • Anonymous
    February 07, 2011
    Michael, Great news! It seems like there's a platform bug here, but it's nice to know it can be worked around in user code. :) Thanks for sharing!

  • Anonymous
    February 15, 2011
    Thanks for your information, It helps me better understanding.. yes Thanks for this Information.

  • Anonymous
    February 17, 2011
    Thank you for your description and the sample. It helped me a lot. Now I have one problem with DeferredLoadListBox. My user can switch between different lists and this works as expected. However if I switch the list the current scrolling position is preserved. So if I happen to scroll three items down and the other list has only one item the screen looks like empty. I need to scroll up manually to see the one item of the new list. I tried DeferredLoadListBox.ScrollIntoView() to scroll to the first item but this will just result in an exception. Do you have an idea how to reset the scroll position of DeferredLoadListBox via code?

  • Anonymous
    February 17, 2011
    addon: with exchanging the list I meant I will exchange the ItemSource of the DeferredLoadListBox and this is when the current scrolling position is preserved.

  • Anonymous
    February 18, 2011
    The comment has been removed

  • Anonymous
    February 21, 2011
    I think my last answer got lost: I made the scrollviewer public and tried the following: MyDeferredList.scrollViewer.ScrollToVerticalOffset(-double.MaxValue); This will fire the exception. The problem is caused by the method UnmaskVisibleContent of DeferredLoadListBox. This method is fired when I try to scroll to the top of the list. A dirty way around the problem is to implement a switch. This switch will prevent UnmaskVisibleContent from running. After the scroll operation I disable the switch. Not a nice solution but it works as expected.

  • Anonymous
    February 22, 2011
    Holger, I'm glad to hear you found something that works for you! FYI that I've made a note to investigate this problem further next time I'm working with this code. Thanks!

  • Anonymous
    February 23, 2011
    David, I have another one. I have two pages. The first page is the starter page and the second page has the DeferredLoadListBox. I navigate to this second page and the DeferredList is populated with images. Now I will swipe with my finger and the list is scrolling (I am not using ScrollToVerticalOffset - just my fingers). Within this scroll operation I will hit the back button of the phone. Now the navigation is going back to the starter page - I see the applicationbar of the starter page coming up. But the images of the DeferredList are still on top of the starter page (like an overlay). So the app remains in an unusable state and I need to force the app to shutdown. I solved the problem by stopping any scroll operation of the DeferredList before I go back to the previous page: protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e) {      MyDeferredList.scroll = true;      MyDeferredList.scrollViewer.ScrollToVerticalOffset(-double.MaxValue);      MyDeferredList.scroll = false;      base.OnNavigatedFrom(e); } Let me know if you need my xap for testing.

  • Anonymous
    February 24, 2011
    Holger, That sounds really weird - and I'm not sure how DeferredLoadListBox could even be responsible: the navigation service should have switched to a new page. If you contact me via the "Email Blog Author" link in the sidebar, I'll reply and let you know how to send a XAP file that I can pass on to the WP7 team. Thanks!

  • Anonymous
    April 13, 2011
    Am I out of luck if I need my listboxitem containers to be different sizes?

  • Anonymous
    April 13, 2011
    Sam, Nope, you should be fine! :) One of the last paragraphs in my original post discusses this: "... it's important to note there's no need for all the containers to have the same fixed height - just that they all need to have a fixed height". In other words, the containers can all be different sizes, but those sizes need to be set on the containers directly via Height=N (instead of calculated by the layout system) because of how I originally implemented things. Hope this helps!

  • Anonymous
    May 03, 2011
    So if Height=N and N is fixed/hard-coded, I'm guessing this may not work well for say twitter search results where you won't know the height ahead of time due to differing data lengths of the tweets? I can set the height to the largest possible height but then of course there will be large gaps for lesser content. I don't believe I can do a binding inside the required Height setter value of the ListBoxItem.

  • Anonymous
    May 03, 2011
    Geoff, I agree the fixed height requirement doesn't play well with the scenario you describe. :( That said, I have just added a note to my TODO list to reconsider whether that limitation is really necessary. It seemed so at the time, but maybe when I revisit this, I'll realize it isn't... PS - If you want to do Bindings in a Silverlight Setter, you can use my SetterValueBindingHelper: blogs.msdn.com/.../the-taming-of-the-phone-new-settervaluebindinghelper-sample-demonstrates-its-usefulness-on-windows-phone-7-and-silverlight-4.aspx

  • Anonymous
    May 14, 2011
    FYI: I occasionally get the below error using the control. It appears to happen sporadically when I'm using threading and I'm refreshing the data for the application while on a panorama item other than the panorama item that contains the DeferredLoadListBox. I customized the error message a bit; the error ws a little misleading as the ItemsPanel is a StackPanel but the condition for throwing the exception is hit. Any ideas? System.NotSupportedException: Couldn't find container for item (ItemsPanel should be a StackPanel); panel is of type StackPanel   at Phone.Common.Controls.DeferredLoadListBox.UnmaskVisibleContent()   at Phone.Common.Controls.DeferredLoadListBox.<PrepareContainerForItemOverride>b__1()   at System.Reflection.RuntimeMethodInfo.InternalInvoke(RuntimeMethodInfo rtmi, Object obj, BindingFlags invokeAttr, Binder binder, Object parameters, CultureInfo culture, Boolean isBinderDefault, Assembly caller, Boolean verifyAccess, StackCrawlMark& stackMark)   at System.Reflection.RuntimeMethodInfo.InternalInvoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, StackCrawlMark& stackMark)   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)   at System.Delegate.DynamicInvokeOne(Object[] args)   at System.MulticastDelegate.DynamicInvokeImpl(Object[] args)   at System.Windows.Threading.DispatcherOperation.Invoke()   at System.Windows.Threading.Dispatcher.Dispatch(DispatcherPriority priority)   at System.Windows.Threading.Dispatcher.OnInvoke(Object context)   at System.Windows.Hosting.CallbackCookie.Invoke(Object[] args)   at System.Windows.RuntimeHost.ManagedHost.InvokeDelegate(IntPtr pHandle, Int32 nParamCount, ScriptParam[] pParams, ScriptParam& pResult)

  • Anonymous
    May 15, 2011
    Geoff, This may be another of those times where the platform calls into some code and things are in a slightly weird state. What's probably happening is that DeferredLoadListBox believes that an item is visible based on its own calculations, but the platform hasn't yet created a ListBoxItem container for it that item. Going by your scenario description above, it could be because you're adding new items within the visible part of the list, but the control itself isn't visible, the containers don't get created. It may be easy to check in the debugger - if that's the case, you might look at whether there's an obvious tweak to my DeferredLoadListBox code or perhaps whether it would be possible to defer adding those items until the control becomes visible and the "normal" behavior will be restored.

  • Anonymous
    February 27, 2012
    The comment has been removed

  • Anonymous
    February 28, 2012
    Prakash, If the principles here work well for you, but you want to customize the containers, you can also modify the code I've shared to take that into account. In this case, assuming everything has the same height allows the code to quickly determine which items are on screen based only on the scroll position. As long as you know the heights of the individual items, you can accomplish the same thing with just a bit more math. :) Hope this helps!

  • Anonymous
    June 17, 2012
    The comment has been removed

  • Anonymous
    June 18, 2012
    gbawa, It sounds like your scenario has a lot going on! What I'd recommend is switching to a normal ListBox and making sure everything is working okay there first. Once it is, switch back to DeferredLoadListBox and hopefully everything keeps working. If not, then I'd recommend looking at the stack trace of your calls that manipulate the AutoResetEvent because it sounds like you've identified that as the cause of the periodic blocking. It's possible some aspect of how DeferredLoadListBox works is interfering with your synchronization code, but I've skimmed the code again just now and it doesn't appear to do anything during unload nor does it start any threads itself. Getting this in the debugger's probably the quickest way to identify the source of the different behavior. Hope this helps!

  • Anonymous
    June 18, 2012
    David, Thanks for response! I checked on ListBox and same problem exist. It seems to be other issue not related to defferedlistbox. I am creating multiple threads which are performing operations on database layer (see below code) and i am using AutoresetEvent  WaitOne() in database methods to avoid multiple thread access to database and here problem starts. It wait so long. I can't provide timeout as it will loose call. Is there anyother way to intract with database in multithread invironment ? Thread t = new Thread(new ParameterizedThreadStart(database.readData)); object[] obj = new object[2] { retval, UserId}; t.Start(obj); public void readData(object o) { try{  OperationOnDatabase.WaitOne();  {      //Here intract with database table  } } Catch{} finally{ OperationOnDatabase.Set();} }

  • Anonymous
    June 19, 2012
    gbawa, I don't think AutoResetEvent has the semantics you want here as it wakes all listeners who are listening when signalled. What seems more appropriate for this scenario is a mutex which you can get in .NET via the simple lock(object) { ... } syntax. I'd suggest trying that as it should simplify the code and do what you want more reliably. Good luck! :)

  • Anonymous
    October 29, 2013
    The comment has been removed

  • Anonymous
    October 29, 2013
    sanath shetty, In cases where the exception is thrown, what IS the type of the container that's causing the code to throw?