如何在WPF中实现虚线或点线边框?

91

我有一个ListViewItem,我正在应用一个Style,我想把一个虚线灰色线作为底部的Border

在WPF中,我该如何实现?我只能看到实色刷子。


你有检查过这个链接吗?https://dev59.com/-knSa4cB1Zd3GeqPSv75 - Alex Aza
1
不,谢谢。你不知道有没有一个简单的方法吗?这似乎有点像一个hack。 - dan
相关帖子,也许是最好的答案 https://dev59.com/l27Xa4cB1Zd3GeqPrplS - jv_
7个回答

127

这在我们的应用程序中非常有效,使我们能够使用真正的边框而不必围绕矩形进行操作:

<Border BorderThickness="1,0,1,1">
   <Border.BorderBrush>
      <DrawingBrush Viewport="0,0,8,8" ViewportUnits="Absolute" TileMode="Tile">
         <DrawingBrush.Drawing>
            <DrawingGroup>
               <GeometryDrawing Brush="Black">
                  <GeometryDrawing.Geometry>
                     <GeometryGroup>
                        <RectangleGeometry Rect="0,0,50,50" />
                        <RectangleGeometry Rect="50,50,50,50" />
                     </GeometryGroup>
                  </GeometryDrawing.Geometry>
               </GeometryDrawing>
            </DrawingGroup>
         </DrawingBrush.Drawing>
      </DrawingBrush>
   </Border.BorderBrush>

   <TextBlock Text="Content Goes Here!" Margin="5"/>
</Border>

请注意,视口决定了线条中虚线的大小。在这种情况下,它生成了八像素的虚线。Viewport="0,0,4,4"将给您四像素的虚线。


如何将此样式应用于其他需要相同样式的元素? - Charanraj Golla
你可以定义一个包含DrawingBrush的样式,然后将该样式应用于任何你想要的元素。 - Rand Scullard
这两个矩形实际上是对齐的,以使此模式在整个边框周围,水平和垂直方向,左侧和右侧都可以使用。(不过最好不要尝试用于非矩形线条...) - ygoe
1
这样做的话,破折号的偏移仍然可以进行动画吗? - jrandomuser
这就是它的工作原理:https://prnt.sc/pWytkHZJt2ea 这是带有50像素边框的情况(只是为了看到它如何填充)。这似乎仅支持1像素边框。此外,矩形的大小可能是分数形式(如10.3像素),因此有时边框会以一些完全意想不到的东西填充。 - Rustem Zinnatullin
显示剩余2条评论

107

您可以使用类似下面代码中的矩形来创建带有点线或虚线的线:

<Rectangle Stroke="#FF000000" Height="1" StrokeThickness="1" StrokeDashArray="4 4"
                                                       SnapsToDevicePixels="True"/>

使用这个开始,并根据你的场景自定义你的列表视图。


2
有没有办法用圆角来实现这个? - Jordan
10
请使用 RadiusX="10" RadiusY="10" - Ondrej Janacek
有没有办法倾斜虚线? - droidmainiac

40

我来晚了,但以下解决方案适用于我。它比其他两个解决方案都稍微简单/更好:

<Border BorderThickness="1">
  <Border.BorderBrush>
    <VisualBrush>
      <VisualBrush.Visual>
        <Rectangle StrokeDashArray="4 2" Stroke="Gray" StrokeThickness="1"
                  Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
                  Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}"/>
      </VisualBrush.Visual>
    </VisualBrush>
  </Border.BorderBrush>

  <TextBlock Text="Whatever" />
</Border>

太棒了。第一个答案有圆角问题,这个解决方案非常出色。只需将矩形的RadiusX/Y设置为与边框上的CornerRadius相同即可。 - Bill Tarbell
应该是首选答案。我喜欢这个答案,因为它是最干净、最短和最易读(直观)的解决方案。 - Erik Bongers
2
然而!如果您将此刷子用作“静态资源”或“动态资源”,则会出现问题。我认为是由于“宽度”和“高度”绑定失败所致。 第一个答案确实可以作为可重复使用的资源。遗憾。我真的很喜欢这个解决方案。 - Erik Bongers

