TreeView с MasterDetails и ToggleButton

Я хочу создать TreeView с MasterDetails на выбранном элементе.

Проблема в том, что в SelectedItem не отображаются дочерние элементы, даже если родительский элемент раскрыт. Каким-то образом HierarchicalDataTemplate, похоже, теряется.

Может быть, я просто не прав с HierarchicalDataTemplate в <TreeView.ItemTemplate>, Должен ли я начать с ItemsPanelTemplate или что-то подобное? Понятия не имею на данный момент.

Вот мой XAML:

<Window x:Class="TreeViewMasterDetails.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TreeViewMasterDetails" 
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.Resources>
            <SolidColorBrush x:Key="GlyphBrush" Color="#444" />
            <BooleanToVisibilityConverter x:Key="booltoVisibilityConverter" />

            <PathGeometry x:Key="TreeArrow">
                <PathGeometry.Figures>
                    <PathFigureCollection>
                        <PathFigure IsFilled="True"
                            StartPoint="0 0"
                            IsClosed="True">
                            <PathFigure.Segments>
                                <PathSegmentCollection>
                                    <LineSegment Point="0 6"/>
                                    <LineSegment Point="6 0"/>
                                </PathSegmentCollection>
                            </PathFigure.Segments>
                        </PathFigure>
                    </PathFigureCollection>
                </PathGeometry.Figures>
            </PathGeometry>

            <Style x:Key="ExpandCollapseToggleStyle"
           TargetType="{x:Type ToggleButton}">
                <Setter Property="Focusable"
                Value="False"/>
                <Setter Property="Width"
                Value="16"/>
                <Setter Property="Height"
                Value="16"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ToggleButton}">
                            <Border Width="16"
                            Height="16"
                            Background="Transparent"
                            Padding="5,5,5,5">
                                <Path x:Name="ExpandPath"
                              Fill="Transparent"
                              Stroke="#FF989898"
                              Data="{StaticResource TreeArrow}">
                                    <Path.RenderTransform>
                                        <RotateTransform Angle="135"
                                                 CenterX="3"
                                                 CenterY="3"/>
                                    </Path.RenderTransform>
                                </Path>
                            </Border>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsMouseOver"
                                 Value="True">
                                    <Setter TargetName="ExpandPath"
                                    Property="Stroke"
                                    Value="#FF1BBBFA"/>
                                    <Setter TargetName="ExpandPath"
                                    Property="Fill"
                                    Value="Transparent"/>
                                </Trigger>

                                <Trigger Property="IsChecked"
                                 Value="True">
                                    <Setter TargetName="ExpandPath"
                                    Property="RenderTransform">
                                        <Setter.Value>
                                            <RotateTransform Angle="180"
                                                     CenterX="3"
                                                     CenterY="3"/>
                                        </Setter.Value>
                                    </Setter>
                                    <Setter TargetName="ExpandPath"
                                    Property="Fill"
                                    Value="#FF595959"/>
                                    <Setter TargetName="ExpandPath"
                                    Property="Stroke"
                                    Value="#FF262626"/>
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>

            <ControlTemplate TargetType="{x:Type TreeViewItem}" x:Key="selectedItemTemplate">
                <Grid Height="Auto" Background="SkyBlue">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"></RowDefinition>
                        <RowDefinition Height="Auto"></RowDefinition>
                        <RowDefinition Height="Auto"></RowDefinition>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="20"></ColumnDefinition>
                        <ColumnDefinition Width="Auto"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <ToggleButton x:Name="Expander"
                                      Style="{StaticResource ExpandCollapseToggleStyle}"
                                      IsChecked="{Binding Path=IsExpanded,RelativeSource={RelativeSource TemplatedParent}}"
                                      ClickMode="Press"
                                      Visibility="{Binding Path=HasItems,RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource booltoVisibilityConverter}}"/>

                    <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Id}"></TextBlock>
                    <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Name}"></TextBlock>
                    <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Description}"></TextBlock>
                </Grid>
            </ControlTemplate>
        </Grid.Resources>

        <TreeView Height="Auto" 
                  HorizontalAlignment="Stretch" 
                  Margin="10" 
                  VerticalAlignment="Stretch" 
                  Width="Auto"
                  ItemsSource="{Binding Items}">
            <TreeView.ItemContainerStyle>
                <Style TargetType="TreeViewItem">
                    <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="Template" Value="{StaticResource selectedItemTemplate}"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TreeView.ItemContainerStyle>
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate DataType="x:Type local:NodeViewModel" ItemsSource="{Binding Children}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="20*" />
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="100*" />
                            <ColumnDefinition Width="100*" />
                            <ColumnDefinition Width="100*" />
                        </Grid.ColumnDefinitions>

                        <TextBlock Grid.Column="0" Text="{Binding Id}"></TextBlock>
                        <TextBlock Grid.Column="1" Text="----"></TextBlock>
                        <TextBlock Grid.Column="2" Text="{Binding Name}"></TextBlock>
                    </Grid>

                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </Grid>
