4

In my WPF application, I would like to style reactangle for dag and drop the file. The appearance of this box should look like this.

What i want

It is possible to achieve this result in XAML?

So far I have managed to achieve just this.

What I currently have

The problem is in the corners I. The appearance of the corners should be like connection line of two dashes. For example, bottom left corner - "L" with possibility to resize the reactangle.

Here is my current code, which a created with help of this answer:
How can I achieve a dashed or dotted border in WPF?

<Rectangle 
        Fill="LightGray"
        AllowDrop="True"
        Stroke="#FF000000"
        StrokeThickness="2" 
        StrokeDashArray="5 4"
        SnapsToDevicePixels="True"
        MinHeight="200"
        MinWidth="200"
        />
Community
  • 1
  • 1
Vrabec0853
  • 43
  • 6

3 Answers3

5

To get these nice, L-shaped corners in WPF you'll have to draw the horizontal and vertical borders separately since the StrokeDashArray will not (always) be the same for both.

Your requirements for the StrokeDashArray are:

  • Each line should start and end with a full dash
  • The length of the dashes should stay the same
  • excess/missing distance should be filled by stretching the space between dashes

To get the precise length needed to draw a line like that you have to calculate the number of lines (+1) and spaces in your dashes line, e.g. like this:

private IEnumerable<double> GetDashArray(double length)
{
    double useableLength = length - StrokeDashLine;
    int lines = (int)Math.Round(useableLength/(StrokeDashLine + StrokeDashSpace));
    useableLength -= lines*StrokeDashLine;

    double actualSpacing = useableLength/lines;

    yield return StrokeDashLine / StrokeThickness;
    yield return actualSpacing / StrokeThickness;
} 

Wrap that up in a custom control and you will get something like this:

Dashed Rectangle with L-shaped corners

<local:NiceCornersControl Fill="LightGray" Stroke="Black" 
  StrokeThickness="2" StrokeDashLine="5" StrokeDashSpace="5">
    <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" 
      Text="Drop files here"/>
</local:NiceCornersControl>

A couple of things you should be aware of:

  • To have your line "inside" of the rectangle you need to offset them by StrokeThickness / 2
  • The DashStyle will scale with your StrokeThickness
  • This will probably look odd for semi-transparent stroke colors

Full code for the control:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace InsertNamespaceHere
{
    public class NiceCornersControl : ContentControl
    {
        public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
            "Stroke", typeof(Brush), typeof(NiceCornersControl), new PropertyMetadata(default(Brush), OnVisualPropertyChanged));

        public Brush Stroke
        {
            get { return (Brush)GetValue(StrokeProperty); }
            set { SetValue(StrokeProperty, value); }
        }

        public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
            "StrokeThickness", typeof(double), typeof(NiceCornersControl), new PropertyMetadata(default(double), OnVisualPropertyChanged));

        public double StrokeThickness
        {
            get { return (double)GetValue(StrokeThicknessProperty); }
            set { SetValue(StrokeThicknessProperty, value); }
        }

        public static readonly DependencyProperty StrokeDashLineProperty = DependencyProperty.Register(
            "StrokeDashLine", typeof(double), typeof(NiceCornersControl), new PropertyMetadata(default(double), OnVisualPropertyChanged));

        public double StrokeDashLine
        {
            get { return (double)GetValue(StrokeDashLineProperty); }
            set { SetValue(StrokeDashLineProperty, value); }
        }

        public static readonly DependencyProperty StrokeDashSpaceProperty = DependencyProperty.Register(
            "StrokeDashSpace", typeof(double), typeof(NiceCornersControl), new PropertyMetadata(default(double), OnVisualPropertyChanged));

        public double StrokeDashSpace
        {
            get { return (double)GetValue(StrokeDashSpaceProperty); }
            set { SetValue(StrokeDashSpaceProperty, value); }
        }

        public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
            "Fill", typeof(Brush), typeof(NiceCornersControl), new PropertyMetadata(default(Brush), OnVisualPropertyChanged));

        public Brush Fill
        {
            get { return (Brush)GetValue(FillProperty); }
            set { SetValue(FillProperty, value); }
        }

        private static void OnVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((NiceCornersControl)d).InvalidateVisual();
        }

        public NiceCornersControl()
        {
            SnapsToDevicePixels = true;
            UseLayoutRounding = true;
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            double w = ActualWidth;
            double h = ActualHeight;
            double x = StrokeThickness / 2.0;

            Pen horizontalPen = GetPen(ActualWidth - 2.0 * x);
            Pen verticalPen = GetPen(ActualHeight - 2.0 * x);

            drawingContext.DrawRectangle(Fill, null, new Rect(new Point(0, 0), new Size(w, h)));

            drawingContext.DrawLine(horizontalPen, new Point(x, x), new Point(w - x, x));
            drawingContext.DrawLine(horizontalPen, new Point(x, h - x), new Point(w - x, h - x));

            drawingContext.DrawLine(verticalPen, new Point(x, x), new Point(x, h - x));
            drawingContext.DrawLine(verticalPen, new Point(w - x, x), new Point(w - x, h - x));
        }

        private Pen GetPen(double length)
        {
            IEnumerable<double> dashArray = GetDashArray(length);
            return new Pen(Stroke, StrokeThickness)
            {
                DashStyle = new DashStyle(dashArray, 0),
                EndLineCap = PenLineCap.Square,
                StartLineCap = PenLineCap.Square,
                DashCap = PenLineCap.Flat
            };
        }

        private IEnumerable<double> GetDashArray(double length)
        {
            double useableLength = length - StrokeDashLine;
            int lines = (int)Math.Round(useableLength / (StrokeDashLine + StrokeDashSpace));
            useableLength -= lines * StrokeDashLine;
            double actualSpacing = useableLength / lines;

            yield return StrokeDashLine / StrokeThickness;
            yield return actualSpacing / StrokeThickness;
        }
    }
}
Manfred Radlwimmer
  • 13,257
  • 13
  • 53
  • 62
  • 1
    @Vrabec0853 You would use it **instead** of the `Rectangle` control (or write a `ControlTemplate` that uses it, or overwrite `Rectangle` instead of `Control` / `ContentControl` etc.). Alternatively you could write a `MultiValueConverter` that takes the width and height of the rectangle and converts it to a ridiculously long StrokeDashArray that contains *every single dash* needed to achieve this appearance. – Manfred Radlwimmer Feb 20 '17 at 11:28
  • @fdsfdsfdsfds If you want the corners to match as described in the question - yes – Manfred Radlwimmer Nov 05 '19 at 19:13
