Создание пользовательской фигуры с помощью BezierSegment
Я хочу изменить существующий ControlTemplate
нарисовать перекод в пользовательском календаре, чтобы нарисовать нижнюю границу, как показано на рисунке ниже:
Электрический ток ControlTemplate
выглядит так:
<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>
Я нашел способ нарисовать нужную форму, указав статические размеры:
<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>
Что приводит к этой форме (взято из конструктора VS):
Следующим шагом является корректное изменение размера. Он должен занимать доступное горизонтальное и вертикальное пространство, а амплитуда формы волны нижней границы определяется значением int (HourSegmentHeight
) и должен оставаться постоянным. Вот почему я создал свойства и свойства зависимостей, которые пересчитываются при изменении размера элемента управления, как показано в приведенном ниже коде:
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>
Код позади:
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
}
}
RereshPoints()
Метод не вычисляет правильные значения для точек Безье 1, 2 и 3, и я не могу понять формулу для использования после прочтения статьи кривой Безье.
Результат для определенного размера элемента управления выглядит следующим образом:
Вопрос:
- Это хороший способ нарисовать фигуру, которую я хочу?
- Если да, можете ли вы помочь мне найти правильную формулу для расчета точек Безье?
2 ответа
А) только вы можете решить это. Если он выглядит так, как вы хотите, чтобы он выглядел, и на его вычисление не уходит вечность, то, безусловно, он достаточно хорош. б) кубические кривые Безье определяются двумя кривыми на кривой и двумя точками вне кривой. Начальная и конечная точки на кривой - это просто место, где фигура должна быть связана с вашим прямоугольником, так что вы должны эту часть вниз. Форма будет "отходить" от начальной точки в направлении первой контрольной точки и "достигать" конечной точки в направлении второй контрольной точки, поэтому вам нужны контрольные точки C1 и C2, которые соответственно (startpoint_x, ...)
а также (endpoint_x, ...)
с координатами у разумно свободно выбирать. Пока C1 и C2 равны максимумам выше и ниже средней точки, кривая будет выглядеть прилично:
with
start = { 0, ... }
end = { width, ... }
d = ...
c1 = { 0, (start_y + end_y)/2 - d }
c2 = { width, (start_y + end_y)/2 + d }
form
curve(start, c1, c2, end)
Просто выберите значение для d и посмотрите, что вам больше всего нравится - это ваша визуализация, мы не можем сказать, хотите ли вы, чтобы вы выглядели лучше всего =)
Этот jsfiddle - простой демонстратор концепции (наведите курсор на графику, чтобы изменить силу d
).
Это всего лишь идея: вы можете нарисовать картинку так, как вы хотите (с фиксированными точками), а затем, в пользовательском элементе управления или контейнере, обернуть фигуру внутри ViewBox
, Таким образом, окно просмотра всегда сохраняет пропорции фигуры.