WPF TreeView和Expander的区别及不同适用场景 (踩坑记录)

文章讲述了在WPF开发中,由于TreeView要求父子节点控件类型一致的限制,作者转而使用Expander来实现类似功能。在使用Expander的过程中,解决了内容高度不可变、多选以及滚动条问题,最终实现了期望的交互效果。
摘要由CSDN通过智能技术生成

一、TreeView要求父子节点控件类型需一致

最近在工作项目中,需要将ListBox (如图1)

图1 ListBox

修改为带展开按钮的树视图(如图2)

图2 带展开按钮的树视图

在修改时,本WPF新手首先想到的是TreeView,结合网上资料边学边折腾。最后发现,在绑定数据源的的前提下,TreeView在父子节点只能应用相同的控件种类。如下图,HierarchicalDataTemplate中的控件被同时应用到了父节点和子节点。

 <TreeView Height="NaN" Width="NaN" x:Name="treeView" ItemsSource="{Binding ListMeasureStation}" pu:TreeViewHelper.SelectedBackground="Red" pu:TreeViewHelper.ItemHeight="100" pu:TreeViewHelper.SelectMode="Any" pu:TreeViewHelper.TreeViewStyle="Standard">
                        <TreeView.ItemTemplate>
                            <HierarchicalDataTemplate ItemsSource="{Binding list_sleeper_detail}">
                                <StackPanel>
                                    <RadioButton Content="{Binding station_number}"/>
                                    <TextBlock Text="{Binding station_number}"/>
                                </StackPanel>
                                
                            </HierarchicalDataTemplate>
                        </TreeView.ItemTemplate>
                        <TreeView.ItemContainerStyle>
                            <Style TargetType="TreeViewItem"  BasedOn="{StaticResource {x:Type TreeViewItem}}">
                                <Setter Property="IsExpanded" Value="True" />
                            </Style>
                        </TreeView.ItemContainerStyle>
                    </TreeView>

图3 RadioButton和TextBlock控件被同时应用到了父节点和子节点

故在本次修改中,放弃TreeView改用Expander。

总结:TreeView只适用于多节点,且父子节点的控件类型相同的应用场景。

二、改用Expander

改用Expander编码时,先是参考了站内的这篇WPF 简易手风琴 (ListBox+Expander)文章,编译运行时发现Expander的内容高度不能改变,查询相关资料Template,ItemsPanel,ItemContainerStyle ,ItemTemplate四个属性辨析后,高度可变。编码大致思路:ListBox套Expander再套ListBox。(由于还未将Style写进Resources中,故代码比较冗余)

