Paint.Net学习笔记——四、窗体粘靠
本节介绍在Windows应用程序中出现的“控件粘靠”效果的实现。
本节介绍在Windows应用程序中出现的“控件粘靠”效果的实现。
之前一直用Winamp播放音乐,直到现在使用千千静听和酷狗,这几款音频播放软件界面一脉相承,并都具有“控件粘靠”效果,现在让我们一起来看看PDN里这种效果是如何实现的。
在PDN中,实现该效果的是由SnapManager类和SnapObstacle(下称障碍物)抽象类实现的,当然还有一些辅助类,譬如SnapDescription(下称粘靠定义)类。
SnapManager顾名思义是粘靠效果的管理类,负责各障碍物的定位、判断是否需要粘靠、保存加载效果、生成粘靠定义对象等工作。
SnapObstacle是一个抽象类,定义了作为“可粘靠”的控件的必要属性,包括障碍物轮廓,粘靠距离,粘靠事件等。
接下来看看它们是怎样在一起工作的。
“粘靠”效果主要应用在浮动窗口(工具栏、历史、颜色和图层)。从上篇文章中,我们已经了解了这几个窗体的继承关系和各自实现的接口,在这里简单复习一下:
PdnBaseForm为所有窗体的父类,实现了ISnapManagerHost接口,所有浮动窗口继承自FloatingToolForm,实现了ISnapObstacleHost接口。所有“粘靠”效果就是由这两个接口提供的。
这两个接口相当简单,各自只提供了一个属性,该属性分别是SnapManager和SnapObstacle。
我们先看看SnapManager类,上面已经解释了该类的主要作用,不再罗嗦,接下来细致看看那里面的代码:
SnapManager字段
1private Dictionary<SnapObstacle, SnapDescription> obstacles =new Dictionary<SnapObstacle, SnapDescription>();
2private const string isSnappedValueName = "IsSnapped";
3private const string leftValueName = "Left";
4private const string topValueName = "Top";
5private const string widthValueName = "Width";
6private const string heightValueName = "Height";
7private const string nullName = "";
8
9private const string snappedToValueName = "SnappedTo";
10private const string horizontalEdgeValueName = "HorizontalEdge";
11private const string verticalEdgeValueName = "VerticalEdge";
12private const string xOffsetValueName = "XOffset";
13private const string yOffsetValueName = "YOffset";
1private Dictionary<SnapObstacle, SnapDescription> obstacles =new Dictionary<SnapObstacle, SnapDescription>();
2private const string isSnappedValueName = "IsSnapped";
3private const string leftValueName = "Left";
4private const string topValueName = "Top";
5private const string widthValueName = "Width";
6private const string heightValueName = "Height";
7private const string nullName = "";
8
9private const string snappedToValueName = "SnappedTo";
10private const string horizontalEdgeValueName = "HorizontalEdge";
11private const string verticalEdgeValueName = "VerticalEdge";
12private const string xOffsetValueName = "XOffset";
13private const string yOffsetValueName = "YOffset";
obstacles字典保存了“障碍物”和“粘靠定义”的一一对应,一连串的字符串用于获取资源中的初始值以及保存粘靠定义,这些都在LoadSnapObstacleData和SaveSnapObstacleData方法中体现。
无论从子类中怎样跟踪进来,我们看见,最重要的,就是AdjustObstacleDestination和AdjustNewLocation方法:
AdjustNewLocation
1/**//// <summary>
2 /// 获取粘靠物新坐标
3 /// </summary>
4 /// <param name="obstacle">障碍物</param>
5 /// <param name="newLocation">新坐标</param>
6 /// <param name="snapDescription">粘靠定义</param>
7 /// <returns>粘靠物的新坐标</returns>
8 private static Point AdjustNewLocation(SnapObstacle obstacle, Point newLocation, SnapDescription snapDescription)
9 {
10 if (snapDescription == null ||
11 (snapDescription.HorizontalEdge == HorizontalSnapEdge.Neither &&
12 snapDescription.VerticalEdge == VerticalSnapEdge.Neither))
13 {
14 //如果粘靠定义为"不粘靠",那么返回障碍物的当前坐标
15 return obstacle.Bounds.Location;
16 }
17
18 Rectangle obstacleRect = new Rectangle(newLocation, obstacle.Bounds.Size);
19 Rectangle snappedToRect = snapDescription.SnappedTo.Bounds;
20 HorizontalSnapEdge hEdge = snapDescription.HorizontalEdge;
21 VerticalSnapEdge vEdge = snapDescription.VerticalEdge;
22 SnapRegion region = snapDescription.SnappedTo.SnapRegion;
23
24 //粘靠Y间距
25 int deltaY = 0;
26
27 //Y间距只需要判断Top和Buttom,所以这里分开四个情况判断
28 if (hEdge == HorizontalSnapEdge.Top && region == SnapRegion.Exterior)
29 {
30 //间距在障碍物外部
31 int newBottomEdge = snappedToRect.Top - snapDescription.YOffset;
32 deltaY = obstacleRect.Bottom - newBottomEdge;
33 }
34 else if (hEdge == HorizontalSnapEdge.Bottom && region == SnapRegion.Exterior)
35 {
36 int newTopEdge = snappedToRect.Bottom + snapDescription.YOffset;
37 deltaY = obstacleRect.Top - newTopEdge;
38 }
39 else if (hEdge == HorizontalSnapEdge.Top && region == SnapRegion.Interior)
40 {
41 //间距在障碍物内部
42 int newTopEdge = Math.Min(snappedToRect.Bottom, snappedToRect.Top + snapDescription.YOffset);
43 deltaY = obstacleRect.Top - newTopEdge;
44 }
45 else if (hEdge == HorizontalSnapEdge.Bottom && region == SnapRegion.Interior)
46 {
47 int newBottomEdge = Math.Max(snappedToRect.Top, snappedToRect.Bottom - snapDescription.YOffset);
48 deltaY = obstacleRect.Bottom - newBottomEdge;
49 }
50
51 //粘靠X边距
52 int deltaX = 0;
53
54 if (vEdge == VerticalSnapEdge.Left && region == SnapRegion.Exterior)
55 {
56 int newRightEdge = snappedToRect.Left - snapDescription.XOffset;
57 deltaX = obstacleRect.Right - newRightEdge;
58 }
59 else if (vEdge == VerticalSnapEdge.Right && region == SnapRegion.Exterior)
60 {
61 int newLeftEdge = snappedToRect.Right + snapDescription.XOffset;
62 deltaX = obstacleRect.Left - newLeftEdge;
63 }
64 else if (vEdge == VerticalSnapEdge.Left && region == SnapRegion.Interior)
65 {
66 int newLeftEdge = Math.Min(snappedToRect.Right, snappedToRect.Left + snapDescription.XOffset);
67 deltaX = obstacleRect.Left - newLeftEdge;
68 }
69 else if (vEdge == VerticalSnapEdge.Right && region == SnapRegion.Interior)
70 {
71 int newRightEdge = Math.Max(snappedToRect.Left, snappedToRect.Right - snapDescription.XOffset);
72 deltaX = obstacleRect.Right - newRightEdge;
73 }
74 //粘靠物新坐标为"障碍物坐标-间距"
75 Point adjustedLocation = new Point(obstacleRect.Left - deltaX, obstacleRect.Top - deltaY);
76 return adjustedLocation;
77 }
1/**//// <summary>
2 /// 获取粘靠物新坐标
3 /// </summary>
4 /// <param name="obstacle">障碍物</param>
5 /// <param name="newLocation">新坐标</param>
6 /// <param name="snapDescription">粘靠定义</param>
7 /// <returns>粘靠物的新坐标</returns>
8 private static Point AdjustNewLocation(SnapObstacle obstacle, Point newLocation, SnapDescription snapDescription)
9 {
10 if (snapDescription == null ||
11 (snapDescription.HorizontalEdge == HorizontalSnapEdge.Neither &&
12 snapDescription.VerticalEdge == VerticalSnapEdge.Neither))
13 {
14 //如果粘靠定义为"不粘靠",那么返回障碍物的当前坐标
15 return obstacle.Bounds.Location;
16 }
17
18 Rectangle obstacleRect = new Rectangle(newLocation, obstacle.Bounds.Size);
19 Rectangle snappedToRect = snapDescription.SnappedTo.Bounds;
20 HorizontalSnapEdge hEdge = snapDescription.HorizontalEdge;
21 VerticalSnapEdge vEdge = snapDescription.VerticalEdge;
22 SnapRegion region = snapDescription.SnappedTo.SnapRegion;
23
24 //粘靠Y间距
25 int deltaY = 0;
26
27 //Y间距只需要判断Top和Buttom,所以这里分开四个情况判断
28 if (hEdge == HorizontalSnapEdge.Top && region == SnapRegion.Exterior)
29 {
30 //间距在障碍物外部
31 int newBottomEdge = snappedToRect.Top - snapDescription.YOffset;
32 deltaY = obstacleRect.Bottom - newBottomEdge;
33 }
34 else if (hEdge == HorizontalSnapEdge.Bottom && region == SnapRegion.Exterior)
35 {
36 int newTopEdge = snappedToRect.Bottom + snapDescription.YOffset;
37 deltaY = obstacleRect.Top - newTopEdge;
38 }
39 else if (hEdge == HorizontalSnapEdge.Top && region == SnapRegion.Interior)
40 {
41 //间距在障碍物内部
42 int newTopEdge = Math.Min(snappedToRect.Bottom, snappedToRect.Top + snapDescription.YOffset);
43 deltaY = obstacleRect.Top - newTopEdge;
44 }
45 else if (hEdge == HorizontalSnapEdge.Bottom && region == SnapRegion.Interior)
46 {
47 int newBottomEdge = Math.Max(snappedToRect.Top, snappedToRect.Bottom - snapDescription.YOffset);
48 deltaY = obstacleRect.Bottom - newBottomEdge;
49 }
50
51 //粘靠X边距
52 int deltaX = 0;
53
54 if (vEdge == VerticalSnapEdge.Left && region == SnapRegion.Exterior)
55 {
56 int newRightEdge = snappedToRect.Left - snapDescription.XOffset;
57 deltaX = obstacleRect.Right - newRightEdge;
58 }
59 else if (vEdge == VerticalSnapEdge.Right && region == SnapRegion.Exterior)
60 {
61 int newLeftEdge = snappedToRect.Right + snapDescription.XOffset;
62 deltaX = obstacleRect.Left - newLeftEdge;
63 }
64 else if (vEdge == VerticalSnapEdge.Left && region == SnapRegion.Interior)
65 {
66 int newLeftEdge = Math.Min(snappedToRect.Right, snappedToRect.Left + snapDescription.XOffset);
67 deltaX = obstacleRect.Left - newLeftEdge;
68 }
69 else if (vEdge == VerticalSnapEdge.Right && region == SnapRegion.Interior)
70 {
71 int newRightEdge = Math.Max(snappedToRect.Left, snappedToRect.Right - snapDescription.XOffset);
72 deltaX = obstacleRect.Right - newRightEdge;
73 }
74 //粘靠物新坐标为"障碍物坐标-间距"
75 Point adjustedLocation = new Point(obstacleRect.Left - deltaX, obstacleRect.Top - deltaY);
76 return adjustedLocation;
77 }
AdjustObstacleDestination
1/**//// <summary>
2 /// Given an obstacle and its attempted destination, determines the correct landing
3 /// spot for an obstacle.
4 /// </summary>
5 /// <summary>
6 /// 使用粘靠定义,决定粘靠物的新坐标
7 /// </summary>
8 /// <param name="movingObstacle">The obstacle that is moving.</param>
9 /// <param name="movingObstacle">移动中的粘靠物(也是障碍物,需要及时调整的)</param>
10 /// <param name="newLocation">The upper-left coordinate of the obstacle's original intended destination.</param>
11 /// <param name="newLocation">粘靠物当前左上角坐标</param>
12 /// <returns>
13 /// A Point that determines where the obstacle should be placed instead. If there are no adjustments
14 /// required to the obstacle's desintation, then the return value will be equal to newLocation.
15 /// </returns>
16 /// <returns>
17 /// 用于替换障碍物的坐标,如果对粘靠物的粘靠没有任何要求,会返回粘靠物的当前坐标(newLocation参数)
18 /// </returns>
19 /// <remarks>
20 /// movingObstacle's SnapDescription will also be updated. The caller of this method is required
21 /// to update the SnapObstacle with the new, adjusted location.
22 /// </remarks>
23 /// <remarks>
24 /// 粘靠定义将会更新,请调用此方法时更新新的粘靠坐标
25 /// </remarks>
26 public Point AdjustObstacleDestination(SnapObstacle movingObstacle, Point newLocation)
27 {
28 //第一次调用,使粘靠物与障碍物实现粘靠
29 Point adjusted1 = AdjustObstacleDestination(movingObstacle, newLocation, false);
30 //第二次调用,粘靠无与四周障碍物调整粘靠
31 Point adjusted2 = AdjustObstacleDestination(movingObstacle, adjusted1, true);
32 return adjusted2;
33 }
34
35 public Point AdjustObstacleDestination(SnapObstacle movingObstacle, Point newLocation, bool considerStickies)
36 {
37 Point adjustedLocation = newLocation;
38 //获取当前粘靠物的粘靠定义
39 SnapDescription sd = this.obstacles[movingObstacle];
40 SnapDescription newSD = null;
41
42 foreach (SnapObstacle avoidee in this.obstacles.Keys)
43 {
44 if (avoidee.StickyEdges != considerStickies)
45 {
46 continue;
47 }
48
49 if (avoidee.Enabled && !object.ReferenceEquals(avoidee, movingObstacle))
50 {
51 SnapDescription newSD2 = DetermineNewSnapDescription(movingObstacle, adjustedLocation, avoidee, newSD);
52
53 if (newSD2 != null)
54 {
55 //获取新坐标
56 Point adjustedLocation2 = AdjustNewLocation(movingObstacle, adjustedLocation, newSD2);
57 newSD = newSD2;
58 adjustedLocation = adjustedLocation2;
59 Rectangle newBounds = new Rectangle(adjustedLocation, movingObstacle.Bounds.Size);
60 }
61 }
62 }
63
64 if (sd == null || !sd.SnappedTo.StickyEdges || newSD == null || newSD.SnappedTo.StickyEdges)
65 {
66 //更新粘靠定义
67 this.obstacles[movingObstacle] = newSD;
68 }
69
70 return adjustedLocation;
71 }
1/**//// <summary>
2 /// Given an obstacle and its attempted destination, determines the correct landing
3 /// spot for an obstacle.
4 /// </summary>
5 /// <summary>
6 /// 使用粘靠定义,决定粘靠物的新坐标
7 /// </summary>
8 /// <param name="movingObstacle">The obstacle that is moving.</param>
9 /// <param name="movingObstacle">移动中的粘靠物(也是障碍物,需要及时调整的)</param>
10 /// <param name="newLocation">The upper-left coordinate of the obstacle's original intended destination.</param>
11 /// <param name="newLocation">粘靠物当前左上角坐标</param>
12 /// <returns>
13 /// A Point that determines where the obstacle should be placed instead. If there are no adjustments
14 /// required to the obstacle's desintation, then the return value will be equal to newLocation.
15 /// </returns>
16 /// <returns>
17 /// 用于替换障碍物的坐标,如果对粘靠物的粘靠没有任何要求,会返回粘靠物的当前坐标(newLocation参数)
18 /// </returns>
19 /// <remarks>
20 /// movingObstacle's SnapDescription will also be updated. The caller of this method is required
21 /// to update the SnapObstacle with the new, adjusted location.
22 /// </remarks>
23 /// <remarks>
24 /// 粘靠定义将会更新,请调用此方法时更新新的粘靠坐标
25 /// </remarks>
26 public Point AdjustObstacleDestination(SnapObstacle movingObstacle, Point newLocation)
27 {
28 //第一次调用,使粘靠物与障碍物实现粘靠
29 Point adjusted1 = AdjustObstacleDestination(movingObstacle, newLocation, false);
30 //第二次调用,粘靠无与四周障碍物调整粘靠
31 Point adjusted2 = AdjustObstacleDestination(movingObstacle, adjusted1, true);
32 return adjusted2;
33 }
34
35 public Point AdjustObstacleDestination(SnapObstacle movingObstacle, Point newLocation, bool considerStickies)
36 {
37 Point adjustedLocation = newLocation;
38 //获取当前粘靠物的粘靠定义
39 SnapDescription sd = this.obstacles[movingObstacle];
40 SnapDescription newSD = null;
41
42 foreach (SnapObstacle avoidee in this.obstacles.Keys)
43 {
44 if (avoidee.StickyEdges != considerStickies)
45 {
46 continue;
47 }
48
49 if (avoidee.Enabled && !object.ReferenceEquals(avoidee, movingObstacle))
50 {
51 SnapDescription newSD2 = DetermineNewSnapDescription(movingObstacle, adjustedLocation, avoidee, newSD);
52
53 if (newSD2 != null)
54 {
55 //获取新坐标
56 Point adjustedLocation2 = AdjustNewLocation(movingObstacle, adjustedLocation, newSD2);
57 newSD = newSD2;
58 adjustedLocation = adjustedLocation2;
59 Rectangle newBounds = new Rectangle(adjustedLocation, movingObstacle.Bounds.Size);
60 }
61 }
62 }
63
64 if (sd == null || !sd.SnappedTo.StickyEdges || newSD == null || newSD.SnappedTo.StickyEdges)
65 {
66 //更新粘靠定义
67 this.obstacles[movingObstacle] = newSD;
68 }
69
70 return adjustedLocation;
71 }
以上在代码中添加了一些注释,就不再罗嗦了,如果有不懂就请留言或来信询问。
在以上我们看出,对于“障碍物”和“粘靠物”的“粘靠”效果新坐标的计算及管理,都是在SnapManager类中实现的,那么接下来,在“障碍物”中的定义就简单多了,只是在适当的时候,调用SnapManager的AdjustNewLocation方法就可以了,在FloatingToolForm中的实现是这样的:
UpdateParking
1/**//// <summary>
2 /// 更新粘靠
3 /// </summary>
4 private void UpdateParking()
5 {
6 if (this.FormBorderStyle == FormBorderStyle.Fixed3D ||
7 this.FormBorderStyle == FormBorderStyle.FixedDialog ||
8 this.FormBorderStyle == FormBorderStyle.FixedSingle ||
9 this.FormBorderStyle == FormBorderStyle.FixedToolWindow)
10 {
11 //获取SnapManager
12 ISnapManagerHost ismh = this.Owner as ISnapManagerHost;
13
14 if (ismh != null)
15 {
16 SnapManager mySM = ismh.SnapManager;
17 //使用SpanManager更新当前窗体的位置
18 mySM.ReparkObstacle(this);
19 }
20 }
21 }
1/**//// <summary>
2 /// 更新粘靠
3 /// </summary>
4 private void UpdateParking()
5 {
6 if (this.FormBorderStyle == FormBorderStyle.Fixed3D ||
7 this.FormBorderStyle == FormBorderStyle.FixedDialog ||
8 this.FormBorderStyle == FormBorderStyle.FixedSingle ||
9 this.FormBorderStyle == FormBorderStyle.FixedToolWindow)
10 {
11 //获取SnapManager
12 ISnapManagerHost ismh = this.Owner as ISnapManagerHost;
13
14 if (ismh != null)
15 {
16 SnapManager mySM = ismh.SnapManager;
17 //使用SpanManager更新当前窗体的位置
18 mySM.ReparkObstacle(this);
19 }
20 }
21 }
以上已经很清楚地说明了“粘靠”效果的实现过程,希望能对大家都开发有所帮助!