WPF TreeView template with different classes

后端 未结 2 1922
迷失自我
迷失自我 2021-01-28 11:13

How to write XAML for WPF TreeView so when I bind ViewModel with following structure?

public class A
{
    int Id { get; set; }
    string Name { get; set; }
             


        
相关标签:
2条回答
  • 2021-01-28 11:45

    You could combine the ItemsB and ItemsC collections of class A into a CompositeCollection.

    Since HierarchicalDataTemplate.ItemsSource is not a collection, but a BindingBase it seems not possible to do this directly in XAML. You may however write a Binding Converter:

    public class ClassAItemsConverter : IValueConverter
    {
        public object Convert(
            object value, Type targetType, object parameter, CultureInfo culture)
        {
            var a = (A)value;
    
            return new CompositeCollection
            {
                new CollectionContainer() { Collection = a.ItemsB },
                new CollectionContainer() { Collection = a.ItemsC }
            };
        }
    
        public object ConvertBack(
            object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
    

    The templates would look like this:

    <Window.Resources>
        <local:ClassAItemsConverter x:Key="AItemsConverter"/>
    
        <HierarchicalDataTemplate DataType="{x:Type local:A}"
            ItemsSource="{Binding Converter={StaticResource AItemsConverter}}">
            <TextBlock Text="{Binding Name}"/>
        </HierarchicalDataTemplate>
    
        <HierarchicalDataTemplate DataType="{x:Type local:C}"
            ItemsSource="{Binding ItemsD}">
            <TextBlock Text="{Binding Name}"/>
        </HierarchicalDataTemplate>
    
        <DataTemplate DataType="{x:Type local:B}">
            <TextBlock Text="{Binding Name}"/>
        </DataTemplate>
    
        <DataTemplate DataType="{x:Type local:D}">
            <TextBlock Text="{Binding Name}"/>
        </DataTemplate>
    </Window.Resources>
    
    0 讨论(0)
  • 2021-01-28 12:06

    If you don't want to change the tree structure (class structure):

    <TreeView.Resources>
      <HierarchicalDataTemplate DataType="{x:Type A}" ItemsSource="{Binding ItemsB}">
        <StackPanel>
          <TextBlock Text="{Binding Id}" />
          <TreeView ItemsSource="{Binding ItemsC}" />
        </StackPanel>
      </HierarchicalDataTemplate>
      <DataTemplate DataType="{x:Type B}">
        <StackPanel>
          <TextBlock Text="{Binding Id}" />
        </StackPanel>
      </DataTemplate>
      <HierarchicalDataTemplate DataType="{x:Type C}" ItemsSource="{Binding ItemsD}">
        <StackPanel>
          <TextBlock Text="{Binding Id}" />
        </StackPanel>
      </HierarchicalDataTemplate>
      <DataTemplate DataType="{x:Type D}">
        <StackPanel>
          <TextBlock Text="{Binding Id}" />
        </StackPanel>
      </DataTemplate>
    </TreeView.Resources>
    

    The Style to adjust the appearance. It's the default TreeViewItem style taken from Microsoft Docs: TreeView Styles and Templates with a modified "Expander" positioning and removed border of the nested TreeView to maintain the default look. You can apply the Style (and its resources) locally by setting the TreeView.ItemContainerStyle property or by putting the Style into a ResourceDictionary within the scope of the target TreeView(e.g., App.xaml):

    <!-- Remove the border of the nested TreeView -->
    <Style TargetType="TreeView">
      <Setter Property="BorderThickness" Value="0" />
    </Style>
    
    <Color x:Key="GlyphColor">#FF444444</Color>
    <Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color>
    <Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color>
    
    <Style x:Key="ExpandCollapseToggleStyle"
           TargetType="ToggleButton">
      <Setter Property="Focusable"
              Value="False" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="ToggleButton">
            <Grid Width="15"
                  Height="13"
                  Background="Transparent">
              <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="CheckStates">
                  <VisualState x:Name="Checked">
                    <Storyboard>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                     Storyboard.TargetName="Collapsed">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{x:Static Visibility.Hidden}" />
                      </ObjectAnimationUsingKeyFrames>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                     Storyboard.TargetName="Expanded">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{x:Static Visibility.Visible}" />
                      </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                  </VisualState>
                  <VisualState x:Name="Unchecked" />
                  <VisualState x:Name="Indeterminate" />
                </VisualStateGroup>
              </VisualStateManager.VisualStateGroups>
              <Path x:Name="Collapsed"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    Margin="1,1,1,1"
                    Data="M 4 0 L 8 4 L 4 8 Z">
                <Path.Fill>
                  <SolidColorBrush Color="{DynamicResource GlyphColor}" />
                </Path.Fill>
              </Path>
              <Path x:Name="Expanded"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    Margin="1,1,1,1"
                    Data="M 0 4 L 8 4 L 4 8 Z"
                    Visibility="Hidden">
                <Path.Fill>
                  <SolidColorBrush Color="{DynamicResource GlyphColor}" />
                </Path.Fill>
              </Path>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
    
    <Style x:Key="TreeViewItemFocusVisual">
      <Setter Property="Control.Template">
        <Setter.Value>
          <ControlTemplate>
            <Border>
              <Rectangle Margin="0,0,0,0"
                         StrokeThickness="5"
                         Stroke="Black"
                         StrokeDashArray="1 2"
                         Opacity="0" />
            </Border>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
    
    <Style x:Key="{x:Type TreeViewItem}"
           TargetType="{x:Type TreeViewItem}">
      <Setter Property="Background"
              Value="Transparent" />
      <Setter Property="HorizontalContentAlignment"
              Value="{Binding Path=HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
      <Setter Property="VerticalContentAlignment"
              Value="{Binding Path=VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
      <Setter Property="Padding"
              Value="1,0,0,0" />
      <Setter Property="Foreground"
              Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
      <Setter Property="FocusVisualStyle"
              Value="{StaticResource TreeViewItemFocusVisual}" />
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type TreeViewItem}">
            <Grid>
              <Grid.ColumnDefinitions>
                <ColumnDefinition MinWidth="19"
                                  Width="Auto" />
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
              </Grid.ColumnDefinitions>
              <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition />
              </Grid.RowDefinitions>
              <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="SelectionStates">
                  <VisualState x:Name="Selected">
                    <Storyboard>
                      <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd"
                                                    Storyboard.TargetProperty="(Panel.Background).
                (SolidColorBrush.Color)">
                        <EasingColorKeyFrame KeyTime="0"
                                             Value="{StaticResource SelectedBackgroundColor}" />
                      </ColorAnimationUsingKeyFrames>
                    </Storyboard>
                  </VisualState>
                  <VisualState x:Name="Unselected" />
                  <VisualState x:Name="SelectedInactive">
                    <Storyboard>
                      <ColorAnimationUsingKeyFrames Storyboard.TargetName="Bd"
                                                    Storyboard.TargetProperty="(Panel.Background).
                (SolidColorBrush.Color)">
                        <EasingColorKeyFrame KeyTime="0"
                                             Value="{StaticResource SelectedUnfocusedColor}" />
                      </ColorAnimationUsingKeyFrames>
                    </Storyboard>
                  </VisualState>
                </VisualStateGroup>
                <VisualStateGroup x:Name="ExpansionStates">
                  <VisualState x:Name="Expanded">
                    <Storyboard>
                      <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                     Storyboard.TargetName="ItemsHost">
                        <DiscreteObjectKeyFrame KeyTime="0"
                                                Value="{x:Static Visibility.Visible}" />
                      </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                  </VisualState>
                  <VisualState x:Name="Collapsed" />
                </VisualStateGroup>
              </VisualStateManager.VisualStateGroups>
    
