如何實現具有層次結構的 TreeView (WPF/TreeView/Style/Template)
阿新 • • 發佈:2019-02-06
打造使用者介面 (UI)
在上一節中,我們已經完成了資料的繫結工作,並通過 DataTemplate 實現了 Name 的縱向顯示。本節將主要介紹下面幾個內容:
1、對 TreeView 進行佈局,實現層次結構的檢視
2、設定 TreeViewItem 的樣式,令其在不同操作下呈現出相應的狀態
值得一提的是,在 dotNET 4.0 中提供的標準控制元件引入了 VisualStateGroup 類,有興趣的朋友可以在自定義控制元件中試試。由於此處部分狀態存在互斥的情況,所以仍使用了3.0的動畫定義方式。
1、對 TreeView 進行佈局,實現層次結構的檢視
預設情況下 TreeView 中的節點是自上而下排列,從左向右展開。在 WPF 中要改變這樣的佈局並不困難,只需要改變承載節點的容器即可。
應用了該樣式後將看到如下圖所示的介面:<TreeView.Style> <Style TargetType="{x:Type TreeView}"> <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> <Setter Property="BorderBrush" Value="#FFB0C4DE"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Foreground" Value="#FF042271"/> <Setter Property="Padding" Value="15,20"/> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.CanContentScroll" Value="true"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TreeView}"> <Grid> <Rectangle x:Name="Rt" Opacity="0" Fill="#40000000"> <Rectangle.Effect> <BlurEffect/> </Rectangle.Effect> </Rectangle> <Border x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="true"> <ScrollViewer x:Name="Scroll" Padding="-20,0,0,0"> <!-- 預設樣式中的標記是 <ItemsPresenter/> 使用的應該是 StackPanel --> <!-- 此處我將其改為 WrapPanel 並設定 IsItemsHost="true" --> <WrapPanel x:Name="ItemsHost" IsItemsHost="True" Margin="{TemplateBinding Padding}" Orientation="Horizontal"/> </ScrollViewer> </Border> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsGrouping" Value="true"> <Setter Property="ScrollViewer.CanContentScroll" Value="false"/> </Trigger> <Trigger Property="IsMouseOver" Value="true"> <Trigger.EnterActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="Rt" Storyboard.TargetProperty="Opacity" Duration="0:0:0.5" To="1"/> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="Rt" Storyboard.TargetProperty="Opacity" Duration="0:0:0.5" To="0"/> </Storyboard> </BeginStoryboard> </Trigger.ExitActions> </Trigger> <Trigger Property="IsEnabled" Value="false"> <Setter TargetName="Bd" Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/> <Setter TargetName="Bd" Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.InactiveBorderBrushKey}}"/> </Trigger> <EventTrigger RoutedEvent="Loaded"> <BeginStoryboard> <Storyboard> <ThicknessAnimation Storyboard.TargetName="Scroll" Storyboard.TargetProperty="Padding" DecelerationRatio="0.9" Duration="0:0:3" To="0"/> </Storyboard> </BeginStoryboard> </EventTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </TreeView.Style>
此時,我們已經實現了節點的橫向排列以及從上往下展開。下面將介紹如何利用模板和觸發器定製節點的樣式。
2、設定 TreeViewItem 的樣式,令其在不同操作下呈現出相應的狀態
下面給出的 XAML 完整的程式碼,重新定義了 TreeViewItem 的邊框、表示含有子節點的箭頭、連線子節點的線段以及承載子節點的容器等。
至於操作狀態,主要包括: 節點被選中、TreeView 失去焦點以及節點無效(IsEnabled=false)等。
<TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> <Setter Property="BorderBrush" Value="#FF336699"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="HorizontalAlignment" Value="Center"/> <Setter Property="HorizontalContentAlignment" Value="Center"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> <Setter Property="Margin" Value="5,0"/> <Setter Property="Padding" Value="10"/> <Setter Property="HorizontalContentAlignment" Value="Center"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TreeViewItem}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid Grid.Row="0" HorizontalAlignment="Center"> <!-- 陰影效果 --> <Rectangle x:Name="el_Shadow" Fill="#30000000"> <Rectangle.Effect> <BlurEffect /> </Rectangle.Effect> </Rectangle> <!-- 底色 --> <Rectangle Fill="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> <!-- 邊框 --> <Border x:Name="el_Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" HorizontalAlignment="Center" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"> <ContentPresenter x:Name="PART_Header" Grid.Row="0" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> </Border> </Grid> <!-- 用於表示節點展開/摺疊的箭頭 --> <Grid x:Name="el_Arrow" Grid.Row="1" Height="15"> <Path Data="M 1,1.5 L 4.5,5 L 8,1.5" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="false"/> </Grid> <!-- 節點展開後與子節點間的連線線 --> <Rectangle x:Name="el_Line" Grid.Row="1" Fill="{TemplateBinding BorderBrush}" Width="1" Height="0" Opacity="0" SnapsToDevicePixels="True"/> <!-- 表示子節點範圍的線段 --> <Border x:Name="el_Range" Grid.Row="2" Background="Transparent" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1,1,1,0" Height="0" Opacity="0" SnapsToDevicePixels="True"/> <!-- 承載子節點的容器 --> <WrapPanel x:Name="el_Host" Grid.Row="3" IsItemsHost="True" Orientation="Horizontal" Visibility="Collapsed"/> </Grid> <!-- 動畫觸發器 --> <ControlTemplate.Triggers> <MultiTrigger> <!-- 當 ListViewItem 的 HasItems/IsExpanded 都為 true 時觸發動畫 --> <MultiTrigger.Conditions> <Condition Property="HasItems" Value="true"/> <Condition Property="IsExpanded" Value="true"/> </MultiTrigger.Conditions> <!-- 1、顯示承載子節點的容器 el_Host(WrapPanel) --> <Setter TargetName="el_Host" Property="Visibility" Value="Visible"/> <!-- 2、分別對 el_Line/el_Range/elArrow 物件的 Opacity/Height 屬性應用動畫效果 0.2s --> <MultiTrigger.EnterActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="el_Line" Storyboard.TargetProperty="Height" To="15" Duration="0:0:0.2"/> <DoubleAnimation Storyboard.TargetName="el_Line" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2"/> <DoubleAnimation Storyboard.TargetName="el_Range" Storyboard.TargetProperty="Height" To="15" Duration="0:0:0.2"/> <DoubleAnimation Storyboard.TargetName="el_Range" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2"/> <DoubleAnimation Storyboard.TargetName="el_Arrow" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0.2"/> <DoubleAnimation Storyboard.TargetName="el_Arrow" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </MultiTrigger.EnterActions> <MultiTrigger.ExitActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="el_Line" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0.2"/> <DoubleAnimation Storyboard.TargetName="el_Line" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.2"/> <DoubleAnimation Storyboard.TargetName="el_Range" Storyboard.TargetProperty="Height" To="0" Duration="0:0:0.2"/> <DoubleAnimation Storyboard.TargetName="el_Range" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.2"/> <DoubleAnimation Storyboard.TargetName="el_Arrow" Storyboard.TargetProperty="Height" To="15" Duration="0:0:0.2"/> <DoubleAnimation Storyboard.TargetName="el_Arrow" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </MultiTrigger.ExitActions> </MultiTrigger> <!-- 展開/摺疊節點觸發器 顯示/隱藏 承載子節點的容器 el_Host(WrapPanel) --> <Trigger Property="IsExpanded" Value="true"> <Trigger.EnterActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="el_Host" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="el_Host" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </Trigger.ExitActions> </Trigger> <!-- 針對 WrapPanel 的 Opacity=0 時觸發的動畫 --> <Trigger SourceName="el_Host" Property="Opacity" Value="0"> <Setter TargetName="el_Host" Property="Visibility" Value="Collapsed"/> </Trigger> <!-- 若不包含子節點 即 ListViewItem.HasItems=false 則隱藏 el_Host(WrapPaenl)/el_Arrow(指示箭頭) --> <Trigger Property="HasItems" Value="false"> <Setter TargetName="el_Host" Property="Visibility" Value="Collapsed"/> <Setter TargetName="el_Arrow" Property="Visibility" Value="Collapsed"/> </Trigger> <!-- 單個子節點觸發器 此時將隱藏表示子節點範圍的線段 el_Range 物件 --> <DataTrigger Binding="{Binding ElementName=el_Host, Path=Children.Count}" Value="1"> <Setter TargetName="el_Range" Property="Visibility" Value="Collapsed"/> </DataTrigger> <!-- 節點被選中觸發器 --> <Trigger Property="IsSelected" Value="true"> <Setter TargetName="el_Border" Property="Background" Value="#FFC5D9F1"/> <Trigger.EnterActions> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="el_Shadow" Storyboard.TargetProperty="Fill.Color" To="#AB000000" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard> <Storyboard> <ColorAnimation Storyboard.TargetName="el_Shadow" Storyboard.TargetProperty="Fill.Color" To="#30000000" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </Trigger.ExitActions> </Trigger> <!-- 節點被選中但 TreeView 失去焦點時的觸發器 --> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsSelected" Value="true"/> <Condition Property="IsSelectionActive" Value="false"/> </MultiTrigger.Conditions> <Setter TargetName="el_Border" Property="Background" Value="#FFDDD9C3"/> </MultiTrigger> <!-- 有效性 IsEnabled 觸發器 --> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </TreeView.ItemContainerStyle>
至此,這篇文章已經完成。寫這篇文章的原因,一是為了總結最近的學習成果,整理出一個完整的思路;二是分享成果,希望能與感興趣的朋友共同提高,文中不足望高手指點!