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.
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
{
ListDisplay,
IndexNotFound,
IndexFound
}
Based on its state, the look and feel of the IndexControl
changes.
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
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
.
JumpListPanel
The JumpListPanel
consists of a WrapPanel
in which the IndexControls
are added.
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
s 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();
valueList.Sort();
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);
}
JumpListPanel.Children.OfType<IndexControl>()
.Where(i => valueDict.Keys.Contains(i.IndexValue))
.All(i =>
{
i.IndexState = IndexStateType.IndexFound;
return true;
});
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 string
s as input. It can be further modified 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
- 22nd May, 2010 - Version 1.0 released