WPF中,UIElement都有个Clip通用属性,我们可以通过该属性对元素进行裁剪。现在我们就通过裁剪的方法,来实现擦除功能。

擦除功能实现包含几个部分:

  1. 擦除初始化
  2. 相同坐标系和不同坐标系之间的擦除
  3. 元素旋转后的擦除
  4. 擦除完成后,移除元素
  5. 撤消重做的实现

具体修改方法如下所示:

一、Geometry合并算法


		//连接俩个矩形,形成一个新的Geometry
    	Geometry ExcludeWithGeometry(RawRect rect1, RawRect rect2)
        {
            if (rect1.Left <= rect2.Left && rect1.Top <= rect2.Top)
            {
                return CombineGeometry(rect1, rect2);
            }
            else if (rect2.Left < rect1.Left && rect2.Top < rect1.Top)
            {
                return CombineGeometry(rect2, rect1);
            }
            else if (rect1.Left <= rect2.Left && rect1.Bottom >= rect2.Bottom)
            {
                return CombineGeometry2(rect1, rect2);
            }
            else if (rect2.Left < rect1.Left && rect2.Bottom > rect1.Bottom)
            {
                return CombineGeometry2(rect2, rect1);
            }

            return null;
        }

		//左边到右边,上面到下面的连续绘制
        Geometry CombineGeometry(RawRect rect1, RawRect rect2)
        {
            
            var streamGeometry = new StreamGeometry();
            var streamGeometryContext = streamGeometry.Open();
            streamGeometryContext.BeginFigure(rect1.TopLeft, true, true);
            streamGeometryContext.LineTo(rect1.TopRight, true, true);
            streamGeometryContext.LineTo(rect2.TopRight, true, true);
            streamGeometryContext.LineTo(rect2.BottomRight, true, true);
            streamGeometryContext.LineTo(rect2.BottomLeft, true, true);
            streamGeometryContext.LineTo(rect1.BottomLeft, true, true);
            streamGeometryContext.Close();
            return streamGeometry;
        }

		//
        Geometry CombineGeometry2(RawRect rect1, RawRect rect2)
        {            
            var streamGeometry = new StreamGeometry();
            var streamGeometryContext = streamGeometry.Open();
            streamGeometryContext.BeginFigure(rect1.TopLeft, true, true);
            streamGeometryContext.LineTo(rect2.TopLeft, true, true);
            streamGeometryContext.LineTo(rect2.TopRight, true, true);
            streamGeometryContext.LineTo(rect2.BottomRight, true, true);
            streamGeometryContext.LineTo(rect1.BottomRight, true, true);
            streamGeometryContext.LineTo(rect1.BottomLeft, true, true);
            streamGeometryContext.Close();
            return streamGeometry;
        }

		//表示一个矩形,该矩形可旋转。
		//之所以需要这个矩形类,主要是要让它支持旋转。自带的Rect是会自动将点集校正成正矩形的。
        struct RawRect
        {
            public double Left => TopLeft.X;

            public double Top => TopLeft.Y;

            public double Bottom => BottomRight.Y;

            public double Right => BottomRight.X;

            public Point TopLeft { get; set; }

            public Point TopRight { get; set; }

            public Point BottomRight { get; set; }

            public Point BottomLeft { get; set; }
        }

