2010-02-22 2 views
20

Мы хотели бы использовать сообщения на воздушном шаре, как описано в UX Guide от Microsoft. Я нашел несколько примеров, в которых используется собственный код из Windows Forms, но нативный код требует дескриптора компонента, который немного сложнее для приложения WPF, поскольку он не соответствует той же концепции.Как реализовать сообщение с воздушным шаром в приложении WPF

Я нашел несколько sample code, которые используют механизм декоратора WPF, но я все еще не убежден, что это самый простой подход для приложения WPF. Может ли возможная реализация реализовать декоратор вокруг всплывающей подсказки?

В конкретном случае у меня есть форма с несколькими текстовыми полями, которые нуждаются в проверке ввода и уведомлении о возможных неправильных входных значениях - что-то, что кажется подходящим для сообщений на воздушном шаре.

Есть ли коммерческий или открытый источник управления, построенный для этого варианта использования в WPF, о котором я должен знать?

+0

Я уже дал ответ в следующей ссылке. Пожалуйста, следуйте им. [Как создать воздушный шар для Caps Lock предупреждение] [1] [1]: http://stackoverflow.com/questions/1092808/wpf-warn-about-capslock/8060520#8060520 –

ответ

4

я в конечном итоге положить TextBlock в Adorner слое:

<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> 

Я также использовал всплывающую подсказку, как показано в каждом примере 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> 

не оптимальна (будет действительно как контроль сообщений на воздушном шаре), но работает достаточно хорошо для необходимости у нас есть.

8

Руководство UX указывает на то, что различия между баллоном и кончиком инструмента являются:

  • шары могут отображаться независимо от текущего местоположения указателя, поэтому они имеют хвост, который указывает на их источник ,

  • Воздушные шары имеют название, основной текст и значок.

  • Воздушные шары могут быть интерактивными, тогда как невозможно щелкнуть кончиком.

Это последнее является единственным камнем преткновения, насколько WPF обеспокоен. Если вам нужно, чтобы пользователь мог взаимодействовать с содержимым воздушного шара, тогда он должен быть всплывающим, а не всплывающей подсказкой. (Вы можете получить сообщение от this, если вы идете по этому маршруту.)

Но если все, что вы делаете, отображает уведомления, вы можете использовать всплывающую подсказку. Вам также не нужно возиться с декораторами; просто создайте шаблон управления для всплывающей подсказки, который выглядит так, как вы хотите, создайте ресурс ToolTip, который использует этот стиль, и установите свойство ToolTip целевого элемента управления на это ToolTip. Используйте ToolTipService, чтобы контролировать, где он отображается относительно цели размещения.

+0

Вызова чтобы вызвать всплывающую подсказку, если курсор не находится над полем. Я отказался от этого решения. Думая о конкретном AdornerLayer, который является более визуальным, чем обычная красная рамка. – tronda

+0

Я также думал о том, чтобы реализовать его как пользовательскую реализацию adorner как Validation.ErrorTemplate ControlTemplate, но не уверен, как ее решить. – tronda

2

В нашем приложении мы реализовали воздушные шары как простое окно WPF. Расположение окна ограничено некоторыми свойствами родительской модели управления. Вот пример кода (где BalloonContainerWindow наследует от окна):

 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(); 
+0

Можно ли добавить в этот пост упрощенный код примера? – tronda

3

Я сделал предупреждающий баллон, чтобы решить проблему предупреждения Caps Lock в моем WPF-проекте.

enter image description here

Если вы хотите добавить это предупреждение воздушного шара в вашем проекте, то выполните следующие действия:

- Добавить новое окно в вашем проекте и дать название «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> 

- Введите следующий код за 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; 
     } 
    }  
} 
+0

Для производства это требует большой работы. изменение размера окна и местоположение изменили обработчики событий для стартеров. Если элемент управления, на который он указывает, находится внизу, справа, независимо от того, какая стрелка и размер должны корректироваться автоматически, в противном случае он выглядит ужасно. Этот код является лишь отправной точкой, а не решением. – Krafty

7

Я пошел вперед и создал сайт CodePlex для этого, что включает в себя «Toast Всплывающие» и управления «Справка» Воздушные шары. Эти версии имеют больше возможностей, чем то, что описано ниже. Code Plex Project.

Вот ссылка на Nuget Package

Вот мое решение для баллонной подписи. Некоторые из вещей, которые я хотел бы сделать по-другому:

  • Затухание при входе мыши.
  • Увядайте, когда мышь уходит и закрывает окно, когда прозрачность достигает 0.
  • Если мышь над окном, непрозрачность будет на 100% и не будет закрыта.
  • Высота окна воздушного шара динамична.
  • Используйте триггеры событий вместо таймеров.
  • Расположите воздушный шар на левой или правой стороне элемента управления.

Screnshotenter image description here

Вот Помощь изображения, которые я использовал.

enter image description hereenter image description here

Я создал 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" /> 

И добавили этот код позади.

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; 
    } 
} 

Вот код XAML для окна воздушного шара, который открывается UserControl.

<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> 

И код позади окна Balloon.

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(); 
     } 
    } 
} 
+0

Я весь день искал то, что делает именно это. К сожалению, несмотря на то, что всплывающие подсказки в вашем проекте CodePlex оформлены в обычном стиле и не смотрятся на внешний вид системы запаса. Есть ли простой способ заставить ваш контроль выглядеть как стандартный воздушный шар? – Ani

+0

В настоящее время у меня нет таких изменений. Этот элемент управления начинает получать некоторую тягу, и меня также попросили добавить некоторые другие функции. Пожалуйста, добавьте свой запрос на сайт CodePlex, и я добавлю его в свой следующий выпуск. Тем временем вы можете загрузить исходный код и изменить XAML, что должно быть довольно легко сделать. Чем вы за ваши комментарии и поддержку. – LawMan

Смежные вопросы