0

This is not an answer, but a suggestion. In fact, I 'm not sure if that is possible in an easy way (maybe you could calculate the StrokeDashArray by getting the Width and Height of the control). However, you can use animation:

<Grid Margin="3">
    <Rectangle  Name="Rect"
    Fill="LightGray"
    AllowDrop="True"
    Stroke="#FF000000"
    StrokeThickness="2" 
    StrokeDashArray="5 4"
    SnapsToDevicePixels="True"
    MinHeight="200"
    MinWidth="200"
    >
        <Rectangle.Triggers>
            <EventTrigger RoutedEvent="Window.Loaded">
                <BeginStoryboard>
                    <Storyboard >
                        <DoubleAnimation To="100" Duration="0:0:10" RepeatBehavior="Forever" By="1" 
                 Storyboard.TargetProperty="StrokeDashOffset" Storyboard.TargetName="Rect"/>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Rectangle.Triggers>
    </Rectangle>
</Grid>
rmojab63
  • 3,513
  • 1
  • 15
  • 28
0

I modified the accepted answer(@Manfred Radlwimmer) to make it look like Rectangle instead of ContentControl.

<c:AlignDashCornerRect Stroke="#7FFF0000"
                       StrokeDashCap="Round"
                       StrokeDashLine="5"
                       StrokeDashSpace="10"
                       StrokeEndLineCap="Round"
                       StrokeStartLineCap="Round"
                       StrokeThickness="4" />

AlignDashCornerRect

