【问题标题】:How can I show a Balloon Tip over a textbox?如何在文本框上显示气球提示?
【发布时间】:2011-09-24 20:36:35
【问题描述】:

我有一个使用 XAML 和 MVVM 的 C# WPF 应用程序。我的问题是:如何在用户输入的一些无效数据的文本框上方显示气球工具提示?

我想为此使用 Microsoft 的 native balloon control。我将如何在我的应用程序中实现这一点?

【问题讨论】:

  • 关于此问题的其他 SO 帖子:stackoverflow.com/questions/2310102/…。可能会帮到你。他们还提到了与您相同的 URL。
  • 这些都没有使用本机 Windows 气球。我已经在那里了,但没有帮助,这就是我发布自己的问题的原因。

标签: c# wpf mvvm balloon-tip


【解决方案1】:

只需添加对 System.Windows.FormsC:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\WindowsFormsIntegration.dll 的引用 然后:

    WindowsFormsHost host =new WindowsFormsHost();

    var toolTip1 = new System.Windows.Forms.ToolTip();

    toolTip1.AutoPopDelay = 5000;
    toolTip1.InitialDelay = 1000;
    toolTip1.ReshowDelay = 500;
    toolTip1.ShowAlways = true;
    toolTip1.IsBalloon = true;
    toolTip1.ToolTipIcon = System.Windows.Forms.ToolTipIcon.Info;
    toolTip1.ToolTipTitle = "Title:";

    System.Windows.Forms.TextBox tb = new System.Windows.Forms.TextBox();
    tb.Text="Go!";
    toolTip1.SetToolTip(tb, "My Info!");
    host.Child = tb;
    grid1.Children.Add(host);  //a container for windowsForm textBox 

这是 WPF 中 WinForm ToolTip Ballon 的示例

希望对您有所帮助!

【讨论】:

  • 你能链接到那个 ToolTip 类吗?是msdn.microsoft.com/en-us/library/… 吗?
  • 这看起来正是我所需要的,我回家后会尝试并奖励赏金如果它有效。谢谢!
  • 这不仅使用 WinForms 工具提示,还将 WinForms TextBox 放入 WPF 应用程序中。这似乎是一条很糟糕的路。
  • 这不起作用,也不是我想要的。我不仅不想要 System.Windows.Forms 和 WindowsFormsIntegration 的引用,而且代码不会产生应有的工具提示气泡,它只是用 Forms 文本框填充我的网格。
  • 除非没有 Microsoft 提供的本机 WPF 工具提示。 - 为什么没有人建议我使用一些 P/Invokes 在本机调用 Tooltip?
【解决方案2】:

This BalloonDecorator Project 是我在当前项目中用来显示帮助提示和错误通知的一种。我知道你可以修改你的错误模板来显示这个装饰器,就像你可以显示一个图标而不是红色边框一样。使用装饰器的好处是你可以让它看起来像你想要的那样,并且不必依赖 WinForms。

BalloonDecorator.cs

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

namespace MyNamespace
{
public class BalloonDecorator : Decorator
{
    private static double _thickness = 0;
    private static int OpeningGap = 10;

    public static readonly DependencyProperty BackgroundProperty =
        DependencyProperty.Register("Background", typeof (Brush), typeof (BalloonDecorator));

    public static readonly DependencyProperty BorderBrushProperty =
        DependencyProperty.Register("BorderBrush", typeof (Brush), typeof (BalloonDecorator));

    public static readonly DependencyProperty PointerLengthProperty = 
        DependencyProperty.Register("PointerLength", typeof (double), typeof (BalloonDecorator),
        new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender |
        FrameworkPropertyMetadataOptions.AffectsMeasure));

