本節介紹在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";
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 }
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 }
以上在代碼中添加了一些注釋,就不再羅嗦了,如果有不懂就請留言或來信詢問。
在以上我們看出,對於“障礙物”和“粘靠物”的“粘靠”效果新坐標的計算及管理,都是在 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 }
以上已經很清楚地說明了“粘靠”效果的實現過程,希望能對大家都開發有所幫助!