Update to the Picker Box control.
Last time I showed you how you could create the Picker Box functionality in your Windows Phone 7 applications. There're a few updates that I had to make in order to match Picker Box's behavior to it snative counterpart.
First of all you will probably notice that the native control will react on selecting an item even if it's already selected. The reason PickerBoxDialog didn't do it is because we are hooked up into the listbox_SelectedChanged event. To make our control to behave we can hook up into the MouseLefButtonUp event on each ListBoxItem in the ListBox control. This is done in the PrepareStoryboards method: To fix this I made the ListBox selection mode to be multi-select and added some logic to manipulate with RemovedItems and AddedItems.
The other issue with the code which I posted before is that the animations that are executed on the dialog's open or close not exactly match the native's dialog behavior. If you watch it closely (I even recorded the screen capture and tried to brake them into a separate frames) you will notice that the native animations have this "rolling" effect. As if the animations on each item are not executed at the same time, but there's a certain delay before animation on each item is started. Thanks to the help of internal folks I was able to figure out how to get this implemented. The "secret" ingredient to achieve the "rolling" effect is to set the BeginTime for each animation with an offset for each item. Also when watching the recording of the screen capture I've noticed that besides the rotational animation, there's also an animation that changes the opacity of the item going on. Here's the snippet that you will see inside of the PrepareStoryboards method:
// Create open animation
animationOpen = CreateRotationAnimation(item, -120, 0, 200, beginTime);
animationOpacity = CreateOpacityAnimation(item, 0, 1, 200, beginTime);
// Add it to the storyboard
OpenStoryboard.Children.Add(animationOpen);
OpenStoryboard.Children.Add(animationOpacity);
// Create close animation
animationClose = CreateRotationAnimation(item, 0, 90, 200, beginTime);
animationCloseOp = CreateOpacityAnimation(item, 1, 0, 200, beginTime);
// Add it to the storyboard
CloseStoryboard.Children.Add(animationClose);
CloseStoryboard.Children.Add(animationCloseOp);
// Increment the begin time for animation
beginTime += 30;
You should also notice another change: I create animations in the code and add them to a children collection of the Storyboard. This means that I can start animation with a single call OpenStoryboard.Begin(). This is how the rotation animation is created:
public static DoubleAnimation CreateRotationAnimation(DependencyObject obj, double from, double value, double milliseconds, double beginTime, EasingMode easing = EasingMode.EaseIn)
{
CubicEase ease = new CubicEase() { EasingMode = easing };
DoubleAnimation animation = new DoubleAnimation();
PropertyPath propPath = new PropertyPath("(UIElement.Projection).(PlaneProjection.RotationX)");
animation.BeginTime = TimeSpan.FromMilliseconds(beginTime);
animation.Duration = new Duration(TimeSpan.FromMilliseconds(milliseconds));
animation.From = from;
animation.To = Convert.ToDouble(value);
animation.FillBehavior = FillBehavior.HoldEnd;
//animation.EasingFunction = ease;
Storyboard.SetTarget(animation, obj);
Storyboard.SetTargetProperty(animation, propPath);
return animation;
}
After all these changes I think the Pickup Box's behavior is pretty close to the native one. You can download the updated code here.
P.S. Seeing certain references to my code on the internet I have to make the following disclaimer:
DISCLAIMER: I don't work for the Windows Phone product group. The code that I create and share with you is not an official implementation of the functionality that exists on Windows Phone 7 device. I do it because I want to learn how to do it myself and I share it with you because I like helping developers to better adapt to a new platform and make the Windows Phone 7 the best device on the market.
UPDATE: I’ve made a few fixes and updates to the code:
- Split the PrepareStoryboards method into two separate methods: PrepareOpenStoryboard and PrepareCloseStoryboard
- Add code to recognize and create animations only for the visible items in the ListBox
- Removed usage of the MouseLeftButtonUp event on the item. Instead I am using SelectionChanged event with a multi-select option on the ListBox
Comments
Anonymous
September 14, 2010
Hi Alex! I really like your control but I tried to fill it with 50 items and it seems like it gives an exception when you exceed 15 items... any idea why? Best regards ØysteinAnonymous
September 14, 2010
@Øystein - this is probably because of the "virtual" nature of the ListBox on WP7. Meaning that it will load the items on demand when you scroll to it. As as side note, I don't think you want to load so many items into this contro anyway. Scrolling through 50 items just to find the one that you need is NOT a good user experience.
- Alex
Anonymous
September 14, 2010
I've just downloaded the latest version and I get the following error: "CS0518: Predefined type 'System.Object' is not defined or imported"Anonymous
September 14, 2010
@dcolunga - the solution was created in the post beta tools. You can either re-create the solution in the beta or wait until tomorrow when the tools go RTM :) Thanks... AlexAnonymous
September 15, 2010
Thank you for your reply. Didn´t know that the ListBox load items as you scroll. Which control would you recommend when the user need to select one out of 50 items? The list will be sorted by the user so he will find the most used items on the top of the list... ØysteinAnonymous
September 15, 2010
It seems that MS use a ListBox when selecting languages & region in Settings....Anonymous
September 15, 2010
@Øystein - please download the latest version in the same blog post (PickerBoxR3.zip). I've fixed the issue that you reported with many items in the listbox.Anonymous
September 16, 2010
Thank you for your work! I have a small issue that you probably should implement. When you open the popup and dismiss it with the backbutton an exception is raised since PrepareCloseStoryboard is only called from listBox_SelectionChanged. I added the following code to PickerBoxDialog_BackKeyPress: // Raise closed eventI if (this.Closed != null) { this.Closed(this, EventArgs.Empty); } this.PrepareCloseStoryboard(); ØysteinAnonymous
September 21, 2010
The comment has been removedAnonymous
September 21, 2010
sorry for posting again, but I have a workaround... instead of directly calling PrepareOpenStoryboard(); StartOpenAnimation(); in listBox_Loaded, I've added a simple timer to delay it for a few ms... It's not that nice but it works. Maybe you have a better solution...Anonymous
October 10, 2011
Thanks for that Control, Alex. I really appreciate your effort, and it works great. I have one request though. Have you thought about the SupportedOrientation-Property? I tried to set it in the PickerBoxDialog, but it doesn't support the property. I guess you'd have to wrap the Control in a PhoneApplicationPage to achieve this behavior?Anonymous
October 10, 2011
Ok, I just read your "migrate to the SL-ToolKit"-post, and the supportedorientation is solved in the ListPicker. Thanks nonetheless!