</Window>

Прошу прощения за блок массива XAML, но сейчас я не знаю, в чем причина.

И моя ViewModel:

public class NodeViewModel : ViewModelBase
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public bool IsExpanded { get; set; }
    public bool HasChildren  // perhaps this can be replaced by HasItems in TemplatedParent?
    {
        get
        {
            if (Children != null)
            {
                Children.Any();
            }

            return false;
        }
    }

    public ObservableCollection<NodeViewModel> Children { get; set; }
}

Как я могу отобразить детей с моим HierarchicalDataTemplate? Почему это теряется?

Обновление Добавили установщик в мой стиль TreeViewItem, чтобы установить IsExpanded в true при выделении, и ToggleButton, кажется, отображает это право.

Есть ли хороший учебник или что-нибудь, где я могу узнать, как обращаться с шаблоном иерархических данных?

Любая идея о том, как я могу продолжать, будет высоко оценена!

1 ответ

Решение

Вы пытались эмулировать элемент дерева с помощью кнопки-переключателя. Я уверен, что был бы способ сделать это, но это сложно. Если вы можете жить с кнопкой расширения, находящейся вне вашего шаблона, попробуйте следующее решение. Изменение визуального стиля элемента древовидной структуры теперь встроено в шаблон HierarchicalDataTemplate.

Вы привязываете TreeView к свойству Items модели представления, которую вы не раскрыли, в то время как HierarchicalDataTemplate использует свойство ChildrenМне пришлось изменить это, потому что я использую NodeViewModel также в качестве "корневой" ViewModel.

Я думаю, что того же можно достичь с помощью DataTemplateSelector.

Xaml:

<UserControl.DataContext>
    <local:NodeViewModel  />
</UserControl.DataContext>
<Grid>
    <Grid.Resources>
        <DataTemplate x:Key="notSelectedItemTemplate" DataType="{x:Type local:NodeViewModel}" >
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="20*" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100*" />
                    <ColumnDefinition Width="100*" />
                    <ColumnDefinition Width="100*" />
                </Grid.ColumnDefinitions>

                <TextBlock Grid.Column="0" Text="{Binding Id}"></TextBlock>
                <TextBlock Grid.Column="1" Text="----"></TextBlock>
                <TextBlock Grid.Column="2" Text="{Binding Name}"></TextBlock>
            </Grid>
        </DataTemplate>

        <DataTemplate x:Key="selectedItemTemplate" DataType="{x:Type local:NodeViewModel}">
            <Grid Height="Auto" Background="SkyBlue" TextElement.Foreground="Black">
                <Grid.RowDefinitions>
                    <!--<RowDefinition Height="Auto"></RowDefinition>-->
                    <RowDefinition Height="Auto"></RowDefinition>
                    <RowDefinition Height="Auto"></RowDefinition>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="20"></ColumnDefinition>
                    <ColumnDefinition Width="Auto"></ColumnDefinition>
                </Grid.ColumnDefinitions>

                <!--<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Id}"></TextBlock>-->
                <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}"></TextBlock>
                <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Description}"></TextBlock>
            </Grid>
        </DataTemplate>
    </Grid.Resources>

    <TreeView Height="Auto" HorizontalAlignment="Stretch" Margin="10" VerticalAlignment="Stretch" Width="Auto" ItemsSource="{Binding Children}">
        <TreeView.Resources>
            <!-- remove normal selected item background -->
            <SolidColorBrush Color="Transparent" x:Key="{x:Static SystemColors.HighlightBrushKey}"/>
        </TreeView.Resources>
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
            </Style>
        </TreeView.ItemContainerStyle>
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate DataType="{x:Type local:NodeViewModel}" ItemsSource="{Binding Children}">
                <ContentPresenter x:Name="item" ContentTemplate="{StaticResource notSelectedItemTemplate}" />
                <HierarchicalDataTemplate.Triggers>
                    <DataTrigger Binding="{Binding IsSelected}" Value="True">
                        <Setter TargetName="item" Property="ContentTemplate" Value="{StaticResource selectedItemTemplate}" />
                    </DataTrigger>
                </HierarchicalDataTemplate.Triggers>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
