.net 如何在 WPF 应用程序中实现气球消息

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2310102/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 13:57:18  来源:igfitidea点击:

How to implement Balloon message in a WPF application

.netwpfuser-interfacexamlwpf-controls

提问by tronda

We would like to use balloon messages as described in the UX Guidefrom Microsoft. I found some samples which uses native code from Windows Forms, but the native code requires a handle to the component which a bit difficult for a WPF application since it doesn't follow the same concept.

我们想使用Microsoft的UX 指南中描述的气球消息。我发现了一些使用来自 Windows 窗体的本机代码的示例,但本机代码需要组件的句柄,这对于 WPF 应用程序来说有点困难,因为它不遵循相同的概念。

I found some sample codewhich uses WPF's decorator mechanism, but I'm still not convinced that this is the easiest approach for WPF application. Could a possible implementation be to implement a decorator around a tooltip?

我发现了一些使用 WPF 装饰器机制的示例代码,但我仍然不相信这是 WPF 应用程序最简单的方法。可能的实现是围绕工具提示实现装饰器吗?

The concrete case I have is a form with several text boxes which need input validation and notification on possible wrong input values - something which seems appropriate for balloon messages.

我拥有的具体案例是一个包含多个文本框的表单,这些文本框需要输入验证并就可能的错误输入值发出通知——这似乎适合气球消息。

Is there a commercial or open source control built for this use case under WPF that I should be aware of?

在 WPF 下是否有为此用例构建的商业或开源控件,我应该注意?

采纳答案by tronda

I ended up putting a TextBlock in the adorner layer:

我最终在装饰层中放置了一个 TextBlock:

<Setter Property="Validation.ErrorTemplate">
    <Setter.Value>
        <ControlTemplate>
            <StackPanel Orientation="Vertical">
                <Border>
                    <AdornedElementPlaceholder  x:Name="adorner"/>
                </Border>
                <TextBlock 
                    Height="20" Margin="10 0" Style="{StaticResource NormalColorBoldWeightSmallSizeTextStyle}"
                    Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"/>
            </StackPanel>
        </ControlTemplate>
    </Setter.Value>
</Setter>

I also used the tooltip as shown in every WPF examples out there:

我还使用了工具提示,如每个 WPF 示例中所示:

<Style.Triggers>
    <Trigger Property="Validation.HasError" Value="True">
        <Setter Property="ToolTip"
                Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}">
        </Setter>
    </Trigger>
</Style.Triggers>

Not optimal (would really like a Balloon Message control), but works good enough for the need we have.

不是最优的(真的很像一个气球消息控件),但足以满足我们的需要。

回答by Robert Rossney

The UX Guide points out that the differences between a balloon and a tool tip are:

UX 指南指出气球和工具提示之间的区别是:

  • Balloons can be displayed independently of the current pointer location, so they have a tail that indicates their source.

  • Balloons have a title, body text, and an icon.

  • Balloons can be interactive, whereas it is impossible to click on a tip.

  • 气球可以独立于当前指针位置显示,因此它们有一个指示其来源的尾部。

  • 气球有标题、正文和图标。

  • 气球可以是互动的,而点击提示是不可能的。

That last is the only sticking point as far as WPF is concerned. If you need the user to be able to interact with the contents of the balloon, then it'll need to be a Popup, not a ToolTip. (You might benefit from thisforum post if you go that route.)

就 WPF 而言,最后一个是唯一的症结所在。如果您需要用户能够与气球的内容进行交互,那么它需要是一个弹出窗口,而不是工具提示。(如果你走那条路,你可能会从这个论坛帖子中受益。)

But if all you're doing is displaying notifications, you can certainly use a ToolTip. You don't need to mess around with decorators either; just build a control template for the ToolTip that looks like what you want, create a ToolTip resource that uses that style, and set the target control's ToolTipproperty to that ToolTip. Use the ToolTipServiceto control where it displays relative to the placement target.

但是,如果您所做的只是显示通知,那么您当然可以使用 ToolTip。你也不需要搞乱装饰器;只需为 ToolTip 构建一个看起来像您想要的控件模板,创建一个使用该样式的 ToolTip 资源,并将目标控件的ToolTip属性设置为该ToolTip. 使用ToolTipService来控制它相对于放置目标的显示位置。

回答by LawMan

I went ahead and created a CodePlex site for this that includes "Toast Popups" and control "Help Balloons". These versions have more features than what's described below. Code Plex Project.

我继续为此创建了一个 CodePlex 站点,其中包括“Toast Popups”和控制“Help Balloons”。这些版本具有比下面描述的更多的功能。 代码丛项目