<ListBox x:Name="ItemBox" MaxHeight="1500" Width="350" ItemsSource="{Binding ListMeasureStation}" Template="{DynamicResource ListBoxControlTemplate_101}">
                        <ListBox.ItemContainerStyle>
                            <Style TargetType="{x:Type ListBoxItem}">
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                            <Expander  Background="#f2f2f2" pu:ExpanderHelper.ExpanderStyle="Classic">
                                                <Expander.Header>
                                                    <Border Height="60" Width="330" >
                                                        <TextBlock Text="{Binding station_number,StringFormat={}第{0}站}" FontSize="32" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                                                    </Border>
                                                </Expander.Header>
                                                <Expander.Content>
                                                    <ListBox MaxHeight="1500" SelectedItem="{Binding Source={StaticResource Locator},Path=MeasureDetail.SleeperDetailSingle}" SelectedIndex="{Binding Source={StaticResource Locator},Path=MeasureDetail.SelectedIndex}" ItemsSource="{Binding list_sleeper_detail}" Template="{DynamicResource ListBoxControlTemplate_101}">
                                                        <behaviors:Interaction.Behaviors>
                                                            <func:AutoScrollBehavior />
                                                        </behaviors:Interaction.Behaviors>
                                                        <ListBox.ItemContainerStyle>
                                                            <Style TargetType="{x:Type ListBoxItem}">
                                                                <Setter Property="Template">
                                                                    <Setter.Value>
                                                                        <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                                                            <Border x:Name="border_Row" Background="#f2f2f2">
                                                                                <Grid x:Name="grid_Row" Height="90" Margin="0,10">
                                                                                    <Grid.ColumnDefinitions>
                                                                                        <ColumnDefinition Width="1*" />
                                                                                        <ColumnDefinition Width="4*" />
                                                                                    </Grid.ColumnDefinitions>
                                                                                    <TextBlock x:Name="txt_Num" Text="1" Loaded="TextBlock_Loaded" Margin="10,20,10,10" TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#32D3CD" FontSize="32" FontWeight="Bold"/>
                                                                                    <Grid Grid.Column="1" Margin="0,10,0,0">
                                                                                        <Grid.RowDefinitions>
                                                                                            <RowDefinition Height="1*" />
                                                                                            <RowDefinition Height="1*" />
                                                                                        </Grid.RowDefinitions>
                                                                                        <TextBlock x:Name="txt_SleeperNumber" Text="{Binding sleeper_number,StringFormat={}{0}(编号)}" Margin="0" TextAlignment="Center" HorizontalAlignment="Left" Foreground="#000000" FontSize="26" FontFamily="苹方-简, 苹方-简-Normal" />
                                                                                        <TextBlock x:Name="txt_Footage" Text="{Binding footage,StringFormat={}DK{0:F5}}" Margin="0" TextAlignment="Center" HorizontalAlignment="Left" Foreground="#000000" FontSize="26"/>
                                                                                    </Grid>
                                                                                </Grid>
                                                                            </Border>
                                                                            <ControlTemplate.Triggers>
                                                                                <Trigger Property="IsSelected" Value="false">
                                                                                    <Setter Property="Foreground" TargetName="txt_SleeperNumber" Value="black" />
                                                                                    <Setter Property="Foreground" TargetName="txt_Footage" Value="black" />
                                                                                    <Setter Property="Background" TargetName="grid_Row" Value="white" />
                                                                                </Trigger>
                                                                                <Trigger Property="IsSelected" Value="true">
                                                                                    <Setter Property="Foreground" TargetName="txt_Num" Value="white" />
                                                                                    <Setter Property="Foreground" TargetName="txt_SleeperNumber" Value="white" />
                                                                                    <Setter Property="Foreground" TargetName="txt_Footage" Value="white" />
                                                                                    <Setter Property="Background" TargetName="grid_Row" Value="#6f99fe" />
                                                                                </Trigger>
                                                                            </ControlTemplate.Triggers>
                                                                        </ControlTemplate>
                                                                    </Setter.Value>
                                                                </Setter>
                                                            </Style>
                                                        </ListBox.ItemContainerStyle>
                                                    </ListBox>
                                                </Expander.Content>
                                            </Expander>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </ListBox.ItemContainerStyle>
                    </ListBox>

实现的效果如下:

图4 ListBox套Expander再套ListBox 使用ItemContainerStyle自定义ListBoxItem子项的控件样式

自我批判:

1、有没有一种可能,找一个完善的UI控件库就不必这么大费周折了。。。

2、或许有更好的解决方案?

三、Expander和ListBox互相嵌套时的“bug”

主体建好后,就到了修bug时间了。之所以bug打引号,是因为有些点不算bug,但影响软件实际使用时的人机交互。

“bug”列表:

  1. 实际只需要同时展开某一个Expander的内容,不需要同时展开多个Expander。

  1. 最内层的多个ListBox的选中项需是唯一的,不能出现如图4中同时选中两个最内层元素的情况。

  1. 鼠标指针停靠在Expander内容区时,默认只会触发内层ListBox的滚动效果,无法实现外层ListBox的滚动。

经过一番折腾,找到了对应的解决方案。

