使用BezierSegment创建自定义形状

时间:2014-07-31 08:05:30

标签: c# wpf wpf-controls bezier geometry-surface

我想修改现有的ControlTemplate在自定义日历中绘制一个recantgle来绘制下边框,如下图所示:

final result

当前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设计师):

wanted shape

下一步是让它正确调整大小。它必须采用可用的水平和垂直空间,并且下边界的波形的幅度由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的正确值,我无法在阅读Bézier curve article之后找出要使用的公式

某个控件大小的结果如下所示:

current shape

问题
  - 这是绘制我想要的形状的好方法吗?   - 如果是的话,你能帮我找到合适的公式来计算贝塞尔点吗?

2 个答案:

答案 0 :(得分:1)

a)只有你可以决定。如果它看起来像你想要它的样子,并且它不需要永远计算,那么它肯定足够好。 b)三次贝塞尔曲线由两个曲线上和两个曲线外曲线定义。开始和结束曲线上的点只是形状需要与矩形连接的位置,因此您可以将该部分放下。形状将从第一个控制点方向的起点“离开”,并从第二个控制点的方向“到达”端点,因此您需要分别为{{1}的控制点C1和C2 }和(startpoint_x, ...)可以合理地自由选择y坐标。只要C1和C2在中点上方和下方的高度相等,曲线就会显得不错:

(endpoint_x, ...)

只需为d选择一个值,然后看看你最喜欢什么 - 这是你的可视化,我们无法告诉你想要你认为看起来最好=)

jsfiddle是该概念的简单演示者(将鼠标移到图形上以改变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) 的强度。)

答案 1 :(得分:0)

这只是一个想法:您可以按照您想要的方式绘制图片(使用固定点),然后在用户控件或容器中将形状包裹在ViewBox内。通过这种方式,视图框始终保持形状的比例。