
Introduction
This is the fifth article in the WPFSpark series. Till now I have covered four controls
in WPFSpark - SprocketControl
, ToggleSwitch
, FluidWrapPanel
, and SparkWindow
.
The previous articles in the WPFSpark series can be accessed here:
- WPFSpark: 1 of n: SprocketControl
- WPFSpark: 2 of n: ToggleSwitch
- WPFSpark: 3 of n: FluidWrapPanel
- WPFSpark: 4 of n: SparkWindow
In this article, I describe in detail the fifth control in this library - the FluidPivotPanel
control.

Inspiration
FluidPivotPanel
is inspired from the PivotControl of Windows Phone 7+.
Metro UI continues to fascinate me. It is so smooth and sleek. FluidPivotPanel
is my attempt at making a similar control for WPF.
FluidPivotPanel Demystified
The Basic Layout

The basic layout of the FluidPivotPanel
comprises of a Panel which hosts all the Pivots. Each Pivot in turn comprises of a Header and a Content. The Pivot, whose
header is active (i.e., currently selected), has its content displayed. The selected header will be the left most header, while the other headers will be arranged in a cyclic manner.
The Components
- IPivotHeader
This interface must be implemented by a control so that it can be used as a Header Item in the FluidPivotPanel
.
It defines a method SetActive
(to activate/deactivate the Header Item) and an event HeaderSelected
(which is fired when the Header is selected).
This provides freedom to the control to choose which of the user inputs would raise the HeaderSelected
events - whether by left mouse button,
right mouse button, or interaction by stylus or even through touch.
public interface IPivotHeader
{
void SetActive(bool isActive);
event EventHandler HeaderSelected;
}
IPivotContent
This interface must be implemented by a control so that it can be used as a Content Item in the FluidPivotPanel
.
It defines a method SetActive
(to activate/deactivate the Content Item).
public interface IPivotContent
{
void SetActive(bool isActive);
}
PivotHeaderControl
PivotHeaderControl
derives from ContentControl
, implements the IPivotHeader
interface, and represents the Header Item as a text.
public class PivotHeaderControl : ContentControl, IPivotHeader
{
#region Dependency Properties
...
#endregion
#region Construction / Initialization
public PivotHeaderControl()
{
IsActive = false;
this.Foreground = InactiveForeground;
this.MouseLeftButtonDown +=new MouseButtonEventHandler(OnMouseDown);
}
#endregion
#region IPivotHeader Members
public void SetActive(bool isActive)
{
IsActive = isActive;
}
public event EventHandler HeaderSelected;
#endregion
#region EventHandlers
void OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (HeaderSelected != null)
{
HeaderSelected(this, new EventArgs());
}
}
#endregion
}
PivotHeaderControl Properties
Dependency Property | Type | Description | Default Value |
ActiveForeground | Brush | Gets or sets the Foreground of the PivotHeaderControl when it is active. |
Brushes.Black |
InactiveForeground | Brush | Gets or sets the Foreground of the PivotHeaderControl when it is inactive. |
Brushes.DarkGray |
IsActive | Boolean | Gets or sets whether the PivotHeaderControl is currently active/inactive. | False |
PivotContentControl
PivotContentControl
derives from ContentControl
, implements the IPivotContent
interface, and represents the Content of the PivotItem
.
public class PivotContentControl : ContentControl, IPivotContent
{
#region Fields
Storyboard fadeInSB;
#endregion
#region DependencyProperties
...
#endregion
#region IPivotContent Members
public void SetActive(bool isActive)
{
if (isActive)
{
this.Visibility = Visibility.Visible;
if (AnimateContent)
fadeInSB.Begin();
}
else
{
this.Visibility = Visibility.Collapsed;
}
}
#endregion
}
PivotContentControl Properties
Dependency Property | Type | Description | Default Value |
AnimateContent | Boolean | Gets or sets whether the PivotContentControl should be animated when it becomes active. |
True |
PivotItem
PivotItem
is a ContentControl
which encapsulates the Pivot Header and Pivot Content to represent a single entity.
Whenever the Pivot Header for a PivotItem
is active, the corresponding Pivot Content also becomes active.
public class PivotItem : ContentControl
{
#region Fields
PivotPanel parent = null;
#endregion
#region Dependency Properties
...
#endregion
#region APIs
public void SetParent(PivotPanel panel)
{
parent = panel;
}
public void SetActive(bool isActive)
{
if (PivotHeader != null)
{
IPivotHeader header = PivotHeader as IPivotHeader;
if (header != null)
header.SetActive(isActive);
}
if (PivotContent != null)
{
IPivotContent content = PivotContent as IPivotContent;
if (content != null)
content.SetActive(isActive);
else
PivotContent.Visibility = isActive ?
Visibility.Visible : Visibility.Collapsed;
}
}
public void Initialize()
{
if (PivotHeader != null)
{
IPivotHeader header = PivotHeader as IPivotHeader;
if (header != null)
header.SetActive(false);
}
if (PivotContent != null)
{
((FrameworkElement)PivotContent).Visibility = Visibility.Collapsed;
}
}
#endregion
}
PivotItem Properties
Dependency Property | Type | Description | Default Value |
PivotHeader | FrameworkElement | Gets or sets the Pivot Header Item for the PivotItem . | null |
PivotContent | FrameworkElement | Gets or sets the Pivot Content Item for the PivotItem . | null |
PivotHeaderPanel

