15

In WPF, is there an easy way to add wavy underlines (like spelling errors in Word) to FlowDocument elements? There's the Underline class, but there seems to be no way to style it.

Tony the Pony
  • 40,327
  • 71
  • 187
  • 281

5 Answers5

30

You can create the wavy effect using the following changes to Robert Macne's solution

Add a visual brush to the Grid.Resources section:

<VisualBrush x:Key="WavyBrush" 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>

And change the pen to:

<Pen Brush="{StaticResource WavyBrush}" Thickness="6" />
bstoney
  • 6,594
  • 5
  • 44
  • 51
  • +1, this is just what I was thinking of but couldn't get it to work correctly, nice! – Robert Macnee Mar 11 '09 at 15:09
  • I found getting a cubic bezier path to scale nicely quite difficult. Two diagonal lines works as well, but didn't look as good. – bstoney Mar 11 '09 at 21:52
  • That WavyBrush above is not a wave I tested it. – Elisabeth Oct 13 '10 at 10:05
  • Lisa - I've re-tested in Expression Blend 4 and updated the visual brush. It now looks a bit more 'wavy' at 100%, the usual scale :) – bstoney Oct 14 '10 at 06:06
  • This worked well for me, although it would be nice if you showed the equivalent code-behind as well as just XAML. – Puppy Jun 14 '13 at 11:24
  • @DeadMG I would only recommend using the XAML as it is much easier to read and reuse. If you need to use the `WavyBrush` in the code behind you can load it from a common resource using `Application.Current.FindResource("WavyBrush");` – bstoney Jun 19 '13 at 03:59
6

A red underline is simple enough:

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid.Resources>
        <FlowDocument x:Key="doc">
            <Paragraph>
                <Run Text="This text is underlined in red.">
                    <Run.TextDecorations>
                        <TextDecoration Location="Underline">
                            <TextDecoration.Pen>
                                <Pen Brush="Red" Thickness="1" DashStyle="{x:Static DashStyles.Dot}"/>
                            </TextDecoration.Pen>
                        </TextDecoration>
                    </Run.TextDecorations>
                </Run>
            </Paragraph>
        </FlowDocument>
    </Grid.Resources>
    <FlowDocumentReader Document="{StaticResource doc}"/>
</Grid>

A wavy red underline would be a bit more involved, but I think you could create a VisualBrush with a wavy red thing in it, and set that as the Brush of the Pen that you specify for the underlining TextDecoration. Edit: see bstoney's post for this.

Robert Macnee
  • 11,650
  • 4
  • 40
  • 35
  • hi Robert, pls see my WPF Question, its possible or not http://stackoverflow.com/questions/17541780/how-to-set-inline-images-vertically-center-in-richtextbox – Avinash Singh Jul 10 '13 at 05:19
5

Here is @bstoney's solution implemented in code.

Pen path_pen = new Pen(new SolidColorBrush(Colors.Red), 0.2);
path_pen.EndLineCap = PenLineCap.Square;
path_pen.StartLineCap = PenLineCap.Square;

Point path_start = new Point(0, 1);
BezierSegment path_segment = new BezierSegment(new Point(1, 0), new Point(2, 2), new Point(3, 1), true);
PathFigure path_figure = new PathFigure(path_start, new PathSegment[] { path_segment }, false);
PathGeometry path_geometry = new PathGeometry(new PathFigure[] { path_figure });

DrawingBrush squiggly_brush = new DrawingBrush();
squiggly_brush.Viewport = new Rect(0, 0, 6, 4);
squiggly_brush.ViewportUnits = BrushMappingMode.Absolute;
squiggly_brush.TileMode = TileMode.Tile;
squiggly_brush.Drawing = new GeometryDrawing(null, path_pen, path_geometry);

TextDecoration squiggly = new TextDecoration();
squiggly.Pen = new Pen(squiggly_brush, 6);
text_box.TextDecorations.Add(squiggly);
Jehanlos
  • 77
  • 1
  • 8
1

I know this is an old question but I prefer this brush. It's a little angular but very clean.

    <VisualBrush x:Key="WavyBrush">
        <VisualBrush.Visual>
            <Path Data="M 0,2 L 2,0 4,2 6,0 8,2 10,0 12,2" Stroke="Red"/>
        </VisualBrush.Visual>
    </VisualBrush>
strattonn
  • 1,790
  • 2
  • 22
  • 41
0

Another way can be : Creating a drawing group with two lines(kind of inverted V) and passing that drawing group as content for drawing brush and having Tile as mode so that it can repeat. But here peaks will be pointed as we are drawing two lines, one starts from others end.

Sample will look like : enter image description here

        // Configuring brush.
        DrawingGroup dg = new DrawingGroup();
        using (DrawingContext dc = dg.Open())
        {
            Pen pen = new Pen(Brushes.Red, 1);
            dc.DrawLine(pen, new System.Windows.Point(0.0, 0.0), new System.Windows.Point(3.0, 3.0));
            dc.DrawLine(pen, new System.Windows.Point(3.0, 3.0), new System.Windows.Point(5.0, 0.0));
        }
        public DrawingBrush brush = new DrawingBrush(dg);
        brush.TileMode = TileMode.Tile;
        brush.Viewport = new Rect(0, 0, 4, 4);
        brush.ViewportUnits = BrushMappingMode.Absolute;
        
        // Drawing line on VisualCollection
        private VisualCollection visualCollection = new VisualCollection(this);
        DrawingVisual dv = new DrawingVisual();
        using (DrawingContext drawingContext = dv.RenderOpen())
        {
            drawingContext.DrawLine(new Pen(brush, 2), new Point(50, 100), new Point(200, 100));
            base.OnRender(drawingContext);
        }
        this.visualCollection.Add(dv);
Priyank Kotiyal
  • 241
  • 2
  • 13