Here's the link to the Nuget Package

这是Nuget 包的链接

Here's my solution for balloon caption. Some of the things that I wanted it to do differently:

这是我的气球标题解决方案。我希望它以不同方式做的一些事情:

  • Fade in when the mouse enters.
  • Fade out when mouse leaves and close the window when the opacity reaches 0.
  • If the mouse is over the window, the opacity will be at 100% and not close.
  • The height of the Balloon window is dynamic.
  • Use event triggers instead of timers.
  • Position the balloon on the left or right side of the control.
  • 当鼠标进入时淡入。
  • 当鼠标离开时淡出并在不透明度达到 0 时关闭窗口。
  • 如果鼠标悬停在窗口上方,则不透明度将为 100% 且不会关闭。
  • Balloon 窗口的高度是动态的。
  • 使用事件触发器而不是定时器。
  • 将气球放在控件的左侧或右侧。

Screnshotenter image description here

截图enter image description here

Here are the Help images that I used.

这是我使用的帮助图像。

enter image description hereenter image description here

enter image description hereenter image description here

I created a UserControl with a simple "Help" icon.

我创建了一个带有简单“帮助”图标的 UserControl。

<UserControl x:Class="Foundation.FundRaising.DataRequest.Windows.Controls.HelpBalloon"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         Name="HelpBalloonControl"
         d:DesignHeight="20" d:DesignWidth="20" Background="Transparent">
    <Image Width="20" Height="20" 
           MouseEnter="ImageMouseEnter" 
           Cursor="Hand"
           IsManipulationEnabled="True" 
           Source="/Foundation.FundRaising.DataRequest.Windows;component/Resources/help20.png" />

And added this to the code behind.

并将其添加到后面的代码中。

public partial class HelpBalloon : UserControl
{
    private Balloon balloon = null;

    public HelpBalloon()
    {
        InitializeComponent();
    }

    public string Caption { get; set; }

    public Balloon.Position Position { get; set; }

    private void ImageMouseEnter(object sender, MouseEventArgs e)
    {
        if (balloon == null)
        {
            balloon = new Balloon(this, this.Caption);
            balloon.Closed += BalloonClosed;
            balloon.Show();
        }
    }

    private void BalloonClosed(object sender, EventArgs e)
    {
        this.balloon = null;
    }
}

Here's the XAML Code for the Balloon Window that the UserControl opens.

下面是 UserControl 打开的气球窗口的 XAML 代码。

<Window x:Class="Foundation.FundRaising.DataRequest.Windows.Balloon"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="90" Width="250" WindowStyle="None" 
    ResizeMode="NoResize" ShowInTaskbar="False"
    Topmost="True" IsTabStop="False" 
    OverridesDefaultStyle="False" 
    SizeToContent="Height"
    AllowsTransparency="True" 
    Background="Transparent" >
   <Grid RenderTransformOrigin="0,1" >        
    <StackPanel Orientation="Vertical">
        <StackPanel Orientation="Horizontal">
            <StackPanel.Resources>
                <Style TargetType="Path">
                    <Setter Property="Fill" Value="#fdfdfd"/>
                    <Setter Property="Stretch" Value="Fill"/>
                    <Setter Property="Width" Value="22"/>
                    <Setter Property="Height" Value="31"/>
                    <Setter Property="Panel.ZIndex" Value="99"/>
                    <Setter Property="VerticalAlignment" Value="Top"/>
                    <Setter Property="Effect">
                        <Setter.Value>
                            <DropShadowEffect Color="#FF757575" Opacity=".7"/>
                        </Setter.Value>
                    </Setter>
                </Style>
            </StackPanel.Resources>
            <Path  
              HorizontalAlignment="Left"  
              Margin="15,3,0,0" 
                Data="M10402.99154,55.5381L10.9919,0.64 0.7,54.9"
              x:Name="PathPointLeft"/>
            <Path  
                HorizontalAlignment="Right"  
                Margin="175,3,0,0"
                Data="M10402.992,55.5381 L10284.783,3.2963597 0.7,54.9"
                x:Name="PathPointRight">
            </Path>
        </StackPanel>

        <Border Margin="5,-3,5,5" 
                CornerRadius="7" Panel.ZIndex="100"
                VerticalAlignment="Top">
            <Border.Background>
                <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                    <LinearGradientBrush.RelativeTransform>
                        <RotateTransform Angle="90" CenterX="0.7" CenterY="0.7" />
                    </LinearGradientBrush.RelativeTransform>
                    <GradientStop Color="#FFFDFDFD" Offset=".2"/>
                    <GradientStop Color="#FFB6FB88" Offset=".8"/>
                </LinearGradientBrush>
            </Border.Background>
            <Border.Effect>
                <DropShadowEffect Color="#FF757575" Opacity=".7"/>
            </Border.Effect>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>

                <Image Grid.Column="0" 
                       Width="35" 
                       Margin="5"
                       VerticalAlignment="Top" Height="35" 
                       Source="Resources/help.png" />

                <TextBlock Grid.Column="1" 
                           TextWrapping="Wrap"
                           Margin="0,10,10,10" 
                           TextOptions.TextFormattingMode="Display"
                           x:Name="textBlockCaption"
                           Text="This is the caption"/>
            </Grid>
        </Border>
    </StackPanel>

    <!-- Animation -->
    <Grid.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard x:Name="StoryboardLoad">
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="0.0" To="1.0" Duration="0:0:2" />
                    <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="1.0" To="0.0" Duration="0:0:3" BeginTime="0:0:3" Completed="DoubleAnimationCompleted"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>

        <EventTrigger RoutedEvent="Mouse.MouseEnter">
            <EventTrigger.Actions>
                <RemoveStoryboard BeginStoryboardName="StoryboardLoad"/>
                <RemoveStoryboard BeginStoryboardName="StoryboardFade"/>
            </EventTrigger.Actions>
        </EventTrigger>

        <EventTrigger RoutedEvent="Mouse.MouseLeave">
            <BeginStoryboard x:Name="StoryboardFade">
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="1.0" To="0.0" Duration="0:0:2" BeginTime="0:0:1" Completed="DoubleAnimationCompleted"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Grid.Triggers>

    <Grid.RenderTransform>
        <ScaleTransform ScaleY="1" />
    </Grid.RenderTransform>