The PivotHeaderPanel
is responsible for hosting the PivotHeader
items. It derives from Canvas
. Whenever a PivotHeader
is selected by the user, the PivotHeaderPanel
moves the PivotHeader
to the left most positions and moves the PivotHeader
items before the selected PivotHeader
item to the end of the list, thus maintaining a cyclic order of the PivotHeader
items.
public class PivotHeaderPanel : Canvas
{
#region Constants
private const int ADD_FADE_IN_DURATION = 250;
private const int UPDATE_FADE_IN_DURATION = 50;
private const int TRANSITION_DURATION = 300;
#endregion
#region Events
public event EventHandler HeaderSelected;
#endregion
#region Fields
Storyboard addFadeInSB;
Storyboard updateFadeInSB;
List<UIElement> headerCollection = null;
Queue<Object[]> animationQueue = null;
bool isAnimationInProgress = false;
object syncObject = new object();
CubicEase easingFn = null;
#endregion
#region Construction / Initialization
public PivotHeaderPanel()
{
DoubleAnimation addFadeInAnim = new DoubleAnimation(0.0, 1.0,
new Duration(TimeSpan.FromMilliseconds(ADD_FADE_IN_DURATION)));
Storyboard.SetTargetProperty(addFadeInAnim,
new PropertyPath(UIElement.OpacityProperty));
addFadeInSB = new Storyboard();
addFadeInSB.Children.Add(addFadeInAnim);
DoubleAnimation updateFadeInAnim = new DoubleAnimation(0.0, 1.0,
new Duration(TimeSpan.FromMilliseconds(UPDATE_FADE_IN_DURATION)));
Storyboard.SetTargetProperty(updateFadeInAnim,
new PropertyPath(UIElement.OpacityProperty));
updateFadeInSB = new Storyboard();
updateFadeInSB.Children.Add(updateFadeInAnim);
updateFadeInSB.Completed += new EventHandler(OnAnimationCompleted);
headerCollection = new List<UIElement>();
easingFn = new CubicEase();
easingFn.EasingMode = EasingMode.EaseOut;
}
#endregion
#region APIs
public void AddChild(UIElement child)
{
if (child == null)
return;
lock (syncObject)
{
Dispatcher.BeginInvoke(new Action(() =>
{
child.Opacity = 0;
child.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
if ((Children.Count == 0) ||
(Children[Children.Count - 1] == headerCollection.Last()))
{
child.RenderTransform = CreateTransform(child);
Children.Add(child);
headerCollection.Add(child);
addFadeInSB.Begin((FrameworkElement)child);
}
else
{
var lastChild = Children[Children.Count - 1];
Children.Add(child);
int index = headerCollection.IndexOf(lastChild) + 1;
if (index >= 1)
{
double newLocationX = ((TranslateTransform)(
((TransformGroup)headerCollection[index].RenderTransform).Children[0])).X;
headerCollection.Insert(index, child);
child.RenderTransform = CreateTransform(new Point(newLocationX, 0.0));
InsertChild(child, index + 1);
}
}
IPivotHeader headerItem = child as IPivotHeader;
if (headerItem != null)
{
headerItem.HeaderSelected += new EventHandler(OnHeaderSelected);
}
}));
}
}
public bool Contains(UIElement child)
{
return Children.Contains(child);
}
public void MoveForward(int count)
{
if ((isAnimationInProgress) || (count <= 0) ||
(count >= headerCollection.Count))
return;
else
isAnimationInProgress = true;
Dispatcher.BeginInvoke(new Action(() =>
{
animationQueue = new Queue<Object[]>();
lock (animationQueue)
{
for (int i = 0; i < count; i++)
{
animationQueue.Enqueue(new object[] { headerCollection[i], true });
}
}
double distanceToMove = ((TranslateTransform)((
(TransformGroup)headerCollection[count].RenderTransform).Children[0])).X;
foreach (UIElement child in headerCollection)
{
double oldTranslationX = ((TranslateTransform)(
((TransformGroup)child.RenderTransform).Children[0])).X;
double newTranslationX = oldTranslationX - distanceToMove;
Storyboard transition = CreateTransition(child,
new Point(newTranslationX, 0.0),
TimeSpan.FromMilliseconds(TRANSITION_DURATION), easingFn);
if (child == headerCollection.Last())
{
transition.Completed += (s, e) =>
{
ProcessAnimationQueue();
};
}
transition.Begin();
}
}));
}
public void MoveBack(int count)
{
if (isAnimationInProgress)
return;
else
isAnimationInProgress = true;
Dispatcher.BeginInvoke(new Action(() =>
{
if ((count <= 0) || (count >= headerCollection.Count))
return;
animationQueue = new Queue<Object[]>();
lock (animationQueue)
{
for (int i = headerCollection.Count - 1; i >= headerCollection.Count - count; i--)
{
animationQueue.Enqueue(new object[] { headerCollection[i], false });
}
}
double distanceToMove =
((TranslateTransform)(((TransformGroup)
headerCollection[headerCollection.Count - 1].RenderTransform).Children[0])).X -
((TranslateTransform)(((TransformGroup)
headerCollection[headerCollection.Count - count].RenderTransform).Children[0])).X +
headerCollection[headerCollection.Count - 1].DesiredSize.Width;
foreach (UIElement child in headerCollection)
{
double oldTranslationX = ((TranslateTransform)(
((TransformGroup)child.RenderTransform).Children[0])).X;
double newTranslationX = oldTranslationX + distanceToMove;
Storyboard transition = CreateTransition(child,
new Point(newTranslationX, 0.0),
TimeSpan.FromMilliseconds(TRANSITION_DURATION), null);
if (child == headerCollection.Last())
{
transition.Completed += (s, e) =>
{
ProcessAnimationQueue();
};
}
transition.Begin();
}
}));
}
public void ClearHeader()
{
foreach (UIElement item in headerCollection)
{
IPivotHeader headerItem = item as IPivotHeader;
if (headerItem != null)
{
headerItem.HeaderSelected -= OnHeaderSelected;
}
}
headerCollection.Clear();
Children.Clear();
}
internal void Reset()
{
if (Children.Count > 0)
{
OnHeaderSelected(Children[0], null);
}
}
#endregion
#region Helpers
private void InsertChild(UIElement child, int index)
{
for (int i = index; i < headerCollection.Count; i++)
{
double oldTranslationX = ((TranslateTransform)(
((TransformGroup)headerCollection[i].RenderTransform).Children[0])).X;
double newTranslationX = oldTranslationX + child.DesiredSize.Width;
headerCollection[i].RenderTransform = CreateTransform(new Point(newTranslationX, 0.0));
}
addFadeInSB.Begin((FrameworkElement)child);
}
private void AppendChild(UIElement child, bool isDirectionForward)
{
Dispatcher.BeginInvoke(new Action(() =>
{
child.Opacity = 0;
child.RenderTransform = CreateTransform(child, isDirectionForward);
headerCollection.Remove(child);
if (isDirectionForward)
headerCollection.Add(child);
else
headerCollection.Insert(0, child);
updateFadeInSB.Begin((FrameworkElement)child);
}));
}
private void OnAnimationCompleted(object sender, EventArgs e)
{
lock (animationQueue)
{
if (animationQueue.Count > 0)
animationQueue.Dequeue();
}
ProcessAnimationQueue();
}
private void ProcessAnimationQueue()
{
lock (animationQueue)
{
if (animationQueue.Count > 0)
{
Object[] next = animationQueue.Peek();
UIElement child = (UIElement)next[0];
bool isDirectionForward = (bool)next[1];
AppendChild(child, isDirectionForward);
}
else
{
isAnimationInProgress = false;
}
}
}
private double GetFirstChildPosition()
{
double transX = 0.0;
UIElement firstChild = headerCollection.FirstOrDefault();
if (firstChild != null)
transX = ((TranslateTransform)(((TransformGroup)
firstChild.RenderTransform).Children[0])).X;
return transX;
}
private double GetNextAvailablePosition()
{
double transX = 0.0;
UIElement lastChild = headerCollection.LastOrDefault();
if (lastChild != null)
transX = ((TranslateTransform)(((TransformGroup)
lastChild.RenderTransform).Children[0])).X + lastChild.DesiredSize.Width;
return transX;
}
private TransformGroup CreateTransform(UIElement child, bool isDirectionForward = true)
{
if (child == null)
return null;
double transX = 0.0;
if (isDirectionForward)
transX = GetNextAvailablePosition();
else
transX = GetFirstChildPosition() - child.DesiredSize.Width;
TranslateTransform translation = new TranslateTransform();
translation.X = transX;
translation.Y = 0.0;
TransformGroup transform = new TransformGroup();
transform.Children.Add(translation);
return transform;
}
private TransformGroup CreateTransform(Point translation)
{
TranslateTransform translateTransform = new TranslateTransform();
translateTransform.X = translation.X;
translateTransform.Y = translation.Y;
TransformGroup transform = new TransformGroup();
transform.Children.Add(translateTransform);
return transform;
}
private Storyboard CreateTransition(UIElement element,
Point translation, TimeSpan period, EasingFunctionBase easing)
{
Duration duration = new Duration(period);
DoubleAnimation translateAnimationX = new DoubleAnimation();
translateAnimationX.To = translation.X;
translateAnimationX.Duration = duration;
if (easing != null)
translateAnimationX.EasingFunction = easing;
Storyboard.SetTarget(translateAnimationX, element);
Storyboard.SetTargetProperty(translateAnimationX,
new PropertyPath("(UIElement.RenderTransform).(
TransformGroup.Children)[0].(TranslateTransform.X)"));
Storyboard sb = new Storyboard();
sb.Children.Add(translateAnimationX);
return sb;
}
#endregion
#region EventHandlers
void OnHeaderSelected(object sender, EventArgs e)
{
if ((isAnimationInProgress) || (headerCollection == null) ||
(headerCollection.Count == 0))
return;
UIElement child = sender as UIElement;
if (child != null)
{
int index = headerCollection.IndexOf(child);
if (index > 0)
{
MoveForward(index);
if (HeaderSelected != null)
HeaderSelected(child, new EventArgs());
}
}
}
#endregion
}
PivotPanel
PivotPanel
is the outermost panel of the FluidPivotPanel
which hosts the PivotHeaderPanel
and the PivotContent
items.
It derives from Canvas
and contains a Grid
which has two rows. The top row has a height defined by the dependency property
HeaderHeight
and hosts the PivotHeaderPanel
. The bottom row takes up the remaining space and hosts the PivotContent
items.
PivotPanel
also implements the INotifiableParent
interface in order to be notified when children are added to it via XAML.
In order to understand the role of the INotifiableParent
interface in detail, check out my blog article
Get notified when a child is added to a custom panel via XAML.
[ContentProperty("NotifiableChildren")]
public class PivotPanel : Canvas, INotifiableParent
{
#region Fields
private Grid rootGrid;
private PivotHeaderPanel headerPanel;
private List<PivotItem> pivotItems = null;
private PivotItem currPivotItem = null;
NotifiableUIElementCollection notifiableChildren = null;
#endregion
#region Dependency Properties
...
#endregion
#region Properties
public NotifiableUIElementCollection NotifiableChildren
{
get
{
return notifiableChildren;
}
}
#endregion
#region Construction / Initialization
public PivotPanel()
{
notifiableChildren = new NotifiableUIElementCollection(this, this);
rootGrid = new Grid();
RowDefinition rd = new RowDefinition();
rd.Height = HeaderHeight;
rootGrid.RowDefinitions.Add(rd);
rd = new RowDefinition();
rd.Height = new GridLength(1, GridUnitType.Star);
rootGrid.RowDefinitions.Add(rd);
Binding backgroundBinding = new Binding();
backgroundBinding.Source = this.Background;
rootGrid.SetBinding(Grid.BackgroundProperty, backgroundBinding);
rootGrid.Width = this.ActualWidth;
rootGrid.Height = this.ActualHeight;
rootGrid.HorizontalAlignment = HorizontalAlignment.Stretch;
rootGrid.VerticalAlignment = VerticalAlignment.Stretch;
headerPanel = new PivotHeaderPanel();
headerPanel.HorizontalAlignment = HorizontalAlignment.Stretch;
headerPanel.VerticalAlignment = VerticalAlignment.Stretch;
headerPanel.HeaderSelected += new EventHandler(OnHeaderSelected);
rootGrid.Children.Add(headerPanel);
this.Children.Add(rootGrid);
pivotItems = new List<PivotItem>();
this.SizeChanged += (s, e) =>
{
if (rootGrid != null)
{
rootGrid.Width = this.ActualWidth;
rootGrid.Height = this.ActualHeight;
}
};
}
#endregion
#region APIs
public int AddChild(PivotItem item)
{
if (pivotItems == null)
pivotItems = new List<PivotItem>();
pivotItems.Add(item);
item.SetParent(this);
if (item.PivotHeader != null)
headerPanel.AddChild(item.PivotHeader as UIElement);
if (item.PivotContent != null)
{
Grid.SetRow(item.PivotContent as UIElement, 1);
item.Initialize();
rootGrid.Children.Add(item.PivotContent as UIElement);
}
return pivotItems.Count - 1;
}
internal void UpdatePivotItemHeader(PivotItem item)
{
if ((pivotItems.Contains(item)) && (item.PivotHeader != null)
&& (!headerPanel.Contains((UIElement)item.PivotHeader)))
{
headerPanel.AddChild(item.PivotHeader as UIElement);
ActivateFirstPivotItem();
}
}
internal void UpdatePivotItemContent(PivotItem item)
{
if ((pivotItems.Contains(item)) && (item.PivotContent != null)
&& (!rootGrid.Children.Contains((UIElement)item.PivotContent)))
{
Grid.SetRow(item.PivotContent as UIElement, 1);
rootGrid.Children.Add(item.PivotContent as UIElement);
ActivateFirstPivotItem();
}
}
public void AddItems(List<PivotItem> items)
{
if (items == null)
return;
foreach (PivotItem item in items)
{
AddChild(item);
}
ActivateFirstPivotItem();
}
public void SetDataContext(object context)
{
if ((pivotItems == null) || (pivotItems.Count == 0))
return;
foreach (PivotItem item in pivotItems)
{
item.PivotContent.DataContext = context;
}
}
public void Reset()
{
if (headerPanel != null)
headerPanel.Reset();
}
#endregion
#region Event Handlers
void OnHeaderSelected(object sender, EventArgs e)
{
FrameworkElement headerItem = sender as FrameworkElement;
if (headerItem == null)
return;
PivotItem pItem = pivotItems.Where(p =>
p.PivotHeader == headerItem).FirstOrDefault();
if ((pItem != null) && (pItem != currPivotItem))
{
if (currPivotItem != null)
{
currPivotItem.SetActive(false);
}
pItem.SetActive(true);
currPivotItem = pItem;
}
}
#endregion
#region Helpers
private void ActivateFirstPivotItem()
{
if ((pivotItems != null) && (pivotItems.Count > 0))
{
pivotItems.First().SetActive(true);
currPivotItem = pivotItems.First();
}
}
void ClearItemsSource()
{
if ((pivotItems == null) || (pivotItems.Count == 0))
return;
if (headerPanel != null)
headerPanel.ClearHeader();
if (rootGrid != null)
{
foreach (PivotItem item in pivotItems)
{
rootGrid.Children.Remove(item.PivotContent);
}
}
pivotItems.Clear();
}
#endregion
#region INotifiableParent Members
public int AddChild(UIElement child)
{
PivotItem pItem = child as PivotItem;
if (pItem != null)
{
return AddChild(pItem);
}
return -1;
}
#endregion
}
PivotPanel Properties
Dependency Property | Type | Description | Default Value |
ContentBackground | Brush | Gets or sets the Background of the area of PivotPanel
which hosts the PivotContent items. | null |
HeaderBackground | Brush | Gets or sets the Background of the area of PivotPanel
which hosts the PivotHeaderPanel . | null |
HeaderHeight | GridLength | Gets or sets the Height of the first row of the grid
in the PivotPanel which hosts the PivotHeaderPanel . | 0.1* |
ItemsSource | ObservableCollection<PivotItem> | Bindable property for specifying
an ObservableCollection of PivotItem s. | null |
FluidPivotPanel - Relating the Components
Here is the rough class diagram which explains the relationship between the above components.

Using FluidPivotPanel in your Application Through XAML
Through XAML, you first need to add PivotPanel
to your layout. Within the PivotPanel
, you can declare the PivotItem
s.
Within each PivotItem
, you have to set the PivotHeader
and PivotContent
properties. If you are adding a PivotContentControl
as the PivotContent
of the PivotItem
and you do not want it to be animated when it is active, you can set the AnimateContent
property of PivotContentControl
to false
.
xmlns:wpfspark="clr-namespace:WPFSpark;assembly=WPFSpark"
...
<wpfspark:PivotPanel x:Name="RootPanel"
HeaderHeight="70"
HorizontalAlignment="Stretch">
<wpfspark:PivotItem>
<wpfspark:PivotItem.PivotHeader>
<wpfspark:PivotHeaderControl FontFamily="Segoe WP"
FontSize="28"
ActiveForeground="White"
InactiveForeground="#444444">Item One</wpfspark:PivotHeaderControl>
</wpfspark:PivotItem.PivotHeader>
<wpfspark:PivotItem.PivotContent>
<wpfspark:PivotContentControl>
<Border Background="LightBlue"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock FontFamily="Segoe WP"
FontSize="48"
Foreground="White"
Text="Item One Content"></TextBlock>
</Border>
</wpfspark:PivotContentControl>
</wpfspark:PivotItem.PivotContent>
</wpfspark:PivotItem>
<wpfspark:PivotItem>
<wpfspark:PivotItem.PivotHeader>
<wpfspark:PivotHeaderControl FontFamily="Segoe WP"
FontSize="28"
ActiveForeground="White"
InactiveForeground="#444444">Item Two</wpfspark:PivotHeaderControl>
</wpfspark:PivotItem.PivotHeader>
<wpfspark:PivotItem.PivotContent>
<wpfspark:PivotContentControl>
<Border Background="LightGoldenrodYellow"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock FontFamily="Segoe WP"
FontSize="48"
Foreground="Black"
Text="Item Two Content"></TextBlock>
</Border>
</wpfspark:PivotContentControl>
</wpfspark:PivotItem.PivotContent>
</wpfspark:PivotItem>
<wpfspark:PivotItem>
<wpfspark:PivotItem.PivotHeader>
<wpfspark:PivotHeaderControl FontFamily="Segoe WP"
FontSize="28"
ActiveForeground="White"
InactiveForeground="#444444">Item Three</wpfspark:PivotHeaderControl>
</wpfspark:PivotItem.PivotHeader>
<wpfspark:PivotItem.PivotContent>
<wpfspark:PivotContentControl>
<Border Background="LightSeaGreen"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock FontFamily="Segoe WP"
FontSize="48"
Foreground="Black"
Text="Item Three Content"></TextBlock>
</Border>
</wpfspark:PivotContentControl>
</wpfspark:PivotItem.PivotContent>
</wpfspark:PivotItem>
</wpfspark:PivotPanel>
You can also create a common style for the PivotHeaderControl
to avoid the repetition of the declaration of their common properties
like FontSize
, FontFamily
, ActiveForeground
, InactiveForeground
, etc. Here is an example of how you can achieve that:
<Window x:Class="TestWPFSpark.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfspark="clr-namespace:WPFSpark;assembly=WPFSpark"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<Style x:Key="PivotHeaderStyle"
TargetType="wpfspark:PivotHeaderControl">
<Setter Property="FontFamily"
Value="Courier New"></Setter>
<Setter Property="FontFamily"
Value="Courier New"></Setter>
<Setter Property="FontSize"
Value="32"></Setter>
<Setter Property="ActiveForeground"
Value="LawnGreen"></Setter>
<Setter Property="InactiveForeground"
Value="#444444"></Setter>
<Setter Property="Margin"
Value="20,10"></Setter>
</Style>
</Window.Resources>
<Grid Background="Black">
<wpfspark:PivotPanel x:Name="RootPivotPanel"
HeaderHeight="70"
HorizontalAlignment="Stretch"
HeaderBackground="Black">
<wpfspark:PivotItem>
<wpfspark:PivotItem.PivotHeader>
<wpfspark:PivotHeaderControl
Style="{StaticResource PivotHeaderStyle}">Item One
</wpfspark:PivotHeaderControl>
</wpfspark:PivotItem.PivotHeader>
<wpfspark:PivotItem.PivotContent>
<wpfspark:PivotContentControl>
<Border Background="LightBlue"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock FontFamily="Segoe WP"
FontWeight="Light"
FontSize="48"
Foreground="White"
Text="Item One Content"></TextBlock>
</Border>
</wpfspark:PivotContentControl>
</wpfspark:PivotItem.PivotContent>
</wpfspark:PivotItem>
<wpfspark:PivotItem>
<wpfspark:PivotItem.PivotHeader>
<wpfspark:PivotHeaderControl
Style="{StaticResource PivotHeaderStyle}">Item Two
</wpfspark:PivotHeaderControl>
</wpfspark:PivotItem.PivotHeader>
<wpfspark:PivotItem.PivotContent>
<wpfspark:PivotContentControl>
<Border Background="LightGoldenrodYellow"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock FontFamily="Segoe WP"
FontWeight="Light"
FontSize="48"
Foreground="Black"
Text="Item Two Content"></TextBlock>
</Border>
</wpfspark:PivotContentControl>
</wpfspark:PivotItem.PivotContent>
</wpfspark:PivotItem>
<wpfspark:PivotItem>
<wpfspark:PivotItem.PivotHeader>
<wpfspark:PivotHeaderControl
Style="{StaticResource PivotHeaderStyle}">Item Three
</wpfspark:PivotHeaderControl>
</wpfspark:PivotItem.PivotHeader>
<wpfspark:PivotItem.PivotContent>
<wpfspark:PivotContentControl>
<Border Background="LightSeaGreen"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock FontFamily="Segoe WP"
FontWeight="Light"
FontSize="48"
Foreground="Black"
Text="Item Three Content"></TextBlock>
</Border>
</wpfspark:PivotContentControl>
</wpfspark:PivotItem.PivotContent>
</wpfspark:PivotItem>
</wpfspark:PivotPanel>
</Grid>
</Window
Using FluidPivotPanel in your Application Through Code
Through code, you have to declare an ObservableCollection
of PivotItem
s and add PivotItem
s to it. Once done, you can set
the ItemsSource
property of the PivotPanel
to this ObservableCollection
.
ObservableCollection<PivotItem> items = new ObservableCollection<PivotItem>();
for (int i = 0; i < colors.Count(); i++)
{
PivotHeaderControl tb = new PivotHeaderControl();
tb.FontFamily = new FontFamily("Segoe WP");
tb.FontWeight = FontWeights.Light;
tb.ActiveForeground = Brushes.White;
tb.InactiveForeground = new SolidColorBrush(Color.FromRgb(48,48,48));
tb.FontSize = 64;
tb.Content = colors[i];
tb.Margin = new Thickness(20, 0, 0, 0);
PivotContentControl pci = new PivotContentControl();
ListBox lb = new ListBox() { FontFamily = new FontFamily("Segoe WP"),
FontSize = 32,
FontWeight = FontWeights.Light,
Foreground = Brushes.Gray,
Background = Brushes.Black,
BorderThickness = new Thickness(0),
};
lb.ItemTemplate = (DataTemplate)this.Resources["ListBoxItemTemplate"];
lb.ItemsSource = data[i];
ScrollViewer.SetHorizontalScrollBarVisibility(lb, ScrollBarVisibility.Disabled);
lb.HorizontalAlignment = HorizontalAlignment.Stretch;
lb.VerticalAlignment = VerticalAlignment.Stretch;
lb.Margin = new Thickness(30,10,10,10);
pci.Content = lb;
PivotItem pi = new PivotItem { PivotHeader = tb, PivotContent = pci };
items.Add(pi);
}
RootPivotPanel.ItemsSource = items;
History
- December 21, 2011: WPFSpark v1.0 released.