你的位置:首页 > ASP.net教程

[ASP.net教程]利用Clip制作打洞效果

起因

如上篇博文所说,连线原型需要在中间文字上下各留15像素的空白。设计师完成原型之后,问我有没有办法实现,我说,我能想到两种实现方式。其中一种就是上篇博文所说的OpacityMask。第二种就是使用Clip了。下面是效果图:

Snap1

代码实现

Clip

Clip定义在UIElement中,类型为Geometry 。MSDN中的解释是获取或设置用于定义元素内容边框的几何图形。实际上不光可以在边框处留住空白,在UI元素里面留出空白也是可以的,只要定义好相关的形状。

矩形空洞

在一个大矩形中去除一个小矩形就行了。

主要代码在RectangleHoleConverter中,代码如下:

namespace HoleWithClip{  using System;  using System.Globalization;  using System.Windows;  using System.Windows.Data;  using System.Windows.Media;  /// <summary>  /// 矩形空洞的转换器  /// </summary>  public class RectangleHoleConverter : IMultiValueConverter  {    /// <summary>    /// 转换成矩形空洞    /// </summary>    /// <param name="values">    /// 转换值列表,第一个表示起始宽度,第二个表示起始高度,    /// 第三个表示总宽度,第四个表示总高度    /// 第五个表示宿主宽度,第六个表示宿主宽度    /// </param>    /// <param name="targetType"></param>    /// <param name="parameter"></param>    /// <param name="culture"></param>    /// <returns></returns>    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)    {      if (values == null || values.Length != 6 || values.HaveNullItem()        || !values.IsAllInstanceOfType(typeof(double)))      {        return DependencyProperty.UnsetValue;      }      var maskStartWidth = (double)values[0];      var maskStartHeight = (double)values[1];      var maskTotalWidth = (double)values[2];      var maskTotalHeight = (double)values[3];      var hostWidth = (double)values[4];      var hostHeight = (double)values[5];      if (hostWidth == 0.0 || hostHeight == 0.0)      {        return null;      }      var maskRectangle = new RectangleGeometry(new Rect(new Size(hostWidth, hostHeight)));      var maskEllipse = new RectangleGeometry(new Rect(        new Point(maskStartWidth, maskStartHeight),        new Size(maskTotalWidth, maskTotalHeight)));      var combinedGeometry = Geometry.Combine(maskRectangle, maskEllipse, GeometryCombineMode.Exclude, null);      return combinedGeometry;    }    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)    {      return new[] { Binding.DoNothing };    }  }}

其中用到了两个扩展方法如下:

namespace HoleTest{  using System;  using System.Collections.Generic;  using System.Linq;  public static class EnumerableExtension  {    /// <summary>    /// 枚举器中是否存在null条目    /// </summary>    /// <typeparam name="T">元素类型</typeparam>    /// <param name="enumerable">元素枚举</param>    /// <returns>存在null条目返回true,否则返回false</returns>    public static bool HaveNullItem<T>(this IEnumerable<T> enumerable)    {      return enumerable.Any(item => item == null);    }    /// <summary>    /// 枚举器中是否全为指定类型的实例    /// </summary>    /// <typeparam name="T">元素类型</typeparam>    /// <param name="enumerable">元素枚举</param>    /// <returns>全为指定类型的实例返回true,否则返回false</returns>    public static bool IsAllInstanceOfType<T>(this IEnumerable<T> enumerable, Type type)    {      return enumerable.All(item => type.IsInstanceOfType(item));    }  }}

椭圆形空洞

跟矩形空洞类似,在大的矩形中间排除一个椭圆。

主要代码在EllipseHoleConverter中,代码如下:

namespace HoleWithClip{  using System;  using System.Globalization;  using System.Windows;  using System.Windows.Data;  using System.Windows.Media;  /// <summary>  /// 椭圆形空洞的转换器  /// </summary>  public class EllipseHoleConverter : IMultiValueConverter  {    /// <summary>    /// 转换成矩形空洞    /// </summary>    /// <param name="values">    /// 转换值列表,第一个表示起始宽度,第二个表示起始高度,    /// 第三个表示总宽度,第四个表示总高度    /// 第五个表示宿主宽度,第六个表示宿主宽度    /// </param>    /// <param name="targetType"></param>    /// <param name="parameter"></param>    /// <param name="culture"></param>    /// <returns></returns>    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)    {      if (values == null || values.Length != 6 || values.HaveNullItem()        || !values.IsAllInstanceOfType(typeof(double)))      {        return DependencyProperty.UnsetValue;      }      var maskEllipseCenterX = (double)values[0];      var maskEllipseCenterY = (double)values[1];      var maskRadiusX = (double)values[2];      var maskRadiusY = (double)values[3];      var hostWidth = (double)values[4];      var hostHeight = (double)values[5];      if (hostWidth == 0.0 || hostHeight == 0.0)      {        return null;      }      var maskRectangle = new RectangleGeometry(new Rect(new Size(hostWidth, hostHeight)));      var maskEllipse = new EllipseGeometry(        new Point(maskEllipseCenterX, maskEllipseCenterY),        maskRadiusX,        maskRadiusY);      var combinedGeometry = Geometry.Combine(maskRectangle, maskEllipse, GeometryCombineMode.Exclude, null);      return combinedGeometry;    }    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)    {      return new[] { Binding.DoNothing };    }  }}

不规则空洞

我们各种Shape中最常用的是Path,因为许多复杂的效果能够在设计工具中绘制成Path。与此类似,Geometry中最强大的还是PathGeometry。上面图片中的五角形就是将Path数据转换成PathGeometry的。XAML代码如下:

<Border Width="180"    Height="180"    Margin="3"    Background="Aquamarine">  <Border Background="LightPink" Clip="M90.000003,0.5 L111.12693,68.873594 L179.50001,68.871913 L124.18408,111.12744 L145.31405,179.49999 L90.000003,137.24175 L34.685959,179.49999 L55.815923,111.12744 L0.50000013,68.871913 L68.87308,68.873594 z" /></Border>

话说,只看XAML代码真看不出这是什么鬼东西。

下载链接

博客园:HoleWithClip

OpacityMask与Clip区别

可以看出,通过OpacityMask和Clip都能实现打洞效果。但是通过OpactiyMask实现的效果在空洞中间会引发相应的事件,而Clip则不会,原因是在于UIElement中作命中测试考虑到了Clip的存在。应根据实际需求选择不同的实现方式。