</Grid>

And the code behind of the Balloon window.

以及气球窗口背后的代码。

public partial class Balloon : Window
{
    public enum Position
    {
        Left,

        Right
    }

    public Balloon(Control control, string caption, Position position)
    {
        InitializeComponent();

        this.textBlockCaption.Text = caption;

        // Compensate for the bubble point
        double captionPointMargin = this.PathPointLeft.Margin.Left;

        Point location = GetControlPosition(control);

        if (position == Position.Left)
        {
            this.PathPointRight.Visibility = Visibility.Hidden;
            this.Left = location.X + (control.ActualWidth / 2) - captionPointMargin;
        }
        else
        {
            this.PathPointLeft.Visibility = Visibility.Hidden;
            this.Left = location.X - this.Width + control.ActualWidth + (captionPointMargin / 2);
        }

        this.Top = location.Y + (control.ActualHeight / 2);
    }

    private static Point GetControlPosition(Control control)
    {
        Point locationToScreen = control.PointToScreen(new Point(0, 0)); 
        var source = PresentationSource.FromVisual(control);
        return source.CompositionTarget.TransformFromDevice.Transform(locationToScreen);
    }

    private void DoubleAnimationCompleted(object sender, EventArgs e)
    {
        if (!this.IsMouseOver)
        {
            this.Close();
        }
    }
}

回答by NASSER

I have made warning balloon to solve Caps Lock warning problem in my WPF-Project.

我制作了警告气球来解决我的 WPF 项目中的 Caps Lock 警告问题。

enter image description here

enter image description here

If you want to add this balloon warning in your project then follow these steps:

如果要在项目中添加此气球警告,请按照以下步骤操作:

-Add new Window in your project and give name "WarningBalloon".
-Add following XAML code against new Window and add warning icon to image folder of project.

-在您的项目中添加新窗口并命名为“WarningBalloon”。
-针对新窗口添加以下 XAML 代码并将警告图标添加到项目的图像文件夹。

