WPF 重写 ListBox 内 ItemTemplate 时 WrapPanel 不会自动换行与出现滚动条的 BUG

参考

环境

软件/系统 版本 说明
Windows windows 10 专业版 22H2 64 位操作系统, 基于 x64 的处理器
Microsoft Visual Studio Community 2022 (64 位) - Current 版本 17.13.6
.NET Framework 4.8

问题

问题梳理可能不太清晰,具体参考代码

  1. 样式定义有先后顺序,需要先定义再引用,否则找不到
  2. UI 线程更新 UI 需要通过 Application.Current.Dispatcher.Invoke
  3. ListBox 可以设置 ItemContainerStyleDataTemplate,其中 ItemContainerStyle 是元素的容器,如果设置了 ControlTemplate ,需要使用 <ContentPresenter /> 显示内容元素;DataTemplate 是元素项的内容
  4. 通过 ItemsPanel 修改 ListBox 的模板,这样就可以去掉隐藏的 ScrollViewer 元素( ScrollViewer 元素默认比 ListBox 窄2像素,就会导致内容如果宽度和父元素一致,则会出现滚动条)
  5. <UniformGrid Columns="4" /> 可以指定几列自动换行
  6. ListBox 宽度没有固定,如果不固定那么 WrapPanel 就不会自动换行
  7. ListBox 未显式设置 Width(默认值为 Auto),则 WrapPanel 绑定 ListBox.Width 会绑定到 NaN(无效值),导致 WrapPanel 宽度由内容决定,可能无法正确换行。显式设置 ListBox.Width 或使用 ActualWidth 替代 Width(处理动态宽度场景)
  8. ListBox 的默认模板中,ScrollViewer 的内容区域(Viewport)可能存在隐含的布局逻辑。即使 ListBoxBorderThickness 设为 0ScrollViewer 的内部计算可能仍会保留极细微的空间(如像素舍入误差或默认样式残留),导致 WrapPanel 的实际可用宽度略小于 ListBox.ActualWidth{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type ScrollViewer}}, Path=ActualWidth}
  9. 自定义 ListBoxItemsPanel 后,给 ListBox 包一个 ScrollViewer 即可正常显示滚动条

预览

image

代码

  1. MainWindow.xaml
    <Window
    	x:Class="WPFWallpaper.MainWindow"
    	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    	xmlns:local="clr-namespace:WPFWallpaper"
    	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    	xmlns:vm="clr-namespace:WPFWallpaper.ViewModel"
    	Title="WPFWallpaper"
    	Width="800"
    	Height="450"
    	mc:Ignorable="d">
    	<Window.DataContext>
    		<vm:MainWindowVM />
    	</Window.DataContext>
    	<DockPanel>
    		<Menu Style="{StaticResource MenuStyle}">
    			<MenuItem Command="{Binding SelectFolderCommand}" Header="选择文件夹" />
    			<MenuItem Header="关于" />
    		</Menu>
    		<StatusBar DockPanel.Dock="Bottom">
    			<TextBlock Text="状态" />
    		</StatusBar>
    		<ScrollViewer VerticalScrollBarVisibility="Auto">
    			<ListBox ItemsSource="{Binding ImageModels}" Style="{StaticResource ListStyle}" />
    		</ScrollViewer>
    
    	</DockPanel>
    </Window>
    
    
  2. MainWindowStyle.xaml
    <ResourceDictionary
    	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    	xmlns:model="clr-namespace:WPFWallpaper.Model">
    	<!--  菜单栏  -->
    	<Style x:Key="MenuStyle" TargetType="{x:Type Menu}">
    		<Setter Property="Height" Value="20" />
    		<Setter Property="DockPanel.Dock" Value="Top" />
    	</Style>
    	<!--  列表外观样式  -->
    	<Style x:Key="ListItemBoxStyle" TargetType="{x:Type ListBoxItem}">
    		<Setter Property="Width" Value="190" />
    		 <!--  如果不需要默认的列表外观样式,可以重写模板  -->
    		<Setter Property="Template">
    			<Setter.Value>
    				<ControlTemplate TargetType="{x:Type ListBoxItem}">
    					<ContentPresenter />
    				</ControlTemplate>
    			</Setter.Value>
    		</Setter>
    	</Style>
    	<!--  项样式  -->
    	<Style x:Key="ImageStyle" TargetType="{x:Type Image}">
    		<Setter Property="Stretch" Value="Fill" />
    		<Setter Property="Source" Value="{Binding FilePath}" />
    	</Style>
    	<DataTemplate x:Key="ListItemTemplate" DataType="{x:Type model:ImageModel}">
    		<StackPanel
    			Margin="10,0,10,10"
    			HorizontalAlignment="Center"
    			VerticalAlignment="Center">
    			<Image Source="{Binding FilePath}" Stretch="Fill" />
    			<Button Content="选择" />
    		</StackPanel>
    	</DataTemplate>
    	<!--  列表  -->
    	<Style x:Key="ListStyle" TargetType="{x:Type ListBox}">
    		<Setter Property="ScrollViewer.HorizontalAlignment" Value="Stretch" />
    		<Setter Property="Margin" Value="0" />
    		<Setter Property="Padding" Value="0" />
    		<!--  Setter Property="Width" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type DockPanel}}, Path=Width}" /  -->
    		<Setter Property="BorderBrush" Value="Transparent" />
    		<Setter Property="BorderThickness" Value="0" />
    		<Setter Property="HorizontalAlignment" Value="Left" />
    		<Setter Property="VerticalAlignment" Value="Top" />
    		<Setter Property="ItemContainerStyle" Value="{StaticResource ListItemBoxStyle}" />
    		<Setter Property="ItemTemplate" Value="{StaticResource ListItemTemplate}" />
    		<Setter Property="ItemsPanel">
    			<Setter.Value>
    				<ItemsPanelTemplate>
    					<!--
    						WrapPanel
    						Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorLevel=1, AncestorType={x:Type ListBox}}, Path=ActualWidth}"
    						Margin="0"
    						Orientation="Horizontal" /
    					-->
    					<WrapPanel Margin="0" Orientation="Horizontal" />
    				</ItemsPanelTemplate>
    			</Setter.Value>
    		</Setter>
    		<Setter Property="Template">
    			<Setter.Value>
    				<ControlTemplate>
    					<ItemsPresenter />
    				</ControlTemplate>
    			</Setter.Value>
    		</Setter>
    	</Style>
    
    </ResourceDictionary>
    
posted @ 2025-05-26 01:31  夏秋初  阅读(60)  评论(0)    收藏  举报