Quantcast
Channel: Jacob Gable's Posterous
Viewing all articles
Browse latest Browse all 17

Create a Win Phone 7 Busy Service with Loady animation

$
0
0

Update: It looks like there may be a problem with wrapping the RootFrame in a Grid, but I haven't found a good answer to why.  Check this Win Phone 7 Forum Post about Changing the RootVisual.

Update 2: I've updated my example to not require changing the RootVisual.  But you will still need to have a Grid as the Root of your Page.  Check out the Updated Busy Service.

I've been spending a lot of time working on data driven Win Phone 7 apps in the past couple weeks and one of the most useful things I've created is a simple Busy Service for Win Phone 7 apps.

Media_http4bpblogspot_dgehh

The Busy Service lets you signal that something is busy, like when retrieving data from a web service or doing a long running calculation. Here is a look at it's interface;

/// <summary>/// An interface for busy services./// </summary>public interface IBusyService{bool IsBusyShown { get; }void ShowBusy(string message);void ShowBusy(TimeSpan timeToShow, string message);void ShowBusy(double milliSecondsToShow, string message);void HideBusy(double millisecondsToWait);void HideBusy(TimeSpan delay);void HideBusy();}


I decided to implement a RootPanelBusyService, which basically means that it expects a "Panel" type of element, a Grid for example, at the RootVisual. The problem with the Win Phone 7 Beta update is that the RootVisual is no longer defined in the App.Xaml, so it would seem like you can't just wrap the Root level Phone Application Frame in a Grid. It turns out, it just takes a little more work, if you open up the App.xaml.cs and take a look at the CompleteInitializePhoneApplication method you will see where the RootVisual is assigned. So with a little code we can add a Grid wrapper around the PhoneApplicationFrame and have a nice place to add our Busy Loady icon.

// Do not add any additional code to this methodprivate void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e){// Set the root visual to allow the application to renderif (RootVisual != RootFrame){// Add a Grid wrapper around the Root application frame.var root = new Grid();root.Children.Add(RootFrame);RootVisual = root;}// Remove this handler since it is no longer neededRootFrame.Navigated -= CompleteInitializePhoneApplication;}


Now that we have our Grid at the Root, we can instantiate a RootPanelBusyService and pass it to our ViewModel to start "Getting Busy...".

// From MainPage.xaml.csprotected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e){base.OnNavigatedTo(e);if (App.Current.RootVisual is Grid){var busyServ = new RootPanelBusyService(App.Current.RootVisual as Grid);this.DataContext = new MainPageVM(busyServ);}}// In our MainPage View Model (MainPageVM.cs)/// <summary>/// A view model for the Main Page./// </summary>public class MainPageVM : ViewModelBase{/// <summary>/// The busy service to use.../// </summary>private IBusyService busyService;/// <summary>/// Gets or sets the do busy command./// </summary>/// <value>The do busy command.</value>public ICommand DoBusy { get; private set; }public MainPageVM(IBusyService busyServ){busyService = busyServ;DoBusy = new DelegateCommand&lt;object>(HandleDoBusy);}/// <summary>/// Handles the do busy command./// </summary>/// <param name="arg">Unused.private void HandleDoBusy(object arg){busyService.ShowBusy(2000, "Getting Busy...");}}


And there you have it, you've got a simple and easy to use busy service for your long running operations. One other trick that I occasionally use it to set a Busy Service static property on my App class, so all ViewModels have easy access to it. To do this, just add a static property to your App class, and assign it after setting the RootVisual, like so:

// Easy access to the busy service.public static IBusyService BusyService { get; private set; }// Rest of this class omitted for brevity....// Do not add any additional code to this methodprivate void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e){// Set the root visual to allow the application to renderif (RootVisual != RootFrame){// Add a Grid wrapper around the Root application frame.var root = new Grid();root.Children.Add(RootFrame);RootVisual = root;// Set our easy access busy service property.BusyService = new RootPanelBusyService(root);}// Remove this handler since it is no longer neededRootFrame.Navigated -= CompleteInitializePhoneApplication;}


If you don't want to download the full example project to get the RootPanelBusyService implementation, here is the source, but you'll need to create your own Loady graphic / control.