6

Xaml

<Grid>
<Grid.RowDefinitions><RowDefinition Height="auto"/></Grid.RowDefinitions>
<Grid.ColumnDefinitions><ColumnDefinition Width="auto"/></Grid.ColumnDefinitions>
<Rectangle RadiusX="9" RadiusY="9" Fill="White" Stroke="Black" StrokeDashArray="1,2"/>
<TextBlock Padding = "4,2" Text="Whatever"/>
</Grid>

3
我们的团队最近收到了这个需求,我们通过创建一个自定义控件 DashedBorder 来解决它,该控件扩展了 Border 并添加了虚线边框功能。
它有三个新的依赖属性:
  • UseDashedBorder(bool)
  • DashedBorderBrush(Brush)
  • StrokeDashArray(DoubleCollection)
可以像这样使用:
<controls:DashedBorder UseDashedBorder="True"
                       DashedBorderBrush="#878787"
                       StrokeDashArray="2 1"
                       Background="#EBEBEB"                               
                       BorderThickness="3"
                       CornerRadius="10 10 10 10">
    <TextBlock Text="Dashed Border"
               Margin="6 2 6 2"/>
</controls:DashedBorder>

UseDashedBorder设置为true时,它将创建一个具有2个矩形的VisualBrush并将其设置为BorderBrush(这就是为什么我们需要一个额外的属性来表示实际BorderBrush的颜色)。第一个矩形用于创建虚线,第二个矩形用于用边框的Background填充空隙。
它将Rectangle的虚线属性映射到DashedBorder属性,如下所示:
  • StrokeDashArray => StrokeDashArray
  • Stroke => DashedBorderBrush
  • StrokeThickness => BorderThickness.Left
  • RadiusX => CornerRadius.TopLeft
  • RadiusY => CornerRadius.TopLeft
  • Width => ActualWidth
  • Height => ActualHeight
