Question

Je souhaite modifier un existant ControlTemplate dessiner un recantgle dans un calendrier personnalisé pour dessiner la bordure inférieure comme indiqué dans l'image ci-dessous :

final result

Le courant ControlTemplate ressemble à ça:

<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>

J'ai trouvé un moyen de dessiner la forme souhaitée en spécifiant des tailles statiques :

<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>

Ce qui donne cette forme (tirée du concepteur VS) :

wanted shape

L'étape suivante consiste à le redimensionner correctement.Il doit occuper l'espace horizontal et vertical disponible et l'amplitude de la forme d'onde de la bordure inférieure est spécifiée par une valeur int (HourSegmentHeight) et doit rester constant.C'est pourquoi j'ai créé des propriétés de dépendance et des propriétés, qui sont recalculées lorsque la taille du contrôle change, comme indiqué dans le code ci-dessous :

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 derrière :

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
    }
}

Le RereshPoints() la méthode ne calcule pas les valeurs correctes pour les points de Bézier 1, 2 et 3 et je n'arrive pas à comprendre la formule à utiliser après avoir lu le Article sur la courbe de Bézier.

Le résultat pour une certaine taille de contrôle ressemble à ceci :

current shape

Question:
- Est-ce une bonne approche pour dessiner la forme que je veux ?
- Si oui, pouvez-vous m'aider à trouver la bonne formule pour calculer les points de Bézier ?

Était-ce utile?

La solution

a) vous seul pouvez en décider.S'il ressemble à ce que vous souhaitez et que le calcul ne prend pas une éternité, alors c'est certainement suffisant.b) les courbes de Bézier cubiques sont définies par deux points sur courbe et deux points hors courbe.Les points de début et de fin sur la courbe sont simplement les endroits où la forme doit être reliée à votre rectangle, vous avez donc cette partie vers le bas.La forme "partira" du point de départ en direction du premier point de contrôle et "arrivera" au point final en direction du deuxième point de contrôle. Vous voulez donc des points de contrôle C1 et C2 qui sont respectivement (startpoint_x, ...) et (endpoint_x, ...) avec les coordonnées y raisonnablement libre de choisir.Tant que C1 et C2 sont égaux au-dessus et en dessous du point médian, la courbe semblera décente :

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)

Choisissez simplement une valeur pour d et voyez ce que vous préférez - c'est votre visualisation, nous ne pouvons pas vous dire ce que vous pensez être le plus beau =)

Ce jsfiddle est une simple démonstration du concept (déplacez la souris sur le graphique pour faire varier la force de d).

Autres conseils

Ceci n'est qu'une idée :vous pouvez dessiner l'image comme vous le souhaitez (avec des points fixes), puis, dans le contrôle utilisateur ou le conteneur, envelopper la forme dans un ViewBox.De cette façon, la zone de visualisation conserve toujours les proportions de la forme.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top