I want to modify an existing ControlTemplate
drawing a recantgle in a custom calendar to draw the lower border as shown in the below picture:
The current ControlTemplate
looks like this:
<ControlTemplate x:Key="FesterBlockTemplate" TargetType="ContentControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Style="{StaticResource ContinueFromPreviousSignStyle}" />
<ContentControl Grid.Row="2" Style="{StaticResource ToBeContinuedSignStyle}" />
<!--Display of the activity text-->
<Border Opacity="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1" BorderThickness="0">
<Border.Background>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type controls:AktivitaetViewBase}}" Path="UpperBackground" />
</Border.Background>
<TextBlock Margin="3" Grid.Row="1" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:AktivitaetViewBase}}, Path=UpperText}"
HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center" TextWrapping="WrapWithOverflow">
<TextBlock.Foreground>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type controls:AktivitaetViewBase}}" Path="UpperTextForeground" />
</TextBlock.Foreground>
<TextBlock.Background>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type controls:AktivitaetViewBase}}" Path="UpperTextBackground" />
</TextBlock.Background>
<TextBlock.LayoutTransform>
<RotateTransform Angle="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:AktivitaetViewBase}}, Path=TextRotationAngle}" />
</TextBlock.LayoutTransform>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:AktivitaetViewBase}}, Path=HasCustomFontSize}" Value="True">
<Setter Property="FontSize" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:AktivitaetViewBase}}, Path=TextFontSize}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
<Path Grid.Row="1" Stretch="Fill" Stroke="Black" StrokeThickness="1" Data="M0,0 L0,1" HorizontalAlignment="Left" VerticalAlignment="Stretch"/>
<Path Grid.Row="1" Stretch="Fill" Stroke="Black" StrokeThickness="1" Data="M0,0 L0,1" HorizontalAlignment="Right" VerticalAlignment="Stretch"/>
</Grid>
</ControlTemplate>
I've found a way to draw the wanted shape by specifying static sizes:
<UserControl x:Class="WpfComplexShapeTest.ComplexShapeControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
Background="Transparent">
<Path Stroke="Black" StrokeThickness="1" Fill="Orange">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,10">
<PathFigure.Segments>
<LineSegment Point="10, 210"/>
<BezierSegment Point1="50,0"
Point2="70,350"
Point3="110,150"/>
<LineSegment Point="110, 10"/>
<LineSegment Point="10, 10"/>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</UserControl>
Which results in this shape (taken from VS designer):
Next step is to make it resize correctly. It has to take the available horizontal and vertical space and the amplitude of the wave form of the lower border is being specified by an int value (HourSegmentHeight
) and has to remain constant. That's why I've created dependency properties and properties, which are being recalculated when the control size changes, as shown in the code below:
XAML:
<UserControl x:Class="WpfComplexShapeTest.OpenEndControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Background="Transparent"
SizeChanged="OnUserControlSizeChanged"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
d:DesignHeight="300"
d:DesignWidth="500">
<Path Stroke="Black" StrokeThickness="1" Fill="Orange" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="0,0">
<PathFigure.Segments>
<LineSegment Point="{Binding LowerLeftPoint}"/>
<BezierSegment Point1="{Binding BezierPoint1}"
Point2="{Binding BezierPoint2}"
Point3="{Binding BezierPoint3}"/>
<LineSegment Point="{Binding UpperRightPoint}"/>
<LineSegment Point="0, 0"/>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</UserControl>
Code behind:
using System.Diagnostics;
using System.Windows;
namespace WpfComplexShapeTest {
/// <summary>
/// Interaction logic for OpenEndControl.xaml
/// </summary>
public partial class OpenEndControl {
#region Private Fields
private int m_hourSegmentHeight;
#endregion
#region Public Properties
public static readonly DependencyProperty UpperRightPointProperty = DependencyProperty.Register("UpperRightPoint", typeof (Point), typeof (OpenEndControl));
public static readonly DependencyProperty LowerLeftPointProperty = DependencyProperty.Register("LowerLeftPoint", typeof (Point), typeof (OpenEndControl));
public static readonly DependencyProperty BezierPoint1Property = DependencyProperty.Register("BezierPoint1", typeof (Point), typeof (OpenEndControl));
public static readonly DependencyProperty BezierPoint2Property = DependencyProperty.Register("BezierPoint2", typeof (Point), typeof (OpenEndControl));
public static readonly DependencyProperty BezierPoint3Property = DependencyProperty.Register("BezierPoint3", typeof (Point), typeof (OpenEndControl));
/// <summary>
/// Gets or sets the upper right point.
/// </summary>
/// <value>
/// The upper right point.
/// </value>
public Point UpperRightPoint {
get { return (Point) GetValue(UpperRightPointProperty); }
set { SetValue(UpperRightPointProperty, value); }
}
/// <summary>
/// Gets or sets the lower left point.
/// </summary>
/// <value>
/// The lower left point.
/// </value>
public Point LowerLeftPoint {
get { return (Point) GetValue(LowerLeftPointProperty); }
set { SetValue(LowerLeftPointProperty, value); }
}
/// <summary>
/// Gets or sets the bezier point 1.
/// </summary>
/// <value>
/// The bezier point 1.
/// </value>
public Point BezierPoint1 {
get { return (Point) GetValue(BezierPoint1Property); }
set { SetValue(BezierPoint1Property, value); }
}
/// <summary>
/// Gets or sets the bezier point 2.
/// </summary>
/// <value>
/// The bezier point 2.
/// </value>
public Point BezierPoint2 {
get { return (Point) GetValue(BezierPoint2Property); }
set { SetValue(BezierPoint2Property, value); }
}
/// <summary>
/// Gets or sets the bezier point 3.
/// </summary>
/// <value>
/// The bezier point 3.
/// </value>
public Point BezierPoint3 {
get { return (Point) GetValue(BezierPoint3Property); }
set { SetValue(BezierPoint3Property, value); }
}
/// <summary>
/// Gets or sets the height of the hour segment.
/// </summary>
/// <value>
/// The height of the hour segment.
/// </value>
public int HourSegmentHeight {
get { return m_hourSegmentHeight; }
set {
if (m_hourSegmentHeight != value) {
m_hourSegmentHeight = value;
RefreshPoints();
}
}
}
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="OpenEndControl"/> class.
/// </summary>
public OpenEndControl() {
InitializeComponent();
RefreshPoints();
}
#endregion
#region Private Methods
/// <summary>
/// Refreshes the points.
/// </summary>
private void RefreshPoints() {
UpperRightPoint = new Point(ActualWidth, 0);
LowerLeftPoint = new Point(0, ActualHeight);
BezierPoint1 = new Point(ActualWidth/2, HourSegmentHeight);
BezierPoint2 = new Point(ActualWidth/2 + 10, 2*HourSegmentHeight);
BezierPoint3 = new Point(ActualWidth, ActualHeight/2 - HourSegmentHeight);
Debug.WriteLine("Width={0}, Height={1}", ActualWidth, ActualHeight);
}
/// <summary>
/// Called when the size of the user control has changed.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="SizeChangedEventArgs"/> instance containing the event data.</param>
private void OnUserControlSizeChanged(object sender, SizeChangedEventArgs e) {
RefreshPoints();
}
#endregion
}
}
The RereshPoints()
method doesn't compute the correct values for the bezier points 1, 2 and 3 and I can't figure out the formula to use after having read the Bézier curve article.
The result for a certain control size looks like this:
Question:
- Is this a good approach to draw the shape I want?
- If yes, can you help me out find the right formula to calculate the bezier points?