Universal Windows Platform – Split Control

Split Control demonstrates how to create an on-screen Split or Split-Flap Control to display the current Time and Date.

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in the Name as SplitControl and select a Location and then select Ok to create the Project
vs2017-new-project-split-control

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then XAML from Installed then select User Control from the list, then type in the Name as Split.xaml before selecting Add to add the file to the Project

vs2017-add-new-item-split-control

Step 7

The Design View will be displayed along with the XAML View and in this above the Grid element, enter the following XAML:

<UserControl.Resources>
	<Style x:Key="SplitLabel" TargetType="TextBlock">
		<Setter Property="FontFamily" Value="Arial"/>
		<Setter Property="Foreground" Value="White"/>
		<Setter Property="FontSize" Value="75"/>
	</Style>
	<Style x:Key="GridStyle" TargetType="Grid">
		<Setter Property="CornerRadius" Value="4"/>
		<Setter Property="Background" Value="White"/>
		<Setter Property="BorderBrush" Value="Gray"/>
		<Setter Property="BorderThickness" Value="1,1,1,1"/>
	</Style>
	<LinearGradientBrush x:Key="BackgroundBrush" EndPoint="0.5,1" StartPoint="0.5,0">
		<GradientStop Color="#FF202020" Offset="1"/>
		<GradientStop Color="#FF404040"/>
	</LinearGradientBrush>
	<Storyboard x:Name="FlipAnimation">
		<DoubleAnimationUsingKeyFrames Storyboard.TargetName="BlockFlip" 
		Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
			<EasingDoubleKeyFrame Value="1" KeyTime="0">
				<EasingDoubleKeyFrame.EasingFunction>
					<BounceEase EasingMode="EaseOut" Bounces="1" Bounciness="6"/>
				</EasingDoubleKeyFrame.EasingFunction>
			</EasingDoubleKeyFrame>
			<EasingDoubleKeyFrame Value="-1" KeyTime="00:00:00.250">
				<EasingDoubleKeyFrame.EasingFunction>
					<BounceEase EasingMode="EaseOut" Bounces="1" Bounciness="6"/>
				</EasingDoubleKeyFrame.EasingFunction>
			</EasingDoubleKeyFrame>
		</DoubleAnimationUsingKeyFrames>
		<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextBlockFlipTop" 
		Storyboard.TargetProperty="(UIElement.Visibility)">
			<DiscreteObjectKeyFrame KeyTime="0">
				<DiscreteObjectKeyFrame.Value>
					<Visibility>Visible</Visibility>
				</DiscreteObjectKeyFrame.Value>
			</DiscreteObjectKeyFrame>
			<DiscreteObjectKeyFrame KeyTime="00:00:00.125">
				<DiscreteObjectKeyFrame.Value>
					<Visibility>Collapsed</Visibility>
				</DiscreteObjectKeyFrame.Value>
			</DiscreteObjectKeyFrame>
		</ObjectAnimationUsingKeyFrames>
		<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TextBlockFlipBottom" 
		Storyboard.TargetProperty="(UIElement.Visibility)">
			<DiscreteObjectKeyFrame KeyTime="0">
				<DiscreteObjectKeyFrame.Value>
					<Visibility>Collapsed</Visibility>
				</DiscreteObjectKeyFrame.Value>
			</DiscreteObjectKeyFrame>
			<DiscreteObjectKeyFrame KeyTime="00:00:00.125">
				<DiscreteObjectKeyFrame.Value>
					<Visibility>Visible</Visibility>
				</DiscreteObjectKeyFrame.Value>
			</DiscreteObjectKeyFrame>
		</ObjectAnimationUsingKeyFrames>
	</Storyboard>
</UserControl.Resources>

Then while still in the XAML View between the Grid and /Grid elements, enter the following XAML:

