0

For the 2nd day I'm scouring the web and have not found a solution. Take an element like this:

<TreeView ItemsSource="{Binding Types}" Width="300">
   <TreeView.Resources>
      <HierarchicalDataTemplate DataType="{x:Type models:Type}"
                                ItemsSource="{Binding SubTypes}">
         <TextBlock Text="{Binding Name}"/>
         <HierarchicalDataTemplate.ItemTemplate>
            <DataTemplate DataType="{x:Type SubType}">
               <TextBlock Text="{Binding Name}"/>
            </DataTemplate>
         </HierarchicalDataTemplate.ItemTemplate>
      </HierarchicalDataTemplate>
   </TreeView.Resources>
</TreeView>

I use the Material NuGet library for base styles. Now however I need to disable the hover, select, etc. on the first level items and only allow the selection/hover for the subitems.

But everything I seem to find is about styling the contents of each item or styling everything globally.

A <- remove selection/hover (pref single click too but that's another topic)
  A1 <- maintain original style, hover and select
  A2 <- maintain original style, hover and select
  A3 <- maintain original style, hover and select
B <- remove selection/hover (pref single click too but that's another topic)
  B1 <- maintain original style, hover and select
  B2 <- maintain original style, hover and select
  B3 <- maintain original style, hover and select
thatguy
  • 21,059
  • 6
  • 30
  • 40
Agony
  • 845
  • 2
  • 9
  • 23

3 Answers3

2

Sounds like you don't really want each top-level item to act like a normal TreeViewItem. In that case, why not move the top-level items outside of the TreeView?

Basically, you'd have an ItemsControl of the top-level items, where the item template acts a bit like an Expander containing a TreeView of the items underneath it. You could style the top-level items to look however you like.

The downside would be that the trees under the top-level items would be virtualized individually, not as a whole. That is, they would not share containers. Unless you have a ton of top-level items, that probably won't be a big deal.

Example:

<ItemsControl xmlns:s="clr-namespace:System;assembly=mscorlib"
              ItemsSource="{Binding Types}">
  <ItemsControl.Resources>
    <ControlTemplate x:Key="ExpanderButtonTemplate" TargetType="ToggleButton">
      <Border Background="Transparent" Padding="3,2">
        <ContentPresenter />
      </Border>
    </ControlTemplate>
    <Style TargetType="Expander">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Expander">
            <DockPanel LastChildFill="True">
              <ToggleButton DockPanel.Dock="Top"
                            IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                            Template="{StaticResource ExpanderButtonTemplate}">
                <ContentPresenter ContentSource="Header" />
              </ToggleButton>
              <Border>
                <ContentPresenter x:Name="contentSite" Visibility="Collapsed" />
              </Border>
            </DockPanel>
            <ControlTemplate.Triggers>
              <Trigger Property="IsExpanded" Value="True">
                <Setter TargetName="contentSite" Property="Visibility" Value="Visible" />
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </ItemsControl.Resources>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <Expander Header="{Binding Name}">
        <TreeView ItemsSource="{Binding SubTypes}" BorderThickness="0" Padding="0">
          <TreeView.ItemTemplate>
            <HierarchicalDataTemplate DataType="{x:Type models:Type}"
                                      ItemsSource="{Binding SubTypes}">
              <TextBlock Text="{Binding Name}"/>
              <HierarchicalDataTemplate.ItemTemplate>
                <DataTemplate DataType="{x:Type models:SubType}">
                  <TextBlock Text="{Binding Name}"/>
                </DataTemplate>
              </HierarchicalDataTemplate.ItemTemplate>
            </HierarchicalDataTemplate>
          </TreeView.ItemTemplate>
        </TreeView>
      </Expander>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

Click on one of the top-level items to expand the tree beneath it.

Screenshot

Mike Strobel
  • 25,075
  • 57
  • 69
  • Quite possible - i'm rather new to XAML - but would it still act as a toggle control to show/hide subitems? The Types contains 3x models:Type and each of those contains unknown number of SubTypes. – Agony Jan 31 '18 at 18:32
  • Sure. I added a simple proof-of-concept for an example. – Mike Strobel Jan 31 '18 at 18:41
  • Seems like this does cover and solve both style and clik issue - now i just need to figure out and replicate the material theme the treeview uses. – Agony Jan 31 '18 at 19:19
2

You can keep the TreeView and set properties or apply a style to your top-level items (assuming your TreeView isn't nested in another TreeView) by searching up the logical hierarchy for a TreeViewItem:

<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="{x:Null}">
  <Setter Property="Background" Value="LightYellow" />
</DataTrigger>

For top-level items this binding spams warnings to debug output, but they can safely be ignored. A more sophisticated version of this trick would be to create an inheritable attached property TreeViewItemLevel that would be set to zero on the TreeView and to one more than the inherited value on TreeViewItems.

Anton Tykhyy
  • 19,370
  • 5
  • 54
  • 56
0

While setting a DataTrigger with a relative source binding works, it will produce binding errors.

<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}}" Value="{x:Null}">

By setting a FallbackValue of x:Null, you will not get any binding errors, but warnings.

<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}, FallbackValue={x:Null}}" Value="{x:Null}">

An alternative approach that will neither yield errors nor warnings, is to create a value converter that essentially does the same a RelativeSource binding, but will handle the errors, too. It returns true for any dependency object passed in, if it has an ancestor of type TreeViewItem, false otherwise.

public class IsRootTreeViewItemConverter : IValueConverter
{
   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
      return value is null ? Binding.DoNothing : !HasTreeViewItemAncestor((DependencyObject)value);
   }

   public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
   {
      throw new InvalidOperationException();
   }

   private static bool HasTreeViewItemAncestor(DependencyObject child)
   {
      var parent = VisualTreeHelper.GetParent(child);

      return parent switch
      {
         null => false,
         TreeViewItem _ => true,
         _ => HasTreeViewItemAncestor(parent)
      };
   }
}

In your TreeViewItem style trigger, you can use the converter by binding to the item itself with Self.

<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource IsRootTreeViewItemConverter}}" Value="True">

This approach works for both statically assigned TreeViewItems and ItemsSource bindings.

thatguy
  • 21,059
  • 6
  • 30
  • 40