I have a basic Clock CustomControl
that has a DateTime
property that updates every second. Its default ControlTemplate
is an AnalogClockFace CustomControl
, but I also have a DigitalClockFace CustomControl
that I can set as the Template
value to have a digital display.
Recently, I thought I'd get adventurous and try making a FlipDownClockFace CustomControl
, something like the old fashioned clocks where the numbers were split vertically half way up and displayed on rolls that would turn and flip down the next number as time progressed. Unfortunately I pretty much fell at the first, well second hurdle.
To simplify this problem somewhat, I need to display two TextBlock
s, with each displaying opposite halves of a Text
value. By that I mean that one should display the top half of a TextBlock.Text
value and the other one should display the bottom half of a TextBlock.Text
value. Actually, I can clip (show) the top half of the TextBlock
ok, it's displaying the bottom half that's the problem.
To be clear, I need the clip area to automatically resize to fit the TextBlock
regardless of the FontSize
value, so I cant just hard code a Height
value. This question just relates to the clipping of the TextBlock
and I will sort out all the Transform Animation
s required for my FlipDownClockFace
control later.
I managed to find out that I could use the RenderedGeometry
property of a Rectangle
element as the TextBlock.Clip
value and so I thought that I could just pop a Rectangle
in each half of a Grid
to clip the two (overlayed) TextBlock
s. This example shows a reconstruction of where it went wrong (the DoubleDivisorConverter
just divides the data bound value (the Height
) by the value of the ConverterParameter
(in this case, 2.0)):
<Grid TextElement.FontSize="72">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.RowSpan="2" Name="FirstValueBottomTextBox" Text="23"
Clip="{Binding RenderedGeometry, ElementName=FirstValueBottomClipRectangle}"
VerticalAlignment="Center" HorizontalAlignment="Center" Background="LightBlue" />
<Rectangle Name="FirstValueBottomClipRectangle" Grid.Row="1" Height="{Binding
ActualHeight, ElementName=FirstValueBottomTextBox, Converter={StaticResource
DoubleDivisorConverter}, ConverterParameter=2.0}" VerticalAlignment="Top" />
</Grid>
Basically I worked out that the problem is that RenderedGeometry
just uses the Geometry
of the Rectangle
and not the position, so this only ever clips (shows) the top half of the text, regardless of the actual position of the Rectangle
. I couldn't find any way to move the clip area to the bottom to just show the bottom half of the text.
Before you suggest it, I can't do this either, as the Rect.Height
property is unfortunately not a DependencyProperty
:
<TextBlock Grid.Row="0" Grid.RowSpan="2" Name="FirstValueBottomTextBox" Text="23"
VerticalAlignment="Center" HorizontalAlignment="Center" Background="LightBlue">
<TextBlock.Clip>
<RectangleGeometry>
<RectangleGeometry.Rect>
<Rect Height="{Binding ActualHeight, ElementName=
FirstValueBottomTextBox, Converter={StaticResource DoubleDivisorConverter},
ConverterParameter=2.0}" />
</RectangleGeometry.Rect>
</RectangleGeometry>
</TextBlock.Clip>
</TextBlock>
So if anyone has any clues on how I can achieve this or if you need any more information, please let me know.
Is it required to use the Clip
functionality? Or is OpacityMask
an option? The following code comes close to such a clock:
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" TextElement.FontSize="36">
<Grid.Resources>
<Style x:Key="UpperHalfStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="0"/>
<Setter Property="OpacityMask">
<Setter.Value>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<GradientStop Offset="0.5" Color="White"/>
<GradientStop Offset="0.5" Color="Transparent"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="LowerHalfStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="0,2,0,0"/>
<Setter Property="OpacityMask">
<Setter.Value>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<GradientStop Offset="0.5" Color="Transparent"/>
<GradientStop Offset="0.5" Color="White"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<StackPanel Orientation="Horizontal">
<Grid>
<TextBlock Text="1" Style="{StaticResource UpperHalfStyle}"/>
<TextBlock Text="1" Style="{StaticResource LowerHalfStyle}"/>
</Grid>
<Grid>
<TextBlock Text="0" Style="{StaticResource UpperHalfStyle}"/>
<TextBlock Text="0" Style="{StaticResource LowerHalfStyle}"/>
</Grid>
<TextBlock Text=":"/>
<Grid>
<TextBlock Text="3" Style="{StaticResource UpperHalfStyle}"/>
<TextBlock Text="3" Style="{StaticResource LowerHalfStyle}"/>
</Grid>
<Grid>
<TextBlock Text="7" Style="{StaticResource UpperHalfStyle}"/>
<TextBlock Text="7" Style="{StaticResource LowerHalfStyle}"/>
</Grid>
</StackPanel>
</Grid>
I just overlay two TextBlock
elements. The first one is only opaque in the upper half, while the second one is only opaque in the lower half. I also added a Margin
to the lower half, so that it becomes clear that they are two different elements. I do not know the rest of your design, but I assume you will want to add borders around the numbers. For this, you might have to restrict the height of the elements somehow. Otherwise the borders will surround the whole TextBlock
and not just the visible part.