0)简述:鉴于本论坛有人提示需要实现类似Word标注的ToolTip,TFSoft近日已经实现。现将源代码无偿奉献。主要特点:01)自动适应提示内容,02)自动调整提示位置,自动调整标准箭头方向,03)用法与ToolTipService一样(采用附加属性实现),04)有多种标注方式(椭圆形、云形、方形、圆角方形),05)比ToolTipService公开了更多的附加属性。
注意:引用了Expression Blend 的一个DLL(C:\Program Files\Microsoft SDKs\Expression\Blend\Silverlight\v4.0\Libraries\Microsoft.Expression.Drawing.dll)
1)主要程序文件(T4CtATToolTipService.cs):
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Effects; using System.Windows.Threading; using Microsoft.Expression.Controls; using Microsoft.Expression.Media; namespace CT { /// <summary> /// 功能描述:为提供与微软Word标注样式类似的ToolTipService。 /// 作者:土肥软件工作室(TFSoft Workshop), /// 联系电话:13576098022 /// 程序版本:1.1 ///最后修改日期:2010-09-23 /// 版权说明:任何人均可免费修改、复制、使用,但建议保留作者信息。 /// 广告语:土肥软件追求简单、美好、高雅(TFSoft = Simple, Nice, Elegant) /// </summary> public class T4ToolTipService : DependencyObject { //局部变量声明 private static Popup TipPopup; //提示弹出容器 private static Callout TipCallout; //提示标准 private static TextBlock TipTextBlock; //提示文本框 private static DispatcherTimer TipTimer; //提示计时器 private static DateTime OwnerTipStartTime; //依赖对象提示开始时间 private static int OwnerTipDuration; //依赖对象提示停留时间 private static bool NotifyPropertyChangeIf; //属性更改通知标志 //静态构造函数(初始化局部变量) static T4ToolTipService() { TipPopup = new Popup(); TipCallout = new Callout(); TipTextBlock = new TextBlock(); TipPopup.Child = TipCallout; TipCallout.Content = TipTextBlock; TipTextBlock.TextWrapping = TextWrapping.Wrap; TipTextBlock.VerticalAlignment = VerticalAlignment.Center; TipTimer = new DispatcherTimer(); TipTimer.Stop(); TipTimer.Tick += TipTimer_Tick; NotifyPropertyChangeIf = true; } //启动计时器 private static void TipTimerStart(FrameworkElement ElOwner) { TipTimer.Stop(); OwnerTipStartTime = DateTime.Now; OwnerTipDuration = GetTipDuration(ElOwner); var TickInterval = OwnerTipDuration / 5; TipTimer.Interval = new TimeSpan(0, 0, 0, 0, TickInterval); TipTimer.Start(); } //停止计时器 private static void TipTimerStop() { TipTimer.Stop(); } //侦测依赖对象提示信息显示时间 private static void TipTimer_Tick(object sender, EventArgs e) { var TipEndTime = DateTime.Now; var TipSpan = TipEndTime.Subtract(OwnerTipStartTime); if (Convert.ToInt32(TipSpan.TotalMilliseconds) > OwnerTipDuration) { TipPopup.IsOpen = false; TipTimer.Stop(); } } //依赖属性定义-提示文本字体 public static FontFamily GetTipTextFontFamily(DependencyObject DpObj) { return (FontFamily)DpObj.GetValue(TipTextFontFamilyProperty); } public static void SetTipTextFontFamily(DependencyObject DpObj, FontFamily DpValue) { DpObj.SetValue(TipTextFontFamilyProperty, DpValue); } public static readonly DependencyProperty TipTextFontFamilyProperty = DependencyProperty.RegisterAttached( "TipTextFontFamily", typeof(FontFamily), typeof(FrameworkElement), new PropertyMetadata(new FontFamily("Arial,NSimSun"))); //依赖属性定义-提示文本字号 public static double GetTipTextFontSize(DependencyObject DpObj) { return (double)DpObj.GetValue(TipTextFontSizeProperty); } public static void SetTipTextFontSize(DependencyObject DpObj, double DpValue) { DpObj.SetValue(TipTextFontSizeProperty, DpValue); } public static readonly DependencyProperty TipTextFontSizeProperty = DependencyProperty.RegisterAttached( "TipTextFontSize", typeof(double), typeof(FrameworkElement), new PropertyMetadata(13.0)); //依赖属性定义-提示文本颜色 public static Brush GetTipTextForeground(DependencyObject DpObj) { return (Brush)DpObj.GetValue(TipTextForegroundProperty); } public static void SetTipTextForeground(DependencyObject DpObj, Brush DpValue) { DpObj.SetValue(TipTextForegroundProperty, DpValue); } public static readonly DependencyProperty TipTextForegroundProperty = DependencyProperty.RegisterAttached( "TipTextForeground", typeof(Brush), typeof(FrameworkElement), new PropertyMetadata(new SolidColorBrush(SystemColors.InfoTextColor))); //依赖属性定义-提示文本边距 public static Thickness GetTipTextMargin(DependencyObject DpObj) { return (Thickness)DpObj.GetValue(TipTextMarginProperty); } public static void SetTipTextMargin(DependencyObject DpObj, Thickness DpValue) { DpObj.SetValue(TipTextMarginProperty, DpValue); } public static readonly DependencyProperty TipTextMarginProperty = DependencyProperty.RegisterAttached( "TipTextMargin", typeof(Thickness), typeof(FrameworkElement), new PropertyMetadata(new Thickness(4.0))); //依赖属性定义-提示标注形状 public static T4TipCalloutStyle GetTipCalloutStyle(DependencyObject DpObj) { return (T4TipCalloutStyle)DpObj.GetValue(TipCalloutStyleProperty); } public static void SetTipCalloutStyle(DependencyObject DpObj, T4TipCalloutStyle DpValue) { DpObj.SetValue(TipCalloutStyleProperty, DpValue); } public static readonly DependencyProperty TipCalloutStyleProperty = DependencyProperty.RegisterAttached( "TipCalloutStyle", typeof(T4TipCalloutStyle), typeof(FrameworkElement), new PropertyMetadata(T4TipCalloutStyle.RoundedRectangle)); //依赖属性定义-提示标注线条颜色 public static Brush GetTipCalloutStroke(DependencyObject DpObj) { return (Brush)DpObj.GetValue(TipCalloutStrokeProperty); } public static void SetTipCalloutStroke(DependencyObject DpObj, Brush DpValue) { DpObj.SetValue(TipCalloutStrokeProperty, DpValue); } public static readonly DependencyProperty TipCalloutStrokeProperty = DependencyProperty.RegisterAttached( "TipCalloutStroke", typeof(Brush), typeof(FrameworkElement), new PropertyMetadata(new SolidColorBrush(Colors.Gray))); //依赖属性定义-提示标填充颜色 public static Brush GetTipCalloutFill(DependencyObject DpObj) { return (Brush)DpObj.GetValue(TipCalloutFillProperty); } public static void SetTipCalloutFill(DependencyObject DpObj, Brush DpValue) { DpObj.SetValue(TipCalloutFillProperty, DpValue); } public static readonly DependencyProperty TipCalloutFillProperty = DependencyProperty.RegisterAttached( "TipCalloutFill", typeof(Brush), typeof(FrameworkElement), new PropertyMetadata(new SolidColorBrush(SystemColors.InfoColor))); //依赖属性定义-提示标箭头高度 public static double GetTipCalloutArrowHeight(DependencyObject DpObj) { return (double)DpObj.GetValue(TipCalloutArrowHeightProperty); } public static void SetTipCalloutArrowHeight(DependencyObject DpObj, double DpValue) { DpObj.SetValue(TipCalloutArrowHeightProperty, DpValue); } public static readonly DependencyProperty TipCalloutArrowHeightProperty = DependencyProperty.RegisterAttached( "TipCalloutArrowHeight", typeof(double), typeof(FrameworkElement), new PropertyMetadata(16.0)); //依赖属性定义-提示体最大宽度 public static double GetTipMaxWidth(DependencyObject DpObj) { return (double)DpObj.GetValue(TipMaxWidthProperty); } public static void SetTipMaxWidth(DependencyObject DpObj, double DpValue) { DpObj.SetValue(TipMaxWidthProperty, DpValue); } public static readonly DependencyProperty TipMaxWidthProperty = DependencyProperty.RegisterAttached( "TipMaxWidth", typeof(double), typeof(FrameworkElement), new PropertyMetadata(400.0)); //依赖属性定义-提示体最大高度 public static double GetTipMaxHeight(DependencyObject DpObj) { return (double)DpObj.GetValue(TipMaxHeightProperty); } public static void SetTipMaxHeight(DependencyObject DpObj, double DpValue) { DpObj.SetValue(TipMaxHeightProperty, DpValue); } public static readonly DependencyProperty TipMaxHeightProperty = DependencyProperty.RegisterAttached( "TipMaxHeight", typeof(double), typeof(FrameworkElement), new PropertyMetadata(300.0)); //依赖属性定义-提示体最小宽度 public static double GetTipMinWidth(DependencyObject DpObj) { return (double)DpObj.GetValue(TipMinWidthProperty); } public static void SetTipMinWidth(DependencyObject DpObj, double DpValue) { DpObj.SetValue(TipMinWidthProperty, DpValue); } public static readonly DependencyProperty TipMinWidthProperty = DependencyProperty.RegisterAttached( "TipMinWidth", typeof(double), typeof(FrameworkElement), new PropertyMetadata(100.00)); //依赖属性定义-提示体最小高度 public static double GetTipMinHeight(DependencyObject DpObj) { return (double)DpObj.GetValue(TipMinHeightProperty); } public static void SetTipMinHeight(DependencyObject DpObj, double DpValue) { DpObj.SetValue(TipMinHeightProperty, DpValue); } public static readonly DependencyProperty TipMinHeightProperty = DependencyProperty.RegisterAttached( "TipMinHeight", typeof(double), typeof(FrameworkElement), new PropertyMetadata(30.0)); //依赖属性定义-提示体停留时间 public static int GetTipDuration(DependencyObject DpObj) { return (int)DpObj.GetValue(TipDurationProperty); } public static void SetTipDuration(DependencyObject DpObj, int DpValue) { DpObj.SetValue(TipDurationProperty, DpValue); } public static readonly DependencyProperty TipDurationProperty = DependencyProperty.RegisterAttached( "TipDuration", typeof(int), typeof(FrameworkElement), new PropertyMetadata(2000)); //依赖属性定义-提示体效果 public static Effect GetTipEffct(DependencyObject DpObj) { return (Effect)DpObj.GetValue(TipEffctProperty); } public static void SetTipEffct(DependencyObject DpObj, Effect DpValue) { DpObj.SetValue(TipEffctProperty, DpValue); } public static readonly DependencyProperty TipEffctProperty = DependencyProperty.RegisterAttached("TipEffct", typeof(Effect), typeof(FrameworkElement), new PropertyMetadata(new DropShadowEffect() { ShadowDepth = 1.0, BlurRadius = 0.5, Color = Colors.Black, })); //依赖属性定义-提示文本内容 public static string GetTipText(DependencyObject DpObj) { return (string)DpObj.GetValue(TipTextProperty); } public static void SetTipText(DependencyObject DpObj, string DpValue) { DpObj.SetValue(TipTextProperty, DpValue); } public static readonly DependencyProperty TipTextProperty = DependencyProperty.RegisterAttached( "TipText", typeof(string), typeof(FrameworkElement), new PropertyMetadata("", TipTextChanged)); //提示文本内容变更通知 private static void TipTextChanged(DependencyObject DpObj, DependencyPropertyChangedEventArgs DpArg) { if (NotifyPropertyChangeIf) { var ElOwner = DpObj as FrameworkElement; ElOwner.MouseEnter -= ElOwner_MouseEnter; ElOwner.MouseMove -= ElOwner_MouseMove; ElOwner.MouseLeave -= ElOwner_MouseLeave; ElOwner.MouseEnter += ElOwner_MouseEnter; ElOwner.MouseMove += ElOwner_MouseMove; ElOwner.MouseLeave += ElOwner_MouseLeave; } } //依赖对象-鼠标进入事件处理 private static void ElOwner_MouseEnter(object sender, MouseEventArgs e) { var ElOwner = sender as FrameworkElement; TipPopup.IsOpen = false; TipPropertiesCheckAndAdjust(ElOwner); InitTips(ElOwner); } //依赖对象-鼠标移动事件处理 private static void ElOwner_MouseMove(object sender, MouseEventArgs e) { var AppRoot = Application.Current.RootVisual as UserControl; var ElOwner = sender as FrameworkElement; var PosMouse = e.GetPosition(AppRoot); LayoutTips(ElOwner, PosMouse); TipPopup.IsOpen = true; TipTimerStart(ElOwner); } //依赖对象-鼠标离开事件处理 private static void ElOwner_MouseLeave(object sender, MouseEventArgs e) { DeInitTips(); TipTimerStop(); } //依赖对象-提示初始化(与位置无关内容) private static void InitTips(FrameworkElement ElOwner) { TipTextBlock.Text = GetTipText(ElOwner); TipTextBlock.Margin = GetTipTextMargin(ElOwner); TipTextBlock.FontFamily = GetTipTextFontFamily(ElOwner); TipTextBlock.FontSize = GetTipTextFontSize(ElOwner); TipTextBlock.Foreground = GetTipTextForeground(ElOwner); TipCallout.Stroke = GetTipCalloutStroke(ElOwner); TipCallout.Fill = GetTipCalloutFill(ElOwner); TipCallout.CalloutStyle = (CalloutStyle)GetTipCalloutStyle(ElOwner); TipCallout.Effect = GetTipEffct(ElOwner); } //依赖对象-提示外观调整(与位置有关内容),是整个类的核心 private static void LayoutTips(FrameworkElement ElOwner, Point PosMouse) { //设置提示对象的大小 var TipCalloutArrowHeight = GetTipCalloutArrowHeight(ElOwner); var TipTextSize = GetTipTextRequiredSize(ElOwner); var TipTextMargin = GetTipTextMargin(ElOwner); TipTextBlock.Margin = TipTextMargin; TipCallout.Width = TipTextSize.Width + TipTextMargin.Left + TipTextMargin.Right + 1; TipCallout.Height = TipTextSize.Height + TipTextMargin.Top + TipTextMargin.Bottom + 1; TipPopup.Width = TipCallout.Width + 1; TipPopup.Height = TipCallout.Height + TipCalloutArrowHeight + 1; //确定提示显示位置 var AppRoot = Application.Current.RootVisual as UserControl; var LeftSpace = PosMouse.X; var RightSpace = AppRoot.ActualWidth - PosMouse.X; var TopSpace = PosMouse.Y; var BottomSpace = AppRoot.ActualHeight - PosMouse.Y; var TipPlacementX = ""; if (RightSpace >= TipPopup.Width) { TipPlacementX = "R"; } else if (LeftSpace >= TipPopup.Width) { TipPlacementX = "L"; } else { TipPlacementX = RightSpace > LeftSpace ? "R" : "L"; } var TipPlacementY = ""; if (BottomSpace >= TipPopup.Height) { TipPlacementY = "B"; } else if (TopSpace >= TipPopup.Height) { TipPlacementY = "T"; } else { TipPlacementY = BottomSpace >= TopSpace ? "B" : "T"; } var TipPlacement = TipPlacementX + TipPlacementY; //根据提示位置,调整提示布局,箭头方向 var AnchorRatioY = TipCalloutArrowHeight / TipCallout.Height; var MouseToPopupX = 4.0; var MouseToPopupY = 4.0; if (TipPlacement == "RB") { TipCallout.AnchorPoint = new Point(0.0, 0.0 - AnchorRatioY); TipCallout.Margin = new Thickness(0.0, TipCalloutArrowHeight, 0.0, 0.0); TipPopup.HorizontalOffset = PosMouse.X + MouseToPopupX; TipPopup.VerticalOffset = PosMouse.Y + MouseToPopupY; } else if (TipPlacement == "RT") { TipCallout.AnchorPoint = new Point(0.0, 1.0 + AnchorRatioY); TipCallout.Margin = new Thickness(0.0, 0.0, 0.0, TipCalloutArrowHeight); TipPopup.HorizontalOffset = PosMouse.X + MouseToPopupX; TipPopup.VerticalOffset = PosMouse.Y - TipPopup.Height - MouseToPopupY; } else if (TipPlacement == "LB") { TipCallout.AnchorPoint = new Point(1.0, 0.0 - AnchorRatioY); TipCallout.Margin = new Thickness(0.0, TipCalloutArrowHeight, 0.0, 0.0); TipPopup.HorizontalOffset = PosMouse.X - TipCallout.Width - MouseToPopupX; TipPopup.VerticalOffset = PosMouse.Y + MouseToPopupY; } else if (TipPlacement == "LT") { TipCallout.AnchorPoint = new Point(1.0, 1.0 + AnchorRatioY); TipCallout.Margin = new Thickness(0.0, 0.0, 0.0, TipCalloutArrowHeight); TipPopup.HorizontalOffset = PosMouse.X - TipCallout.Width - MouseToPopupX; TipPopup.VerticalOffset = PosMouse.Y - TipPopup.Height - MouseToPopupY; } TipPopup.IsOpen = true; } //依赖对象-提示反初始化 private static void DeInitTips() { TipTimerStop(); TipPopup.IsOpen = false; } //依赖对象-依赖属性值合理性检查,如不合理,自动调整或抛出异常。 private static void TipPropertiesCheckAndAdjust(FrameworkElement ElOwner) { NotifyPropertyChangeIf = false; //这里以后有时间再写,以防治设置不合理的属性,导致出错。 //暂时虽然未检查,但正常属性设置情况下,不会影响使用。 //检查内容:主要是边距、最大、最小高宽等的逻辑合理性。 NotifyPropertyChangeIf = true; } //依赖对象-计算提示文本所需空间 private static Size GetTipTextRequiredSize(FrameworkElement ElOwner) { //采用的方法是通过建立一个临时的文本框来检测其所需空间。 var Rlt = new Size(); var TipTextMargin = GetTipTextMargin(ElOwner); var TipTextMaxSize = new Size() { Width = GetTipMaxWidth(ElOwner) - TipTextMargin.Left - TipTextMargin.Right, Height = GetTipMaxHeight(ElOwner) - TipTextMargin.Top - TipTextMargin.Bottom, }; var TipTextMinSize = new Size() { Width = GetTipMinWidth(ElOwner) - TipTextMargin.Left - TipTextMargin.Right, Height = GetTipMinHeight(ElOwner) - TipTextMargin.Top - TipTextMargin.Bottom, }; var TmpTextBlock = new TextBlock() { FontFamily = GetTipTextFontFamily(ElOwner), FontSize = GetTipTextFontSize(ElOwner), Width = TipTextMaxSize.Width, Height = TipTextMinSize.Height, Foreground = GetTipTextForeground(ElOwner), Text = GetTipText(ElOwner), TextWrapping = TextWrapping.Wrap, }; Rlt.Width = TmpTextBlock.ActualWidth; if (Rlt.Width > TipTextMaxSize.Width) { Rlt.Width = TipTextMaxSize.Width; }; if (Rlt.Width < TipTextMinSize.Width) { Rlt.Width = TipTextMinSize.Width; } Rlt.Height = TmpTextBlock.ActualHeight; if (Rlt.Height > TipTextMaxSize.Height) { Rlt.Height = TipTextMaxSize.Height; }; if (Rlt.Height < TipTextMinSize.Height) { Rlt.Height = TipTextMinSize.Height; } return Rlt; } } /// <summary> /// 提示标注形状定义 /// </summary> public enum T4TipCalloutStyle { Cloud = CalloutStyle.Cloud, //云形 Oval = CalloutStyle.Oval, //椭圆性 Rectangle = CalloutStyle.Rectangle, //方形 RoundedRectangle = CalloutStyle.RoundedRectangle //圆角方形 } }
2)功能测试文件(PgAppTest.xaml, 关联的C#文件里面没有代码,就不给出)
<UserControl x:Class="SlTest1.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ct="clr-namespace:CT" FontSize="13" FontFamily="Arial,NSimSun" mc:Ignorable="d" d:DesignHeight="600" d:DesignWidth="800"> <Grid x:Name="GrdRoot"> <Grid.RowDefinitions> <RowDefinition Height="40"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="40"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="100"></ColumnDefinition> </Grid.ColumnDefinitions> <Button Grid.Row="0" Grid.Column="0" Content="左上角元素" ct:T4ToolTipService.TipText="左上角元素,提示一般出现在鼠标当前位置的右下角,并自动调整箭头方向,自动调整提示框大小。"></Button> <Button Grid.Row="0" Grid.Column="2" Content="右上角元素" ct:T4ToolTipService.TipText="右上角元素,提示一般出现在鼠标当前位置的左下角,并自动调整箭头方向,自动调整提示框大小。"></Button> <Grid Grid.Row="1" Grid.Column="1"> <Grid.RowDefinitions> <RowDefinition Height="40"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="40"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="100"></ColumnDefinition> </Grid.ColumnDefinitions> <Button Grid.Row="0" Grid.Column="0" Content="方形提示" ct:T4ToolTipService.TipText="这是方形提示" ct:T4ToolTipService.TipCalloutStyle="Rectangle"> </Button> <Button Grid.Row="0" Grid.Column="2" Content="圆角方形提示" ct:T4ToolTipService.TipText="这是圆角方形标注提示" ct:T4ToolTipService.TipCalloutStyle="RoundedRectangle"> </Button> <Button Grid.Row="1" Grid.Column="0" Content="提示时间较长" ct:T4ToolTipService.TipDuration="5000" ct:T4ToolTipService.TipText="提示停留时间为5秒"> </Button> <Button Grid.Row="1" Grid.Column="1" Content="中间的元素" ct:T4ToolTipService.TipText="中间的元素,提示会根据上下左右的空间大小出现在恰当的位置,并自动调整箭头方向,自动调整提示框大小。"></Button> <Button Grid.Row="2" Grid.Column="0" Content="椭圆形提示" ct:T4ToolTipService.TipMinWidth="200" ct:T4ToolTipService.TipMaxWidth="200" ct:T4ToolTipService.TipMinHeight="60" ct:T4ToolTipService.TipMaxHeight="60" ct:T4ToolTipService.TipCalloutFill="Blue" ct:T4ToolTipService.TipTextForeground="White" ct:T4ToolTipService.TipText="这是椭圆形标注提示" ct:T4ToolTipService.TipCalloutStyle="Oval"> </Button> <Button Grid.Row="1" Grid.Column="2" Content="提示时间较短" ct:T4ToolTipService.TipDuration="1000" ct:T4ToolTipService.TipText="提示停留时间为1秒"> </Button> <Button Grid.Row="2" Grid.Column="2" Content="云形提示" ct:T4ToolTipService.TipMinWidth="200" ct:T4ToolTipService.TipMaxWidth="200" ct:T4ToolTipService.TipMinHeight="60" ct:T4ToolTipService.TipMaxHeight="60" ct:T4ToolTipService.TipCalloutFill="White" ct:T4ToolTipService.TipCalloutStroke="Red" ct:T4ToolTipService.TipText="这是云形标注提示" ct:T4ToolTipService.TipCalloutStyle="Cloud"> </Button> </Grid> <Button Grid.Row="2" Grid.Column="0" Content="左下角的元素" ct:T4ToolTipService.TipText="左下角的元素,提示一般出现在鼠标当前位置的右上角,并自动调整箭头方向,自动调整提示框大小。"></Button> <Button Grid.Row="2" Grid.Column="2" Content="右下角的元素" ct:T4ToolTipService.TipText="右下角的元素,提示一般出现在鼠标当前位置的左上角,并自动调整箭头方向,自动调整提示框大小。"></Button> </Grid> </UserControl>