2016-09-30 99 views
3

WPF弹出我想实现看起来像下面的设计以弹出框形式:与箭头式的

enter image description here

灰色方块代表UIElement,显示弹出窗口点击时。弹出式样式只是一个边框(简单部分),带有一个指向目标元素中心(硬部分)的箭头。此外,对齐也很重要,当控件放置在窗口的右侧时,弹出窗口应该对齐到右侧,否则向左对齐。

有没有一个例子或一些文件,指导我如何进行?

+0

你的反馈是什么? – AnjumSKhan

回答

2

我已经使用了CustomPopupPlacementCallback Delegate。我甚至考虑过垂直移动你的箭。所以,现在在下面的这个例子中,箭头向左/向右,向上/向下移动。

可以按原样使用此示例。

Window1.xaml

<Window ...> 

    <Grid> 

     <Button Click="Btn_Click" Width="110" Height="25" Content="Button" HorizontalAlignment="Left" Margin="437,26,0,0" VerticalAlignment="Top"/> 
     <Button Click="Btn_Click" Content="Button" HorizontalAlignment="Left" Margin="10,90,0,0" VerticalAlignment="Top" Width="75"/>   
     <Button Click="Btn_Click" Content="Button" HorizontalAlignment="Left" Margin="139,146,0,0" VerticalAlignment="Top" Width="75"/> 
     <Button Click="Btn_Click" Content="Button" HorizontalAlignment="Left" Margin="180,0,0,0" VerticalAlignment="Top" Width="74"/> 
     <Button Click="Btn_Click" Content="Button" HorizontalAlignment="Left" Margin="224,333,0,0" VerticalAlignment="Top" Width="76"/> 
     <Button Click="Btn_Click" Content="Button" HorizontalAlignment="Right" VerticalAlignment="Top" Width="75"/> 
     <Button Click="Btn_Click" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="75" /> 
     <Button Click="Btn_Click" Content="Button" HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="75" /> 
     <Button Click="Btn_Click" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" /> 

     <Popup x:Name="Popup1" Placement="Custom" StaysOpen="False" Opened="Popup1_Opened"> 
      <Grid x:Name="Grd" Width="300" Height="100" Background="AliceBlue"> 
       <Canvas x:Name="Cnv"> 
        <Path x:Name="TopArrow" Canvas.Left="50" Canvas.Top="25" Margin="5" Data="M0,0 L-5,-5 L-10,0 z" Fill="Black" Stroke="Black" StrokeThickness="2"/> 
        <TextBlock Canvas.Top="35" FontSize="18" x:Name="Tb1"/> 
       </Canvas> 
      </Grid> 
     </Popup> 

    </Grid> 

</Window> 

Window1.xaml.cs

using System; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Controls.Primitives; 
using System.Windows.Shapes; 

namespace ... 
{ 
    /// <summary> 
    /// Interaction logic for Window1.xaml 
    /// </summary> 
    public partial class Window1 : Window 
    { 
     public Window1() 
     { 
      InitializeComponent(); 

      Popup1.CustomPopupPlacementCallback = 
       new CustomPopupPlacementCallback(placePopup); 
     } 

     public CustomPopupPlacement[] placePopup(Size popupSize, 
              Size targetSize, 
              Point offset) 
     { 
      CustomPopupPlacement placement2 = 
       new CustomPopupPlacement(new Point(-(popupSize.Width - targetSize.Width/2), targetSize.Height), PopupPrimaryAxis.Vertical); 

      CustomPopupPlacement placement1 = 
       new CustomPopupPlacement(new Point(targetSize.Width/2, targetSize.Height), PopupPrimaryAxis.Vertical); 

      CustomPopupPlacement placement3 = 
       new CustomPopupPlacement(new Point(targetSize.Width/2, -popupSize.Height), PopupPrimaryAxis.Horizontal); 

      CustomPopupPlacement placement4 = 
       new CustomPopupPlacement(new Point(-(popupSize.Width - targetSize.Width/2), -popupSize.Height), PopupPrimaryAxis.Horizontal); 

      CustomPopupPlacement[] ttplaces = 
        new CustomPopupPlacement[] { placement1, placement2, placement3, placement4 }; 

      return ttplaces; 
     } 