DashedBorder.cs会产生以下结果:enter image description here
    public class DashedBorder : Border
    {
        private static DoubleCollection? emptyDoubleCollection;
        private static DoubleCollection EmptyDoubleCollection()
        {
            if (emptyDoubleCollection == null)
            {
                DoubleCollection doubleCollection = new DoubleCollection();
                doubleCollection.Freeze();
                emptyDoubleCollection = doubleCollection;
            }
            return emptyDoubleCollection;
        }

        public static readonly DependencyProperty UseDashedBorderProperty =
          DependencyProperty.Register(nameof(UseDashedBorder),
                                      typeof(bool),
                                      typeof(DashedBorder),
                                      new FrameworkPropertyMetadata(false, OnUseDashedBorderChanged));

        public static readonly DependencyProperty DashedBorderBrushProperty =
          DependencyProperty.Register(nameof(DashedBorderBrush),
                                      typeof(Brush),
                                      typeof(DashedBorder),
                                      new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty StrokeDashArrayProperty =
          DependencyProperty.Register(nameof(StrokeDashArray),
                                      typeof(DoubleCollection),
                                      typeof(DashedBorder),
                                      new FrameworkPropertyMetadata(EmptyDoubleCollection()));

        private static void OnUseDashedBorderChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            DashedBorder dashedBorder = (DashedBorder)target;
            dashedBorder.UseDashedBorderChanged();
        }

        private Rectangle GetBoundRectangle()
        {
            Rectangle rectangle = new Rectangle();

            rectangle.SetBinding(Rectangle.StrokeThicknessProperty, new Binding() { Source = this, Path = new PropertyPath("BorderThickness.Left") });
            rectangle.SetBinding(Rectangle.RadiusXProperty, new Binding() { Source = this, Path = new PropertyPath("CornerRadius.TopLeft") });
            rectangle.SetBinding(Rectangle.RadiusYProperty, new Binding() { Source = this, Path = new PropertyPath("CornerRadius.TopLeft") });
            rectangle.SetBinding(Rectangle.WidthProperty, new Binding() { Source = this, Path = new PropertyPath(ActualWidthProperty) });
            rectangle.SetBinding(Rectangle.HeightProperty, new Binding() { Source = this, Path = new PropertyPath(ActualHeightProperty) });

            return rectangle;
        }

        private Rectangle GetBackgroundRectangle()
        {
            Rectangle rectangle = GetBoundRectangle();
            rectangle.SetBinding(Rectangle.StrokeProperty, new Binding() { Source = this, Path = new PropertyPath(BackgroundProperty) });
            return rectangle;
        }

        private Rectangle GetDashedRectangle()
        {
            Rectangle rectangle = GetBoundRectangle();
            rectangle.SetBinding(Rectangle.StrokeDashArrayProperty, new Binding() { Source = this, Path = new PropertyPath(StrokeDashArrayProperty) });
            rectangle.SetBinding(Rectangle.StrokeProperty, new Binding() { Source = this, Path = new PropertyPath(DashedBorderBrushProperty) });
            Panel.SetZIndex(rectangle, 2);
            return rectangle;
        }

        private VisualBrush CreateDashedBorderBrush()
        {
            VisualBrush dashedBorderBrush = new VisualBrush();
            Grid grid = new Grid();
            Rectangle backgroundRectangle = GetBackgroundRectangle();
            Rectangle dashedRectangle = GetDashedRectangle();
            grid.Children.Add(backgroundRectangle);
            grid.Children.Add(dashedRectangle);
            dashedBorderBrush.Visual = grid;
            return dashedBorderBrush;
        }

        private void UseDashedBorderChanged()
        {
            if (UseDashedBorder)
            {
                BorderBrush = CreateDashedBorderBrush();
            }
            else
            {
                ClearValue(BorderBrushProperty);
            }
        }

        public bool UseDashedBorder
        {
            get { return (bool)GetValue(UseDashedBorderProperty); }
            set { SetValue(UseDashedBorderProperty, value); }
        }

        public Brush DashedBorderBrush
        {
            get { return (Brush)GetValue(DashedBorderBrushProperty); }
            set { SetValue(DashedBorderBrushProperty, value); }
        }

        public DoubleCollection StrokeDashArray
        {
            get { return (DoubleCollection)GetValue(StrokeDashArrayProperty); }
            set { SetValue(StrokeDashArrayProperty, value); }
        }
    }

0

正在开发一个用户控件... 我一直在尝试使用故事板来制作蚂蚁行进边框。基本的网格与矩形和文本一起使用时效果很好,因为没有交互。但是当尝试将按钮放入网格中时,要么只有矩形可见,要么只有按钮可见,两者都不可能同时存在。

从另一篇帖子中得到启示: 高级 XAML 动画效果。脉冲、蚂蚁行进、旋转。警报

使用 dotNet 的 VisualBrush 解决方案,将矩形移动到带有按钮的边框上。这个方法非常完美。

<UserControl.Resources>
    <ResourceDictionary>
        <Style TargetType="{x:Type TextBlock}" x:Key="LOC_DG_Cell_Mid" BasedOn="{StaticResource DG_TextBlock_Mid}" >
            <Setter Property="Margin" Value="5 0"/>
        </Style>
        <Storyboard x:Key="MarchingAnts">
            <DoubleAnimation BeginTime="00:00:00"
                Storyboard.TargetName="AlertBox"                                
                Storyboard.TargetProperty="StrokeThickness"
                To="4"
                Duration="0:0:0.25" />
            <!-- If you want to run counter-clockwise, just swap the 'From' and 'To' values. -->
            <DoubleAnimation BeginTime="00:00:00" RepeatBehavior="Forever" Storyboard.TargetName="AlertBox" Storyboard.TargetProperty="StrokeDashOffset" 
                            Duration="0:3:0" From="1000" To="0"/>
        </Storyboard>
    </ResourceDictionary>

