wpf 创建自定义语音气泡工具提示,使其箭头/指针适应/放置到每种情况

1aaf6o9v  于 2022-11-30  发布在  其他
关注(0)|答案(2)|浏览(144)

我有一个WPF的语音气泡工具提示,这是工作正常。

<Style x:Key="{x:Type ToolTip}" TargetType="ToolTip">
    <Setter Property="OverridesDefaultStyle" Value="true" />
    <Setter Property="HorizontalOffset" Value="1" />
    <Setter Property="VerticalOffset" Value="1" />
    <Setter Property="Background" Value="White" />
    <Setter Property="Foreground" Value="Black" />
    <Setter Property="FontSize" Value="12" />
    <Setter Property="FontFamily" Value="Segoe UI" />
    <Setter Property="DataContext" Value="{Binding Path=PlacementTarget.DataContext, RelativeSource={x:Static RelativeSource.Self}}"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ToolTip">
                <Canvas Width="225" Height="131">
                    <Path x:Name="Container"
                          Canvas.Left="0"
                          Canvas.Top="0"
                          Margin="0"
                          Data="M8,7.41 L15.415,0 L22.83,7.41 L224,7.41 L224,130 L0,130 L0,7.41 L8,7.41"
                          Fill="{TemplateBinding Background}"
                          Stroke="Gray">
                        <Path.Effect>
                            <DropShadowEffect BlurRadius="10"
                                              Opacity="0.5"
                                              ShadowDepth="4" />
                        </Path.Effect>
                    </Path>
                    <TextBlock Canvas.Left="10"
                               Canvas.Top="10"
                               Width="100"
                               Height="65"
                               Text="{TemplateBinding Content}"
                               TextWrapping="WrapWithOverflow" />
                </Canvas>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

上述方法的问题是,无论在何种情况下,语音气泡工具提示(路径)的箭头/指针总是放置在相同的位置,我希望它能适应这种情况,并使用以下方式之一(上述样式实现了放置在左上角的箭头,下面截图中的第一个工具提示):

我怎么能做到这一点?这可能吗?

k5ifujac

k5ifujac1#