     private void Btn_Click(object sender, RoutedEventArgs e) 
     { 
      Popup1.PlacementTarget = sender as Button; 
      Popup1.IsOpen = true; 
     } 

     private void Popup1_Opened(object sender, EventArgs e) 
     { 
      Path arrow = ((Path)Popup1.FindName("TopArrow")); 

      Grid grd = ((Grid)Popup1.FindName("Grd")); 
      UIElement elem = (UIElement)Popup1.PlacementTarget; 

      Point elem_pos_lefttop = elem.PointToScreen(new Point(0, 0)); 
      Point popup_pos_lefttop = grd.PointToScreen(new Point(0, 0)); 

      if ( (elem_pos_lefttop.Y < popup_pos_lefttop.Y) 
        && 
        ((elem_pos_lefttop.X > popup_pos_lefttop.X)) 
       ) 
      { 
        Canvas.SetLeft(arrow, 280); 
        Canvas.SetTop(arrow, 25); 
      } 
      if ((elem_pos_lefttop.Y < popup_pos_lefttop.Y) 
        && 
        ((elem_pos_lefttop.X < popup_pos_lefttop.X)) 
       ) 
      { 
       Canvas.SetLeft(arrow, 30); 
       Canvas.SetTop(arrow, 25); 
      } 
      if ((elem_pos_lefttop.Y > popup_pos_lefttop.Y) 
        && 
        ((elem_pos_lefttop.X > popup_pos_lefttop.X)) 
       ) 
      { 
       Canvas.SetLeft(arrow, 280); 
       Canvas.SetTop(arrow, 90); 
      } 
      if ((elem_pos_lefttop.Y > popup_pos_lefttop.Y) 
        && 
        ((elem_pos_lefttop.X < popup_pos_lefttop.X)) 
       ) 
      { 
       Canvas.SetLeft(arrow, 30); 
       Canvas.SetTop(arrow, 90); 
      } 

      Tb1.Text = String.Format("Element = {0} \r\n Popup = {1}", elem_pos_lefttop, popup_pos_lefttop); 
     } 
    } 
} 

请告诉我们,如果这能解决您的问题。

+0

在为您提供的示例中永远不会调用'placePopup'方法。 –

+0

@DanielPeñalba整个例子就像一个魅力。再检查一遍。 – AnjumSKhan

+0

@DanielPeñalba如果你愿意来聊天 – AnjumSKhan

3

好的,我有一个解决方案。这非常复杂。

如果你只是在一个普通的弹出窗口之后,只需要一个尾部,你可以使用这个(ActualLayout和UpdateTail逻辑)的块。如果您在整个帮助提示体验之后,您会遇到不愉快的行程。

我确实认为,尽管沿着Adorner路线走下去可能会更好(并且我正在考虑重新使用adorener)。我发现了一些问题,它仍然在工作。使用弹出窗口会导致它们出现在其他窗口顶部的设计器中,这真的很烦人。我也注意到,由于某些奇怪的原因,它们没有在某些计算机上正确定位(但我没有安装Visual Studio来正确调试)。

它生产的是这样的:

enter image description here

以下标准:

  • 只有一个单一的帮助提示每次都能将

  • 在显示屏幕上如果用户更改了选项卡,并且帮助提示所附的控件不再可见,则帮助提示消失,并且ne显示

  • 一旦封闭XT帮助尖,该类型的帮助,提示不会再次显示

  • 帮助提示可以通过一个中央选项

好吧关闭。所以,实际的帮助提示是一个完全透明并添加到用户界面的用户控件。它有一个使用静态类管理的弹出窗口。这里的控制:

