带有ListItem的Grid的ListBox产生奇怪的绑定错误
我有一个ListBox控件,并且在网格布局中呈现了固定数量的ListBoxItem对象。因此,我将ItemsPanelTemplate设置为Grid。
我正在从后面的代码访问Grid,以配置RowDefinitions和ColumnDefinitions。
到目前为止,一切都按我的预期进行。我有一些自定义IValueConverter实现,用于返回每个ListBoxItem应该出现的Grid.Row和Grid.Column。
但是,有时我会得到奇怪的绑定错误,而且我无法弄清楚它们为什么会发生,即使它们在我的代码中也是如此。
这是我得到的错误:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=HorizontalContentAlignment; DataItem=null; target element is 'ListBoxItem' (Name=''); target property is 'HorizontalContentAlignment' (type 'HorizontalAlignment')
有人可以解释发生了什么吗?
哦,这是我的XAML:
<UserControl.Resources> <!-- Value Converters --> <v:GridRowConverter x:Key="GridRowConverter" /> <v:GridColumnConverter x:Key="GridColumnConverter" /> <v:DevicePositionConverter x:Key="DevicePositionConverter" /> <v:DeviceBackgroundConverter x:Key="DeviceBackgroundConverter" /> <Style x:Key="DeviceContainerStyle" TargetType="{x:Type ListBoxItem}"> <Setter Property="FocusVisualStyle" Value="{x:Null}" /> <Setter Property="Background" Value="Transparent" /> <Setter Property="Grid.Row" Value="{Binding Path=DeviceId, Converter={StaticResource GridRowConverter}}" /> <Setter Property="Grid.Column" Value="{Binding Path=DeviceId, Converter={StaticResource GridColumnConverter}}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <Border CornerRadius="2" BorderThickness="1" BorderBrush="White" Margin="2" Name="Bd" Background="{Binding Converter={StaticResource DeviceBackgroundConverter}}"> <TextBlock FontSize="12" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Path=DeviceId, Converter={StaticResource DevicePositionConverter}}" > <TextBlock.LayoutTransform> <RotateTransform Angle="270" /> </TextBlock.LayoutTransform> </TextBlock> </Border> <ControlTemplate.Triggers> <Trigger Property="IsSelected" Value="true"> <Setter TargetName="Bd" Property="BorderThickness" Value="2" /> <Setter TargetName="Bd" Property="Margin" Value="1" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </UserControl.Resources> <Border CornerRadius="3" BorderThickness="3" Background="#FF333333" BorderBrush="#FF333333" > <Grid ShowGridLines="False"> <Grid.RowDefinitions> <RowDefinition Height="15" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackPanel Grid.Row="0" Orientation="Horizontal"> <Image Margin="20,3,3,3" Source="Barcode.GIF" Width="60" Stretch="Fill" /> </StackPanel> <ListBox ItemsSource="{Binding}" x:Name="lstDevices" Grid.Row="1" ItemContainerStyle="{StaticResource DeviceContainerStyle}" Background="#FF333333" SelectedItem="{Binding SelectedDeviceResult, ElementName=root, Mode=TwoWay}" > <ListBox.ItemsPanel> <ItemsPanelTemplate> <Grid> <Grid.LayoutTransform> <RotateTransform Angle="90" /> </Grid.LayoutTransform> </Grid> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox> </Grid> </Border>
解决方案
根据MSDN上的数据模板概述,应将DataTemplates用作ItemTemplate来定义数据的呈现方式,而将Style用作ItemContainerStyle来仅对生成的容器进行样式设置,例如ListBoxItem。
但是,我们似乎正在尝试使用后者来完成前者的工作。如果没有更多代码,我将无法重现情况,但我怀疑以容器样式进行数据绑定可能会在假定的视觉/逻辑树上投入一把扳手。
我也忍不住想,基于项目信息的项目自定义布局需要创建自定义的"面板"。对于自定义的"面板"来布局项目,可能比使用IValueConverters的Rube Goldberg布局进行布局更好。
绑定问题来自ListBoxItem的默认样式。默认情况下,将样式应用于元素时,WPF会查找默认样式,并应用默认样式中未在自定义样式中专门设置的每个属性。有关此行为的更多详细信息,请参阅Ian Griffiths撰写的精彩博客文章。
回到我们的问题。这是ListBoxItem的默认样式:
<Style xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" TargetType="{x:Type ListBoxItem}"> <Style.Resources> <ResourceDictionary/> </Style.Resources> <Setter Property="Panel.Background"> <Setter.Value> <SolidColorBrush> #00FFFFFF </SolidColorBrush> </Setter.Value> </Setter> <Setter Property="Control.HorizontalContentAlignment"> <Setter.Value> <Binding Path="HorizontalContentAlignment" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ItemsControl, AncestorLevel=1}"/> </Setter.Value> </Setter> <Setter Property="Control.VerticalContentAlignment"> <Setter.Value> <Binding Path="VerticalContentAlignment" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ItemsControl, AncestorLevel=1}"/> </Setter.Value> </Setter> <Setter Property="Control.Padding"> <Setter.Value> <Thickness> 2,0,0,0 </Thickness> </Setter.Value> </Setter> <Setter Property="Control.Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> ... </ControlTemplate> </Setter.Value> </Setter> </Style>
请注意,我删除了ControlTemplate以使其紧凑(我使用StyleSnooper检索了样式)。我们可以看到存在一个绑定,该绑定的相对源设置为祖先,类型为ItemsControl。因此,在情况下,绑定时创建的ListBoxItems找不到其ItemsControl。我们能否提供更多信息,以及ListBox的ItemsSource是什么?
附注:消除错误的一种方法是在自定义样式中为HorizontalContentAlignment和VerticalContentAlignment创建新的设置器。
这是ListBoxItem和其他临时容器* Item的常见问题。它们是异步/即时创建的,而ItemsControl是加载/渲染的。我们必须添加到ListBox.ItemContainerGenerator的
StatusChanged事件,并等待Status变为
ItemsGenerated`,然后才能尝试访问它们。
在ItemContainerStyle中将OverridesDefaultStyle设置为True也会解决这些问题。
<Style TargetType="ListBoxItem"> <Setter Property="OverridesDefaultStyle" Value="True"/> <!-- set the rest of your setters, including Template, here --> </Style>
如果我们想完全替换ListBoxItem
模板,使得看不到任何选择(也许我们想让ItemsControl
看起来具有ListBox
的分组/等特性),则可以使用以下样式:
<Style TargetType="ListBoxItem"> <Setter Property="Margin" Value="2" /> <Setter Property="FocusVisualStyle" Value="{x:Null}" /> <Setter Property="OverridesDefaultStyle" Value="True" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListBoxItem}"> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" HorizontalAlignment="Stretch" VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" /> </ControlTemplate> </Setter.Value> </Setter> </Style>
该模板还排除了标准的"边框"包装器。如果需要,可以使用以下模板替换模板:
<Border BorderThickness="{TemplateBinding Border.BorderThickness}" Padding="{TemplateBinding Control.Padding}" BorderBrush="{TemplateBinding Border.BorderBrush}" Background="{TemplateBinding Panel.Background}" SnapsToDevicePixels="True"> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" /> </Border>
如果不需要所有这些TemplateBinding
值,则可以删除一些以提高性能。