Создание пользовательской фигуры с помощью 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, Таким образом, окно просмотра всегда сохраняет пропорции фигуры.

Другие вопросы по тегам