WPF : Curly lines are getting drawn differently on different height( y coordinate)

Priyank Kotiyal 6 Reputation points
2022-01-31T10:19:44.99+00:00

Issue: I am facing issue in wpf while trying to draw curly lines i.e curls are appearing fine at some points but not on all points for y coordinate.

Observation : Looks like it draws curves correctly only if y coordinate is multiple of width of rect of viewport setting. Like in following code sample it draws line at 126(and other that are multiple of 6) correctly as its a multiple of 6 but not on 140.

Output, First at 126 second at 140 :
169804-image.png

Expected : It should draw same at all points.

I need curls for all points. May be I am missing something. Any help will be appreciated, Thanks in advance!

Code Sample :

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading.Tasks;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  
  
namespace WpfApp1  
{  
    /// <summary>  
    /// Interaction logic for MainWindow.xaml  
    /// </summary>  
    public partial class MainWindow : Window  
    {  
        public MainWindow()  
        {  
            InitializeComponent();  
        }  
  
        protected override void OnRender(DrawingContext dc)  
        {  
            base.OnRender(dc);  
            dc.DrawLine(new Pen(GetBrush(), 2), new Point(50, 126), new Point(200, 126)); // Draws fine.  
            dc.DrawLine(new Pen(GetBrush(), 2), new Point(50, 140), new Point(200, 140));  // Incorrectly draws.  
        }  
  
        private DrawingBrush GetBrush()  
        {  
            Pen path_pen = new Pen(Brushes.Red, 0.5);  
            path_pen.EndLineCap = PenLineCap.Square;  
            path_pen.StartLineCap = PenLineCap.Square;  
  
  
            Point path_start = new Point(0, 2);  
            BezierSegment path_segment = new BezierSegment(new Point(2, 0), new Point(4, 4), new Point(6, 2), true);  
            PathFigure path_figure = new PathFigure(path_start, new PathSegment[] { path_segment }, false);  
            PathGeometry path_geometry = new PathGeometry(new PathFigure[] { path_figure });  
  
  
  
            DrawingBrush brush = new DrawingBrush(new GeometryDrawing(null, path_pen, path_geometry));  
            brush.TileMode = TileMode.Tile;  
            brush.Viewport = new Rect(0, 0, 6, 4);  
            brush.ViewportUnits = BrushMappingMode.Absolute;  
  
            return brush;  
        }  
  
    }  
}  
Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,671 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
762 questions
{count} votes

4 answers

