
问题场景
在HandyControl 版本升级过程中,由3.4.x 升级到3.5.x 过程中,遇到ListView 添加 ListViewItem 或者 自定义DataTemplate时,运行时ListView中无内容显示,查看可视化树,控件无子节点。

复现版本
原有版本为3.4.0,升级版本使用版本为3.5.0。
xml
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<!--<PackageReference Include="HandyControl" Version="3.6.0-rc3" />
<PackageReference Include="HandyControl" Version="3.4.0" />-->
<PackageReference Include="HandyControl" Version="3.5.0" />
</ItemGroup>
</Project>
实体数据
案例数据主要为两层,MainWindowViewModel 作为主窗口的VM,CaseItemViewModel作为集合的VM。
cs
public class MainWindowViewModel
{
public List<CaseItemViewModel> MyPropertys { get; set; }
public MainWindowViewModel()
{
MyPropertys = new List<CaseItemViewModel>
{
new CaseItemViewModel { MyProperty = "1", MyProperty1 = "1", MyProperty2 = "1", MyProperty3 = "1" },
new CaseItemViewModel { MyProperty = "2", MyProperty1 = "2", MyProperty2 = "2", MyProperty3 = "2" },
new CaseItemViewModel { MyProperty = "3", MyProperty1 = "3", MyProperty2 = "3", MyProperty3 = "3" },
new CaseItemViewModel { MyProperty = "4", MyProperty1 = "4", MyProperty2 = "4", MyProperty3 = "4" },
new CaseItemViewModel { MyProperty = "5", MyProperty1 = "5", MyProperty2 = "5", MyProperty3 = "5" }
};
}
}
Xaml页面
xaml
<ListView IsManipulationEnabled="False" ItemsSource="{Binding MyPropertys}">
<!--<ListViewItem>1</ListViewItem>
<ListViewItem>2</ListViewItem>
<ListViewItem>3</ListViewItem>
<ListViewItem>4</ListViewItem>
<ListViewItem>5</ListViewItem>-->
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyProperty}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
运行效果

解决方案
幸运的是,该问题在3.6.x中已经被修复,主要针对3.5.x 使用用户,因为历史原因无法正常升级到3.6.x 版本,通过对比3.6.x 以及3.5.x 对应生成的Theme.xaml 代码和官方issues,找到如下变更。
代码比对
左侧为3.5.0,右侧为3.6.0-rc3版本反编译的变更差异。

官方变更,这个变更在2024年就已经修复。

解决思路
如果3.5.x 版本用户要进行本地修复,可以直接拷贝src/Shared/HandyControl_Shared/Themes/Styles/ListView.xaml 中 ListViewBaseStyle样式以及设置ListView 默认样式。
xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hc="https://handyorg.github.io/handycontrol">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style x:Key="ListViewBaseStyle" TargetType="ListView">
<!--省略,文末提供完整内容-->
</Style>
<Style BasedOn="{StaticResource ListViewBaseStyle}" TargetType="ListView" />
</ResourceDictionary>
再次运行案例,效果如下,表明问题已经解决。