    public static readonly DependencyProperty CornerRadiusProperty = 
        DependencyProperty.Register("CornerRadius", typeof (double), typeof (BalloonDecorator),
        new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender |
        FrameworkPropertyMetadataOptions.AffectsMeasure));

    public Brush Background
    {
        get { return (Brush) GetValue(BackgroundProperty); }
        set { SetValue(BackgroundProperty, value); }
    }

    public Brush BorderBrush
    {
        get { return (Brush) GetValue(BorderBrushProperty); }
        set { SetValue(BorderBrushProperty, value); }
    }

    public double PointerLength
    {
        get { return (double) GetValue(PointerLengthProperty); }
        set { SetValue(PointerLengthProperty, value); }
    }

    public double CornerRadius
    {
        get { return (double) GetValue(CornerRadiusProperty); }
        set { SetValue(CornerRadiusProperty, value); }
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        UIElement child = Child;
        if (child != null)
        {
            double pLength = PointerLength;
            Rect innerRect =
                Rect.Inflate(new Rect(pLength, 0, Math.Max(0, arrangeSize.Width - pLength), arrangeSize.Height),
                             -1 * _thickness, -1 * _thickness);
            child.Arrange(innerRect);
        }

        return arrangeSize;
    }

    protected override Size MeasureOverride(Size constraint)
    {
        UIElement child = Child;
        Size size = new Size();
        if (child != null)
        {
            Size innerSize = new Size(Math.Max(0, constraint.Width - PointerLength), constraint.Height);
            child.Measure(innerSize);
            size.Width += child.DesiredSize.Width;
            size.Height += child.DesiredSize.Height;
        }

        Size borderSize = new Size(2 * _thickness, 2 * _thickness);
        size.Width += borderSize.Width + PointerLength;
        size.Height += borderSize.Height;

        return size;
    }

    protected override void OnRender(DrawingContext dc)
    {
        Rect rect = new Rect(0, 0, RenderSize.Width, RenderSize.Height);

        dc.PushClip(new RectangleGeometry(rect));
        dc.DrawGeometry(Background, new Pen(BorderBrush, _thickness), CreateBalloonGeometry(rect));
        dc.Pop();
    }

    private StreamGeometry CreateBalloonGeometry(Rect rect)
    {
        double radius = Math.Min(CornerRadius, rect.Height / 2);
        double pointerLength = PointerLength;

        // All the points on the path
        Point[] points =
            {
                new Point(pointerLength + radius, 0), new Point(rect.Width - radius, 0), // Top
                new Point(rect.Width, radius), new Point(rect.Width, rect.Height - radius), // Right
                new Point(rect.Width - radius, rect.Height), // Bottom
                new Point(pointerLength + radius, rect.Height), // Bottom
                new Point(pointerLength, rect.Height - radius), // Left
                new Point(pointerLength, radius) // Left
            };

        StreamGeometry geometry = new StreamGeometry();
        geometry.FillRule = FillRule.Nonzero;
        using (StreamGeometryContext ctx = geometry.Open())
        {
            ctx.BeginFigure(points[0], true, true);
            ctx.LineTo(points[1], true, false);
            ctx.ArcTo(points[2], new Size(radius, radius), 0, false, SweepDirection.Clockwise, true, false);
            ctx.LineTo(points[3], true, false);
            ctx.ArcTo(points[4], new Size(radius, radius), 0, false, SweepDirection.Clockwise, true, false);
            ctx.LineTo(points[5], true, false);

            ctx.ArcTo(points[6], new Size(radius, radius), 0, false, SweepDirection.Clockwise, true, false);

            // Pointer
            if (pointerLength > 0)
            {
                ctx.LineTo(rect.BottomLeft, true, false);
                ctx.LineTo(new Point(pointerLength, rect.Height - radius - OpeningGap), true, false);
            }
            ctx.LineTo(points[7], true, false);

            ctx.ArcTo(points[0], new Size(radius, radius), 0, false, SweepDirection.Clockwise, true, false);
        }
        return geometry;
    }
}
}

只需确保将此类的命名空间加载到 XAML 导入中(我使用名为“Framework”的命名空间),并且使用起来很简单:

    <Framework:BalloonDecorator  Background="#FFFF6600" PointerLength="50"
     CornerRadius="5" Opacity=".9" Margin="200,120,0,0"
     HorizontalAlignment="Left" VerticalAlignment="Top" Visibility="{Binding UnitPriceChangedBalloonVisibility}">
        <Border CornerRadius="2">
            <Border CornerRadius="2">
                <Button Height="Auto" Command="{Binding CloseUnitPriceChangedBalloonCommand}" Background="Transparent" BorderBrush="{x:Null}">
                <TextBlock Text="Please review the price. The Units have changed."
                     HorizontalAlignment="Left"
                     VerticalAlignment="Top"
                     FontStyle="Italic"
                     TextWrapping="Wrap"
                     Margin="10"
                     />
                     </Button>
            </Border>
        </Border>
    </Framework:BalloonDecorator>

显然,我将可见性与绑定联系起来,但您可以将其设置为 true 并将其放入您的 Validation.ErrorTemplate 中。

希望这会有所帮助!

【讨论】:

  • 是的,这是一个气球,但我必须覆盖其中正在使用的按钮的鼠标悬停样式,因为它在 Windows 7 上看起来很糟糕,而且定位都是错误的(理想情况下它不是表单布局的一部分,而是一个独立的浮动实体)。这不是我要找的,抱歉。
  • 当然,没问题。如果您不介意混合 WinForms/WPF 解决方案,那么这可能是您最好的选择,而不是为工具提示编写 WinAPI 的 WPF 封装。
  • 当然,我更喜欢它,但只有当我尝试调用它时它不会抛出 NullReferenceExceptions 时。 ;)
【解决方案3】:

我一直在寻找比 BalloonDecorator 更好的解决方案,并遇到了 http://www.hardcodet.net/projects/wpf-notifyicon 项目。它在最低级别使用 WinAPI,这可能会让您在构建自己的解决方案方面领先一步。乍一看,它似乎有可能解决它,但我没有足够的时间来验证 BalloonTip 是否可以按照您所描述的方式运行。

祝你的项目好运!

【讨论】:

  • 有可能...我更喜欢本地 Windows 调用,但我可能不得不接受这样的事情。感谢您的链接。
  • 没问题。如果您找到合适的解决方案,请将其发布,以便我们其他人可以看到您已完成的工作。
  • 所以,我宁愿有一个自定义 WPF 窗口作为工具提示,也不愿将旧的 Winforms 东西合并到我的应用程序中,因为这个项目是最好的例子,它得到了赏金。如果您可以处理较旧的文本框(或任何其他控件),则将 Winforms 项添加到您的 WPF 窗口中是一个可行的选择,但这不是我想要的。另一种解决方案是在 Win32 中 P/Invoke 工具提示调用,但在 WPF 中执行自定义窗口可能更容易/更快。
【解决方案4】:

也许您可以使用 WindowsFormsHost 类型在 WPF 中托管 Windows 窗体控件。

在 MSDN 上有一个关于如何执行此操作的演练:

Hosting a Windows Forms Composite Control in WPF

使用这种技术,您也许可以使用System.Windows.Forms.ToolTip 控件。如果将此控件的 IsBalloon property 设置为 true,它将显示为气球窗口。

【讨论】:

  • 我不希望引用过时的 System.Windows.Forms 库。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-15
相关资源
最近更新 更多