Sort by: Most helpful
  1. Hui Liu-MSFT 38,251 Reputation points Microsoft Vendor
    2022-02-01T08:32:12.383+00:00

    I reproduced your problem. I update the code dc.DrawLine(new Pen(GetBrush(),2), new Point(50, 126), new Point(200, 126));to dc.DrawLine(new Pen(GetBrush(),600), new Point(0, 126), new Point(400, 200));
    The wavy lines your code shows are taken from the brushed canvas.
    The result:

    170125-image.png

    The problem is related to the setting of brush.Viewport. You could adjust it according to your needs .Modify the code as follows:

    brush.Viewport = new Rect(0, 0, 6,6);  
    

    The result:

    170132-image.png
    brush.Viewport = new Rect(0, 0, 1,1);
    The result:
    169999-image.png

    Zoom in :
    170133-image.png


    If the answer is the right solution, please click Accept Answer and kindly upvote it. If you have extra questions about this answer, please click Comment.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

  2. Priyank Kotiyal 6 Reputation points
    2022-02-01T16:03:12.41+00:00

    Yeah, We were also thinking as its something related to ViewPort, but not able to get how to adjust it(basically what calculation to use). Let me first make the use case clear, so we are trying to achieve curly line behavior similar to word's default spell checker. We want to draw curly lines below some words/sentences, so we are fetching points for the word then drawing lines on it. So issue is that if points returned are in some multiple of viewport then it draws correctly but when not then draws incorrectly, on some places it works fine for same word depending on its location in visible area(like if we scroll its location will change). So we want to draw a single curly line from one point to another below it. Is there are better way to achieve it? Rect 1,1 is too small and we want it to be visible on first sight without any zoom in. The Rect 6,6 also not helping.

    0 comments No comments

  3. Hui Liu-MSFT 38,251 Reputation points Microsoft Vendor
    2022-02-02T03:16:38.1+00:00

    If you want to mark the text, you could try to use the following method.

    <Window.Resources>  
            <VisualBrush x:Key="WavyBrush1" Viewbox="0,0,3,2" ViewboxUnits="Absolute" Viewport="0,0.8,6,4" ViewportUnits="Absolute" TileMode="Tile">  
                <VisualBrush.Visual>  
                    <Path Data="M 0,1 C 1,0 2,2 3,1" Stroke="Red" StrokeThickness="0.2" StrokeEndLineCap="Square" StrokeStartLineCap="Square" />  
                </VisualBrush.Visual>  
            </VisualBrush>  
            <VisualBrush x:Key="WavyBrush"   
                       Viewbox="0,0,3,2" ViewboxUnits="Absolute" Viewport="0,1,4,5" ViewportUnits="Absolute" TileMode="Tile">  
                <VisualBrush.Visual>  
                    <Path Data="M 0,1 C1,1 1,2 3,1" Stroke="Red" StrokeThickness="0.2" StrokeEndLineCap="Square" StrokeStartLineCap="Square" />  
                </VisualBrush.Visual>  
            </VisualBrush>  
        </Window.Resources>  
        <StackPanel Orientation="Horizontal">  
            <Canvas HorizontalAlignment="Left" Width="300"  Height="400">  
                <RichTextBox Width="300">  
                    <FlowDocument>  
                        <Paragraph>  
                            Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.  
                            <Run Text="At vero eos et accusam et justo">  
                                <Run.TextDecorations>  
                                    <TextDecoration Location="Underline">  
                                        <TextDecoration.Pen>  
                                            <Pen Brush="{StaticResource WavyBrush}" Thickness="6"/>  
                                        </TextDecoration.Pen>  
                                    </TextDecoration>  
                                </Run.TextDecorations>  
                            </Run>  
                            duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata  
                            <Run Text="At vero eos et accusam et justo">  
                                <Run.TextDecorations>  
                                    <TextDecoration Location="Underline">  
                                        <TextDecoration.Pen>  
                                            <Pen Brush="{StaticResource WavyBrush}" Thickness="6"/>  
                                        </TextDecoration.Pen>  
                                    </TextDecoration>  
                                </Run.TextDecorations>  
                            </Run> sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,   
                               consetetur sadipscing  
                            <Run Text="At vero eos et accusam et justo">  
                                <Run.TextDecorations>  
                                    <TextDecoration Location="Underline">  
                                        <TextDecoration.Pen>  
                                            <Pen Brush="{StaticResource WavyBrush}" Thickness="6"/>  
                                        </TextDecoration.Pen>  
                                    </TextDecoration>  
                                </Run.TextDecorations>  
                            </Run> elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.   
                               At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gube  
                            <Run Text="At vero eos et accusam et justo">  
                                <Run.TextDecorations>  
                                    <TextDecoration Location="Underline">  
                                        <TextDecoration.Pen>  
                                            <Pen Brush="{StaticResource WavyBrush}" Thickness="6"/>  
                                        </TextDecoration.Pen>  
                                    </TextDecoration>  
                                </Run.TextDecorations>  
                            </Run> rgren, no sea takimata sanctus est Lorem ipsum dolor sit amet.  
                        </Paragraph>  
                    </FlowDocument>  
                </RichTextBox>  
            </Canvas>  
            <Canvas Name="chartCanvas" Width="250" Height="200" ClipToBounds="True">  
                <RichTextBox Width="300">  
                    <FlowDocument>  
                        <Paragraph>  
                            Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.  
                            <Run Text="At vero eos et accusam et justo">  
                                <Run.TextDecorations>  
                                    <TextDecoration Location="Underline">  
                                        <TextDecoration.Pen>  
                                            <Pen Brush="{StaticResource WavyBrush1}" Thickness="6"/>  
                                        </TextDecoration.Pen>  
                                    </TextDecoration>  
                                </Run.TextDecorations>  
                            </Run>  
                        </Paragraph>  
                    </FlowDocument>  
                </RichTextBox>  
            </Canvas>  
    
        </StackPanel>  
    

    The result:
    170279-image.png


    If the response is helpful, please click "Accept Answer" and upvote it.
     Note: Please follow the steps in our [documentation][5] to enable e-mail notifications if you want to receive the related email notification for this thread. 

    [5]: https://learn.microsoft.com/en-us/answers/articles/67444/email-notifications.html


  4. Hui Liu-MSFT 38,251 Reputation points Microsoft Vendor
    2022-02-11T02:09:26.143+00:00

    You could try to refer the following code to your project.
    MainWindow.xaml:

    <Window.DataContext>  
            <local:ViewModel />  
        </Window.DataContext>  
        <Grid>  
            <Grid.ColumnDefinitions>  
                <ColumnDefinition Width="Auto" />  
                <ColumnDefinition Width="*" />  
            </Grid.ColumnDefinitions>  
            <Grid Margin="10,10,10,10">  
                <Grid.RowDefinitions>  
                    <RowDefinition Height="50" />  
                    <RowDefinition Height="Auto" />  
                    <RowDefinition Height="Auto" />  
                    <RowDefinition Height="Auto" />  
                    <RowDefinition Height="Auto" />  
                    <RowDefinition Height="Auto" />  
                    <RowDefinition Height="Auto" />  
                    <RowDefinition Height="Auto" />  
                    <RowDefinition Height="Auto" />  
                </Grid.RowDefinitions>  
                <Grid Grid.Row="1" Margin="10,10,10,10">  
                    <StackPanel>  
                        <TextBlock Text="StartPoint" />  
                        <Grid Margin="10,1,10,1">  
                            <Grid.ColumnDefinitions>  
                                <ColumnDefinition Width="Auto" />  
                                <ColumnDefinition MinWidth="60" />  
                            </Grid.ColumnDefinitions>  
                            <TextBlock VerticalAlignment="Center">x:</TextBlock>  
                            <TextBox Grid.Column="1" Text="{Binding FirstPointX}" />  
                        </Grid>  
                        <Grid Margin="10,1,10,1">  
                            <Grid.ColumnDefinitions>  
                                <ColumnDefinition Width="Auto" />  
                                <ColumnDefinition MinWidth="60" />  
                            </Grid.ColumnDefinitions>  
                            <TextBlock VerticalAlignment="Center">Y:</TextBlock>  
                            <TextBox Grid.Column="1" Text="{Binding FirstPointY}" />  
                        </Grid>  
                    </StackPanel>  
                </Grid>  
                <Grid Grid.Row="2" Margin="10,10,10,10">  
                    <StackPanel>  
                        <TextBlock Text="EndPoint" />  
                        <Grid Margin="10,1,10,1">  
                            <Grid.ColumnDefinitions>  
                                <ColumnDefinition Width="Auto" />  
                                <ColumnDefinition MinWidth="60" />  
                            </Grid.ColumnDefinitions>  
                            <TextBlock VerticalAlignment="Center">x:</TextBlock>  
                            <TextBox Grid.Column="1" Text="{Binding SecondPointX}" />  
                        </Grid>  
                        <Grid Margin="10,1,10,1">  
                            <Grid.ColumnDefinitions>  
                                <ColumnDefinition Width="Auto" />  
                                <ColumnDefinition MinWidth="60" />  
                            </Grid.ColumnDefinitions>  
                            <TextBlock VerticalAlignment="Center">Y:</TextBlock>  
                            <TextBox Grid.Column="1" Text="{Binding SecondPointY}" />  
                        </Grid>  
                    </StackPanel>  
                </Grid>  
                <Grid Grid.Row="3" Margin="10,10,10,10">  
                    <Grid>  
                        <Grid.ColumnDefinitions>  
                            <ColumnDefinition Width="Auto" />  
                            <ColumnDefinition MinWidth="60" />  
                        </Grid.ColumnDefinitions>  
                        <TextBlock VerticalAlignment="Center">frequency</TextBlock>  
                        <TextBox Grid.Column="1" Text="{Binding WaveLength}" />  
                    </Grid>  
                </Grid>  
                <Grid Grid.Row="4" Margin="10,10,10,10">  
                    <Grid>  
                        <Grid.ColumnDefinitions>  
                            <ColumnDefinition Width="Auto" />  
                            <ColumnDefinition MinWidth="60" />  
                        </Grid.ColumnDefinitions>  
                        <TextBlock VerticalAlignment="Center">amplitude</TextBlock>  
                        <TextBox Grid.Column="1" Text="{Binding WaveHeight}" />  
                    </Grid>  
                </Grid>  
                <Grid Grid.Row="5" Margin="10,10,10,10">  
                    <Grid>  
                        <Grid.ColumnDefinitions>  
                            <ColumnDefinition Width="Auto" />  
                            <ColumnDefinition MinWidth="60" />  
                        </Grid.ColumnDefinitions>  
                        <TextBlock VerticalAlignment="Center">increase</TextBlock>  
                        <TextBox Grid.Column="1" Text="{Binding CurveSquaring}" />  
                    </Grid>  
                </Grid>  
                <Grid Grid.Row="6" Margin="10,10,10,10">  
                    <Grid>  
                        <Grid.ColumnDefinitions>  
                            <ColumnDefinition Width="Auto" />  
                            <ColumnDefinition MinWidth="60" />  
                        </Grid.ColumnDefinitions>  
                        <TextBlock VerticalAlignment="Center">line width</TextBlock>  
                        <TextBox Grid.Column="1" Text="{Binding BorderThickness}" />  
                    </Grid>  
                </Grid>  
                <Button x:Name="draw" Grid.Row="7" Margin="10,10,10,10" Content="DrawWavyLine" Click="DrawWaveLine_OnClick" />  
                <Button x:Name="remove" Grid.Row="8" Margin="10,10,10,10" Content="RemoveWavyLine" Click="CleanGrid_OnClick" />  
            </Grid>  
            <Grid Grid.Column="1" x:Name="WaveLinePanel">  
    
            </Grid>  
        </Grid>  
    

    MainWindow.xaml.cs:

    using System;  
    using System.Collections.Generic;  
    using System.ComponentModel;  
    using System.Runtime.CompilerServices;  
    using System.Windows;  
    using System.Windows.Media;  
    
    namespace WavyLineDemo  
    {  
      public partial class MainWindow : Window  
      {  
        public MainWindow()  
        {  
          InitializeComponent();  
          ViewModel = (ViewModel)DataContext;  
        }  
        public ViewModel ViewModel { get; set; }  
    
        private void DrawWaveLine_OnClick(object sender, RoutedEventArgs e)  
        {  
          var waveLine = ViewModel.Draw();  
          WaveLinePanel.Children.Add(waveLine);  
        }  
    
        private void CleanGrid_OnClick(object sender, RoutedEventArgs e)  
        {  
          WaveLinePanel.Children.Clear();  
        }  
      }  
      public class WaveLine : UIElement  
      {  
        public WaveLine()  
        {  
          _waveLineList = new VisualCollection(this);  
        }  
    
        public double BorderThickness { get; set; } = 1.0;  
        public double WaveLength { get; set; } = 4;  
        public double WaveHeight { get; set; } = 5;  
    
        public double CurveSquaring { get; set; } = 0.57;  
    
        protected override int VisualChildrenCount => _waveLineList.Count;  
    
        public void DrawWaveLine(Point startPoint, Point endPoint)  
        {  
          var p1 = startPoint;  
          var p2 = endPoint;  
    
          var distance = (p1 - p2).Length;  
    
          var angle = CalculateAngle(p1, p2);  
          var waveLength = WaveLength;  
          var waveHeight = WaveHeight;  
          var howManyWaves = distance / waveLength;  
          var waveInterval = distance / howManyWaves;  
          var maxBcpLength =  
              Math.Sqrt(waveInterval / 4.0 * (waveInterval / 4.0) + waveHeight / 2.0 * (waveHeight / 2.0));  
    
          var curveSquaring = CurveSquaring;  
          var bcpLength = maxBcpLength * curveSquaring;  
          var bcpInclination = CalculateAngle(new Point(0, 0), new Point(waveInterval / 4.0, waveHeight / 2.0));  
    
          var wigglePoints = new List<(Point bcpOut, Point bcpIn, Point anchor)>();  
          var prevFlexPt = p1;  
          var polarity = 1;  
    
          for (var waveIndex = 0; waveIndex < howManyWaves * 2; waveIndex++)  
          {  
            var bcpOutAngle = angle + bcpInclination * polarity;  
            var bcpOut = new Point(prevFlexPt.X + Math.Cos(bcpOutAngle) * bcpLength,  
                prevFlexPt.Y + Math.Sin(bcpOutAngle) * bcpLength);  
            var flexPt = new Point(prevFlexPt.X + Math.Cos(angle) * waveInterval / 2.0,  
                prevFlexPt.Y + Math.Sin(angle) * waveInterval / 2.0);  
            var bcpInAngle = angle + (Math.PI - bcpInclination) * polarity;  
            var bcpIn = new Point(flexPt.X + Math.Cos(bcpInAngle) * bcpLength,  
                flexPt.Y + Math.Sin(bcpInAngle) * bcpLength);  
    
            wigglePoints.Add((bcpOut, bcpIn, flexPt));  
    
            polarity *= -1;  
            prevFlexPt = flexPt;  
          }  
    
          var streamGeometry = new StreamGeometry();  
          using (var streamGeometryContext = streamGeometry.Open())  
          {  
            streamGeometryContext.BeginFigure(wigglePoints[0].anchor, true, false);  
    
            for (var i = 1; i < wigglePoints.Count; i += 1)  
            {  
              var (bcpOut, bcpIn, anchor) = wigglePoints[i];  
    
              streamGeometryContext.BezierTo(bcpOut, bcpIn, anchor, true, false);  
            }  
          }  
    
          var visual = new DrawingVisual();  
          var fillBrush = Brushes.Transparent;  
          var lineBrush = Brushes.DarkSeaGreen;  
          var borderThickness = BorderThickness;  
          var strokePen = new Pen(lineBrush, borderThickness);  
          using (var context = visual.RenderOpen())  
          {  
            context.DrawGeometry(fillBrush, strokePen, streamGeometry);  
    
          }  
    
          _waveLineList.Add(visual);  
        }  
        private readonly VisualCollection _waveLineList;  
    
        private static double CalculateAngle(Point p1, Point p2)  
        {  
          return Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);  
        }  
    
        protected override Visual GetVisualChild(int index)  
        {  
          return _waveLineList[index];  
        }  
      }  
      public class ViewModel : INotifyPropertyChanged  
      {  
        public string WaveLength  
        {  
          get => _waveLength;  
          set  
          {  
            if (value == _waveLength) return;  
            _waveLength = value;  
            OnPropertyChanged();  
          }  
        }  
    
        public string WaveHeight  
        {  
          get => _waveHeight;  
          set  
          {  
            if (value == _waveHeight) return;  
            _waveHeight = value;  
            OnPropertyChanged();  
          }  
        }  
    
        public string CurveSquaring  
        {  
          get => _curveSquaring;  
          set  
          {  
            if (value == _curveSquaring) return;  
            _curveSquaring = value;  
            OnPropertyChanged();  
          }  
        }  
    
        public string BorderThickness  
        {  
          get => _borderThickness;  
          set  
          {  
            if (value == _borderThickness) return;  
            _borderThickness = value;  
            OnPropertyChanged();  
          }  
        }  
    
        public string FirstPointY  
        {  
          get => _firstPointY;  
          set  
          {  
            if (value == _firstPointY) return;  
            _firstPointY = value;  
            OnPropertyChanged();  
          }  
        }  
    
        public string SecondPointX  
        {  
          get => _secondPointX;  
          set  
          {  
            if (value == _secondPointX) return;  
            _secondPointX = value;  
            OnPropertyChanged();  
          }  
        }  
    
        public string SecondPointY  
        {  
          set  
          {  
            if (value == _secondPointY) return;  
            _secondPointY = value;  
            OnPropertyChanged();  
          }  
          get => _secondPointY;  
        }  
    
        public string FirstPointX  
        {  
          set  
          {  
            if (value == _firstPointX) return;  
            _firstPointX = value;  
            OnPropertyChanged();  
          }  
          get => _firstPointX;  
        }  
    
        public WaveLine Draw()  
        {  
          double.TryParse(FirstPointX, out var firstPointX);  
          double.TryParse(FirstPointY, out var firstPointY);  
          double.TryParse(SecondPointX, out var secondPointX);  
          double.TryParse(SecondPointY, out var secondPointY);  
          double.TryParse(BorderThickness, out var borderThickness);  
          double.TryParse(CurveSquaring, out var curveSquaring);  
          double.TryParse(WaveHeight, out var waveHeight);  
          double.TryParse(WaveLength, out var waveLength);  
    
          var waveLine = new WaveLine  
          {  
            BorderThickness = borderThickness,  
            CurveSquaring = curveSquaring,  
            WaveHeight = waveHeight,  
            WaveLength = waveLength  
          };  
          waveLine.DrawWaveLine(new Point(firstPointX, firstPointY), new Point(secondPointX, secondPointY));  
          return waveLine;  
        }  
        private string _borderThickness = 1.ToString();  
        private string _curveSquaring = 0.57.ToString();  
        private string _firstPointX = 50.ToString();  
        private string _firstPointY = 290.ToString();  
        private string _secondPointX = 760.ToString();  
        private string _secondPointY = 226.ToString();  
        private string _waveHeight = 90.ToString();  
        private string _waveLength = 100.ToString();  
    
        public event PropertyChangedEventHandler PropertyChanged;  
        protected void OnPropertyChanged([CallerMemberName] string name = "") =>  
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));  
      }  
    }  
    

    The result:
    173328-image.png


    If the response is helpful, please click "Accept Answer" and upvote it.
     Note: Please follow the steps in our [documentation][5] to enable e-mail notifications if you want to receive the related email notification for this thread. 

    [5]: https://learn.microsoft.com/en-us/answers/articles/67444/email-notifications.html

    0 comments No comments