<Grid Height="80" Width="50">
	<Grid.RowDefinitions>
		<RowDefinition Height="0.5*"/>
		<RowDefinition Height="0.5*"/>
	</Grid.RowDefinitions>
	<Grid x:Name="BlockTop" Grid.Row="0" Style="{StaticResource GridStyle}" 
	Background="{StaticResource BackgroundBrush}">
		<TextBlock x:Name="TextBlockTop" Style="{StaticResource SplitLabel}" 
		HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,-2,0,0"/>
	</Grid>
	<Grid x:Name="BlockBottom" Grid.Row="1" Style="{StaticResource GridStyle}">
		<Grid.Background>
			<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
				<GradientStop Color="#FF202020"/>
				<GradientStop Color="#FF404040" Offset="1"/>
			</LinearGradientBrush>
		</Grid.Background>
		<TextBlock x:Name="TextBlockBottom" Style="{StaticResource SplitLabel}" 
		HorizontalAlignment="Center" VerticalAlignment="Bottom" 
		RenderTransformOrigin="0.5,0.5" Margin="0,0,0,-4"/>
	</Grid>
	<Grid x:Name="BlockFlip" Style="{StaticResource GridStyle}" 
	Background="{StaticResource BackgroundBrush}" RenderTransformOrigin="0.5,1">
		<Grid.RenderTransform>
			<TransformGroup>
				<ScaleTransform/>
				<SkewTransform/>
				<RotateTransform/>
				<TranslateTransform/>
			</TransformGroup>
		</Grid.RenderTransform>
		<TextBlock x:Name="TextBlockFlipTop" Style="{StaticResource SplitLabel}" 
			HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,-2,0,0"/>
		<TextBlock x:Name="TextBlockFlipBottom" Style="{StaticResource SplitLabel}" 
			HorizontalAlignment="Center" VerticalAlignment="Bottom" Visibility="Collapsed"
			RenderTransformOrigin="0.5,0.5" Margin="0,0,0,-4">
			<TextBlock.RenderTransform>
				<TransformGroup>
					<ScaleTransform ScaleY="-1"/>
					<SkewTransform/>
					<RotateTransform/>
					<TranslateTransform Y="40"/>
				</TransformGroup>
			</TextBlock.RenderTransform>
		</TextBlock>
	</Grid>
</Grid>

Within the UserControl.Resources block of XAML above the Grid Element contains various Resources Style items for SplitLabel and GridStyle. There is a LinearGradientBrush for the colours of the Control, then there is a Storyboard for DoubleAnimationUsingKeyFrames and ObjectAnimationUsingKeyFrames to produce the Animation for the flipping of the Split itself.

Within the main Grid Element, the first block of XAML is a Grid Control which has two Rows and Columns, in the first Row is the Upper part of the Control as BlockTop and in the second Row is the Lower Part as BlockBottom. There is another Grid as BlockFlip for producing the Flipped portion of the Split Control with parts for the Upper and Lower portions of this as TextBlockFlipTop and TextBlockFlipBottom.

Step 8

From the Menu choose View and then Code

vs2017-view-code

Step 9

Once in the Code View, below the end of public Split() { … } Constructor the following Code should be entered:

private string _value;
private string _from;

public string Value
{
	get { return _value; }
	set
	{
		_value = value;
		if (_from != null)
		{
			if (_from != value)
			{
				TextBlockTop.Text = TextBlockFlipBottom.Text = value;
				TextBlockFlipTop.Text = _from;
				FlipAnimation.Begin();
				FlipAnimation.Completed -= (s, e) => { };
				FlipAnimation.Completed += (s, e) => TextBlockBottom.Text = _from;
			}
		}
		if (_from == null)
		{
			TextBlockFlipTop.Text = TextBlockBottom.Text = value;
		}
		_from = value;
	}
} 

Then still in the Code View, below the end of public sealed partial class Split : UserControl { … } Class the following Code should be entered:

public class Splits : StackPanel
{
	private const char space = ' ';

	private string _value;
	private int _count;

	public enum Sources
	{
		Value = 0,
		Time = 1,
		Date = 2,
		TimeDate = 3
	}

	public static readonly DependencyProperty SourceProperty =
	DependencyProperty.Register("Source", typeof(Sources),
	typeof(Split), new PropertyMetadata(Sources.Time));

	public Sources Source
	{
		get { return (Sources)GetValue(SourceProperty); }
		set { SetValue(SourceProperty, value); }
	}

	private void Add(string name)
	{
		FrameworkElement element = new Split() { Tag = name };
		if (name == null)
		{
			element = new Canvas { Width = 5 };
		}
		this.Children.Add(element);
	}

	private void SetSplit(string name, char glyph)
	{
		FrameworkElement element = this.Children.Cast<FrameworkElement>()
		.FirstOrDefault(f => (string)f.Tag == name);
		if(element is Split)
		{
			((Split)element).Value = glyph.ToString();
		}
	}

