效果如圖:
雖然說是自適應可關閉的TabControl,但TabControl並不需要改動,不如叫自適應可關閉的TabItem.
大體思路:建一個用戶控件,繼承自TabItem,裏面放個按鈕,點擊的時候在TabControl中移除自身.在添加,移除TabItem和TabControl尺寸變化時,通過Items的個數計算合適的Width.
新建用戶控件
新建用戶控件,並繼承自TabItem,這樣它就擁有TabItem所有的屬性和事件.而這個功能不需要自定義依賴屬性和事件.它的用法就和TabItem完全一樣.
建完後把UserControl換成TabItem,去掉多餘部分
後臺繼承自UserControl改成繼承自TabItem
更改樣式添加關閉按鈕
在Xmal裏添加一個自己喜歡的樣式,最主要的是在Template裏添加一個按鈕,註冊一個Click事件,用於關閉.
<Style TargetType="{x:Type TabItem}">
<Setter Property="BorderBrush" Value="Black"></Setter>
<Setter Property="Background" Value="White"></Setter>
<Setter Property="Foreground" Value="Black"></Setter>
<Setter Property="Padding" Value="5,0,0,0"></Setter>
<Setter Property="HorizontalAlignment" Value="Left"></Setter>
<Setter Property="VerticalAlignment" Value="Center"></Setter>
<Setter Property="HorizontalContentAlignment" Value="Left"></Setter>
<Setter Property="VerticalContentAlignment" Value="Center"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border CornerRadius="5,0,0,0" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="20"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" ContentSource="Header" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"></ContentPresenter>
<Button Grid.Column="1" Name="btn_Close" Click="btn_Close_Click"></Button>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" Value="#FFFF923E"></Setter>
<Setter Property="Foreground" Value="White"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
後臺的邏輯
查找父級TabControl
注意TabItem並不能關閉自身,這裏所說的關閉其實是在他父級TabControl的Items集合裏移除.而且父級TabControl的尺寸改變時還要註冊事件去改變每個Item的Width.所以我決定找到它的父級TabControl,聲明一個私有變量添加對父級的引用.
可以通過可視化樹的幫助類VisualTreeHelper來找到它的父級TabControl.當然並不是它的父級直接就是TabControl了,需要遞歸去查找
/// <summary>
/// 遞歸找父級TabControl
/// </summary>
/// <param name="reference">依賴對象</param>
/// <returns>TabControl</returns>
private TabControl FindParentTabControl(DependencyObject reference)
{
DependencyObject dObj = VisualTreeHelper.GetParent(reference);
if (dObj == null)
return null;
if (dObj.GetType() == typeof(TabControl))
return dObj as TabControl;
else
return FindParentTabControl(dObj);
}
計算尺寸
既然是自適應,總得有一個正常的尺寸,只有空間不足的時候纔去縮小每個Item.我想到的最簡單的辦法就是做個約定,把這個尺寸放到父級TabControl的Tag裏,這樣可以通過對父級TabControl的引用,輕鬆拿到這個尺寸.
計算方法就是取父級TabControl運行時的寬度ActualWidth除以約定的尺寸,取整形int,這個就是保持約定寬度item個數的臨界值了.
小於等於這個值就用約定寬度,大於這個值就用父級運行寬度除以Items的個數求出平均寬度,然後遍歷父級TabControl的Items,都賦上這個平均值.
需要注意的是,如果所有Items的尺寸加起來大於等於父級的尺寸,Items會換行,感覺有點醜啊.所以我取的是父級運行寬度-5做的運算,這樣就永遠也抵達不到邊界,不會換行.
不過也可以改寫TabControl的控件模版,把放Hrader的容器換成Stackpanel就不會換行了,我只是覺得上面的方法比較簡單.
父級尺寸改變
可以通過TabControl的SizeChanged事件監測到.需要乾的事就是重新計算尺寸.
關閉按鈕
在父級TabControl的Items集合裏移除自身後,注意重新計算下尺寸和移除註冊SizeChanged事件的方法.
最後附上代碼 自適應可關閉的Tab.zip
這個效果比較常見,可能您已經做過了,有更好的想法希望您能分享出來,大家共同進步.
2016-08-16更新:感謝園友 日日夜夜 的反饋,源碼已改正
1.TabItem.Resources的關閉按鈕樣式添加了Key,模版裏的關閉按鈕添加了對資源的引用.
2.去掉了TabItem樣式的HorizontalContentAlignment="Left",VerticalContentAlignment="Center".頭部內容的佈局方式改爲HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}".