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.
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.
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 method private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) { // Set the root visual to allow the application to render if (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 needed RootFrame.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.cs protected 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<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 method private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) { // Set the root visual to allow the application to render if (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 needed RootFrame.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.
/// <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 <see cref="RootPanelBusyService"> class. /// </see></summary> /// The root visual element. public RootPanelBusyService(Panel rootVisualElement) { rootVisual = rootVisualElement; } #region IBusyService Members private 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
No comments:
Post a Comment