下面是此任务的完整代码:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Decorators
{
public enum Position 
{
    None,
    Top,
    Bottom,
    RightSide,
    LeftSide,
}

public enum SpecificPosition
{
    None,
    LeftOrTop = 25,
    Center = 50,
    RightOrBottom = 75,
}

internal class BubbleTextDecorator : Decorator
{

    #region DependencyProperties
    public static readonly DependencyProperty VerticalMarginProperty = DependencyProperty.Register("VerticalMargin", 
                                                                                                   typeof(double), 
                                                                                                   typeof(BubbleTextDecorator), 
                                                                                                   new FrameworkPropertyMetadata(0.0, 
                                                                                                                                 FrameworkPropertyMetadataOptions.AffectsMeasure | 
                                                                                                                                 FrameworkPropertyMetadataOptions.AffectsRender));

    public double VerticalMargin
    {
        get { return (double)GetValue(VerticalMarginProperty); }
        set { SetValue(VerticalMarginProperty, value); }
    }

    public static readonly DependencyProperty HorizontalMarginProperty = DependencyProperty.Register("HorizontalMargin", 
                                                                                                     typeof(double),
                                                                                                     typeof(BubbleTextDecorator),
                                                                                                     new FrameworkPropertyMetadata(0.0,
                                                                                                                                   FrameworkPropertyMetadataOptions.AffectsMeasure |
                                                                                                                                   FrameworkPropertyMetadataOptions.AffectsRender));

    public double HorizontalMargin
    {
        get { return (double)GetValue(HorizontalMarginProperty); }
        set { SetValue(HorizontalMarginProperty, value); }
    }


    public static readonly DependencyProperty PointerPositionProperty = DependencyProperty.Register("PointerPosition", 
                                                                                                    typeof(Position), 
                                                                                                    typeof(BubbleTextDecorator), 
                                                                                                    new FrameworkPropertyMetadata(Position.None, 
                                                                                                                                  FrameworkPropertyMetadataOptions.AffectsRender |
                                                                                                                                  FrameworkPropertyMetadataOptions.AffectsMeasure));

    public Position PointerPosition
    {
        get { return (Position)GetValue(PointerPositionProperty); }
        set { SetValue(PointerPositionProperty, value); }
    }

    public static readonly DependencyProperty AlignmentPositionProperty = DependencyProperty.Register("AlignmentPosition",
                                                                                            typeof(SpecificPosition),
                                                                                            typeof(BubbleTextDecorator),
                                                                                            new FrameworkPropertyMetadata(SpecificPosition.None,
                                                                                                                          FrameworkPropertyMetadataOptions.AffectsRender |
                                                                                                                          FrameworkPropertyMetadataOptions.AffectsMeasure));

    public SpecificPosition AlignmentPosition
    {
        get { return (SpecificPosition)GetValue(AlignmentPositionProperty); }
        set { SetValue(AlignmentPositionProperty, value); }
    }

    public static readonly DependencyProperty PointerHeightProperty = DependencyProperty.Register("PointerHeight", 
                                                                                                  typeof(double), 
                                                                                                  typeof(BubbleTextDecorator), 
                                                                                                  new FrameworkPropertyMetadata(0.0, 
                                                                                                      FrameworkPropertyMetadataOptions.AffectsMeasure |
                                                                                                      FrameworkPropertyMetadataOptions.AffectsRender));

    public double PointerHeight
    {
        get { return (double)GetValue(PointerHeightProperty); }
        set { SetValue(PointerHeightProperty, value); }
    }

    public static readonly DependencyProperty PointerWidthProperty = DependencyProperty.Register("PointerWidth", 
                                                                                                 typeof(double), 
                                                                                                 typeof(BubbleTextDecorator), 
                                                                                                 new FrameworkPropertyMetadata(0.0,
                                                                                                     FrameworkPropertyMetadataOptions.AffectsMeasure |
                                                                                                     FrameworkPropertyMetadataOptions.AffectsArrange |
                                                                                                     FrameworkPropertyMetadataOptions.AffectsRender));

    public double PointerWidth
    {
        get { return (double)GetValue(PointerWidthProperty); }
        set { SetValue(PointerWidthProperty, value); }
    }

    #endregion

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        Size desiredSize = base.ArrangeOverride(arrangeSize);
        if (Child != null) 
        {

            switch (PointerPosition)
            {
                case Position.Top:
                    Child.Arrange(new Rect(new Point(0.0, PointerHeight), new Point(desiredSize.Width, desiredSize.Height)));
                    break;
                case Position.Bottom:
                    Child.Arrange(new Rect(new Point(0.0, 0.0), new Point(desiredSize.Width, desiredSize.Height - PointerHeight)));
                    break;
                case Position.LeftSide:
                    Child.Arrange(new Rect(new Point(PointerHeight, 0.0), new Point(desiredSize.Width, desiredSize.Height)));
                    break;
                case Position.RightSide:
                    Child.Arrange(new Rect(new Point(0.0, 0.0), new Point(desiredSize.Width - PointerHeight, desiredSize.Height)));
                    break;
            }
        }
        return arrangeSize;
    }

    protected override Size MeasureOverride(Size constraint)
    {
        Size desiredSize = base.MeasureOverride(constraint);
        Size size = (PointerPosition == Position.Top || PointerPosition == Position.Bottom)
            ? new Size(desiredSize.Width + (HorizontalMargin * 2), desiredSize.Height + (VerticalMargin * 2) + PointerHeight)
            : new Size(desiredSize.Width + (HorizontalMargin * 2) + PointerHeight, desiredSize.Height + (VerticalMargin * 2));

        return size;
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        Brush renderBrush = Brushes.Transparent;
        Pen renderPen = new Pen(Brushes.Black, 1);
        StreamGeometry geom = new StreamGeometry();

        switch (PointerPosition) 
        {
            case Position.Top:
                DrawTop(geom);
                break;
            case Position.Bottom:
                DrawBottom(geom);
                break;
            case Position.RightSide:
                DrawRight(geom);
                break;
            case Position.LeftSide:
                DrawLeft(geom);
                break;

        }
        // Some arbitrary drawing implements.
        drawingContext.DrawGeometry(renderBrush, renderPen, geom);
    }

    private void DrawLeft(StreamGeometry geom)
    {
        using (StreamGeometryContext ctx = geom.Open())
        {
            ctx.BeginFigure(
                new Point(PointerHeight, 0.0),
                true,
                true);
            ctx.LineTo(
                new Point(ActualWidth, 0.0),
                true,
                false);
            ctx.LineTo(
                new Point(ActualWidth, ActualHeight),
                true,
                false);
            ctx.LineTo(
                new Point(PointerHeight, ActualHeight),
                true,
                false);
            ctx.LineTo(
                new Point(PointerHeight, (ActualHeight * (double)AlignmentPosition / 100) + (PointerWidth / 2)),
                true,
                false);
            ctx.LineTo(
                new Point(0.0, ActualHeight * (double)AlignmentPosition / 100),
                true,
                false);
            ctx.LineTo(
                new Point(PointerHeight, (ActualHeight * (double)AlignmentPosition / 100) - (PointerWidth / 2)),
                true,
                false);
            ctx.LineTo(
                new Point(PointerHeight, 0.0),
                true,
                false);
        }
    }

    private void DrawRight(StreamGeometry geom)
    {
        using (StreamGeometryContext ctx = geom.Open())
        {
            ctx.BeginFigure(
                new Point(0.0, 0.0),
                true,
                true);
            ctx.LineTo(
                new Point(ActualWidth - PointerHeight, 0.0),
                true,
                false);
            ctx.LineTo(
                new Point(ActualWidth - PointerHeight, (ActualHeight * (double)AlignmentPosition / 100) - (PointerWidth / 2)),
                true,
                false);
            ctx.LineTo(
                new Point(ActualWidth, ActualHeight * (double)AlignmentPosition / 100),
                true,
                false);
            ctx.LineTo(
                new Point(ActualWidth - PointerHeight, (ActualHeight * (double)AlignmentPosition / 100) + (PointerWidth / 2)),
                true,
                false);
            ctx.LineTo(
                new Point(ActualWidth - PointerHeight, ActualHeight),
                true,
                false);
            ctx.LineTo(
                new Point(0.0, ActualHeight),
                true,
                false);
            ctx.LineTo(
                new Point(0.0, 0.0),
                true,
                false);
        }
    }

    private void DrawBottom(StreamGeometry geom)
    {
        using (StreamGeometryContext ctx = geom.Open())
        {
            ctx.BeginFigure(
                new Point(0.0, 0.0),
                true,
                true);
            ctx.LineTo(
                new Point(ActualWidth, 0.0),
                true,
                false);
            ctx.LineTo(
               new Point(ActualWidth, ActualHeight - PointerHeight),
               true,
               false);
            ctx.LineTo(
                new Point((ActualWidth * (double)AlignmentPosition / 100) + (PointerWidth / 2), ActualHeight - PointerHeight),
                true,
                false);
            ctx.LineTo(
                new Point(ActualWidth * (double)AlignmentPosition / 100, ActualHeight),
                true,
                false);
            ctx.LineTo(
                new Point((ActualWidth * (double)AlignmentPosition / 100) - (PointerWidth / 2), ActualHeight - PointerHeight),
                true,
                false);
            ctx.LineTo(
                new Point(0.0, ActualHeight - PointerHeight),
                true,
                false);
            ctx.LineTo(
                new Point(0.0, 0.0),
                true,
                false);
        }
    }

    private void DrawTop(StreamGeometry geom)
    {
        using (StreamGeometryContext ctx = geom.Open())
        {
            ctx.BeginFigure(
                new Point(0.0, PointerHeight),
                true,
                true);
            ctx.LineTo(
                new Point((ActualWidth * (double)AlignmentPosition / 100) - (PointerWidth / 2), PointerHeight),
                true,
                false);
            ctx.LineTo(
                new Point(ActualWidth * (double)AlignmentPosition / 100, 0.0),
                true,
                false);
            ctx.LineTo(
                new Point((ActualWidth * (double)AlignmentPosition / 100) + (PointerWidth / 2), PointerHeight),
                true,
                false);
            ctx.LineTo(
                new Point(ActualWidth, PointerHeight),
                true,
                false);
            ctx.LineTo(
               new Point(ActualWidth, ActualHeight),
               true,
               false);
            ctx.LineTo(
                new Point(0.0, ActualHeight),
                true,
                false);
            ctx.LineTo(
                new Point(0.0, PointerHeight),
                true,
                false);
        }
    }
}
}

