Paint.Net学习笔记——四、窗体粘靠
本节介绍在Windows应用程序中出现的“控件粘靠”效果的实现。
本节介绍在Windows应用程序中出现的“控件粘靠”效果的实现。
之前一直用Winamp播放音乐,直到现在使用千千静听和酷狗,这几款音频播放软件界面一脉相承,并都具有“控件粘靠”效果,现在让我们一起来看看PDN里这种效果是如何实现的。
在PDN中,实现该效果的是由SnapManager类和SnapObstacle(下称障碍物)抽象类实现的,当然还有一些辅助类,譬如SnapDescription(下称粘靠定义)类。
SnapManager顾名思义是粘靠效果的管理类,负责各障碍物的定位、判断是否需要粘靠、保存加载效果、生成粘靠定义对象等工作。
SnapObstacle是一个抽象类,定义了作为“可粘靠”的控件的必要属性,包括障碍物轮廓,粘靠距离,粘靠事件等。
接下来看看它们是怎样在一起工作的。
“粘靠”效果主要应用在浮动窗口(工具栏、历史、颜色和图层)。从上篇文章中,我们已经了解了这几个窗体的继承关系和各自实现的接口,在这里简单复习一下:
PdnBaseForm为所有窗体的父类,实现了ISnapManagerHost接口,所有浮动窗口继承自FloatingToolForm,实现了ISnapObstacleHost接口。所有“粘靠”效果就是由这两个接口提供的。
这两个接口相当简单,各自只提供了一个属性,该属性分别是SnapManager和SnapObstacle。
我们先看看SnapManager类,上面已经解释了该类的主要作用,不再罗嗦,接下来细致看看那里面的代码:

SnapManager字段
1
private Dictionary<SnapObstacle, SnapDescription> obstacles =new Dictionary<SnapObstacle, SnapDescription>();
2
private const string isSnappedValueName = "IsSnapped";
3
private const string leftValueName = "Left";
4
private const string topValueName = "Top";
5
private const string widthValueName = "Width";
6
private const string heightValueName = "Height";
7
private const string nullName = "";
8
9
private const string snappedToValueName = "SnappedTo";
10
private const string horizontalEdgeValueName = "HorizontalEdge";
11
private const string verticalEdgeValueName = "VerticalEdge";
12
private const string xOffsetValueName = "XOffset";
13
private const string yOffsetValueName = "YOffset";


1

2

3

4

5

6

7

8

9

10

11

12

13

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


2

3

4

5

6

7

8

9



10

11

12

13



14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29



30

31

32

33

34

35



36

37

38

39

40



41

42

43

44

45

46



47

48

49

50

51

52

53

54

55



56

57

58

59

60



61

62

63

64

65



66

67

68

69

70



71

72

73

74

75

76

77



1


2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27



28

29

30

31

32

33

34

35

36



37

38

39

40

41

42

43



44

45



46

47

48

49

50



51

52

53

54



55

56

57

58

59

60

61

62

63

64

65



66

67

68

69

70

71

以上在代码中添加了一些注释,就不再罗嗦了,如果有不懂就请留言或来信询问。
在以上我们看出,对于“障碍物”和“粘靠物”的“粘靠”效果新坐标的计算及管理,都是在SnapManager类中实现的,那么接下来,在“障碍物”中的定义就简单多了,只是在适当的时候,调用SnapManager的AdjustNewLocation方法就可以了,在FloatingToolForm中的实现是这样的:


1


2

3

4

5



6

7

8

9

10



11

12

13

14

15



16

17

18

19

20

21

以上已经很清楚地说明了“粘靠”效果的实现过程,希望能对大家都开发有所帮助!