              <!-- Adjust the positioning of the item expander -->
              <ToggleButton x:Name="Expander"
                            Style="{StaticResource ExpandCollapseToggleStyle}"
                            ClickMode="Press"
                            VerticalAlignment="Top"
                            Margin="0,2,0,0"
                            IsChecked="{Binding IsExpanded, 
        RelativeSource={RelativeSource TemplatedParent}}" />
    
              <Border x:Name="Bd"
                      Grid.Column="1"
                      Background="{TemplateBinding Background}"
                      BorderBrush="{TemplateBinding BorderBrush}"
                      BorderThickness="{TemplateBinding BorderThickness}"
                      Padding="{TemplateBinding Padding}">
                <ContentPresenter x:Name="PART_Header"
                                  ContentSource="Header"
                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" />
              </Border>
              <ItemsPresenter x:Name="ItemsHost"
                              Grid.Row="1"
                              Grid.Column="1"
                              Grid.ColumnSpan="2"
                              Visibility="Collapsed" />
            </Grid>
            <ControlTemplate.Triggers>
              <Trigger Property="HasItems"
                       Value="false">
                <Setter TargetName="Expander"
                        Property="Visibility"
                        Value="Hidden" />
              </Trigger>
              <MultiTrigger>
                <MultiTrigger.Conditions>
                  <Condition Property="HasHeader"
                             Value="false" />
                  <Condition Property="Width"
                             Value="Auto" />
                </MultiTrigger.Conditions>
                <Setter TargetName="PART_Header"
                        Property="MinWidth"
                        Value="75" />
              </MultiTrigger>
              <MultiTrigger>
                <MultiTrigger.Conditions>
                  <Condition Property="HasHeader"
                             Value="false" />
                  <Condition Property="Height"
                             Value="Auto" />
                </MultiTrigger.Conditions>
                <Setter TargetName="PART_Header"
                        Property="MinHeight"
                        Value="19" />
              </MultiTrigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
    

    Recommended

    You should definitely change the class structure and introduce a common base type:

    interface IDevice
    {
      int Id { get; set; }
      List<IDevice> Items { get; set; }
    }
    
    class A : IDevice
    {
      public A()
      {
        this.Items = new List<IDevice> { new B(), new C() };
      }
    }
    
    class B : IDevice
    {}
    

    You can now add every type that implements IDevice to the child collection, even mixed. Simply add a HierachricalDataTemplate for each implementation of IDevice:

    <TreeView.Resources>
      <HierarchicalDataTemplate DataType="{x:Type A}" ItemsSource="{Binding Items}">
        <TextBlock Text="{Binding Id}" />
      </HierarchicalDataTemplate>
      <HierarchicalDataTemplate DataType="{x:Type B}" ItemsSource="{Binding Items}">
        <TextBlock Text="{Binding Id}" />
      </HierarchicalDataTemplate>
      <HierarchicalDataTemplate DataType="{x:Type C}" ItemsSource="{Binding Items}">
        <TextBlock Text="{Binding Id}" />
      </HierarchicalDataTemplate>
      <HierarchicalDataTemplate DataType="{x:Type D}" ItemsSource="{Binding Items}">
        <TextBlock Text="{Binding Id}" />
      </HierarchicalDataTemplate>
    </TreeView.Resources>
    
    0 讨论(0)
提交回复
热议问题