二、擦除实现部分代码

		//启动擦除

		//将CurrentSlide的坐标转换成指定元素上,包含旋转。
		//然后调用Geometry合并算法进行合并
		//area 表示擦除的矩形面积,element表示要擦除的对象,CurrentSlide表示画布(即element容器)
        var topLeft = CurrentSlide.TranslatePoint(area.TopLeft, element);
        var topRight = CurrentSlide.TranslatePoint(area.TopRight, element);
        var bottomLeft = CurrentSlide.TranslatePoint(area.BottomLeft, element);
        var bottomRight = CurrentSlide.TranslatePoint(area.BottomRight, element);
				
        if (element.Clip == null)
        {
			//初始化Element的Clip Geometry。
            element.Clip = new RectangleGeometry(new Rect(element.Bounds.X - element.X, element.Bounds.Y - element.Y, element.Bounds.Width, element.Bounds.Height));
        }
        else
        {
			//合并矩形
            StreamGeometry streamGeometry = new StreamGeometry();
            var cxt = streamGeometry.Open();
            cxt.BeginFigure(topLeft, true, true);
            cxt.LineTo(topRight, false, true);
            cxt.LineTo(bottomRight, false, true);
            cxt.LineTo(bottomLeft, false, true);
            cxt.Close();

            var geo = Geometry.Combine(element.Clip, streamGeometry, GeometryCombineMode.Exclude, null);

            element.Clip = geo;
        }


		//持续擦除
		//_lastElementRawRect 是记录上一次擦除的矩形位置的字典

		var leftTop = CurrentSlide.TranslatePoint(new Point(position.X - _area.Width / 2, position.Y - _area.Height / 2), element);
        var leftBottom = CurrentSlide.TranslatePoint(new Point(position.X - _area.Width / 2, position.Y + _area.Height / 2), element);
        var rightTop = CurrentSlide.TranslatePoint(new Point(position.X + _area.Width / 2, position.Y - _area.Height / 2), element);
        var rightBottom = CurrentSlide.TranslatePoint(new Point(position.X + _area.Width / 2, position.Y + _area.Height / 2), element);
        var newrect = new RawRect() { TopLeft = leftTop, BottomLeft = leftBottom, BottomRight = rightBottom, TopRight = rightTop };

        var lastRawRect = _lastElementRawRect[element];
        var geo = ExcludeWithGeometry(lastRawRect, newrect);
        if (geo == null)
         {
            StreamGeometry streamGeometry = new StreamGeometry();
            var cxt = streamGeometry.Open();
            cxt.BeginFigure(leftTop, true, true);
            cxt.LineTo(rightTop, false, true);
            cxt.LineTo(rightBottom, false, true);
            cxt.LineTo(leftBottom, false, true);
            cxt.Close();
            element.Clip = Geometry.Combine(element.Clip, streamGeometry, GeometryCombineMode.Exclude, null);
        }
        else
            element.Clip = Geometry.Combine(element.Clip, geo, GeometryCombineMode.Exclude, null);

            _lastElementRawRect[element] = newrect;

