WPF:使用Popup制作带箭头的弹出提示框

先上效果图:
在这里插入图片描述
思路:

  1. 制作带箭头的边框:使用Path
    首先我们要制作带箭头,且有4个圆角的“边框”,简单点的可以去找个图片当背景,但是遇到复杂的就不适用了。所以这里我们选择使用Path来画出这个带箭头圆角边框。 看下图(PPT画图好难)
    在这里插入图片描述
    我们分析一下这个边框形状,为了简单起见我们先假定长宽一样。
    1、中间蓝色是个长宽=100的正方形,就是效果图中浅蓝色那部分,在该部分可以放任意多的内容。
    2、假定4个圆角的半径都为5,箭头那里三角形底(黄色线),那就假定为2*r=10。
    3、以左上角为坐标原点,应该不难算出图中所有绿色、红色点的坐标;以红色点为绘图起点,逆时针画图。
    不难写出Path的代码,ArcSegmentLineSegmentPoint属性就是坐标值,其中ArcSegment中的Size属性可以设置弧度,也就是设置弧所在圆的半径。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<Path Stroke="Black" Margin="20">
    <Path.Data>
        <PathGeometry>
            <PathFigure StartPoint="5,0" IsClosed="True">
                <ArcSegment Point="0,5" Size="5,5" SweepDirection="Counterclockwise"/>
                <LineSegment Point="0,95"/>
                <ArcSegment Point="5,100" Size="5,5"/>
                <LineSegment Point="45,100"/>
                <LineSegment Point="50,105"/>
                <LineSegment Point="55,100"/>
                <LineSegment Point="95,100"/>
                <ArcSegment Point="100,95" Size="5,5"/>
                <LineSegment Point="100,5"/>
                <ArcSegment Point="95,0" Size="5,5" SweepDirection="Counterclockwise"/>
            </PathFigure>
        </PathGeometry>
    </Path.Data>
</Path>

结果:
在这里插入图片描述
2. 动态改变Path的大小

就如上述代码,Path的形状大小已经写死了,如何根据提示框中的内容,自动改变大小呢?显然不能将Path写在布局文件中,应该放在后台,动态添加Path。

后台代码很简单,就是把写死的宽度、高度、半径全部换成变量就好了,如我们写个返回类型是PathGeometry的函数GetPathGeometry,它有三个参数,分别是提示框边框包裹的内容(上面那个PPT画的图的蓝色部分)的实际宽度、实际高度,以及想设置的圆角半径值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private PathGeometry GetPathGeometry(double width, double height, double r)
{<!-- -->
    width += 2*r;
    height += 2*r;
    PathGeometry geometry = new PathGeometry();
    PathFigure pathfigure = new PathFigure();
    pathfigure.IsClosed = true;
    pathfigure.StartPoint = new Point(r, 0);
    pathfigure.Segments.Add(new ArcSegment(new Point(0, r), new Size(r, r), 0, false, SweepDirection.Counterclockwise, true));
    pathfigure.Segments.Add(new LineSegment(new Point(0, height + r), true));
    pathfigure.Segments.Add(new ArcSegment(new Point(r, height + 2*r), new Size(r, r), 0, false, SweepDirection.Counterclockwise, true));
    pathfigure.Segments.Add(new LineSegment(new Point(width / 2, height + 2*r), true));
    pathfigure.Segments.Add(new LineSegment(new Point(width / 2 + r, height + 3*r), true));
    pathfigure.Segments.Add(new LineSegment(new Point(width / 2 + 2*r, height + 2*r), true));
    pathfigure.Segments.Add(new LineSegment(new Point(width + r, height + 2*r), true));
    pathfigure.Segments.Add(new ArcSegment(new Point(width + 2*r, height + r), new Size(r, r), 0, false, SweepDirection.Counterclockwise, true));
    pathfigure.Segments.Add(new LineSegment(new Point(width + 2*r, r), true));
    pathfigure.Segments.Add(new ArcSegment(new Point(width + r, 0), new Size(r, r), 0, false, SweepDirection.Counterclockwise, true));
    geometry.Figures.Add(pathfigure);
    return geometry;
}
  1. 完整代码
    布局文件
1
2
3
4
5
6
7
8
9
10
11
12
<Button Name="btn" Content="测试popup提示框" Width="120" Height="50" Click="btn_Click" />
<Popup PlacementTarget="{Binding ElementName=btn}"
       Placement="Top" StaysOpen="False" IsOpen="False"
       AllowsTransparency="True" VerticalOffset="-2" Name="popup">
    <Grid>
        <Path Name="path" Fill="#90000000" SnapsToDevicePixels="True"/>
        <Grid Name="popup_grid" Width="200" Height="100" Background="AliceBlue">
            <!--该grid里面可以放任何东西-->
            <TextBlock Text="我是提示框" TextWrapping="Wrap"/>
        </Grid>
    </Grid>
</Popup>

按钮点击事件:

1
2
3
4
5
6
7
8
9
10
private void btn_Click(object sender, RoutedEventArgs e)
{<!-- -->
    if(popup.IsOpen == false)
    {<!-- -->
        popup.IsOpen = true;
        double r = 5;
        popup.HorizontalOffset = (popup_grid.ActualWidth + 2*r  - btn.ActualWidth) / (-2);
        path.Data = GetPathGeometry(popup_grid.ActualWidth, popup_grid.ActualHeight, r);
    }
}

最后,记得调整Popup的VerticalOffsetHorizontalOffsetVerticalOffset=-2就是稍微上移一点,离按钮一点距离;如果提示框里面的内容很多,提示框变得很大,箭头相对于按钮来说就会往右偏,就要动态设置HorizontalOffset

最后的最后,放一张我用的效果图:
在这里插入图片描述