引言
项目中有时需要在图片上标注热区,在HTML中有<area>标签,但在WPF中目前还没现成的控件来实现这这一功能。至于图片热区功能有道词典的【图解词典】是个不错的例子,如图1:
图 1
什么是Adorner?
Adoner 是一个绑定到某个UIElement自定义的FrameWorkElemnt 。Adoner被渲染在AdornerLayer,而AdornerLayer又被渲染在被装饰的Element或者Element集合上面。Adorner的渲染独立于它所装饰的UIElement。Adoner通常使用相对坐标来定位到它所装饰的Element上。
Adorner应用场景:
- 为UIElement添加功能句柄,从而使用户能够通过某种方式操作改Element(如Resize,Rotate,Reposition等)
- 可以提供多种状态提示或者响应许多事件
- 在UIElement上叠加一些可视修饰(外观修饰)
- 遮罩UIElement的部分或者全部
本文所涉及的功能一方面是要添加一个装饰元素去标记热区另一方面就是为该热区添加响应事件。
ImageWithHotSpot
先看下效果图:
当用户在图片上点击红色圆圈内部是,将会触发一个点击事件。
实现思路:
首先新建一个用户自定义控件ImageWithHotSpots.xaml并在其中添加一个Image控件来存放要添加热点的图片,为了在Image控件上添加热点Adorner,我新建了个ImageHotSpotAdorner类,为了方便控制热点的形状外观我独立定义了个ImageHotSpot类,另一方面在我们引用该自定义控件时不用关注具体的Adorner,而只需要关注热点本身就可以了。
代码结构
代码:
ImageHotSpot Class code:
public class ImageHotSpot { public int Id { get; set; } public Point Location { get; set; } public double With { get; set; } public double Height { get; set; } public Color BorderColor { get; set; } public double BorderThickness { get; set; } public event EventHandler MouseClick; public void InvokeMouseClickEvent() { if (MouseClick != null) { MouseClick(this, EventArgs.Empty); } } }
ImageWithHotSpots.xaml
<UserControl x:Class="WPFImageHotSopts.ImageWithHotSpots" 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" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <Image Name="ImageForHotSpot"/> </Grid> </UserControl>
ImageWithHotSpots.cs
/// <summary> /// Interaction logic for ImageWithHotSpots.xaml /// </summary> public partial class ImageWithHotSpots : UserControl { private List<ImageHotSpot> _imageHotSpotsList = new List<ImageHotSpot>(); public ImageWithHotSpots() { InitializeComponent(); Loaded += OnLoaded; } private void OnLoaded(object sender, RoutedEventArgs args) { foreach (var imageHotSpot in _imageHotSpotsList) { AddImageHotSpot(imageHotSpot); } } public ImageSource Source { get { return ImageForHotSpot.Source; } set { ImageForHotSpot.Source = value; } } public List<ImageHotSpot> ImageHotSpots { get { return _imageHotSpotsList; } } public void AddImageHotSpot(ImageHotSpot hotSpot) { var adorner = new ImageHotSpotAdorner(ImageForHotSpot,hotSpot); _imageHotSpotsList.Add(hotSpot); var layer = AdornerLayer.GetAdornerLayer(ImageForHotSpot); layer.Add(adorner); } public void RemoveImageHotSpot(ImageHotSpot hotSpot) { var layer = AdornerLayer.GetAdornerLayer(ImageForHotSpot); var adorners=layer.GetAdorners(ImageForHotSpot); if (adorners != null) { var adorner = adorners.FirstOrDefault(a => ((ImageHotSpot) a.Tag).Id == hotSpot.Id); layer.Remove(adorner); } } }
ImageHotSpotAdorner.cs
public class ImageHotSpotAdorner : Adorner { #region Data private Ellipse _control; private Point _location; private ArrayList _logicalChildren; #endregion Data #region Constructor public ImageHotSpotAdorner(Image adornedImage, ImageHotSpot hotSpot) : base(adornedImage) { if (hotSpot == null) throw new ArgumentNullException("hotSpot is null"); _location = hotSpot.Location; _control = new Ellipse(); _control.Height = 0.1 * adornedImage.ActualHeight; _control.Width = 0.1 * adornedImage.ActualWidth; _control.Stroke = new SolidColorBrush(hotSpot.BorderColor); _control.StrokeThickness = hotSpot.BorderThickness; _control.Fill=new SolidColorBrush(Colors.Transparent); _control.MouseLeftButtonUp += _control_MouseLeftButtonUp; _control.MouseEnter += _control_MouseEnter; _control.MouseLeave += _control_MouseLeave; _control.Tag = hotSpot; base.AddLogicalChild(_control); base.AddVisualChild(_control); } void _control_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e) { this.Cursor=Cursors.Arrow; } void _control_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e) { this.Cursor = Cursors.Hand; } private void _control_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { if (_control != null && e.ClickCount == 1) { var hotSpot = (ImageHotSpot)_control.Tag; hotSpot.InvokeMouseClickEvent(); } } #endregion Constructor #region UpdateTextLocation public void UpdateTextLocation(Point newLocation) { _location = newLocation; _control.InvalidateArrange(); } #endregion UpdateTextLocation #region Measure/Arrange /// <summary> /// Allows the control to determine how big it wants to be. /// </summary> /// <param name="constraint">A limiting size for the control.</param> protected override Size MeasureOverride(Size constraint) { _control.Measure(constraint); return _control.DesiredSize; } /// <summary> /// Positions and sizes the control. /// </summary> /// <param name="finalSize">The actual size of the control.</param> protected override Size ArrangeOverride(Size finalSize) { Rect rect = new Rect(_location, finalSize); _control.Arrange(rect); return finalSize; } #endregion Measure/Arrange #region Visual Children /// <summary> /// Required for the element to be rendered. /// </summary> protected override int VisualChildrenCount { get { return 1; } } /// <summary> /// Required for the element to be rendered. /// </summary> protected override Visual GetVisualChild(int index) { if (index != 0) throw new ArgumentOutOfRangeException("index"); return _control; } #endregion Visual Children #region Logical Children /// <summary> /// Required for the displayed element to inherit property values /// from the logical tree, such as FontSize. /// </summary> protected override IEnumerator LogicalChildren { get { if (_logicalChildren == null) { _logicalChildren = new ArrayList(); _logicalChildren.Add(_control); } return _logicalChildren.GetEnumerator(); } } #endregion Logical Children }
参考文档
MSDN Adorners Overview http://msdn.microsoft.com/en-us/library/ms743737(v=vs.110).aspx
Annotating an Image in WPF http://www.codeproject.com/Articles/20457/Annotating-an-Image-in-WPF
源码下载:WPFImageHotSopts.rar