这是你如何使用它:

<localdecorators:BubbleTextDecorator PointerHeight="10"
                                    PointerWidth="20"
                                    PointerPosition="LeftSide"
                                    AlignmentPosition="Center"
                                    VerticalMargin="30"
                                    HorizontalMargin="30"
                                    HorizontalAlignment="Left">
<TextBlock Text="this"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"/>
</localdecorators:BubbleTextDecorator>

ymzxtsji

ymzxtsji2#

这是一个创建装饰器的典型例子。我曾经在一个文本周围做了一个可定制的ArrowBorder。你需要从装饰器类继承。

internal class ArrowBorderDecorator : Decorator

然后你需要一些DependencyProperties,这样它就很容易定制。在我的例子中,这是ArrowTipToArrowTriangleBaseDistance属性,它意味着箭头应该有多“尖”。在你的例子中,气泡文本箭头应该在哪里。

public static readonly DependencyProperty ArrowTipToArrowTriangleBaseDistanceProperty = DependencyProperty.Register("ArrowTipToArrowTriangleBaseDistance", typeof(double), typeof(ArrowBorderDecorator), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender));

    public double ArrowTipToArrowTriangleBaseDistance
    {
        get { return (double)GetValue(ArrowTipToArrowTriangleBaseDistanceProperty); }
        set { SetValue(ArrowTipToArrowTriangleBaseDistanceProperty, value); }
    }

然后,您需要覆盖ArrangeOverrideMeasureOverrideOnRender方法。前两个来自Decorator类,第三个来自UIElement类。
这里有一个很好的link来理解前两个。在OnRender中,你有一个DrawingContext来使用DependenyProperties绘制你想要的形状。
完成这些之后,您可以简单地在xaml中使用装饰器,如下所示:

<localdecorators:ArrowBorderDecorator ArrowBaseHalfSegment="0"
             FillColor="{DynamicResource MahApps.Brushes.Accent3}"
             StrokeColor="{DynamicResource MahApps.Brushes.ThemeForeground}"
             ArrowBorderThickness="1"
             ArrowTipToArrowTriangleBaseDistance="10">
<TextBlock Text="{Binding Path=Title}"
           Foreground="{DynamicResource MahApps.Brushes.IdealForeground}"
           Padding="10 1 10 1"
           VerticalAlignment="Center"
           FontWeight="Bold">
</TextBlock></localdecorators:ArrowBorderDecorator>

第一次

相关问题