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
Anonymous
January 17, 2011
Fantastic! Just one quick... I have a strange behavior if I disable the scrollviewer inside.. and have one surrounding it...Anonymous
January 17, 2011
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.Anonymous
January 25, 2011
Just trying this out. After 5min copying and pasting, it worked instantly. Just adding some tweaks to the template now. Really great work! Thanks!Anonymous
January 31, 2011
The comment has been removedAnonymous
January 31, 2011
@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.)Anonymous
February 12, 2011
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.Anonymous
March 20, 2011
The comment has been removedAnonymous
March 21, 2011
@Neel, you need to use an ObservableCollection<Quote> instead of a List<Quote>.Anonymous
March 21, 2011
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?Anonymous
March 21, 2011
Thanks Jason :) After posting this post y'day , I did the same changes which you have suggested and wolha!!! it worked :)Anonymous
April 27, 2011
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.Anonymous
April 29, 2011
@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.Anonymous
May 01, 2011
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 SerranoAnonymous
May 01, 2011
@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. :)Anonymous
May 01, 2011
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 =)Anonymous
May 10, 2011
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/fwlinkAnonymous
May 10, 2011
@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/licenseAnonymous
May 11, 2011
thank you for the reply jason, i was just concerned and i did not want to break any rulesAnonymous
June 21, 2011
The comment has been removedAnonymous
June 22, 2011
The comment has been removedAnonymous
June 26, 2011
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 executedAnonymous
June 27, 2011
@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_ThreadingAnonymous
June 27, 2011
Thanks one more time These tip help me a lot.Anonymous
July 28, 2011
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, StephanAnonymous
July 29, 2011
@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.Anonymous
July 29, 2011
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éterAnonymous
August 30, 2011
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(); } }Anonymous
August 30, 2011
i forgot.... Best regards Enno :)Anonymous
October 23, 2011
Hi Jason, great work! but can you help a little, how I can put wrap panel to items panel? What I need to change? ThanksAnonymous
October 24, 2011
@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.Anonymous
October 26, 2011
"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, olafAnonymous
November 10, 2011
The comment has been removedAnonymous
December 18, 2011
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, JorisAnonymous
February 04, 2012
This is awesome.... thanks for sharing. You should probably put this up on Codeplex or something. Great work!Anonymous
March 01, 2012
Amazing work! Thanks for sharing.Anonymous
April 13, 2012
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!Anonymous
April 15, 2012
I found out it's because i forgot to change the position of the DragInterceptor. Please ignore my question :P Thank youAnonymous
May 05, 2012
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.Anonymous
May 07, 2012
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.Anonymous
June 22, 2012
Thanks so much. It works like a charm with a list has >300 items :)Anonymous
November 23, 2012
No we need this control as an "ReorderListView" for Windows 8! :)Anonymous
December 04, 2012
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 ?Anonymous
December 26, 2012
I've just tried with WP8 and it works fine.Anonymous
January 15, 2013
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 WP7Anonymous
February 18, 2013
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;Anonymous
March 05, 2013
working good but how can i get the index of the dragged item and the index of the dropped position? thanksAnonymous
March 06, 2013
Index of position to drop: dropTargetIndexAnonymous
April 12, 2013
Lookig at the code I cannot figure out how to programatically move an item from position X to position Y. Is it possible?Anonymous
September 01, 2013
The comment has been removedAnonymous
September 01, 2013
The comment has been removedAnonymous
September 17, 2013
Hi Jason Ginchereau, In your ReorderListBox, how can i disable top item? Don't alow move and show drag icon? Thank you in advance.Anonymous
October 24, 2013
Hi Jason Ginchereau, How I can handle event reorder action? thanksAnonymous
October 27, 2013
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.Anonymous
July 09, 2014
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.Anonymous
July 09, 2014
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.Anonymous
March 11, 2015
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?Anonymous
March 16, 2015
is there a version for wp8.1(window runtime)?