总结归纳
在定位并解决的过程中,也对ListView 本身有了更加深入的了解,对于ListView 父类为ListBox ,其样式也对ListView 可以生效,同理ItemControl 作为ListBox 的父类,对应的ItemsControl.HasItems 也能作为ListView 的属性触发使用。对应的笔者使用的ListView.xaml 样式代码完整内容如下:
xml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hc="https://handyorg.github.io/handycontrol">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style x:Key="ListViewBaseStyle" TargetType="ListView">
<Setter Property="Background" Value="{DynamicResource SecondaryRegionBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource BorderBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="6" />
<Setter Property="Foreground" Value="{DynamicResource PrimaryTextBrush}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.CanContentScroll" Value="true" />
<Setter Property="ScrollViewer.PanningMode" Value="Both" />
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="ItemContainerStyle" Value="{StaticResource ListViewItemBaseStyle}" />
<Setter Property="hc:BorderElement.CornerRadius" Value="{StaticResource DefaultCornerRadius}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListView">
<ControlTemplate.Resources>
<Storyboard x:Key="Storyboard1">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PART_VerticalScrollBar">
<EasingDoubleKeyFrame KeyTime="0:0:.2" Value=".8" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PART_HorizontalScrollBar">
<EasingDoubleKeyFrame KeyTime="0:0:.2" Value=".8" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="Storyboard2">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PART_VerticalScrollBar">
<EasingDoubleKeyFrame KeyTime="0:0:.2" Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="PART_HorizontalScrollBar">
<EasingDoubleKeyFrame KeyTime="0:0:.2" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<Border CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
<ScrollViewer Focusable="false">
<ScrollViewer.Template>
<ControlTemplate TargetType="ScrollViewer">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Grid.ColumnSpan="2">
<ScrollViewer DockPanel.Dock="Top" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" Focusable="false">
<GridViewHeaderRowPresenter AllowsColumnReorder="{Binding View.AllowsColumnReorder, RelativeSource={RelativeSource AncestorType=ListView}}" ColumnHeaderContainerStyle="{Binding View.ColumnHeaderContainerStyle, RelativeSource={RelativeSource AncestorType=ListView}}" ColumnHeaderToolTip="{Binding View.ColumnHeaderToolTip, RelativeSource={RelativeSource AncestorType=ListView}}" ColumnHeaderContextMenu="{Binding View.ColumnHeaderContextMenu, RelativeSource={RelativeSource AncestorType=ListView}}" ColumnHeaderTemplate="{Binding View.ColumnHeaderTemplate, RelativeSource={RelativeSource AncestorType=ListView}}" ColumnHeaderTemplateSelector="{Binding View.ColumnHeaderTemplateSelector, RelativeSource={RelativeSource AncestorType=ListView}}" Columns="{Binding View.Columns, RelativeSource={RelativeSource AncestorType=ListView}}" Margin="2,0" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</DockPanel>
<hc:ToggleBlock Grid.Row="1" Grid.ColumnSpan="2" Grid.RowSpan="2" IsChecked="{Binding HasItems,RelativeSource={RelativeSource AncestorType=ListView},Mode=OneWay}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch">
<hc:ToggleBlock.CheckedContent>
<ScrollContentPresenter x:Name="PART_ScrollContentPresenter" CanContentScroll="{TemplateBinding CanContentScroll}" />
</hc:ToggleBlock.CheckedContent>
<hc:ToggleBlock.UnCheckedContent>
<hc:Empty Background="{DynamicResource RegionBrush}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" />
</hc:ToggleBlock.UnCheckedContent>
</hc:ToggleBlock>
<ScrollBar Style="{StaticResource ScrollBarBaseStyle}" Opacity="0" x:Name="PART_VerticalScrollBar" Grid.Column="1" Grid.Row="1" Maximum="{TemplateBinding ScrollableHeight}" Orientation="Vertical" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportHeight}" />
<ScrollBar Style="{StaticResource ScrollBarBaseStyle}" Opacity="0" x:Name="PART_HorizontalScrollBar" Grid.Row="1" Maximum="{TemplateBinding ScrollableWidth}" Orientation="Horizontal" VerticalAlignment="Bottom" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" ViewportSize="{TemplateBinding ViewportWidth}" />
</Grid>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard Storyboard="{StaticResource Storyboard1}" />
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard Storyboard="{StaticResource Storyboard2}" />
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ScrollViewer.Template>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true" />
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false" />
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
</MultiTrigger>
<Trigger Property="ItemsControl.HasItems" Value="False">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListView">
<Border CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
<hc:Empty Background="{DynamicResource RegionBrush}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
<Style BasedOn="{StaticResource ListViewBaseStyle}" TargetType="ListView" />
</ResourceDictionary>