WP7 ReorderListBox improvements: rearrange animations and more


Update 2013-10-08: Created a ReorderListBox CodePlex project and pushed a ReorderListBox NuGet package. Use either of those to get the latest version of this control.

Update 2011-12-19: Updated the attached demo project to target the WP 7.1 (Mango) SDK. This required a small change to the auto-scroll logic.

The ReorderListBox control I shared last week was moderately popular, and I’ve received some great feedback. Today I’m sharing an update to that control that polishes some minor issues and adds a cool new feature I call “rearrange animations”. This feature allows the application to programmatically move, insert, or delete items in the list while showing a nice visual indication of what is happening. Specifically, the animations are as follows:

  • Inserted items fade in while later items slide down to make space.
  • Removed items fade out while later items slide up to close the gap.
  • Moved items slide from their previous location to their new location.
  • Moved items which move out of or in to the visible area also fade out / fade in while sliding.

The rearrange animations are complementary to the interactive drag-and-drop reordering, and both features can be enabled at the same time. Unfortunately the rearrange animations are not customizable in XAML due to the way they must be dynamically generated. And when using them you’ll have to keep performance in mind: if you make a lot of changes to a large list (such as a full shuffle or sort) then the rearrange animations may take some time to prepare, although once the animations start they should be smooth. In my tests on a Samsung Focus, such complex rearrangements took up to about a second to prepare. Simpler operations such as moving, adding, or removing an item or two are much faster.

Updated source is attached at the ReorderListBox CodePlex site. Here’s a complete list of what else has changed since last week:

  • When an item is dropped, it now quickly slides into its proper position rather than suddenly appearing there.
  • Control templates are corrected: a few things were missing compared to the standard ListBox control template, most notably the selected-item accent color.
  • The scrollbar is now made visible while drag-scrolling.
  • Fixed a bug that prevented moving a taller item to the top or bottom position when a shorter item was there.
  • Fixed a bug that caused the list items to jump slightly after moving an item down by more than a page then dropping it.

And now, a video preview of the improved ReorderListBox control!

[Video]