三、全部擦除后移除元素

        /// <summary>
        /// 通过此属性来判断是否擦除完成,擦除完成后即可从容器中移除
        /// </summary>
        public bool IsErased
        {
            get
            {
                if (Clip == null) return false;

                if (Clip.FillContainsWithDetail(GetVisualGeometry(this)) == IntersectionDetail.Empty)
                {
                    return true;
                }

                return false;
            }
        }

        /// <summary>
        /// 表示透明度的值,默认为 0
        /// </summary>
        private readonly byte _alpha = 0;

        private Geometry GetVisualGeometry(Visual rootVisual)
        {
            Geometry visualGeometry = Geometry.Empty;
            var drawing = VisualTreeHelper.GetDrawing(rootVisual);
            var visualCount = VisualTreeHelper.GetChildrenCount(rootVisual);
            if (drawing == null)
            {
                for (int i = 0; i < visualCount; i++)
                {
                    var visual = VisualTreeHelper.GetChild(rootVisual, i) as Visual;
                    if (visual != null)
                        GetVisualGeometry(visual);
                }
            }
            else
            {
                foreach (var drawingItem in drawing.Children)
                {
                    CombineVisualGeometry(ref visualGeometry, drawingItem);
                }
            }
            return visualGeometry;
        }

        private void CombineVisualGeometry(ref Geometry targetGeometry, Drawing drawing)
        {
            if (drawing is GeometryDrawing geometryDrawing)
            {
                if (!IsColorTransparent(geometryDrawing.Brush))
                {
                    targetGeometry = Geometry.Combine(targetGeometry, geometryDrawing.Geometry, GeometryCombineMode.Union, null);
                }
                if (!IsColorTransparent(geometryDrawing.Pen.Brush))
                {
                    var outlineGeometry = geometryDrawing.Geometry.GetWidenedPathGeometry(geometryDrawing.Pen);
                    targetGeometry = Geometry.Combine(targetGeometry, outlineGeometry, GeometryCombineMode.Union, null);
                }
            }
            else if (drawing is ImageDrawing imageDrawing)
            {
                targetGeometry = Geometry.Combine(targetGeometry, new RectangleGeometry(imageDrawing.Rect), GeometryCombineMode.Union, null);
            }
            else if (drawing is VideoDrawing videoDrawing)
            {
                targetGeometry = Geometry.Combine(targetGeometry, new RectangleGeometry(videoDrawing.Rect), GeometryCombineMode.Union, null);
            }
            else if (drawing is GlyphRunDrawing glyphRunDrawing)
            {
                if (!IsColorTransparent(glyphRunDrawing.ForegroundBrush))
                {
                    var glyphRunGeometry = glyphRunDrawing.GlyphRun.BuildGeometry();

                    targetGeometry = Geometry.Combine(targetGeometry, glyphRunGeometry, GeometryCombineMode.Union, null);
                }
            }
            else if (drawing is DrawingGroup drawingGroup)
            {
                foreach (var subDrawing in drawingGroup.Children)
                {
                    CombineVisualGeometry(ref targetGeometry, subDrawing);
                }
            }
            else
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// 画刷是否为透明色
        /// </summary>
        /// <param name="brush"></param>
        /// <returns></returns>
        private bool IsColorTransparent(Brush brush)
        {
            var solidColorBrush = brush as SolidColorBrush;
            if (solidColorBrush != null)
            {
                return IsSolidColorBrushTransparent(solidColorBrush);
            }
            var linearGradientBrush = brush as LinearGradientBrush;

            if (linearGradientBrush != null)
            {
                return IsLinearGradientBrushTransparent(linearGradientBrush);
            }
            var radialGradientBrush = brush as RadialGradientBrush;
            if (radialGradientBrush != null)
            {
                return IsRadialGradientBrushTransparent(radialGradientBrush);
            }
            return false;
        }
        /// <summary>
        /// 单一色是否透明
        /// </summary>
        /// <param name="solidColorBrush"></param>
        /// <returns></returns>
        private bool IsSolidColorBrushTransparent(SolidColorBrush solidColorBrush)
        {
            if (solidColorBrush.Color.A <= _alpha)
            {
                return true;
            }
            return false;
        }
        /// <summary>
        /// 径向渐变色是否透明
        /// </summary>
        /// <param name="radialGradientBrush"></param>
        /// <returns></returns>
        private bool IsRadialGradientBrushTransparent(GradientBrush radialGradientBrush)
        {
            return IsColorTransparent(radialGradientBrush.GradientStops);
        }

        /// <summary>
        /// 线性渐变色是否透明
        /// </summary>
        /// <param name="linearGradientBrush"></param>
        /// <returns></returns>
        private bool IsLinearGradientBrushTransparent(GradientBrush linearGradientBrush)
        {
            return IsColorTransparent(linearGradientBrush.GradientStops);
        }

        /// <summary>
        /// 颜色是否为透明色
        /// </summary>
        /// <param name="gradientStops"></param>
        /// <returns></returns>
        private bool IsColorTransparent(GradientStopCollection gradientStops)
        {
            return gradientStops.All(gradientStop => gradientStop.Color.A <= _alpha);
        }

撤销重做就不做示例了,比较简单。

注意事项:通用代码贴出了完整代码,业务方面代码只贴出了核心部分代码,剩余部分同学们可根据自己实际情况修改


本文会经常更新,请阅读原文: https://huchengv5.gitee.io//post/WPF-%E6%93%A6%E9%99%A4%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名胡承(包含链接: https://huchengv5.gitee.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系