解决方案:

  1. 将Expander的IsExpanded属性绑定到父容器ListBoxItem的IsSelected属性。

 <Expander MaxHeight="900" Background="Transparent" pu:ExpanderHelper.ExpanderStyle="Standard" BorderThickness="0" IsExpanded="{Binding  RelativeSource ={ RelativeSource  Mode=FindAncestor, AncestorType=ListBoxItem},  Path=IsSelected}">
  1. 放弃SelectedItem,改用 SelectedValue。当在ListBox中找不到对应的SelectedItem时,绑定SelectedItem属性不会取消选择原选项。而绑定SelectedValue属性会取消选择原选项。ps:SelectedValue属性取消选择多个原选项时,会多次触发SelectionChanged事件,可能导致SelectedIndex属性跳变多次,丢失正确index值。

<ListBox SelectedValue="{Binding MySelectedItem}" />
  1. 暂缓处理。(在修复前两个bug后,此bug的问题得到缓冲,后续处理时再补充此文)

图5 最终效果

最终版代码:

<ListBox x:Name="lbox_MeasureStation" MaxHeight="1500" Width="350" ItemsSource="{Binding ListMeasureStation}" Template="{DynamicResource ListBoxControlTemplate_101}" BorderThickness="0" Background="Transparent">
                        <ListBox.ItemContainerStyle>
                            <Style TargetType="{x:Type ListBoxItem}">
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                            <Expander MaxHeight="900" Background="Transparent" pu:ExpanderHelper.ExpanderStyle="Standard" BorderThickness="0" IsExpanded="{Binding  RelativeSource ={ RelativeSource  Mode=FindAncestor, AncestorType=ListBoxItem},  Path=IsSelected}">
                                                <Expander.Header>
                                                    <Border Height="60" Width="330">
                                                        <TextBlock Text="{Binding station_number,StringFormat={}第{0}站}" FontSize="32" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                                                    </Border>
                                                </Expander.Header>
                                                <Expander.Content>
                                                    <ListBox x:Name="lbox_MeasureDetail" MaxHeight="900" SelectionChanged="lbox_MeasureDetail_SelectionChanged" SelectedValue="{Binding Source={StaticResource Locator},Path=MeasureDetail.SleeperDetailSingle}" ItemsSource="{Binding list_sleeper_detail}" Template="{DynamicResource ListBoxControlTemplate_101}" BorderThickness="0" Background="Transparent">
                                                        <behaviors:Interaction.Behaviors>
                                                            <func:AutoScrollBehavior />
                                                        </behaviors:Interaction.Behaviors>
                                                        <i:Interaction.Triggers>
                                                            <i:EventTrigger EventName="SelectionChanged">
                                                                <mvvm:EventToCommand Command="{Binding Source={StaticResource Locator},Path=MeasureDetail.DataSwitchCommand}" />
                                                            </i:EventTrigger>
                                                        </i:Interaction.Triggers>
                                                        <ListBox.ItemContainerStyle>
                                                            <Style TargetType="{x:Type ListBoxItem}">
                                                                <Setter Property="Template">
                                                                    <Setter.Value>
                                                                        <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                                                            <Border x:Name="border_Row" Background="#f2f2f2">
                                                                                <Grid x:Name="grid_Row" Height="90" Margin="0,10">
                                                                                    <Grid.ColumnDefinitions>
                                                                                        <ColumnDefinition Width="1*" />
                                                                                        <ColumnDefinition Width="4*" />
                                                                                    </Grid.ColumnDefinitions>
                                                                                    <TextBlock x:Name="txt_Num" Text="1" Loaded="TextBlock_Loaded" Margin="10,20,10,10" TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#32D3CD" FontSize="32" FontWeight="Bold" />
                                                                                    <Grid Grid.Column="1" Margin="0,10,0,0">
                                                                                        <Grid.RowDefinitions>
                                                                                            <RowDefinition Height="1*" />
                                                                                            <RowDefinition Height="1*" />
                                                                                        </Grid.RowDefinitions>
                                                                                        <TextBlock x:Name="txt_SleeperNumber" Text="{Binding sleeper_number,StringFormat={}{0}}" Margin="0" TextAlignment="Center" HorizontalAlignment="Left" Foreground="#000000" FontSize="26" />
                                                                                        <TextBlock x:Name="txt_Footage" Text="{Binding footage,StringFormat={}DK{0:F5}}" Margin="0" TextAlignment="Center" HorizontalAlignment="Left" Foreground="#000000" FontSize="26" Grid.Row="1" />
                                                                                    </Grid>
                                                                                </Grid>
                                                                            </Border>
                                                                            <ControlTemplate.Triggers>
                                                                                <Trigger Property="IsSelected" Value="false">
                                                                                    <Setter Property="Foreground" TargetName="txt_SleeperNumber" Value="black" />
                                                                                    <Setter Property="Foreground" TargetName="txt_Footage" Value="black" />
                                                                                    <Setter Property="Background" TargetName="grid_Row" Value="white" />
                                                                                </Trigger>
                                                                                <Trigger Property="IsSelected" Value="true">
                                                                                    <Setter Property="Foreground" TargetName="txt_Num" Value="white" />
                                                                                    <Setter Property="Foreground" TargetName="txt_SleeperNumber" Value="white" />
                                                                                    <Setter Property="Foreground" TargetName="txt_Footage" Value="white" />
                                                                                    <Setter Property="Background" TargetName="grid_Row" Value="#6f99fe" />
                                                                                </Trigger>
                                                                            </ControlTemplate.Triggers>
                                                                        </ControlTemplate>
                                                                    </Setter.Value>
                                                                </Setter>
                                                            </Style>
                                                        </ListBox.ItemContainerStyle>
                                                    </ListBox>
                                                </Expander.Content>
                                            </Expander>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </ListBox.ItemContainerStyle>
                    </ListBox>

