WPF 控件庫——可拖動選項卡的TabControl

WPF 控件庫系列博文地址:html

WPF 控件庫——仿製Chrome的ColorPickergit

WPF 控件庫——仿製Windows10的進度條github

WPF 控件庫——輪播控件ide

WPF 控件庫——帶有慣性的ScrollViewer動畫

WPF 控件庫——可拖動選項卡的TabControlui

 

1、先看看效果spa

 

2、原理rest

一、選項卡大小和位置code

  此次給你們介紹的控件是比較經常使用的TabControl,網上常見的TabControl樣式有不少,其中一部分也支持拖動選項卡,可是帶動畫效果的不多見。這也是有緣由的,由於想要作一個不失原有功能,還須要添加動畫效果的控件可不是一行代碼的事。要作成上圖中的效果,咱們不能一蹴而就,最忌諱的是一上來就想實現全部效果。htm

  一開始,咱們最好先用Blend看看原生的TabControl樣式模板部分是如何實現的,這樣咱們也好有個參考。咱們先從資產面板中拖一個TabControl放到窗體中,調整好合適的大小:

  而後在它上面右鍵,編輯模板->編輯副本->肯定,在自動生成的xaml代碼中關鍵部分是這裏:

  能夠看到,全部的選項卡(也就是TabItem)其實都是放在TabControl內部維護的一個TabPanel中,知道這些就夠了,咱們徹底能夠作一個定製的TabPanel來替換它: public class TabPanel : Panel 。既然這個TabPanel是一個容器,因此它必須負責計算TabItem的大小還要安排它的位置,咱們能夠重載父類Panel的 MeasureOverride 方法來處理這些邏輯: protected override Size MeasureOverride(Size constraint) 。在這個方法中咱們經過 InternalChildren 這個只讀屬性來獲取選項卡,選項卡的高度咱們由 TabItemHeight 屬性指定,因爲TabPanel對用戶是透明的,因此咱們還要定製一個TabControl,裏面加上 TabItemHeight 屬性,讓它和TabPanel的綁定。以後的 TabItemWidth 和 IsEnableTabFill 也同理。而選項卡的寬度則要分狀況討論了,若是 IsEnableTabFill = true 咱們則要平分寬度,例如容器寬度爲100,選項卡有10個,那麼每一個選項卡的寬度就是10。在這裏要注意的是,選項卡的寬度最好不要有小數點,雖然有諸如 UseLayoutRounding 這種特性的幫助能夠必定程度去除模糊,但在一個個連續排列的選項卡上反而會拔苗助長,你會發現兩兩之間的分割線寬度是不一致的,最好的辦法就是「不公平的平分」,貼上一段代碼來解釋:

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);

  由於選項卡左右都有邊距,減去一個左邊距,二者間的間隔就是一個邊距了。

  選項卡大小和位置的邏輯處理大體是上述的過程,因爲篇幅有限,加之我不喜歡一貼一大段代碼,因此只挑重點來討論,完整的代碼還要考慮各類狀況,這裏就再也不贅述了。

 

二、動畫處理

  這一部分咱們的關注點就是鼠標了,對選項卡而言,鼠標按下、鼠標移動、鼠標擡起,這些咱們都要關注,因此分別給它們訂閱一下事件。與之對應的,咱們還要給選項卡添加幾個標私有字段,用以記錄狀態,好比 _isDragging 、 _isDragged 、 _dragPoint 、 _isWaiting ,前兩個我就不說了,都是字面意思,第三個則用來暫存鼠標移動時的位置,每次進入選項卡的 OnMouseMove 事件,都要將 _isDragged 和其舊值做差,以求得當前選項卡應該移動的距離。 _isWaiting 用途比較特殊,在用戶拖動選項卡時,咱們最好等待一個粘滯距離,好比20個單位寬度,也就是說,在水平方向鼠標移動了超過20個像素無關單位後,選項卡纔開始被拖動。

  在一開始的gif中能夠看到,被拖動的選項卡改變位置時,其他的選項卡也會動態改變位置,那麼位置改變的時機是如何肯定的呢?很簡單,只要將被拖動的選項卡到容器(TabPanel)左邊界的這個距離除以 ItemWidth ,結果四捨五入就是這個選項卡當前應該所處的位置,緊接着下一步就是要把這個位置上的選項卡和當前被拖動的換個位置。此刻咱們終於能夠用動畫來實現了,因爲這個系列的文章屢次講過動畫的代碼了,因此就再也不贅述。

  上面一段講的是換位置,那麼添加選項卡、刪除選項卡呢?其實有個捷徑能夠走,就是使用 FluidMoveBehavior ,把他往樣式裏一塞,好了,效果出來了!

  可是這裏有一個坑要注意, FluidMoveBehavior 雖然能夠化簡一部分動畫邏輯,可是它有點越權了,它把你位置移動的邏輯也給作了,你會發現,若是不加處理,在你本身的動畫結束後它還會再來一遍它的動畫。能夠將 FluidMoveBehavior 的 Duration 屬性暫時歸零來解決這個問題: FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0)); 。

  這篇文章只是大體介紹一下實現的過程和思路,感興趣的能夠下載源碼,多多交流,共同提升。

 

3、源碼

  本文所討論的控件源碼已經在github開源:https://github.com/NaBian/HandyControl

相關文章
相關標籤/搜索