WinRT XAML Calendar
Today, I want to share with you my last developed control : a WinRT Calendar Control
This control is very simple. It’s based on 3 ListBox. There is no infinite scroll because a simple ListBox doesn’t support such Behavior.
You can grab the code on codeplex : https://xamlwinrtcalendar.codeplex.com/
This is a short video on how this calendar works in a simple Windows 8 application :
Some particular points of interests :
VisualStates
By design the Visual States and Transition from the Listbox control didn’t work correctly with the control, I had to make my own transitions and disable standards transitions.
There is three States : Selected, UnSelected and Transparent:
[TemplateVisualState(Name = "PickerItemSelected", GroupName = "Picker")]
[TemplateVisualState(Name = "PickerItemUnSelected", GroupName = "Picker")]
[TemplateVisualState(Name = "PickerItemTransparent", GroupName = "Picker")]
public sealed class PickerSelectorItem : ListBoxItem
{
..
And the Template of course :
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Picker">
<VisualState x:Name="PickerItemTransparent">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="InnerGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource TransparentColor}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentPresenter">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource TransparentColor}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PickerItemSelected">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="InnerGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PurpleColor}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentPresenter">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource WhiteColor}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PickerItemUnSelected">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="InnerGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource DarkGrayColor}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="ContentPresenter">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource WhiteColor}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
</VisualState>
<VisualState x:Name="PointerOver" >
</VisualState>
<VisualState x:Name="Disabled" >
</VisualState>
<VisualState x:Name="Pressed" >
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="SelectionStates">
<VisualState x:Name="Unselected">
</VisualState>
<VisualState x:Name="Selected">
</VisualState>
<VisualState x:Name="SelectedUnfocused" >
</VisualState>
<VisualState x:Name="SelectedDisabled" >
</VisualState>
<VisualState x:Name="SelectedPointerOver">
</VisualState>
<VisualState x:Name="SelectedPressed">
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused" >
</VisualState>
<VisualState x:Name="Unfocused">
</VisualState>
<VisualState x:Name="PointerFocused">
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Center the selected ListBoxItem
When you select an item in one of the three ListBox, you can’t center the ListBoxItem (especialy when it’s the first one)
To achieve this goal, there is a simple method : apply some margin on the ItemsPresenter like this:
/// <summary>
/// Applies the margin on items presenter to allow first and last item to be centered
/// </summary>
public void ApplyMarginOnItemsPresenter()
{
var itemsPresenter = this.ScrollViewer.GetVisualDescendent<ItemsPresenter>();
Thickness t = new Thickness(0, this.ScrollViewer.ViewportHeight / 2, 0, this.ScrollViewer.ViewportHeight / 2);
itemsPresenter.Margin = t;
}
ScrollViewer animation
We can’t override default animation of the ScrollViewer. Like WPF or Silverlight this action is not accessible.
I need to animate the ScrollViewer to correctly place the selected item in the center of the control. To achieve this goal, I used an internal Scrollbar which will be animated.
On each value change, we will make a scroll to vertical offset on the ScrollViewer.
The slider used :
sliderVertical = new Slider
{
SmallChange = 0.0000000001,
Minimum = double.MinValue,
Maximum = double.MaxValue,
StepFrequency = 0.0000000001
};
sliderVertical.ValueChanged += OnVerticalOffsetChanged;
the Vertical offset changed Handler:
/// <summary>
/// Hook to animate the scrollviewer
/// </summary>
private void OnVerticalOffsetChanged(object sender, RangeBaseValueChangedEventArgs e)
{
if (this.ScrollViewer == null) return;
this.viewer.ViewChanged -= ViewerViewChanged;
this.ScrollViewer.ScrollToVerticalOffset(e.NewValue);
}
And the animation used to center the item :
/// <summary>
/// Select the current item and snap it on the good placement (middle) with animation
/// </summary>
internal void SelectAndSnapTo(DateTimeWrapper wrapper)
{
double viewerVerticalOffestTarget = GetViewerVerticalOffestTarget(wrapper);
if (viewerVerticalOffestTarget <= 0d)
return;
storyBoardSnap = new Storyboard();
var animationSnap = new DoubleAnimation
{
EnableDependentAnimation = true,
From = viewer.VerticalOffset,
To = viewerVerticalOffestTarget,
Duration = animationDuration,
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
};
storyBoardSnap.Children.Add(animationSnap);
Storyboard.SetTarget(storyBoardSnap, sliderVertical);
Storyboard.SetTargetProperty(animationSnap, "Value");
storyBoardSnap.Completed += (sender, o) =>
{
// Reactivate Scrollviewer
viewer.VerticalScrollMode = ScrollMode.Enabled;
viewer.ViewChanged -= ViewerViewChanged;
viewer.ViewChanged += ViewerViewChanged;
this.GoToUnFocused();
};
this.GoToFocusState();
// Prevent viewer to scroll beacause the animation will force scrollviewer to scroll
viewer.VerticalScrollMode = ScrollMode.Disabled;
storyBoardSnap.Begin();
Get the correct item and the correct animation
When we select an item, we need to know:
- Wich item (UIElement) ?
- What is its position?
- What is the delta beetween this item and the center of listbox ?
To get the correct item, we can use the ItemContainerGenerator. To get its bound, we can use a GeneralTransform :
rowElement = this.ItemContainerGenerator.ContainerFromItem(dtw) as PickerSelectorItem;
if (rowElement == null)
return 0d;
Point origin, bottom;
GeneralTransform transform = rowElement.TransformToVisual(this.ScrollViewer);
if (transform == null || !transform.TryTransform(new Point(), out origin) ||
!transform.TryTransform(new Point(rowElement.ActualWidth, rowElement.ActualHeight), out bottom))
return 0d;
For each item, selected or not, tapped or manipulated, I always need to know what is the center item or what item I tapped and the Delta of vertical offset to animate
Thanks to VerticalOffset and ViewportHeight, we have all what we need to calculate this delta:
// Get delta ScrollViewer
double viewerVerticalOffset = this.ScrollViewer.VerticalOffset;
// Vertical size of the Viewer
double viewerPortHeight = this.ScrollViewer.ViewportHeight;
// get the middle
var middle = viewerPortHeight / 2;
// Get top element
var topElementTarget = (middle - (rowElement.ActualHeight / 2));
// Set Delta
var delta = rectItem.Top - topElementTarget;
// Déduction du vertical offset cible :
return viewerVerticalOffset + delta;
Globalization
This control supports globalization. You have to specify the correct language “before” the first invoke of the control
In the sample, you can see this code line in the OnLaunched Event :
// Change current application language
Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = "fr-FR";
Support ?
This control is provider “as is”. I know there is some limitations and ( maybe ? ) some bugs. Feel free to post comments here or on Codeplex !
Happy Calendar time
Comments
Anonymous
March 05, 2013
But how can I change the date? The day, month, year are not changing. Is it perhaps Touch-only? I'm using my mouse.Anonymous
March 05, 2013
Yes, it's touch only for the moment. However, you can scroll with the mouse Wheel but the user experience is not very conclusive. I will add the Keyboard support later.Anonymous
March 19, 2013
I'm very much happy to see this datepicker for win 8..but i don know how to use this in my app..can u pls help me..Anonymous
March 19, 2013
Nachi, what is your problem exactly ?Anonymous
March 19, 2013
I'm developing an windows store app..I need to have a datepicker control in my app.Hw can i use this control in my application..thanks in advanceAnonymous
March 19, 2013
Nachi, Just go to the Codeplex website : xamlwinrtcalendar.codeplex.com Go to the SOURCE CODE tab and clic DOWNLOAD. It will download the Visual Studio Solution. Open it with Visual Studio. You just have to reproduce the sample in your project. That's all.Anonymous
March 19, 2013
i am unable to change the date using mouse too.what is the solution for that.