<Window x:Class="MyNameSpace.WarningBalloon"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Height="160" Width="469" WindowStyle="None" ResizeMode="NoResize" ShowInTaskbar="False" Topmost="True" IsTabStop="False" OverridesDefaultStyle="False" AllowsTransparency="True" Background="Transparent" Opacity="1" >
        <Grid Height="126" Width="453">
            <Grid.RowDefinitions>
                <RowDefinition Height="81" />
                <RowDefinition Height="45*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="177*" />
                <ColumnDefinition Width="72*" />
                <ColumnDefinition Width="0*" />
                <ColumnDefinition Width="170*" />
            </Grid.ColumnDefinitions>
            <Border Margin="12,32,0,0"
          CornerRadius="10,10,10,10" Grid.ColumnSpan="4" HorizontalAlignment="Left" Width="429" Height="82" VerticalAlignment="Top" Grid.RowSpan="2">
                <Border.Effect>
                    <DropShadowEffect
              Color="#FF474747" />
                </Border.Effect>
                <Border.Background>
                    <LinearGradientBrush
              EndPoint="0.5,1"
              StartPoint="0.5,0">
                        <GradientStop
                Color="#FF58C2FF"
                Offset="0" />
                        <GradientStop
                Color="#FFFFFFFF"
                Offset="1" />
                    </LinearGradientBrush>
                </Border.Background>
                <Grid Height="76" Name="grid1" Width="441">
                    <Image Height="35" HorizontalAlignment="Left" Margin="6,6,0,0" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="35" Source="/MyNameSpace;component/Images/warning-icon.png" />
                    <Label Content="Caps Lock is ON" Height="31" HorizontalAlignment="Left" Margin="125,-6,0,0" Name="lblWarningHeader" VerticalAlignment="Top" FontSize="16" FontWeight="Bold" />
                    <TextBlock HorizontalAlignment="Right" Margin="0,22,17,-1" Name="txbMessage" Width="379">Having Caps Lock on may cause you to enter your password incorrectly. <LineBreak/> <LineBreak/> You should press Caps Lock to turn it of before entering your password. VerticalAlignment="Top" Width="346" FontSize="11"</TextBlock>
                </Grid>
            </Border>
            <Image
            Source="{Binding Path=IconSource}" Width="16" HorizontalAlignment="Left" Margin="-56,0,0,-38" Height="16" VerticalAlignment="Bottom" Grid.Row="1" />
            <Path Data="M10402.99154,55.5381L10.9919,0.64 0.7,54.9" Fill="LightSkyBlue" HorizontalAlignment="Left" Margin="32,3,0,0" Stretch="Fill" Stroke="Black" Width="22" Height="31" VerticalAlignment="Top" />
        </Grid>
    </Window>

-Type the following code behind the LoginForm.

-在 LoginForm 后面键入以下代码。

    private Point location;
    public static  bool balloonVisFlag = false;
    private DispatcherTimer timer;
    WarningBalloon Balloon = null;

    private void ShowHideBalloon()
    {            
        if (System.Windows.Forms.Control.IsKeyLocked(System.Windows.Forms.Keys.CapsLock))
        {
            if (timer == null)
            {
                timer = new DispatcherTimer();
            }
            location = GetControlPosition(psbPassword);
            Balloon.Left = location.X;
            Balloon.Top = location.Y;
            Balloon.Show();
            balloonVisFlag = true;
            timer.Interval = TimeSpan.FromMilliseconds(5000);
            timer.IsEnabled = true;
            timer.Tick += new EventHandler(Timer_Tick);
            psbPassword.Focus();
        }
        else
        {
            Balloon.Hide();
            balloonVisFlag = false;
            psbPassword.Focus();
        }
    }

    Point GetControlPosition(Control myControl)
    {
        Point locationToScreen = myControl.PointToScreen(new Point(0, 0));
        PresentationSource source = PresentationSource.FromVisual(myControl);
        return source.CompositionTarget.TransformFromDevice.Transform(locationToScreen);
    }     

    private void psbPassword_KeyDown(object sender, KeyEventArgs e)
    {
        ShowHideBalloon();
    }

    private void Window_LocationChanged(object sender, EventArgs e)
    {
        if (balloonVisFlag == true)
        {
            ShowHideBalloon();
        }
    }

    private void Timer_Tick(object sender, EventArgs e)
    {
        if (balloonVisFlag == true)
        {
            Balloon.Hide();
            balloonVisFlag = false;
        }
    }    
}

回答by Amittai Shapira

In our application we've implemented balloons as a simple WPF Window. The Window location is bounded to some of the parent control model properties. Here's a sample code (where BalloonContainerWindow inherits from Window):

在我们的应用程序中,我们将气球实现为一个简单的 WPF 窗口。Window 位置受限于某些父控件模型属性。这是一个示例代码(其中 BalloonContainerWindow 继承自 Window):

        BaloonContainterWindow newBalloon = new BaloonContainterWindow();
        newBalloon.CreateBaloon(balloonType, balloonData);

        // Allow input and output when theis window is on top of winforms window
        SetBalloonLocation(newBalloon, sequenceId, stepId, rulerModel);

        newBalloon.Show();
        newBalloon.CloseOnDeactivation = false;
        newBalloon.Activate();