Global Progress Indicator

Posted on Февраль 22, 2012

0


Hello,

Today we will start with creating a global progress indicator.

Many people are doing it in totally different ways: they are creating a content control on the page and add style with progress bar, other are creating some super-code to perform this simple task. With mango release, developers achieved a great opportunity — the have an access to system tray. This was the first ring, that helped me to decide to create a totally global progress bar that will be displayed by means of the system. When you think about this task, it is becoming quite obvious how to do this thing, but let’s start from the beginning:

First of all, System Tray

Yeah, we have it in Microsoft.Phone.Shell namespace. It has some properties that we are interested in:

  • IsVisible
  • Opacity
  • ProgressIndicator
For your note: Opacity set to zero leaves icons of system tray visible but background is not visible.
What we need here is a Progress Indicator. As you might thought it has some more properties that we need:
  • IsIndterminate
  • IsVisible
  • Text

They will help us to leave user connected to us 🙂

This example uses MVVM to develop application. See MVVM Light toolkit.

We start with a default Project, so in solution explorer we will see:

You have a View-Model and View Model Locator that contains current view-model. We will use Main VM as common view-model for our application. So this VM will help you to have same data all over the application. You will see a bit later what we need this for.

In ViewModel we need to add Property that will hold our count of requests. So when you send a request — you increase counter, and on CallBack — decrease. And we will use public property that will indicate is there any request running now and return true or false.

So, here you are, the content of MainViewModel:

private int requestCounter;

/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
	if (this.IsInDesignMode)
	{
		// Code runs in Blend --> create design time data.
	}
	else
	{
		this.IncreaseCommand = new RelayCommand<bool>(this.CounterOperations);
		this.DecreaseCommand = new RelayCommand<bool>(this.CounterOperations);
	}
}

public string ApplicationTitle
{
	get { return "Global Progress Bar"; }
}

public string PageName
{
	get { return "page 1"; }
}

public ICommand IncreaseCommand { get; private set; }
public ICommand DecreaseCommand { get; private set; }

/// <summary>
/// Gets or sets the request counter value that indicates count of the requests.
/// </summary>
private int RequestCounter
{
	get { return this.requestCounter; }
	set
	{
		this.requestCounter = value;
		if (this.requestCounter < 0)
		{
			throw new ArgumentOutOfRangeException("RequestCounter");
		}
		this.RaisePropertyChanged("IsBusy");
	}
}

/// <summary>
/// Gets a value indicating whether there is a request in progress.
/// </summary>
public bool IsBusy
{
	get { return this.RequestCounter > 0; }
}

private void CounterOperations(bool increaseCounter)
{
	if (increaseCounter)
	{
		this.RequestCounter++;
	}
	else
	{
		this.RequestCounter--;
	}
}

Pretty easy, yeah?

As we know, every page has own Progress Indicator. So if you have 10-20 pages, you will need to access it for each page… This is really annoying and bad, so i decided to create a base page. All pages are inherited from PhoneApplicationPage. If we create a page that will be inherited from PhoneApplicationPage, we could use it for all other pages in application. So we need to add class named BasePage:

public class BasePage : PhoneApplicationPage

We will need a binding and View Model here. So, we will set SystemTray visible and will use loaded event in order to bind to property.

public class BasePage : PhoneApplicationPage
{
	/// <summary>
	/// Binding for system tray progress indicator
	/// </summary>
	private readonly Binding isIndeterminateBinding;

	/// <summary>
	/// Common view model instance
	/// </summary>
	private readonly MainViewModel mainViewModel;

	/// <summary>
	/// Initializes a new instance of the <see cref="BasePage"/> class.
	/// </summary>
	public BasePage()
	{
		this.mainViewModel = ViewModelLocator.MainStatic;
		this.SetValue(SystemTray.OpacityProperty, 1d);
		this.isIndeterminateBinding = new Binding("IsBusy") {Source = this.mainViewModel};
		this.Loaded += this.BasePageLoaded;
	}

	/// <summary>
	/// Bases the page loaded.
	/// </summary>
	/// <param name="sender">The sender (page).</param>
	/// <param name="e">The Routed Event Args<see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
	private void BasePageLoaded(object sender, RoutedEventArgs e)
	{
		ProgressIndicator progressIndicator = SystemTray.ProgressIndicator;
		if (progressIndicator == null)
		{
			progressIndicator = new ProgressIndicator();
			BindingOperations.SetBinding(progressIndicator, ProgressIndicator.IsVisibleProperty, this.isIndeterminateBinding);
			progressIndicator.SetValue(ProgressIndicator.IsIndeterminateProperty, true);
			progressIndicator.SetValue(ProgressIndicator.TextProperty, "Loading...");
			SystemTray.SetProgressIndicator(this, progressIndicator);
		}
	}
}

This code just binded visibility of indicator to IsBusy property of View Model. IsBusy is indicating is there a request running. So every request will increase counter and IsBusy would be raised. This will lead to binding update and indicator will show 🙂

In order to demonstrate work of all this, i created two pages: First Page and Second Page 🙂

Content of MainPage:

<Helpers:BasePage x:Class="GlobalProgressBar.MainPage"
                  xmlns:Helpers="clr-namespace:GlobalProgressBar.Helpers"
				  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                  xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
                  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                  xmlns:System="clr-namespace:System;assembly=mscorlib"
                  FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}"
                  Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="Portrait" Orientation="Portrait"
                  mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768" shell:SystemTray.IsVisible="True"
                  DataContext="{Binding Main, Source={StaticResource Locator}}">

	<!--LayoutRoot contains the root grid where all other page content is placed-->
	<Grid x:Name="LayoutRoot" Background="Transparent">
		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition Height="*" />
		</Grid.RowDefinitions>

		<!--TitlePanel contains the name of the application and page title-->
		<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="24,24,0,12">
			<TextBlock x:Name="ApplicationTitle" Text="{Binding ApplicationTitle}" Style="{StaticResource PhoneTextNormalStyle}" />
			<TextBlock x:Name="PageTitle" Text="{Binding PageName}" Margin="-3,-8,0,0"
			           Style="{StaticResource PhoneTextTitle1Style}" />
		</StackPanel>

		<!--ContentPanel - place additional content here-->
		<Grid x:Name="ContentGrid" Grid.Row="1">

			<StackPanel>
				<Button Content="Increase request count" Command="{Binding IncreaseCommand}">
					<Button.CommandParameter>
						<System:Boolean>True</System:Boolean>
					</Button.CommandParameter>
				</Button>
				<Button Content="Decrease request count" Command="{Binding DecreaseCommand}">
					<Button.CommandParameter>
						<System:Boolean>False</System:Boolean>
					</Button.CommandParameter>
				</Button>

				<HyperlinkButton Content="PAGE 2" NavigateUri="/Page2.xaml"></HyperlinkButton>
			</StackPanel>
		</Grid>
	</Grid>

</Helpers:BasePage>

And it’s codebehind:

namespace GlobalProgressBar
{
	public partial class MainPage 
	{
		public MainPage()
		{
			this.InitializeComponent();
		}
	}
}

As we can see, it is easy to use Base Page. All pages, that you want to have indicator, should be BasePage. Now two screenshots, video, and code. Comments are appreciated!

And small video:

Download all code from here.

Develop cool apps 🙂

Реклама