Introduction

I am deeply impressed by Metro UI, the typography based design created by Microsoft. It is very clean and fast. In this article, I am demonstrating a simple example of how a WP7 JumpList can be created in WPF. The WP7 JumpList was demonstrated in the MIX '11 event.

WP7 JumpList Image

Using the code

The WPF JumpList control is composed of two controls-

  • IndexControl - used to display the indices.
  • JumpListControl - encapsulates the contents of the JumpList.

IndexControl

The IndexControl is used to display the index in the ValuesPanel as well as the JumpListPanel. It has a dependency property IndexValue which is used to display the index value in the IndexControl.

public static readonly DependencyProperty IndexValueProperty =
	DependencyProperty.Register("IndexValue", typeof(string), typeof(IndexControl),
		new FrameworkPropertyMetadata((new PropertyChangedCallback(OnIndexValueChanged))));

The IndexControl also has a dependency property IndexState to define its state.

public static readonly DependencyProperty IndexStateProperty =
	DependencyProperty.Register("IndexState", typeof(IndexStateType), typeof(IndexControl),
		new FrameworkPropertyMetadata(IndexStateType.ListDisplay, (new PropertyChangedCallback(OnIndexStateChanged))));

There are three states, defined by the enum IndexStateType.

public enum IndexStateType
{
    // The state when the IndexControl is displayed in the long list.
    ListDisplay,
    // The state when the IndexControl is displayed in the JumpList and this index has NO values
    IndexNotFound,
    // The state when the IndexControl is displayed in the JumpList and this index has values
    IndexFound
}

Based on its state the look and feel of the IndexControl changes.

IndexState Types

JumpListControl

The WPF JumpList control has two panels -

  • ValuesPanel - used to display the values (names) in a long list format.
  • JumpListPanel - used to display the indices.

At any given instance, only one of the above panels is visible in the JumpListControl, while the other panel remains hidden.

ValuesPanel

WPF JumpList Image

The ValuesPanel displays the values (or names) in a long list format. For each index, the IndexControl and the set of Names are encapsulated in a StackPanel and added to the parent StackPanel.

Values Panel Image

JumpListPanel

WPF JumpList Image

The JumpListPanel consists of a WrapPanel in which the IndexControls are added.

JumpList Panel Image

The JumpListPanel defines two dependency properties -

  • Values - ObservableCollection<string> - serves as an input for the control.
  • public static readonly DependencyProperty ValuesProperty =
    	DependencyProperty.Register("Values", typeof(ObservableCollection<string>), typeof(JumpListControl),
    		new FrameworkPropertyMetadata((new PropertyChangedCallback(OnValuesChanged))));
    
  • SelectedValue - indicates the value (or Name) selected by the user.
  • public static readonly DependencyProperty SelectedValueProperty =
    	DependencyProperty.Register("SelectedValue", typeof(string), typeof(JumpListControl),
    		new FrameworkPropertyMetadata((new PropertyChangedCallback(OnSelectedValueChanged))));
    

When the JumpListControl is initialized, it adds IndexControls, to the JumpListPanel, for the indices a-z. The state of each of the IndexControl is set to IndexNotFound.

public JumpListControl()
{
	InitializeComponent();

	JumpListScrollView.Visibility = System.Windows.Visibility.Hidden;

	foreach (char idx in indexes)
	{
		IndexControl idxCtrl = new IndexControl
		{
			Width = 50,
			Height = 50,
			IndexValue = idx.ToString(),
			IndexState = IndexStateType.IndexNotFound,
			Margin = new Thickness(4, 4, 0, 0)
		};

		idxCtrl.MouseLeftButtonDown += new MouseButtonEventHandler(OnIndexClickedInJumpList);

		JumpListPanel.Children.Add(idxCtrl);
	}
}

When the Values dependency property is set by the user, the JumpListControl parses the list and creates the indexed long list in the ValuesPanel and changes the state of the IndexControls, in the JumpListPanel, from IndexNotFound to IndexFound.

private void Parse(ObservableCollection<string> values)
{
	Dictionary<string, List<string>> valueDict = new Dictionary<string, List<string>>();

	List<string> valueList = values.ToList();

	// Sort the values
	valueList.Sort();

	// Get the distinct indexes
	foreach (string str in valueList)
	{
		string key = Char.ToLower(str[0]).ToString();
		if (!valueDict.ContainsKey(key))
		{
			valueDict[key] = new List<string>();
		}
		valueDict[key].Add(str);
	}

	// Set the IndexState of all the IndexControls whose index has been found as IndexFound
	JumpListPanel.Children.OfType<IndexControl>()
						  .Where(i => valueDict.Keys.Contains(i.IndexValue))
						  .All(i =>
						  {
							  i.IndexState = IndexStateType.IndexFound;
							  return true;
						  });


	// Add the index and the related names to the Values Panel
	foreach (string key in valueDict.Keys)
	{
		StackPanel stkPanel = new StackPanel { HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch };

		IndexControl idxCtrl = new IndexControl
		{
			Width = 50,
			Height = 50,
			IndexValue = key,
			IndexState = IndexStateType.ListDisplay,
			Margin = new Thickness(4),
			HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
			VerticalAlignment = System.Windows.VerticalAlignment.Center
		};

		idxCtrl.MouseLeftButtonDown += new MouseButtonEventHandler(OnIndexClickedInValuesPanel);

		stkPanel.Children.Add(idxCtrl);

		foreach (string str in valueDict[key])
		{
			TextBlock tb = new TextBlock
			{
				FontFamily = font,
				FontWeight = FontWeights.Light,
				FontSize = 22,
				Foreground = Brushes.White,
				TextAlignment = TextAlignment.Left,
				Margin = new Thickness(4, 4, 0, 0),
				Text = str,
				HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
				VerticalAlignment = System.Windows.VerticalAlignment.Center
			};

			tb.MouseLeftButtonDown += new MouseButtonEventHandler(OnValueSelected);

			stkPanel.Children.Add(tb);
		}

		ValuesPanel.Children.Add(stkPanel);
	}
}

When the ValuesPanel is being displayed and the user clicks on any of the indices, then the ValuesPanel is hidden and the JumpListPanel is displayed. When the user clicks on any of the indices in the JumpListPanel, the JumpListPanel is hidden, the ValuesPanel is displayed and the ValuePanel scrolls to the index selected by the user.

Clicking on any of the Names will set the SelectedValue property of the JumpListControl.

EndPoint

What I have attempted here is a basic implementation of the JumpList control which accepts a list of strings as input. It can be further modifield to accept a collection of object's (say a person's record) and an image can be displayed alongside each name in the ValuesPanel.

Points of Interest

An important lesson I learnt during the development of this control is that when you are adding items to a WrapPanel (say with Orientation=Horizontal) and the items extend beyond the height of the WrapPanel, then the vertical scrollbar will not appear automatically (even though you set ScrollViewer.VerticalScrollBarVisibility="Auto").

To overcome this problem, you need to wrap your WrapPanel with a ScrollViewer.

<ScrollViewer Name="JumpListScrollView"
			  HorizontalScrollBarVisibility="Disabled"
			  VerticalScrollBarVisibility="Auto">
	<WrapPanel Name="JumpListPanel"
			   Orientation="Horizontal">
	</WrapPanel>
</ScrollViewer>

History

23rd May, 2010 - Version 1.0 released.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"