/// &lt;summary>/// An interface for busy services./// </summary>public interface IBusyService{bool IsBusyShown { get; }void ShowBusy(string message);void ShowBusy(TimeSpan timeToShow, string message);void ShowBusy(double milliSecondsToShow, string message);void HideBusy(double millisecondsToWait);void HideBusy(TimeSpan delay);void HideBusy();}/// <summary>/// A busy service that expects a Panel / Grid as the root visual./// </summary>public class RootPanelBusyService : IBusyService{private UIElement currentBusyElement;private Panel rootVisual;/// <summary>/// Initializes a new instance of the &lt;see cref="RootPanelBusyService"> class./// </see></summary>/// The root visual element.public RootPanelBusyService(Panel rootVisualElement){rootVisual = rootVisualElement;}#region IBusyService Membersprivate bool _busyShown = false;public bool IsBusyShown{get { return _busyShown; }}public void ShowBusy(double milliSecondsToShow, string message = "Loading..."){ShowBusy(TimeSpan.FromMilliseconds(milliSecondsToShow), message);}public void ShowBusy(TimeSpan timeToShow, string message = "Loading..."){ShowBusy(message);ThreadPool.QueueUserWorkItem(new WaitCallback(time =>{Thread.Sleep((TimeSpan)time);// Hide the busy after a sleep.rootVisual.Dispatcher.BeginInvoke(() => HideBusy());}), timeToShow);}public void ShowBusy(string message = "Loading..."){_busyShown = true;var busyEl = CreateBusyElement(message);busyEl.Opacity = 0;try{if (rootVisual is Panel){var root = rootVisual;root.Children.Add(busyEl);var anim = CreateAnimation(busyEl, Grid.OpacityProperty, 0.8, 0.00, 900, EasingMode.EaseOut);var story = new Storyboard();story.Children.Add(anim);story.Begin();}}catch{Debug.WriteLine("Problem showing busy.");}currentBusyElement = busyEl;}public void HideBusy(double milliseconds){HideBusy(TimeSpan.FromMilliseconds(milliseconds));}public void HideBusy(TimeSpan delay){var root = rootVisual;ThreadPool.QueueUserWorkItem(new WaitCallback((arg) =>{Thread.Sleep(delay);((Panel)arg).Dispatcher.BeginInvoke(() =>{HideBusy();});}), root);}public void HideBusy(){try{if (rootVisual is Panel){var root = rootVisual;var anim = CreateAnimation(currentBusyElement, Grid.OpacityProperty, 0.00, .80, 1000, EasingMode.EaseOut);var story = new Storyboard();story.Children.Add(anim);story.Begin();// Remove the element a second later; I do it this way because StoryCompleted is flaky.ThreadPool.QueueUserWorkItem(new WaitCallback(obj =>{Thread.Sleep(900);root.Dispatcher.BeginInvoke(() =>{root.Children.Remove(currentBusyElement);currentBusyElement = null;});}), null);                  }}catch{Debug.WriteLine("Problem hiding busy.");}_busyShown = false;}#endregion/// <summary>/// Creates the busy element; A Loady with a text block underneath./// </summary>/// The message to show below the loady.      private UIElement CreateBusyElement(string message = "Loading..."){// create a grid to hold the loady and swallow the entire screen.var root = new Grid(){Background = new SolidColorBrush(Colors.Black)};// The spinny loady thingy...root.Children.Add(new Loady(){HorizontalAlignment = HorizontalAlignment.Center,VerticalAlignment = VerticalAlignment.Center});// The text below the loady...root.Children.Add(new TextBlock(){Text = message,Margin = new Thickness(0, 150, 0, 0),FontSize = 30,HorizontalAlignment = System.Windows.HorizontalAlignment.Center,VerticalAlignment = System.Windows.VerticalAlignment.Center,Foreground = new SolidColorBrush(Colors.White)});return root;          }/// <summary>/// A helper method for creating double animations./// </summary>public DoubleAnimation CreateAnimation(DependencyObject obj, DependencyProperty prop, double value, double milliseconds, EasingMode easing = EasingMode.EaseOut){var from = Convert.ToDouble(obj.GetValue(prop));return CreateAnimation(obj, prop, value, from, milliseconds, easing);}/// <summary>/// A helper method for creating double animations./// </summary>public DoubleAnimation CreateAnimation(DependencyObject obj, DependencyProperty prop, double value, double from, double milliseconds, EasingMode easing = EasingMode.EaseOut){CubicEase ease = new CubicEase() { EasingMode = easing };DoubleAnimation animation = new DoubleAnimation{Duration = new Duration(TimeSpan.FromMilliseconds(milliseconds)),From = from,To = value,FillBehavior = FillBehavior.HoldEnd,EasingFunction = ease};Storyboard.SetTarget(animation, obj);Storyboard.SetTargetProperty(animation, new PropertyPath(prop));return animation;}}




Now Playing: The Roots - Get Busy

Permalink | Leave a comment  »


Viewing all articles
Browse latest Browse all 17

Trending Articles