WPF中,UIElement都有个Clip通用属性,我们可以通过该属性对元素进行裁剪。现在我们就通过裁剪的方法,来实现擦除功能。
擦除功能实现包含几个部分:
- 擦除初始化
- 相同坐标系和不同坐标系之间的擦除
- 元素旋转后的擦除
- 擦除完成后,移除元素
- 撤消重做的实现
具体修改方法如下所示:
一、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/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。