原文: WPF 控件庫——可拖動頁籤的TabControl 一、先看看效果

二、原理
1、頁籤大小和位置
這次給大家介紹的控件是比較常用的TabControl,網上常見的TabControl樣式有很多,其中一部分也支援拖動頁籤,但是帶動畫效果的很少見。這也是有原因的,因為想要做一個不失原有功能,還需要添加動畫效果的控件可不是一行代碼的事。要做成上圖中的效果,我們不能一蹴而就,最忌諱的是一上來就想實作所有效果。
一開始,我們最好先用Blend看看原生的TabControl樣式模闆部分是如何實作的,這樣我們也好有個參考。我們先從資産面闆中拖一個TabControl放到窗體中,調整好合适的大小:
public static int[] DivideInt2Arr(int num, int count)
{
var arr = new int[count];
var div = num / count;
var rest = num % count;
for (int i = 0; i < count; i++)
{
arr[i] = div;
}
for (int i = 0; i < rest; i++)
{
arr[i] += 1;
}
return arr;
}
假設現在的容器寬度是108,頁籤還是10個,通過 MeasureOverride 方法處理後,前八個的寬度則是11,後兩個是10。如果 IsEnableTabFill = false 則不要平分了,直接放入容器即可。
現在頁籤大小搞定了,位置呢?太簡單了,一個for循環不斷疊加每個頁籤的寬度就可以了: size.Width += tabItem.ItemWidth; 。最後通過調用 Element.Arrange 即可排布頁籤的位置:
var rect = new Rect
{
X = size.Width - tabItem.BorderThickness.Left,
Width = itemWidth,
Height = TabItemHeight
};
tabItem.Arrange(rect);
因為頁籤左右都有邊距,減去一個左邊距,兩者間的間隔就是一個邊距了。
頁籤大小和位置的邏輯處理大緻是上述的過程,由于篇幅有限,加之我不喜歡一貼一大段代碼,是以隻挑重點來讨論,完整的代碼還要考慮各種情況,這裡就不再贅述了。
2、動畫處理
這一部分我們的關注點就是滑鼠了,對頁籤而言,滑鼠按下、滑鼠移動、滑鼠擡起,這些我們都要關注,是以分别給它們訂閱一下事件。與之對應的,我們還要給頁籤添加幾個标私有字段,用以記錄狀态,比如 _isDragging 、 _isDragged 、 _dragPoint 、 _isWaiting ,前兩個我就不說了,都是字面意思,第三個則用來暫存滑鼠移動時的位置,每次進入頁籤的 OnMouseMove 事件,都要将 _isDragged 和其舊值作差,以求得目前頁籤應該移動的距離。 _isWaiting 用途比較特殊,在使用者拖動頁籤時,我們最好等待一個粘滞距離,比如20個機關寬度,也就是說,在水準方向滑鼠移動了超過20個像素無關機關後,頁籤才開始被拖動。
在一開始的gif中可以看到,被拖動的頁籤改變位置時,其餘的頁籤也會動态改變位置,那麼位置改變的時機是如何确定的呢?很簡單,隻要将被拖動的頁籤到容器(TabPanel)左邊界的這個距離除以 ItemWidth ,結果四舍五入就是這個頁籤目前應該所處的位置,緊接着下一步就是要把這個位置上的頁籤和目前被拖動的換個位置。此刻我們終于可以用動畫來實作了,由于這個系列的文章多次講過動畫的代碼了,是以就不再贅述。
上面一段講的是換位置,那麼添加頁籤、删除頁籤呢?其實有個捷徑可以走,就是使用 FluidMoveBehavior ,把他往樣式裡一塞,好了,效果出來了!
但是這裡有一個坑要注意, FluidMoveBehavior 雖然可以化簡一部分動畫邏輯,但是它有點越權了,它把你位置移動的邏輯也給做了,你會發現,如果不加處理,在你自己的動畫結束後它還會再來一遍它的動畫。可以将 FluidMoveBehavior 的 Duration 屬性暫時歸零來解決這個問題: FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0)); 。
這篇文章隻是大緻介紹一下實作的過程和思路,感興趣的可以下載下傳源碼,多多交流,共同提高。
三、源碼
本文所讨論的控件源碼已經在github開源:
https://github.com/NaBian/HandyControl