Comments (57)

  1. Cristóvão Morgado (cristovao.morgado@gmail.com) says:

    Fantastic!

    Just one quick… I have a strange behavior if I disable the scrollviewer inside.. and have one surrounding it…

  2. jasongin says:

    The drag-and-drop reorder behavior integrates with the ListBox's internal ScrollViewer for several reasons, including auto-scrolling when you drag near the top or bottom edge of the ListBox. I'm not surprised that it doesn't work with an external ScrollViewer. It should be possible to fix the code to handle that, but I have not tried.

  3. Bert says:

    Just trying this out. After 5min copying and pasting, it worked instantly. Just adding some tweaks to the template now.

    Really great work! Thanks!

  4. cohoman says:

    Thanks for that great piece of example code. I was able to compile and run your code as-is in the WP7 Emulator, but when I try to copy your files into a new project I get the following error:

    The tag 'List' does not exist in XML namespace 'clr-namespace:System.Collections.Generic;assembly=mscorlib'. c:usersdavedocumentsvisual studio 2010Projectslistbox_reorder_test_2listbox_reorder_test_2DesignData.xaml 1 2 listbox_reorder_test_2

    I added these files to my new project:

    Themes/Generic.xaml

    DesignData.xaml

    ReorderListBox.cs

    ReorderListBoxItem.cs

    and I added the following line to my MainPage.xaml file:

    xmlns:rlb="clr-namespace:ReorderListBoxDemo"

    Any idea on why I'm getting this error message? I did a Google search, but didn't find an answer.

    Thanks.

  5. jasongin says:

    @cohoman, You don't need to copy that DesginData.xml file into your project. It is only needed for the demo application. (To actually fix the error, I think you need to set the Build Action to "DesignData" in the project item properties.)

  6. Bert says:

    Any suggestions on how to make this work with the MVVM pattern? The reorder listbox moves items within its source collection (a collection of viewmodels), but the source often is readonly. Wouldn't it be better to raise an event to request to move an item so it can be handled by the model, which is then reflected in the viewmodel collection? In fact, I've tried the latter but it doesn't seem to work because some methods aren't called.

    Bert.

  7. Neel says:

    Hi Jason,

    I am new to WP7 Development and need to do the same functionality like you have shown above in my application. I have downloaded your code and it worked great on emulator πŸ™‚

    When I look into the code I have observed that you have used ObservableCollection of type string, where as in my case I am retrieving the json data from server and parse it to Object.

    Please have a look on below my code…

    IList<Quote> ResultQuoteInfo = new List<Quote>();

    IList<JToken> results = null;

    //retrived the JSON data

    JObject yahooQuote = JObject.Parse(e.Result);

    //Converted into List

    results = yahooQuote["query"]["results"]["quote"].Children().ToList();

    foreach (JToken Quoteinfo in results)

    {

        //Deserialize the object and mapped to Quote class

        Quote QuoteInfoResult = JsonConvert.DeserializeObject<Quote>(Quoteinfo.ToString());                    

        //Added the deserialize object into list

        ResultQuoteInfo.Add(QuoteInfoResult);

    }

    this.reorderListBox.ItemsSource = null;

    this.reorderListBox.Items.Clear();

    //Bind the list to list box control

    this.reorderListBox.ItemsSource = ResultQuoteInfo;

    ———————————————————————————————————————————

    When I execute the code it properly populate the data in listbox and render on UI but when I tried to re-arrange the items it gives below error at sourceList.RemoveAt(fromIndex); in MoveItem function (ReorderListBox.cs)

    InvalidOperationException was unhandled

    "Operation not supported on read-only collection"

    Please suggest where I am doing mistake and where to do the code changes to make my code working.

    Thanks in advance πŸ™‚

  8. jasongin says:

    @Neel, you need to use an ObservableCollection<Quote> instead of a List<Quote>.

  9. Perry says:

    Love your control.  I am trying to use it with a Context Menu attached to it.  I am running into an issue with getting the underlying item that brought up the Context Menu.

    When using a normal Listbox control, the below works great:

    ListBoxItem selectedListBoxItem = this.mylistbox.ItemContainerGenerator.ContainerFromItem((sender as MenuItem).DataContext) as ListBoxItem;

    When using the Reorderlistbox control, the above code will not return the correct entry if the listbox item was 'reordered'.

    Any ideas?

  10. Neel says:

    Thanks Jason πŸ™‚

    After posting this post y'day , I did the same changes which you have suggested and wolha!!! it worked πŸ™‚

  11. AvSomeren says:

    Great control you have here. Before digging in too deep, do you think this control is portable to WPF?

    I get a lot of discrepancies in terms of RootVisual, WritableBitmap and FindElementsInHostCoordinates that need to be addressed.

  12. jasongin says:

    @Perry, Sorry I don't know what the problem could be there, I would expect that to work.

    @AvSomeren, It has been a while since I've done much WPF programming, so I don't think I can give a good answer. I would expect you could find WPF equivalents for the things you mention, although you may encounter other problems if the WPF ListBox control happens to work differently than it does in Silverlight.

  13. Eduardo Serrano says:

    Hi Jason, great work!

    I have one question/suggestion: would you be able to change the code so that it will still performs animations if we change the ListBox ItemsPanelTemplate? For instance:

    <rlb:ReorderListBox.ItemsPanel>

                           <ItemsPanelTemplate>

                               <toolkit:WrapPanel Name="_wrapPanel"  Orientation="Horizontal" Width="800"/>

                           </ItemsPanelTemplate>

    <rlb:ReorderListBox.ItemsPanel>

    If i do this the animations won't be what one would like to see…. Would you consider adapting the code to account for other ItemsPanelTemplates? Or maybe this would be too complex?

    Regards,

    Eduardo Serrano

  14. jasongin says:

    @Eduardo, That should be possible to do, by making the animation-generation code more generic based on the before and after positions of each item and the visible area of the items panel. But don't wait for me, I suggest you try it out yourself. πŸ™‚

  15. Eduardo Serrano says:

    Thank you for your quick reply Jason. I'll definitely give it a try. I'm trying to finish something first and then i'll work on it. If i get it to work (or hit a wall) i'll tell you something =)

  16. jperry says:

    Thank you for posting this example, but i have a question.  I would like to use some of your ideas in my app however after reading the Microsoft Public License (MS-PL) included in your code I am not sure I can.

    I read the agreement to mean that if I use any portion of your code my app must be open source as well since your code will be distributed along with mine to a users phone.  Is this correct?

    Link: go.microsoft.com/fwlink

  17. jasongin says:

    @jperry, the MS-PL does not require you to open-source any part of your app.

    Lots of closed-source apps are built with MS-PL code, for example the popular Silverlight Toolkit: silverlight.codeplex.com/license

  18. jperry says:

    thank you for the reply jason, i was just concerned and i did not want to break any rules

  19. jesus says:

    Hello,

    The control works fine, but when I bind the listbox to an observablecollection instead of a list, the "moveItem" method throws an exception in the third line:

    object itemsSource = this.ItemsSource;

    System.Collections.IList sourceList = itemsSource as System.Collections.IList;

    int fromIndex = sourceList.IndexOf(item);   <—-NullReferenceException

    help please! :-S

    Note: The itemsSource contains an OrderedEnumerable<MyObject>

    thanks!

  20. jasongin says:

    @jesus, Are you sure you're binding to an ObservableCollection? The ObservableCollection<T> class extends Collection<T> which does implement System.Collections.IList, so that code should work. The demo app uses an ObservableCollection<string> as its data source.

    If you're actually binding to an IOrderedEnumerable<T>, that is not supported as a data source.

  21. First of all thanks for this great peace of code

    I need to upgrade the xml (since the order of the items change) that I used to populate the listbox Where is the best place to insert my code in the ReorderListBox Class.

    I have been trying in the move Method but the drop of the item been moved kind of frees for a fraction of time while my code executed

  22. jasongin says:

    @Hidroilio, You shouldn't need to modify the ReorderListBox class to do that. Just use an ObservableCollection<T> as the data source, and handle the CollectionChanged event for the collection. In that event-handler you'll have all the information you need to update the XML

    To avoid freezing the UI thread, you should do any time-consuming work on a background thread. See this link for some discussion of how to do that: wildermuth.com/…/Architecting_WP7_-_Part_9_of_10_Threading

  23. Thanks one more time

    These tip help me a lot.

  24. Stephan says:

    Hello Jason,

    AutoScrolling doesnΒ΄t work for me when i drag near the top or bottom of the list, including the Sample Project.

    Any Ideas?

    Best Regards,

    Stephan

  25. jasongin says:

    @Stephan, you must be using the Mango SDK. In Mango there is a breaking change to the ScrollViewer behavior. Try setting ScrollViewer.ManipulationMode="Control" to restore the old behavior.

  26. Hi Jason,

    I want to change the background under the up/down arrows. In the Generic.xaml I modified the

    <Style TargetType="rlb:ReorderListBoxItem">

           <Setter Property="Background"  Value="Transparent" />

    to:

    <Style TargetType="rlb:ReorderListBoxItem">

           <Setter Property="Background"  Value="{StaticResource ListItemBackground}" />

    Each list item should have this background, that I defined in the begining of the Generic.xaml and its xaml:

    <LinearGradientBrush x:Key="ListItemBackground" EndPoint="0.5,1" StartPoint="0.5,0">

    <GradientStop Color="#FFDDDDDD" Offset="1"/>

    <GradientStop Color="White"/>

    </LinearGradientBrush>

    After the change it looks fine in Blend and at the begining of the application start. But if I want to reorder an item, the application will stop.

    Do you have any ida what am I doing wrong and how could I fix it?

    Regards,

    PΓ©ter

  27. Enno says:

    Hi,

    i found a bug in the reordered listbox class.

    if anybody uses two applicationbar buttons to sort a list ascending and descending and press one button twice and then the other one, you will get a ordering issue with the rearrangeCanvas (this holds the items in the background). For fixing this, you must add in the "else" tree on line 826 following code:

    this.rearrangeCanvas.Children.Clear();

    here the complete fixed function AnimateRearrangeInternal:

    private void AnimateRearrangeInternal(Action rearrangeAction, Duration animationDuration)

           {

               // Find the indices of items in the view. Animations are optimzed to only include what is visible.

               int viewFirstIndex, viewLastIndex;

               this.GetViewIndexRange(true, out viewFirstIndex, out viewLastIndex);

               // Collect information about items and their positions before any changes are made.

               RearrangeItemInfo[] rearrangeMap = this.BuildRearrangeMap(viewFirstIndex, viewLastIndex);

               // Call the rearrange action callback which actually makes the changes to the source list.

               // Assuming the source list is properly bound, the base class will pick up the changes.

               rearrangeAction();

               this.rearrangeCanvas.Visibility = Visibility.Visible;

               // Update the layout (positions of all items) based on the changes that were just made.

               this.UpdateLayout();

               // Find the NEW last-index in view, which may have changed if the items are not constant heights

               // or if the view includes the end of the list.

               viewLastIndex = this.FindViewLastIndex(viewFirstIndex);

               // Collect information about the NEW items and their NEW positions, linking up to information

               // about items which existed before.

               RearrangeItemInfo[] rearrangeMap2 = this.BuildRearrangeMap2(rearrangeMap,

                   viewFirstIndex, viewLastIndex);

               // Find all the movements that need to be animated.

               IEnumerable<RearrangeItemInfo> movesWithinView = rearrangeMap

                   .Where(rii => !Double.IsNaN(rii.FromY) && !Double.IsNaN(rii.ToY));

               IEnumerable<RearrangeItemInfo> movesOutOfView = rearrangeMap

                   .Where(rii => !Double.IsNaN(rii.FromY) && Double.IsNaN(rii.ToY));

               IEnumerable<RearrangeItemInfo> movesInToView = rearrangeMap2

                   .Where(rii => Double.IsNaN(rii.FromY) && !Double.IsNaN(rii.ToY));

               IEnumerable<RearrangeItemInfo> visibleMoves =

                   movesWithinView.Concat(movesOutOfView).Concat(movesInToView);

               // Set a clip rect so the animations don't go outside the listbox.

               this.rearrangeCanvas.Clip = new RectangleGeometry() { Rect = new Rect(new Point(0, 0), this.rearrangeCanvas.RenderSize) };

               // Create the animation storyboard.

               Storyboard rearrangeStoryboard = this.CreateRearrangeStoryboard(visibleMoves, animationDuration);

               if (rearrangeStoryboard.Children.Count > 0)

               {

                   // The storyboard uses an overlay canvas with item snapshots.

                   // While that is playing, hide the real items.

                   this.scrollViewer.Visibility = Visibility.Collapsed;

                   rearrangeStoryboard.Completed += delegate

                   {

                       rearrangeStoryboard.Stop();

                       this.rearrangeCanvas.Children.Clear();

                       this.rearrangeCanvas.Visibility = Visibility.Collapsed;

                       this.scrollViewer.Visibility = Visibility.Visible;

                       this.AnimateNextRearrange();

                   };

                   this.Dispatcher.BeginInvoke(rearrangeStoryboard.Begin);

               }

               else

               {

                   this.rearrangeCanvas.Children.Clear(); //remove item layer if no items are rearranged

                   this.rearrangeCanvas.Visibility = Visibility.Collapsed;

                   this.AnimateNextRearrange();

               }

           }

  28. Enno says:

    i forgot….

    Best regards

    Enno πŸ™‚

  29. Serg says:

    Hi Jason, great work! but can you help a little, how I can put wrap panel to items panel? What I need to change? Thanks

  30. jasongin says:

    @Serg, I think it would be difficult to change this to use a WrapPanel. Currently the drag-reordering and rearrange-animation logic is all done with the assumption that items are moving only up and down. At a minimum you'd have to re-write a lot of that code, which is moderately complex now and would be much more complex when items can move both horizontally and vertically. I'm not sure what else would have to change.

  31. Olaf says:

    "you must be using the Mango SDK. In Mango there is a breaking change to the ScrollViewer behavior. Try setting ScrollViewer.ManipulationMode="Control" to restore the old behavior."

    doesn t work.

    any other idea?

    thanks,

    olaf

  32. hi,

    Thanks for making a nice control.

    I have tried your control and notice one strange thing..

    I have added 100 items in list box.

    Scroll down down to last item.. scroll up down up down..

    and suddenly Image area for Rearrange is gone…. (Image on which user click and move item up and down)

    Any idea regarding this issue?

    Thanks & Regards,

    Joyous Suhas

  33. Joris Zwaenepoel says:

    Hi,

    Thanks for this great control.  I am using VB.NET for my app, and it works perfectly when referencing the C# phone application project that you created.  However, when installing on a phone, both applications are installed and that is not the desired behaviour.

    So, I created a C# class library (Silverlight for Windows Phone) and put the Themes/Generic.xaml + both the ReorderListBox and the ReorderListBoxItem classes in there.  At runtime, I keep getting the "ReorderListBoxItem must have a DragHandle ContentPresenter part." error.  Any idea how to solve this?

    Thanks,

    Joris

  34. rockyMtnRajah says:

    This is awesome…. thanks for sharing. You should probably put this up on Codeplex or something. Great work!

  35. Vit says:

    Amazing work! Thanks for sharing.

  36. huntert says:

    It's awesome! Thanks for such a great work!

    One question, how should I move the draggable area to the front(left).

    I tried to switch the column position of ContentContainer and HandleContainer, I also change the column definition, but it seems does not work.

    Could you teach me how to change the draggable area position?

    Thank you very much! Once again, excellent work!

  37. huntert says:

    I found out it's because i forgot to change the position of the DragInterceptor.

    Please ignore my question πŸ˜›

    Thank you

  38. peter says:

    Great work that I am thankfully re-using. I found an issue when using it in my application where the selected item would often not be shown in the accent color. A normal listbox does not have this issue. After a lot of experimentation I found out that this issue can be resolved by changing the type of the DragHandle in Generic.xaml to ContentControl i.s.o. ContentContainer. The same type change must be done in file ReorderListBoxItem.cs in lines 21, 224 and 237.

    Thanks again for a great control.

  39. Amy says:

    hi, i'm facing some problems with the dropping part. There's no error in the codes but when i drag the item, it will jump back to its original position. How can this be resolved?

    Thanks for sharing this code.

  40. Quoc Nguyen says:

    Thanks so much.

    It works like a charm with a list has >300 items πŸ™‚

  41. FacilisDK says:

    No we need this control as an "ReorderListView" for Windows 8! πŸ™‚

  42. Jeremy says:

    Hi Jason,

    I use your control in my todo-list app 2Day (www.2day-app.com). However, the control does not work when I upgrade the project to WP8. In this case, the dragged item does not move but all the other items are dragged. Do you have any idea ?

  43. Alexander Sychev says:

    I've just tried with WP8 and it works fine.

  44. Zepto says:

    Hi Jason

    Thanks so much !

    I aslo use your control in WP8

    The problem is the focusd item dosn't move but the whole list is moving

    i don't know why ,it's worked in WP7

  45. Reconstructor says:

    OK, here is fix for WP8:

    In function dragInterceptor_ManipulationStarted() on the first row add:

    scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;

    Then in function dragInterceptor_ManipulationCompleted() on the last row add:

    scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Visible;

  46. vts says:

    working good but how can i get the index of the dragged item and the index of the dropped position?

    thanks

  47. Alexander says:

    Index of position to drop: dropTargetIndex

  48. Igor Kulman says:

    Lookig at the code I cannot figure out how to programatically move an item from position X to position Y. Is it possible?

  49. We are working on Windows Phone 8 app and try to use the reorder list box control. For previous version of our app it's working. But now, when i bind the collection to the reorder list box, i've got exception "System.InvalidOperationException"

    In details, i see this message.

           System.InvalidOperationException: ReorderListBoxItem must have a DragHandleContentPresenter part. at ReorderListBoxDemo.ReorderListBoxItem.OnApplyTemplate()}

    If anybody see such problem or know anything about it – please help.But raw binding is not working at all.

  50. We are working on Windows Phone 8 app and try to use the reorder list box control. For previous version of our app it's working. But now, when i bind the collection to the reorder list box, i've got exception "System.InvalidOperationException"

    In details, i see this message.

           System.InvalidOperationException: ReorderListBoxItem must have a DragHandleContentPresenter part. at ReorderListBoxDemo.ReorderListBoxItem.OnApplyTemplate()}

    If anybody see such problem or know anything about it – please help.But raw binding is not working at all.

  51. Toan-MSP says:

    Hi Jason Ginchereau,

    In your ReorderListBox, how can i disable top item? Don't alow move and show drag icon?

    Thank you in advance.

  52. luan nguyen says:

    Hi Jason Ginchereau,

    How I can handle event reorder action? thanks

  53. Andrew Martynenko says:

    Hi Jason, excellent work! But in my app i need to use StackPanel instead of VirtualizingStackPanel and it fails in AnimateDrop() because itemContainer.RenderSize is zero. Can you help me with this fix? Thanks.

  54. Mohamed Thaufeeq says:

    Hi Jason,

    This is fantastic feature. I'm very excited to add this feature to my application. But I got following error when I set ItemSource from the IQueryable list, as below:

               var Query = from it in AppDB.TableName

                           select it;

               ReorderListBox.ItemsSource = Query.ToList();

    here's the details of my error.

    "InvalidOperationException", Additional information: Operation not supported on read-only collection.

  55. jasongin says:

    Mohamed, that error message is pretty self-explanatory: you cannot reorder a read-only collection. The Enumerable.ToList() method returns a read-only list. Try wrapping your query results in a new List<T> or ObservableCollection<T> before using it as the ItemsSource.

  56. Sweta says:

    Thanks for the code πŸ™‚

    It really helped me a lot.

    But I have one doubt,that is their a way to hide the re-order image and make the entire row to get selected and dragged?

  57. riaware says:

    is there a version for wp8.1(window runtime)?