	private void GetLayout()
	{
		char[] array = _value.ToCharArray();
		int length = array.Length;
		IEnumerable<int> list = Enumerable.Range(0, length);
		if (_count != length)
		{
			this.Children.Clear();
			foreach (int item in list)
			{
				Add((array[item] == space) 
				? null : item.ToString());
			}
			_count = length;
		}
		foreach (int item in list)
		{
			SetSplit(item.ToString(), array[item]);
		}
	}

	public Splits()
	{
		this.Orientation = Orientation.Horizontal;
		DispatcherTimer timer = new DispatcherTimer()
		{
			Interval = TimeSpan.FromMilliseconds(250)
		};
		timer.Tick += (object s, object args) =>
		{
			if (Source != Sources.Value)
			{
				string format = string.Empty;
				switch (Source)
				{
					case Sources.Time:
						format = "HH mm ss";
						break;
					case Sources.Date:
						format = "dd MM yyyy";
						break;
					case Sources.TimeDate:
						format = "HH mm ss  dd MM yyyy";
						break;
				}
				Value = DateTime.Now.ToString(format);
			}
		};
		timer.Start();
	}

	public string Value
	{
		get
		{
			return _value;
		}
		set
		{
			_value = value;
			GetLayout();
		}
	}
}

Below the Split() Constructor are some string Members and a string Property of Value which sets the Text Property of TextBlockTop to Text Property of TextBlockFlipBottom is then set to value. Then then the FlipAnimation has any Event Handlers for the Completed Event removed with -= then one added with a Lambda with += which will when Triggered will set the Text Property of TextBlockBottom to _from. Then the Begin Method of FlipAnimation is called to start the Animation. If _from is null then the Text Property of TextBlockFlipTop is set to the Text Property of TextBlockBottom which is in turn set to value and then _from is set to value.

Below the Split Class but within the namespace of SplitControl there is a Splits Class which contains const for space and Members for string and int. There is an enum for Sources along with an DependencyProperty and Property which can update this. There is a Add Method to create a Split Control or a Canvas and a SetSplit Method which can update an existing Split with a Value.

While still in the Splits Class there is a GetLayout Method to help create the set of Split Controls to produce the relevant output, then in the Splits Constructor this sets up a DispatcherTimer which uses the Value of Sources to determine the output where values will be contained with a Split Control and Spaces will be a Canvas and there is a Value Property to get or set what is displayed by the Split Controls.

Step 10

Once done select from the Menu, Build, then Build Solution

vs2017-build-build-solution

Step 11

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-split-control

Step 12

From the Menu choose View and then Designer

vs2017-view-designer

Step 13

The Design View will be displayed along with the XAML View and in this between the Grid and /Grid elements, enter the following XAML:

<Viewbox>
	<local:Splits Margin="50" Name="Display" Source="TimeDate" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Viewbox>

The main block of XAML represents the Splits Control itself where the Source is set to TimeDate and is within a Viewbox Control.

Step 14

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 15

After the Application has started running it should then appear displaying the set of Split Control that will show the current Time and Date

ran-split-control

Step 16

To Exit the Application select the Close button in the top right of the Application

vs2017-close

This control shows how to create a control that emulates the look-and-feel of a physical Flipping Clock using a Split-flap Display. This Control was developed many years ago for Silverlight and Windows Phone 7 many years ago as part of another Tutorial back then. Thanks to byrialsen for their feedback for a quick fix to improve the Animation.

Creative Commons License

3 thoughts on “Universal Windows Platform – Split Control

  1. Hi.
    I really love this user control and will use it in one of my apps. But I believe there is a minor error in the way you animate the flipping. Try to slow down the animation and it will be easier to see for yourself.

    Like

  2. Maybe there is a more clean fix than this but it sure helps the feel of the animation I believe:

    public Split()
    {
    this.InitializeComponent();
    FlipAnimation.Completed += (s, e) => TextBlockBottom.Text = _from;
    }

    public string Value
    {
    get { return _value; }
    set
    {
    _value = value;
    if (_from != null)
    {
    if (_from != value)
    {
    TextBlockTop.Text = TextBlockFlipBottom.Text = value;
    TextBlockFlipTop.Text = _from;
    FlipAnimation.Begin();
    }
    }
    if (_from == null)
    {
    TextBlockFlipTop.Text = TextBlockBottom.Text = value;
    }
    _from = value;
    }
    }

    Like

  3. Thanks for this, I’d wanted to improve the animation but had run out of time when trying to figure it out myself so will give this a go, thanks for the feedback it’s much appreciated!

    Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s