</UserControl.Resources>
<UserControl.Triggers>
    <EventTrigger RoutedEvent="FrameworkElement.Loaded">
        <BeginStoryboard Storyboard="{StaticResource MarchingAnts}"/>
    </EventTrigger>
</UserControl.Triggers>

<Grid>
    <Border BorderThickness="1">
        <Border.BorderBrush>
            <VisualBrush>
                <VisualBrush.Visual>
                    <Rectangle x:Name="AlertBox" Stroke="Red" StrokeDashOffset="2" StrokeDashArray="5" Margin="5"
                      Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
                      Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}"/>
                </VisualBrush.Visual>
            </VisualBrush>
        </Border.BorderBrush>

        <Button x:Name="FinishedButton" Padding="0 5" Margin="0" Style="{StaticResource IconButton}" >
            <StackPanel Orientation="Horizontal" >
                <Label Style="{StaticResource ButtonLabel}" Content="Processing has Finished" />
            </StackPanel>
        </Button>
    </Border>
</Grid>

0

如果您正在寻找像素完美的虚线

Note that there is no shadow/blur at the end of of each line

public static class DashBrushFactory
    {
        public static Brush CreateBrush(double dpiScale, SolidColorBrush solidColorBrush)
        {
            const double dashLength = 4;
            const double dashSpace = 4;

            double dashLengthPixelSnapped = SnapToPixel(dashLength, dpiScale);
            double dashSpacePixelSnapped = SnapToPixel(dashSpace, dpiScale);

            ImageBrush imageBrush = new ImageBrush();
            DrawingImage drawingImage = new DrawingImage();
            GeometryDrawing geometryDrawing = new GeometryDrawing();
            GeometryGroup geometryGroup = new GeometryGroup();
            RectangleGeometry rectangleGeometry1 = new RectangleGeometry();
            RectangleGeometry rectangleGeometry2 = new RectangleGeometry();

            rectangleGeometry1.Rect = new Rect(0, 0, dashLengthPixelSnapped, dashLengthPixelSnapped);
            rectangleGeometry2.Rect = new Rect(dashLengthPixelSnapped, dashLengthPixelSnapped, dashSpacePixelSnapped, dashSpacePixelSnapped);

            rectangleGeometry1.Freeze();
            rectangleGeometry2.Freeze();

            geometryGroup.Children.Add(rectangleGeometry1);
            geometryGroup.Children.Add(rectangleGeometry2);
            geometryGroup.Freeze();

            geometryDrawing.Brush = solidColorBrush;
            geometryDrawing.Geometry = geometryGroup;
            geometryDrawing.Freeze();

            drawingImage.Drawing = geometryDrawing;
            drawingImage.Freeze();
            imageBrush.TileMode = TileMode.Tile;
            imageBrush.ViewportUnits = BrushMappingMode.Absolute;
            imageBrush.Viewport = new Rect(0, 0, dashLengthPixelSnapped * 2, dashSpacePixelSnapped * 2);
            imageBrush.ImageSource = drawingImage;
            imageBrush.Freeze();

            return imageBrush;
        }

        private static double SnapToPixel(double value, double dpiScale)
        {
            double newValue;

            // If DPI == 1, don't use DPI-aware rounding.
            if (DoubleUtil.AreClose(dpiScale, 1.0) == false)
            {
                newValue = Math.Round(value * dpiScale) / dpiScale;
                // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value.
                if (DoubleUtil.IsNaN(newValue) ||
                    Double.IsInfinity(newValue) ||
                    DoubleUtil.AreClose(newValue, Double.MaxValue))
                {
                    newValue = value;
                }
            }
            else
            {
                newValue = Math.Round(value);
            }

            return newValue;
        }
    }

https://referencesource.microsoft.com/#WindowsBase/Shared/MS/Internal/DoubleUtil.cs


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接