2010-02-22 4 views
20

Nous souhaitons utiliser des messages de bulle comme décrit dans le document UX Guide de Microsoft. J'ai trouvé quelques exemples qui utilisent le code natif de Windows Forms, mais le code natif nécessite un handle pour le composant qui est un peu difficile pour une application WPF car il ne suit pas le même concept.Comment implémenter un message Balloon dans une application WPF

J'ai trouvé quelques sample code qui utilisent le mécanisme de décorateur de WPF, mais je ne suis toujours pas convaincu que ce soit l'approche la plus simple pour l'application WPF. Une implémentation possible pourrait-elle être de mettre en œuvre un décorateur autour d'une info-bulle?

Le cas concret que j'ai est un formulaire avec plusieurs zones de texte qui ont besoin de validation d'entrée et de notification sur les valeurs d'entrée erronées possibles - quelque chose qui semble approprié pour les messages ballon.

Y at-il un contrôle commercial ou open source construit pour ce cas d'utilisation sous WPF dont je devrais être au courant?

+0

J'ai déjà donné une réponse dans le lien suivant. S'il vous plaît suivez-le. [Comment Créer ballon pour Caps avertissement Lock] [1] [1]: http://stackoverflow.com/questions/1092808/wpf-warn-about-capslock/8060520#8060520 –

Répondre

4

J'ai fini par mettre un TextBlock dans la couche 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> 

J'ai aussi utilisé l'info-bulle comme indiqué dans tous les exemples WPF sur il:

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

pas optimale (serait vraiment un contrôle du message de ballon), mais fonctionne assez bien pour le besoin que nous avons.

8

Le UX Guide souligne que les différences entre un ballon et une infobulle sont:

  • Les ballons peuvent être affichés indépendamment de l'emplacement du pointeur, donc ils ont une queue qui indique la source . Les ballons ont un titre, un corps de texte et une icône.

  • Les ballons peuvent être interactifs, alors qu'il est impossible de cliquer sur une astuce.

Ce dernier est le seul point d'achoppement en ce qui concerne WPF. Si vous avez besoin que l'utilisateur puisse interagir avec le contenu du ballon, il doit alors s'agir d'un popup, pas d'une info-bulle. (Vous pouvez tirer profit du message du forum this si vous suivez cette route.)

Mais si tout ce que vous faites est d'afficher des notifications, vous pouvez certainement utiliser une info-bulle. Vous n'avez pas besoin de jouer avec les décorateurs non plus; Créez simplement un modèle de contrôle pour l'info-bulle qui ressemble à ce que vous voulez, créez une ressource Info-bulle qui utilise ce style et définissez la propriété ToolTip du contrôle cible sur ToolTip. Utilisez le ToolTipService pour contrôler l'emplacement par rapport à la cible de placement.

+0

Challenge est pour déclencher l'info-bulle à afficher lorsque le curseur n'est pas sur le champ. J'ai abandonné cette solution. En pensant à un AdornerLayer spécifique qui est plus visuel que la bordure rouge normale – tronda

+0

Je pensais aussi à l'implémenter comme une implémentation d'adorner personnalisée comme le Validation.ErrorTemplate ControlTemplate, mais je ne sais pas comment l'aborder. – tronda

2

Dans notre application, nous avons implémenté des bulles comme une simple fenêtre WPF. L'emplacement de la fenêtre est limité à certaines propriétés du modèle de contrôle parent. Voici un exemple de code (où BalloonContainerWindow hérite de la fenêtre):

 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

Est-il possible d'ajouter un exemple de code simplifié à cet article? – tronda

3

J'ai fait un ballon d'avertissement pour résoudre le problème d'avertissement Caps Lock dans mon projet WPF.

enter image description here

Si vous voulez ajouter cet avertissement de ballon dans votre projet, procédez comme suit:

- Ajouter une nouvelle fenêtre dans votre projet et de donner le nom « WarningBalloon ».
- Ajouter code suivant XAML contre une nouvelle fenêtre et ajouter l'icône d'avertissement dans le dossier d'image du projet.

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

- Tapez le code suivant derrière le 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

Pour la production, cela demande beaucoup de travail. redimensionnement de la fenêtre et l'emplacement a changé les gestionnaires d'événements pour les démarreurs. Si le contrôle vers lequel il pointe est en bas, à droite, quelle que soit la flèche et la taille à ajuster automatiquement, sinon ça a l'air terrible. Ce code est juste un point de départ - pas une solution. – Krafty

7

je suis allé de l'avant et créé un site CodePlex pour ce qui inclut « Toast popups » et de contrôle « Ballons d'aide ». Ces versions ont plus de fonctionnalités que ce qui est décrit ci-dessous. Code Plex Project.

Voici le lien vers le Nuget Package

Voici ma solution pour la légende du ballon. Certaines des choses que je voulais qu'il fasse différemment:

  • fondu lorsque la souris entre. Fondu lorsque la souris quitte et ferme la fenêtre lorsque l'opacité atteint 0.
  • Si la souris est au-dessus de la fenêtre, l'opacité sera à 100% et ne se fermera pas.
  • La hauteur de la fenêtre de ballon est dynamique.
  • Utiliser les déclencheurs d'événements au lieu de minuteries.
  • Placez le ballon sur le côté gauche ou à droite du contrôle.

Screnshotenter image description here

Voici les images d'aide que j'ai utilisé.

enter image description hereenter image description here

J'ai créé un UserControl avec une simple icône "Aide".

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

Et a ajouté à son code derrière.

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

Voici le code XAML de la fenêtre Balloon que le contrôle UserControl ouvre.

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

Et le code derrière la fenêtre ballon.

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

J'ai regardé toute la journée pour quelque chose qui fait exactement cela. Malheureusement, bien que génial, les infobulles dans votre projet CodePlex sont personnalisées et ne vont pas avec un look système de stock. Y a-t-il un moyen facile d'obtenir que votre contrôle ressemble à un pourboire standard? – Ani

+0

Actuellement, je ne suis pas exposé à changer. Ce contrôle commence à prendre de l'ampleur et on m'a demandé d'ajouter d'autres fonctionnalités. Veuillez ajouter votre demande au site CodePlex, et je l'ajouterai à ma prochaine version. En attendant, vous pouvez télécharger le code source et modifier le code XAML, ce qui devrait être assez facile à faire. Que vous pour vos commentaires et votre soutien. – LawMan

Questions connexes