ListBox滚动条样式:

<ControlTemplate x:Key="ListBoxControlTemplate_101" TargetType="{x:Type ListBox}">
            <Border x:Name="Bd" Grid.ColumnSpan="2" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="1" SnapsToDevicePixels="True">
                <ScrollViewer Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" Padding="{TemplateBinding Padding}">
                    <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                </ScrollViewer>
            </Border>
            <ControlTemplate.Triggers>
                <Trigger Property="IsEnabled" Value="False">
                    <Setter Property="Background" TargetName="Bd" Value="#FFD9D9D9" />
                </Trigger>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <!--<Condition Property="IsGrouping" Value="True" />-->
                        <Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="False" />
                    </MultiTrigger.Conditions>
                    <Setter Property="ScrollViewer.CanContentScroll" Value="False" />
                </MultiTrigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
WPF TreeView是一种在WPF(Windows Presentation Foundation)应用程序中使用的控件,用于以分层结构显示数据。它类似于树形结构,其中每个节点可以有零个或多个子节点。 TreeView由一系列TreeNode组成,每个TreeNode代表一个节点。每个TreeNode可以包含一个或多个子节点,并且可以通过展开和折叠来显示或隐藏这些子节点。每个节点可以具有自定义的显示内容,通常是文本,也可以是任何WPF元素。 使用WPF TreeView,您可以创建具有层次结构的导航菜单、文件资源管理器、组织架构图等应用程序。您可以自定义节点的外观和行为,以及处理节点的选择和展开事件。 要使用WPF TreeView,您需要在XAML中声明TreeView和TreeNode。然后,您可以通过添加和删除TreeNode来动态构建树状结构,并使用数据绑定来显示和编辑树上的数据。 以下是一个简单的示例,演示如何在XAML中创建一个简单的TreeView: ```xaml <TreeView> <TreeViewItem Header="Root"> <TreeViewItem Header="Child 1" /> <TreeViewItem Header="Child 2" /> <TreeViewItem Header="Child 3"> <TreeViewItem Header="Grandchild 1" /> <TreeViewItem Header="Grandchild 2" /> </TreeViewItem> </TreeViewItem> </TreeView> ``` 这将创建一个具有三个一级节点的简单树状结构。您可以通过展开和折叠节点来显示或隐藏子节点。 希望这可以帮助您了解WPF TreeView的基本概念和用法。如果您有更具体的问题,请随时提问!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值