WPF 的 TreeView 控件自帶的樣式如圖 1 的左邊所示,節點前的箭頭還不錯,但是選中效果實在是不給力,如果想更加華麗的話,那么很有必要把 TreeView 的樣式好好自定義一番。我最后得到的結果如圖 1 的右邊所示,是一個 Win8 風格的 TreeView。
圖 1 TreeView 樣式對比
在 WPF 中,自定義控件的外觀是一件非常簡單的事情,但對於 TreeView 來說,最大的困難則在於如何做到節點的整行選擇。這是因為 TreeView 的項模板默認是如圖 2 所示的。
圖 2 TreeView 默認的項模板
最外層是一個三列兩行的 Grid,其中第一列用於放置 Expander(節點前的箭頭),剩下的列則放置 PART_Header(標頭內容)和子節點列表。因為子節點列表前空出了 Expander 那一列,所以 TreeView 的每層自然就有了縮進,同時也導致繪制邊框時,無法令邊框填滿整行——因為不知道當前節點之前有多少層縮進。雖然也有取巧的辦法,例如在繪制邊框時,令 Margin.Left 足夠小,但這僅適用於無邊框背景,在定義 Win8 樣式時不可用。
所以需要有一種辦法來計算出當前節點所在的層次(即深度)才可以。這里提供了一個好辦法,即通過在可視化樹中沿着 TreeViewItem 的父對象向上遍歷,從而得到 TreeViewItem 所在的深度,代碼如下所示:
public static int GetDepth(this TreeViewItem item) { int depth = 0; while ((item = item.GetAncestor<TreeViewItem>()) != null) { depth++; } return depth; } public static T GetAncestor<T>(this DependencyObject source) where T : DependencyObject { do { source = VisualTreeHelper.GetParent(source); } while (source != null && !(source is T)); return source as T; }
這里需要使用 VisualTreeHelper.GetParent 方法來得到父對象,直接用 item.Parent 得到的會是 null。
得到了深度,接下來就將 TreeViewItem 的模板重新定義一下,如圖 3 所示。
圖 3 自定義的 TreeView 項模板
在自定義的模板中,所有節點都是使用 StackPanel 層疊排列的,所以此時是沒有層次關系的,Border 是可以整行顯示的。層次關系則是通過為 Grid 指定不同的 Margin.Left 來實現的,這就需要根據當前節點的深度,計算出需要縮進的距離。計算過程是利用 IValueConverter 來完成的:
<ControlTemplate.Resources> <!-- 計算節點縮進的轉換器 --> <cw:IndentConverter Indent="10" MarginLeft="5" x:Key="IndentConverter" /> </ControlTemplate.Resources> <Grid Margin="{Binding Converter={StaticResource IndentConverter}, RelativeSource={RelativeSource TemplatedParent}}" />
public sealed class IndentConverter : IValueConverter { public double Indent { get; set; } public double MarginLeft { get; set; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { TreeViewItem item = value as TreeViewItem; if (item == null) { return new Thickness(0); } return new Thickness(this.MarginLeft + this.Indent * item.GetDepth(), 0, 0, 0); } }
這里的縮進轉換器,我定義了兩個屬性:Indent 和 MarginLeft,這是因為我將縮進距離由 19 調整到了 12,這樣會使樹更好看點,但會導致根節點距離左邊框過近,所以就加入了 MarginLeft,使得根節點離左邊框更遠些,如圖 4 所示。
圖 4 縮進距離對比
最后,就是樣式的調整了。Win8 中的資源管理器樹狀列表的樣式如圖 5 所示,這個樣式與 Win7 差不多,但是沒有了漸變和圓角,實現起來要容易一些。需要注意的是被選中的節點,在鼠標經過的時候是會改變顏色的(雖然不是很明顯),而且箭頭無論是展開還是收起狀態,在鼠標經過的時候都會變成藍色。樣式的定義沒有什么好說的,一些顏色也在下圖中標注出來了。
圖 5 Win8 中的資源管理器
Win8 資源管理器樹狀列表最華麗的一點是,當控件沒有焦點且鼠標未經過的時候,所有箭頭是隱藏的,有焦點的時候才出現,可惜這個不知道該怎么實現,只好先放下了。
所有的樣式定義我都放在了 \Resources\Win8Theme\TreeView.xaml 文件中,這是一個資源字典,只要在需要的地方使用 <ResourceDictionary Source="Resources/Win8Theme/TreeView.xaml" /> 導入資源字典,TreeView 控件就可以以 Win8 樣式顯示。完整的代碼和示例可以在這里下載。