<UserControl x:Class="...HelpPopup" 
      d:DesignHeight="0" d:DesignWidth="0"> 
    <UserControl.Resources> 
     <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/> 
    </UserControl.Resources> 
    <Canvas> 
     <Popup x:Name="Popup" 
       d:DataContext="{d:DesignInstance {x:Null}}" 
       DataContext="{Binding HelpTip, ElementName=userControl}" 
       StaysOpen="True" PopupAnimation="Fade" 
       AllowsTransparency="True" 
       materialDesign:ShadowAssist.ShadowDepth="Depth3" 
       Placement="{Binding Placement, ElementName=userControl}" 
       HorizontalOffset="-10" 
       VerticalOffset="{Binding VerticalOffset, ElementName=userControl}"> 
      <Grid Margin="0,0,0,0" SnapsToDevicePixels="True"> 
       <Canvas Margin="10"> 
        <local:RoundedCornersPolygon Fill="{StaticResource PrimaryHueDarkBrush}" 
               SnapsToDevicePixels="True" 
               ArcRoundness="4" 
               Points="{Binding PolygonPath, ElementName=userControl}" 
               Effect="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Popup}, Path=(materialDesign:ShadowAssist.ShadowDepth), Converter={x:Static converters:ShadowConverter.Instance}}"/> 
       </Canvas> 
       <Border BorderBrush="Transparent" BorderThickness="10,25,10,25"> 
        <Grid x:Name="PopupChild"> 
         <materialDesign:ColorZone Mode="PrimaryDark" Margin="5"> 
          <StackPanel> 
           <Grid> 
            <Grid.ColumnDefinitions> 
             <ColumnDefinition Width="*"/> 
             <ColumnDefinition Width="AUTO"/> 
            </Grid.ColumnDefinitions> 
            <TextBlock Text="Useful Tip" 
               FontWeight="Bold" 
               Margin="2,0,0,0" 
               Grid.ColumnSpan="2" 
               VerticalAlignment="Center"/> 

            <Button Style="{StaticResource MaterialDesignToolButton}" Click="CloseButton_Click" Grid.Column="1" Margin="0" Padding="0" Height="Auto"> 
             <Button.Content> 
              <materialDesign:PackIcon Kind="CloseCircle" Height="20" Width="20" Foreground="{StaticResource PrimaryHueLightBrush}"/> 
             </Button.Content> 
            </Button> 

           </Grid> 
           <TextBlock Text="{Binding Message}" 
              TextWrapping="Wrap" 
              MaxWidth="300" 
              Margin="2,4,2,4"/> 
           <StackPanel Orientation="Horizontal" HorizontalAlignment="Right"> 
            <Button Content="Close" Padding="8,2" Height="Auto" Click="CloseButton_Click" 
              Margin="2" 
              Style="{StaticResource MaterialDesignFlatButtonInverted}"/> 
            <Button Content="Never show again" 
              Margin="2" 
              Padding="8,2" 
              Height="Auto" 
              Click="NeverShowButton_Click" 
              Style="{StaticResource MaterialDesignFlatButtonInverted}"/> 
           </StackPanel> 
          </StackPanel> 
         </materialDesign:ColorZone> 
        </Grid> 
       </Border> 
      </Grid> 
     </Popup> 
    </Canvas> 
</UserControl> 

你可以改变这种样式你想要的。我使用了一个自定义的圆角多边形类和MaterialDesign颜色区域。无论你想要替换这些。现在

,后面的代码是......嗯,有很多的它,和它的并不愉快:

public enum ActualPlacement { TopLeft, TopRight, BottomLeft, BottomRight } 

