天天看點

WPF自适應可關閉的TabControl 類似浏覽器的标簽頁

原文: WPF自适應可關閉的TabControl 類似浏覽器的标簽頁 效果如圖:

WPF自适應可關閉的TabControl 類似浏覽器的标簽頁

雖然說是自适應可關閉的TabControl,但TabControl并不需要改動,不如叫自适應可關閉的TabItem.

大體思路:建一個使用者控件,繼承自TabItem,裡面放個按鈕,點選的時候在TabControl中移除自身.在添加,移除TabItem和TabControl尺寸變化時,通過Items的個數計算合适的Width.

建立使用者控件

建立使用者控件,并繼承自TabItem,這樣它就擁有TabItem所有的屬性和事件.而這個功能不需要自定義依賴屬性和事件.它的用法就和TabItem完全一樣.

建完後把UserControl換成TabItem,去掉多餘部分

WPF自适應可關閉的TabControl 類似浏覽器的标簽頁

背景繼承自UserControl改成繼承自TabItem

WPF自适應可關閉的TabControl 類似浏覽器的标簽頁

更改樣式添加關閉按鈕

在Xmal裡添加一個自己喜歡的樣式,最主要的是在Template裡添加一個按鈕,注冊一個Click事件,用于關閉.

WPF自适應可關閉的TabControl 類似浏覽器的标簽頁
WPF自适應可關閉的TabControl 類似浏覽器的标簽頁
1 <Style TargetType="{x:Type TabItem}">
 2             <Setter Property="BorderBrush" Value="Black"></Setter>
 3             <Setter Property="Background" Value="White"></Setter>
 4             <Setter Property="Foreground" Value="Black"></Setter>
 5             <Setter Property="Padding" Value="5,0,0,0"></Setter>
 6             <Setter Property="HorizontalAlignment" Value="Left"></Setter>
 7             <Setter Property="VerticalAlignment" Value="Center"></Setter>
 8             <Setter Property="HorizontalContentAlignment" Value="Left"></Setter>
 9             <Setter Property="VerticalContentAlignment" Value="Center"></Setter>
10             <Setter Property="Template">
11                 <Setter.Value>
12                     <ControlTemplate TargetType="{x:Type TabItem}">
13                         <Border CornerRadius="5,0,0,0" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
14                             <Grid>
15                                 <Grid.ColumnDefinitions>
16                                     <ColumnDefinition Width="*"></ColumnDefinition>
17                                     <ColumnDefinition Width="20"></ColumnDefinition>
18                                 </Grid.ColumnDefinitions>
19                                 <ContentPresenter Grid.Column="0" ContentSource="Header" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"></ContentPresenter>
20                                 <Button Grid.Column="1" Name="btn_Close" Click="btn_Close_Click"></Button>
21                             </Grid>
22                         </Border>
23                         <ControlTemplate.Triggers>
24                             <Trigger Property="IsSelected" Value="true">
25                                 <Setter Property="Background" Value="#FFFF923E"></Setter>
26                                 <Setter Property="Foreground" Value="White"></Setter>
27                             </Trigger>
28                         </ControlTemplate.Triggers>
29                     </ControlTemplate>
30                 </Setter.Value>
31             </Setter>
32         </Style>      

View Code

背景的邏輯

查找父級TabControl

注意TabItem并不能關閉自身,這裡所說的關閉其實是在他父級TabControl的Items集合裡移除.而且父級TabControl的尺寸改變時還要注冊事件去改變每個Item的Width.是以我決定找到它的父級TabControl,聲明一個私有變量添加對父級的引用.

可以通過可視化樹的幫助類VisualTreeHelper來找到它的父級TabControl.當然并不是它的父級直接就是TabControl了,需要遞歸去查找

WPF自适應可關閉的TabControl 類似浏覽器的标簽頁
WPF自适應可關閉的TabControl 類似浏覽器的标簽頁
1 /// <summary>
 2         /// 遞歸找父級TabControl
 3         /// </summary>
 4         /// <param name="reference">依賴對象</param>
 5         /// <returns>TabControl</returns>
 6         private TabControl FindParentTabControl(DependencyObject reference)
 7         {
 8             DependencyObject dObj = VisualTreeHelper.GetParent(reference);
 9             if (dObj == null)
10                 return null;
11             if (dObj.GetType() == typeof(TabControl))
12                 return dObj as TabControl;
13             else
14                 return FindParentTabControl(dObj);
15         }      
計算尺寸

既然是自适應,總得有一個正常的尺寸,隻有空間不足的時候才去縮小每個Item.我想到的最簡單的辦法就是做個約定,把這個尺寸放到父級TabControl的Tag裡,這樣可以通過對父級TabControl的引用,輕松拿到這個尺寸.

計算方法就是取父級TabControl運作時的寬度ActualWidth除以約定的尺寸,取整形int,這個就是保持約定寬度item個數的臨界值了.

小于等于這個值就用約定寬度,大于這個值就用父級運作寬度除以Items的個數求出平均寬度,然後周遊父級TabControl的Items,都賦上這個平均值.

需要注意的是,如果所有Items的尺寸加起來大于等于父級的尺寸,Items會換行,感覺有點醜啊.是以我取的是父級運作寬度-5做的運算,這樣就永遠也抵達不到邊界,不會換行.

不過也可以改寫TabControl的控件模版,把放Hrader的容器換成Stackpanel就不會換行了,我隻是覺得上面的方法比較簡單.

父級尺寸改變

可以通過TabControl的SizeChanged事件監測到.需要幹的事就是重新計算尺寸.

關閉按鈕

在父級TabControl的Items集合裡移除自身後,注意重新計算下尺寸和移除注冊SizeChanged事件的方法.

最後附上代碼

自适應可關閉的Tab.zip http://files.cnblogs.com/files/tsliwei/%E8%87%AA%E9%80%82%E5%BA%94%E5%8F%AF%E5%85%B3%E9%97%AD%E7%9A%84Tab.rar

這個效果比較常見,可能您已經做過了,有更好的想法希望您能分享出來,大家共同進步.

2016-08-16更新:感謝園友 

日日夜夜

 的回報,源碼已改正

1.TabItem.Resources的關閉按鈕樣式添加了Key,模版裡的關閉按鈕添加了對資源的引用.

2.去掉了TabItem樣式的HorizontalContentAlignment="Left",VerticalContentAlignment="Center".頭部内容的布局方式改為HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}".