public class AlignDashCornerRect : FrameworkElement
{
    public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
        "Fill", typeof(Brush), typeof(AlignDashCornerRect), new PropertyMetadata(default(Brush), OnVisualPropertyChanged));

    public static readonly DependencyProperty StrokeDashCapProperty =
        DependencyProperty.Register("StrokeDashCap", typeof(PenLineCap), typeof(AlignDashCornerRect), new PropertyMetadata(PenLineCap.Flat, OnVisualPropertyChanged));

    public static readonly DependencyProperty StrokeDashLineProperty = DependencyProperty.Register(
            "StrokeDashLine", typeof(double), typeof(AlignDashCornerRect), new PropertyMetadata(default(double), OnVisualPropertyChanged));

    public static readonly DependencyProperty StrokeDashSpaceProperty = DependencyProperty.Register(
        "StrokeDashSpace", typeof(double), typeof(AlignDashCornerRect), new PropertyMetadata(default(double), OnVisualPropertyChanged));

    public static readonly DependencyProperty StrokeEndLineCapProperty =
        DependencyProperty.Register("StrokeEndLineCap", typeof(PenLineCap), typeof(AlignDashCornerRect), new PropertyMetadata(PenLineCap.Flat, OnVisualPropertyChanged));

    public static readonly DependencyProperty StrokeLineJoinProperty =
        DependencyProperty.Register("StrokeLineJoin", typeof(PenLineJoin), typeof(AlignDashCornerRect), new PropertyMetadata(PenLineJoin.Miter, OnVisualPropertyChanged));

    public static readonly DependencyProperty StrokeMiterLimitProperty =
        DependencyProperty.Register("StrokeMiterLimit", typeof(double), typeof(AlignDashCornerRect), new PropertyMetadata(10.0d, OnVisualPropertyChanged));

    public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
                    "Stroke", typeof(Brush), typeof(AlignDashCornerRect), new PropertyMetadata(default(Brush), OnVisualPropertyChanged));

    public static readonly DependencyProperty StrokeStartLineCapProperty =
        DependencyProperty.Register("StrokeStartLineCap", typeof(PenLineCap), typeof(AlignDashCornerRect), new PropertyMetadata(PenLineCap.Flat, OnVisualPropertyChanged));

    public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
            "StrokeThickness", typeof(double), typeof(AlignDashCornerRect), new PropertyMetadata(default(double), OnVisualPropertyChanged));

    public Brush Fill
    {
        get => (Brush)GetValue(FillProperty);
        set => SetValue(FillProperty, value);
    }

    public Brush Stroke
    {
        get => (Brush)GetValue(StrokeProperty);
        set => SetValue(StrokeProperty, value);
    }

    public PenLineCap StrokeDashCap
    {
        get => (PenLineCap)GetValue(StrokeDashCapProperty);
        set => SetValue(StrokeDashCapProperty, value);
    }

    public double StrokeDashLine
    {
        get => (double)GetValue(StrokeDashLineProperty);
        set => SetValue(StrokeDashLineProperty, value);
    }

    public double StrokeDashSpace
    {
        get => (double)GetValue(StrokeDashSpaceProperty);
        set => SetValue(StrokeDashSpaceProperty, value);
    }

    public PenLineCap StrokeEndLineCap
    {
        get => (PenLineCap)GetValue(StrokeEndLineCapProperty);
        set => SetValue(StrokeEndLineCapProperty, value);
    }

    public PenLineJoin StrokeLineJoin
    {
        get => (PenLineJoin)GetValue(StrokeLineJoinProperty);
        set => SetValue(StrokeLineJoinProperty, value);
    }

    public double StrokeMiterLimit
    {
        get => (double)GetValue(StrokeMiterLimitProperty);
        set => SetValue(StrokeMiterLimitProperty, value);
    }

    public PenLineCap StrokeStartLineCap
    {
        get => (PenLineCap)GetValue(StrokeStartLineCapProperty);
        set => SetValue(StrokeStartLineCapProperty, value);
    }

    public double StrokeThickness
    {
        get => (double)GetValue(StrokeThicknessProperty);
        set => SetValue(StrokeThicknessProperty, value);
    }

    public AlignDashCornerRect()
    {
        SnapsToDevicePixels = true;
        UseLayoutRounding = true;
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        double w = ActualWidth;
        double h = ActualHeight;
        double x = StrokeThickness / 2.0;

        var horizontalPen = GetPen(ActualWidth - (2.0 * x));
        var verticalPen = GetPen(ActualHeight - (2.0 * x));

        drawingContext.DrawRectangle(Fill, null, new Rect(new Point(0, 0), new Size(w, h)));

        drawingContext.DrawLine(horizontalPen, new Point(x, x), new Point(w - x, x));
        drawingContext.DrawLine(horizontalPen, new Point(x, h - x), new Point(w - x, h - x));

        drawingContext.DrawLine(verticalPen, new Point(x, x), new Point(x, h - x));
        drawingContext.DrawLine(verticalPen, new Point(w - x, x), new Point(w - x, h - x));
    }

    private static void OnVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((AlignDashCornerRect)d).InvalidateVisual();
    }

    private IEnumerable<double> GetDashArray(double length)
    {
        double useableLength = length - StrokeDashLine;
        int lines = (int)Math.Round(useableLength / (StrokeDashLine + StrokeDashSpace));
        useableLength -= lines * StrokeDashLine;
        double actualSpacing = useableLength / lines;

        yield return StrokeDashLine / StrokeThickness;
        yield return actualSpacing / StrokeThickness;
    }

    private Pen GetPen(double length)
    {
        var dashArray = GetDashArray(length);

        return new Pen(Stroke, StrokeThickness) {
            DashStyle = new DashStyle(dashArray, 0),
            DashCap = StrokeDashCap,
            StartLineCap = StrokeStartLineCap,
            EndLineCap = StrokeEndLineCap,
            LineJoin = StrokeLineJoin,
            MiterLimit = StrokeMiterLimit
        };
    }
}
CodingNinja
  • 83
  • 1
  • 1
  • 11