/// <summary> 
/// Interaction logic for HelpPopup.xaml 
/// </summary> 
public partial class HelpPopup : UserControl, INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    private ActualPlacement actualPlacement = ActualPlacement.TopRight; 
    public ActualPlacement ActualPlacement 
    { 
     get { return actualPlacement; } 
     internal set 
     { 
      if (actualPlacement != value) 
      { 
       if (actualPlacement == ActualPlacement.BottomLeft || ActualPlacement == ActualPlacement.BottomRight) 
       { 
        Console.WriteLine("-10"); 
        VerticalOffset = 10; 
       } 
       else if (actualPlacement == ActualPlacement.TopLeft || ActualPlacement == ActualPlacement.TopRight) 
       { 
        VerticalOffset = -10; 
        Console.WriteLine("10"); 
       } 

       actualPlacement = value; 
       UpdateTailPath(); 
       NotifyOfPropertyChange("ActualPlacement"); 

      } 
     } 
    } 

    public void UpdateTailPath() 
    { 
     double height = PopupChild.ActualHeight + 30; 
     double width = PopupChild.ActualWidth; 

     switch (actualPlacement) 
     { 
      case ActualPlacement.TopRight: 
       polygonPath = "0.5,15.5 " + (width - 0.5) + ",15.5 " + (width - 0.5) + "," + (height - 15.5) + 
           " 15.5," + (height - 15.5) + " 0.5," + height + " 0.5,15.5"; ; 
       break; 
      case ActualPlacement.TopLeft: 
       polygonPath = "0.5,15.5 " + (width - 0.5) + ",15.5 " + (width - 0.5) + "," + height + " " + (width - 15.5) + "," + (height - 15.5) + 
           " 0.5," + (height - 15.5) + " 0.5,15.5"; 
       break; 
      case ActualPlacement.BottomRight: 
       polygonPath = "0.5,0.5 15.5,15.5 " + (width - 0.5) + ",15.5 " + (width - 0.5) + "," + (height - 15.5) + 
           " 0.5," + (height - 15.5) + " 0.5,0.5"; 
       break; 
      case ActualPlacement.BottomLeft: 
       polygonPath = "0.5,15.5 " + (width - 15.5) + ",15.5 " + (width - 0.5) + ",0.5 " + (width - 0.5) + "," + (height - 15.5) + 
           " 0.5," + (height - 15.5) + " 0.5,15.5"; 
       break; 
     } 
     NotifyOfPropertyChange("PolygonPath"); 
    } 

    private String polygonPath; 
    public String PolygonPath 
    { 
     get { return polygonPath; } 
    } 

    public PlacementMode Placement 
    { 
     get { return (PlacementMode)GetValue(PlacementProperty); } 
     set { SetValue(PlacementProperty, value); } 
    } 

    // Using a DependencyProperty as the backing store for Placement. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty PlacementProperty = 
     DependencyProperty.Register("Placement", typeof(PlacementMode), typeof(HelpPopup), new PropertyMetadata(PlacementMode.Top)); 

    public int VerticalOffset 
    { 
     get { return (int)GetValue(VerticalOffsetProperty); } 
     set { SetValue(VerticalOffsetProperty, value); } 
    } 

    // Using a DependencyProperty as the backing store for VerticalOffset. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty VerticalOffsetProperty = 
     DependencyProperty.Register("VerticalOffset", typeof(int), typeof(HelpPopup), new PropertyMetadata(-10)); 

    public HelpTip HelpTip 
    { 
     get { return (HelpTip)GetValue(HelpTipProperty); } 
     set { SetValue(HelpTipProperty, value); } 
    } 

    // Using a DependencyProperty as the backing store for Message. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty HelpTipProperty = 
     DependencyProperty.Register("HelpTip", typeof(HelpTip), typeof(HelpPopup), new PropertyMetadata(new HelpTip() { Message = "No help message found..." }, HelpTipChanged)); 

    private static void HelpTipChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     if ((d as HelpPopup).HelpTipOnScreenInstance == null) 
     { 
      (d as HelpPopup).HelpTipOnScreenInstance = new HelpTipOnScreenInstance((d as HelpPopup)); 
     } 
     (d as HelpPopup).HelpTipOnScreenInstance.HelpTip = (e.NewValue as HelpTip); 
    } 

    private static void HelpTipOnScreenInstance_PropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     HelpTipOnScreenInstance htosi = sender as HelpTipOnScreenInstance; 
     if (e.PropertyName.Equals(nameof(htosi.IsOpen))) 
     { 
      //open manually to avoid stupid COM errors 
      if (htosi != null) 
      { 
       try 
       { 
        htosi.HelpPopup.Popup.IsOpen = htosi.IsOpen; 
       } 
       catch (System.ComponentModel.Win32Exception ex) 
       { 
        Canvas parent = htosi.HelpPopup.Popup.Parent as Canvas; 
        htosi.HelpPopup.Popup.IsOpen = false; 
        parent.Children.Remove(htosi.HelpPopup.Popup); 
        Application.Current.Dispatcher.BeginInvoke(new Action(() => { 
         htosi.HelpPopup.Popup.IsOpen = true; 
         parent.Children.Add(htosi.HelpPopup.Popup); 
         htosi.HelpPopup.UpdatePositions(); 
        }), DispatcherPriority.SystemIdle); 

       } 
      } 
     } 
    } 

    private HelpTipOnScreenInstance helpTipOnScreenInstance; 
    public HelpTipOnScreenInstance HelpTipOnScreenInstance 
    { 
     get { return helpTipOnScreenInstance; } 
     set 
     { 
      if (helpTipOnScreenInstance != value) 
      { 
       if (helpTipOnScreenInstance != null) 
       { 
        HelpTipOnScreenInstance.PropertyChanged -= HelpTipOnScreenInstance_PropertyChanged; 
       } 
       helpTipOnScreenInstance = value; 
       HelpTipOnScreenInstance.PropertyChanged += HelpTipOnScreenInstance_PropertyChanged; 
       NotifyOfPropertyChange("HelpTipOnScreenInstance"); 
      } 
     } 
    } 

    private double popupX; 
    public double PopupX 
    { 
     get { return popupX; } 
     set 
     { 
      if (popupX != value) 
      { 
       popupX = value; 
       NotifyOfPropertyChange("PopupX"); 
      } 
     } 
    } 

    private double popupY; 
    public double PopupY 
    { 
     get { return popupY; } 
     set 
     { 
      if (popupY != value) 
      { 
       popupY = value; 
       NotifyOfPropertyChange("PopupY"); 
      } 
     } 
    } 

    private void NotifyOfPropertyChange(string propertyName) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) 
     { 
      handler(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 

    public HelpPopup() 
    { 
     InitializeComponent(); 

     // Wire up the Loaded handler instead 
     this.Loaded += new RoutedEventHandler(View1_Loaded); 
     this.Unloaded += HelpPopup_Unloaded; 

     Popup.Opened += Popup_Opened; 

     //PopupChild.LayoutUpdated += HelpPopup_LayoutUpdated; 
     PopupChild.SizeChanged += HelpPopup_SizeChanged; 
     UpdatePositions(); 
    } 

    private void Popup_Opened(object sender, EventArgs e) 
    { 
     UpdateTail(); 
     UpdateTailPath(); 
    } 

    private void HelpPopup_SizeChanged(object sender, SizeChangedEventArgs e) 
    { 
     Console.WriteLine(HelpTip.Message + ": " + e.PreviousSize.ToString() + " to " + e.NewSize.ToString()); 
     UpdateTail(); 
     UpdateTailPath(); 
    } 

    private void HelpPopup_Unloaded(object sender, RoutedEventArgs e) 
    { 
     //don't waste resources on never show popups 
     if (HelpTip.NeverShow) 
     { 
      return; 
     } 
     HelpTipOnScreenInstance.IsOnscreen = false; 
    } 

    /// Provides a way to "dock" the Popup control to the Window 
    /// so that the popup "sticks" to the window while the window is dragged around. 
    void View1_Loaded(object sender, RoutedEventArgs e) 
    { 
     //don't waste resources on never show popups 
     if (HelpTip.NeverShow) 
     { 
      return; 
     } 

     //wait for a few seconds, then set this to on-screen 
     HelpTipOnScreenInstance.IsOnscreen = true; 

     //update so tail is facing right direction 
     UpdateTail(); 

     Window w = Window.GetWindow(this); 
     // w should not be Null now! 
     if (null != w) 
     { 
      w.LocationChanged += delegate (object sender2, EventArgs args) 
      { 
       // "bump" the offset to cause the popup to reposition itself 
       // on its own 
       UpdatePositions(); 
      }; 
      // Also handle the window being resized (so the popup's position stays 
      // relative to its target element if the target element moves upon 
      // window resize) 
      w.SizeChanged += delegate (object sender3, SizeChangedEventArgs e2) 
      { 
       UpdatePositions(); 
      }; 
     } 
    } 

    private void UpdatePositions() 
    { 
     var offset = Popup.HorizontalOffset; 
     Popup.HorizontalOffset = offset + 1; 
     Popup.HorizontalOffset = offset; 

     UpdateTail(); 
    } 

    private void UpdateTail() 
    { 
     UIElement container = VisualTreeHelper.GetParent(this) as UIElement; 
     Point relativeLocation = PopupChild.TranslatePoint(new Point(5, 5), container); //It HAS(!!!) to be this.Child 

     if (relativeLocation.Y < 0) 
     { 
      if (relativeLocation.X < -(PopupChild.ActualWidth-5/2)) 
      { 
       ActualPlacement = ActualPlacement.TopLeft; 
      } 
      else 
      { 
       ActualPlacement = ActualPlacement.TopRight; 
      } 
     } 
     else 
     { 
      if (relativeLocation.X < -(PopupChild.ActualWidth-5/2)) 
      { 
       ActualPlacement = ActualPlacement.BottomLeft; 
      } 
      else 
      { 
       ActualPlacement = ActualPlacement.BottomRight; 
      } 
     } 
    } 

    private void CloseButton_Click(object sender, RoutedEventArgs e) 
    { 
     lock (HelpTip.Lock) 
     { 
      HelpTip.Closed = true; 
      HelpTipOnScreenInstance.IsOpen = false; 
     } 
    } 

    private void NeverShowButton_Click(object sender, RoutedEventArgs e) 
    { 
     lock (HelpTip.Lock) 
     { 
      HelpTip.Closed = true; 
      HelpTip.NeverShow = true; 
      HelpTipOnScreenInstance.IsOpen = false; 
     } 
    } 
} 

需要注意的事项。

  • 有“ActualPlacement”来管理弹出窗口的实际位置,因为设置位置只是WPF的一个建议。

  • UpdateTailPath()正在重新绘制多边形,以在放置更改后获取 正确位置中的尾部。

  • 我们有控制无论是 在屏幕上HelpTip类,存储信息(标题,内容 等),和HelpTipOnScreenInstance。原因是我们可以在屏幕上显示多个帮助提示 ,并且只想显示一个。

  • 弹出事件的各种侦听器来触发尾部更新。

  • 我们附加到加载和卸载usercontrol的事件。这个 允许我们跟踪控件是否在屏幕上,并且 是否应该显示帮助提示 (HelpTipOnScreenInstance.IsOnscreen = true)。

  • 我们还监听窗口更改事件,所以如果窗口被调整大小或移动,我们可以更新弹出窗口的位置 。现在

,HelpTipOnScreenInstance和HelpTip:

public class HelpTipOnScreenInstance : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    public object Lock = new Object(); 

    private void NotifyOfPropertyChange(string propertyName) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) 
     { 
      //handler(this, new PropertyChangedEventArgs(propertyName)); 
      handler(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 

    private HelpTip helpTip; 
    public HelpTip HelpTip 
    { 
     get { return helpTip; } 
     set 
     { 
      if (helpTip != value) 
      { 
       helpTip = value; 
       NotifyOfPropertyChange("HelpTip"); 
      } 
     } 
    } 

    private bool isOpen = false; 
    public bool IsOpen 
    { 
     get { return isOpen; } 
     set 
     { 
      if (isOpen != value) 
      { 
       isOpen = value; 
       Console.WriteLine("Opening " + HelpTip.Message); 
       NotifyOfPropertyChange("IsOpen"); 
      } 
     } 
    } 

    private bool isOnscreen = false; 
    public bool IsOnscreen 
    { 
     get { return isOnscreen; } 
     set 
     { 
      if (isOnscreen != value) 
      { 
       isOnscreen = value; 
       NotifyOfPropertyChange("IsOnscreen"); 
      } 
     } 
    } 

    private HelpPopup helpPopup; 
    public HelpPopup HelpPopup 
    { 
     get { return helpPopup; } 
     set 
     { 
      if (helpPopup != value) 
      { 
       helpPopup = value; 
       NotifyOfPropertyChange("HelpPopup"); 
      } 
     } 
    } 

    public HelpTipOnScreenInstance(HelpPopup helpPopup) 
    { 
     HelpPopup = helpPopup; 
     HelpTipManager.AddHelpTip(this); 
    } 
} 

public class HelpTip : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    public object Lock = new Object(); 

    private void NotifyOfPropertyChange(string propertyName) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) 
     { 
      //handler(this, new PropertyChangedEventArgs(propertyName)); 
      handler(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 

    private String id; 
    public String ID 
    { 
     get { return id; } 
     set { id = value; } 
    } 

    private String message; 
    public String Message 
    { 
     get { return message; } 
     set 
     { 
      if (message != value) 
      { 
       message = value; 
       NotifyOfPropertyChange("Message"); 
      } 
     } 
    } 

    private bool closed; 
    public bool Closed 
    { 
     get { return closed; } 
     set 
     { 
      if (closed != value) 
      { 
       closed = value; 
       NotifyOfPropertyChange("Closed"); 
      } 
     } 
    } 

    public bool NeverShow { get; set; } 
} 

然后一个静态管理类,跟踪一个什么样的屏幕上,什么不是,并选择谁得到的旁边显示:

public static class HelpTipManager 
{ 
    public static object Lock = new Object(); 

    private static bool displayHelpTips = false; 
    public static bool DisplayHelpTips 
    { 
     get { return displayHelpTips; } 
     set { 
      if (displayHelpTips != value) 
      { 
       displayHelpTips = value; 

       if (displayHelpTips) 
       { 
        //open next! 
        OpenNext(); 
       } 
       else 
       { 
        //stop displaying all 
        foreach(HelpTipOnScreenInstance helpTip in helpTipsOnScreen) 
        { 
         lock (helpTip.HelpTip.Lock) 
         { 
          helpTip.IsOpen = false; 
         } 
        } 
       } 
      } 
     } 
    } 

    private static List<HelpTipOnScreenInstance> helpTips = new List<HelpTipOnScreenInstance>(); 
    private static List<HelpTipOnScreenInstance> helpTipsOnScreen = new List<HelpTipOnScreenInstance>(); 
    private static bool supressOpenNext = false; 

    public static void AddHelpTip(HelpTipOnScreenInstance helpTip) 
    { 
     helpTip.PropertyChanged += HelpTip_PropertyChanged; 
     helpTips.Add(helpTip); 
    } 

    private static void HelpTip_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 
    { 
     HelpTipOnScreenInstance helpTip = sender as HelpTipOnScreenInstance; 
     if (helpTip != null) 
     { 
      //is this on screen or not? 
      switch (e.PropertyName) 
      { 
       case "IsOnscreen": 
        //Update our onscreen lists and perform related behaviour 
        if (helpTip.IsOnscreen) 
        { 
         AddedToScreen(helpTip); 
        } 
        else 
        { 
         RemovedFromScreen(helpTip); 
        } 
        break; 
       case "IsOpen": 
        lock (helpTip.Lock) 
        { 
         if (!supressOpenNext) 
         { 
          if (!helpTip.IsOpen) 
          { 
           OpenNext(); 
          } 
         } 
        } 
        break; 
      } 
     } 
    } 

    private static void OpenNext() 
    { 
     if (DisplayHelpTips) 
     { 
      if (helpTipsOnScreen.Count > 0) 
      { 
       //check if none of them are open 
       if (helpTipsOnScreen.Count(ht => ht.IsOpen) == 0) 
       { 
        //open the first that's not been closed! 
        HelpTipOnScreenInstance firstNotClosed = helpTipsOnScreen.FirstOrDefault(ht => !ht.HelpTip.Closed); 
        if (firstNotClosed != null) 
        { 
         lock (firstNotClosed.Lock) 
         { 
          firstNotClosed.IsOpen = true; 
         } 
        } 
       } 
      } 
     } 
    } 

    private static void AddedToScreen(HelpTipOnScreenInstance helpTip) 
    { 
     lock (Lock) 
     { 
      helpTipsOnScreen.Add(helpTip); 
      OpenNext(); 
     } 
    } 

    private static void RemovedFromScreen(HelpTipOnScreenInstance helpTip) 
    { 
     lock (Lock) 
     { 
      helpTipsOnScreen.Remove(helpTip); 
      supressOpenNext = true; 
      helpTip.IsOpen = false; 
      //OpenNext(); 
      supressOpenNext = false; 
     } 
    } 
} 

那么如何使用它?您可以在generic.xaml添加帮助提示数据或像这样的资源库:

<controls:HelpTip x:Key="KPIGraphMenu" ID="KPIGraphMenu" Message="Right click to change the colour, remove, or move KPI to view as a stacked trace. KPI can also be dragged onto other charts of any type."/> 

,并在这样的实际应用中使用它们,我喜欢它们叠加在与他们相关的控制网格使用对齐来确定尾部指向的位置:

<controls:HelpPopup HelpTip="{StaticResource KPIGraphMenu}" HorizontalAlignment="Center" VerticalAlignment="Center"/>