</Grid>

NodeViewModel (добавлено свойство IsSelected):

public class NodeViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    private ObservableCollection<NodeViewModel> _children;
    public ObservableCollection<NodeViewModel> Children { get { return _children; } set { _children = value; OnPropertyChanged("Children"); } }


    private string _id;
    public string Id { get { return _id; } set { _id = value; OnPropertyChanged("ID"); } }
    private string _name;
    public string Name { get { return _name; } set { _name = value; OnPropertyChanged("ID"); } }
    private string _description;
    public string Description { get { return _description; } set { _description = value; OnPropertyChanged("Description"); } }
    private bool _isExpanded;
    public bool IsExpanded { get { return _isExpanded; } set { _isExpanded = value; OnPropertyChanged("IsExpanded"); } }
    private bool _isSelected;
    public bool IsSelected { get { return _isSelected; } set { _isSelected = value; OnPropertyChanged("IsSelected"); } }
    public bool HasChildren  // perhaps this can be replaced by HasItems in TemplatedParent?
    {
        get
        {
            if (Children != null)
            {
                Children.Any();
            }

            return false;
        }
    }

    private static bool _setData = true; // hack for example data

    public NodeViewModel()
    {
        if (_setData)
        {
            _setData = false;
            SetExampleData();
        }
    }

    public void SetExampleData()
    {
        Children = new ObservableCollection<NodeViewModel>()
        {
            new NodeViewModel() { Name = "1", Description = "One"    },
            new NodeViewModel() { Name = "2", Description = "Two"    },
            new NodeViewModel() { Name = "3", Description = "Three"  },
            new NodeViewModel() { Name = "4", Description = "Four"   },
            new NodeViewModel() { Name = "5", Description = "Five"   },
            new NodeViewModel() { Name = "6", Description = "Six"    },
            new NodeViewModel() { Name = "7", Description = "Seven"  },
            new NodeViewModel() { Name = "8", Description = "Eight"  }
        };

        Children[0].Children = new ObservableCollection<NodeViewModel>() 
        {  
            new NodeViewModel() { Name = "1.1", Description="One.One" },
            new NodeViewModel() { Name = "1.2", Description="One.Two" },
            new NodeViewModel() { Name = "1.3", Description="One.Three" }
        };

        Children[0].Children[0].Children = new ObservableCollection<NodeViewModel>() 
        {  
            new NodeViewModel() { Name = "1.1.1", Description="One.One.One" },
            new NodeViewModel() { Name = "1.1.2", Description="One.One.Two" },
        };

        Children[1].Children = new ObservableCollection<NodeViewModel>() 
        {  
            new NodeViewModel() { Name = "2.1", Description="Two.One" },
            new NodeViewModel() { Name = "2.2", Description="Two.Two" },
        };
    }
}
Другие вопросы по тегам