ListBox使用Grid作为ItemsPanelTemplate会产生奇怪的绑定错误

37

我有一个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>


当我在运行时筛选列表时,我遇到了同样的错误。 - Ingó Vals
12个回答

32
绑定问题源于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的setter。

2
指向Ian Griffith帖子的+1。那是我读过的最好的元素如何被样式化的描述之一,毫无疑问。 - cplotts
5
在我的自定义样式中为HorizontalContentAlignment设置setter似乎对我没有影响(这是针对ComboBoxItem的)。 - cplotts
整个回答都应该加1分,并标记为正确答案,对我来说完美无缺。 - David
1
我尝试为我的ListBoxItem样式设置HorizontalContentAlignment、VerticalContentAlignment、HorizontalAlignment和VerticalAlignment,但失败了。我认为这与ListBoxItem和ControlTemplate的scrollviewer之间的交互有关。 - William

25

在您的ItemContainerStyle中将OverridesDefaultStyle设置为True也可以解决这些问题。

<Style TargetType="ListBoxItem">
    <Setter Property="OverridesDefaultStyle" Value="True"/>
    <!-- set the rest of your setters, including Template, here -->
</Style>

1
没错,但这也导致我的项目无法正确显示。确保如果你这样做,你有足够的指定样式,不需要从默认样式中获取任何东西(参见上面@ligaz的答案)。 - Drew Noakes
@JTango它有帮助;我对我的CustromTreeViewItem对象进行了操作,现在不再出现像描述的那样的异常了。 - Drake
这也解决了我遇到的问题。在我们的控件中,我们完全重新设计它们,所以我们根本不想使用默认样式。我甚至不知道 Style 的这个属性。谢谢! - scobi
1
这会隐藏我的列表项。要解决此问题,请在您的“样式”中添加BasedOn = "{StaticResource {x:Type ListBoxItem}}",紧接着TargetType = "ListBoxItem"。我正在使用自定义的VirtualizingWrapPanel - Meow Cat 2012
@MeowCat2012,你真是个天才,非常感谢。我遇到了完全相同的问题,它隐藏了我的项目,然后我发现了你的评论来解决它。 - Prasenjit Chatterjee

9
这是其他回答的综合,但对我来说,在使用自定义的VirtualizingWrapPanel时,我必须在两个地方应用Setter才能解决错误。
如果我删除下面任一一个Setter声明,则我的错误会重新出现。
        <ListView>
            <ListView.Resources>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment" Value="Left" />
                    <Setter Property="VerticalContentAlignment" Value="Top" />
                </Style>
            </ListView.Resources>
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment" Value="Left" />
                    <Setter Property="VerticalContentAlignment" Value="Top" />
                </Style>
            </ListView.ItemContainerStyle>
            <ListView.ItemsPanel>
                <ItemsPanelTemplate>
                    <controls:VirtualizingWrapPanel />
                </ItemsPanelTemplate>
            </ListView.ItemsPanel>
        </ListView>

目前我没有时间进一步调查,但我怀疑这与JTango在他的答案中提到的默认样式有关 - 我并没有非常定制我的模板。

我认为其他答案可能更有价值,但我想发帖是为了有机会帮助处于同样境地的人。

David Schmitt的答案看起来可能描述了根本原因。


我们本应该使用相同的VirtualizingWrapPanel,但它在某种程度上是“不同的”,这使问题稍有变化。为ListView.ItemContainerStyle添加BasedOn="{StaticResource {x:Type ListBoxItem}}"<Setter Property="OverridesDefaultStyle" Value="True"/>也会有所帮助。 - Meow Cat 2012
好的提示,看起来有一些微妙之处需要理解。 - Chris
谢谢,这对我们在Chem4Word中有效。 - Mike Williams

7

1
你能详细解释一下吗?我认为这是我的问题所在。我正在试图通过样式将IsSelected属性绑定到我的ListBoxItem上,但由于此异常抛出,当ListBox.SelectionMode=Extended时,组选择未按预期切换。我该如何拦截IsSelected项目之间的通信,并让它等待StatusChanged事件完成触发? - William

4
我和你一样也遇到了同样的问题,我想分享一下我的解决方案。 我尝试了这篇帖子中的所有选项,但最后一个对我来说是最好的 - 感谢 Chris。
所以我的代码:
<ListBox.Resources>
    <Style x:Key="listBoxItemStyle" TargetType="ListBoxItem">
        <Setter Property="HorizontalContentAlignment" Value="Center" />
        <Setter Property="VerticalContentAlignment" Value="Center" />
        <Setter Property="MinWidth" Value="24"/>
        <Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
    </Style>

    <Style TargetType="ListBoxItem" BasedOn="{StaticResource listBoxItemStyle}"/>
</ListBox.Resources>

<ListBox.ItemContainerStyle>
    <Binding Source="{StaticResource listBoxItemStyle}"/>
</ListBox.ItemContainerStyle>

<ListBox.ItemsPanel>
    <ItemsPanelTemplate>
        <WrapPanel Orientation="Horizontal" IsItemsHost="True" MaxWidth="170"/>
    </ItemsPanelTemplate>
</ListBox.ItemsPanel>

我还发现,当不存在自定义的ItemsPanelTemplate时,此错误不会出现。


3

这个对我有用。将它放在你的Application.xaml文件中。

<Application.Resources>
    <Style TargetType="ListBoxItem">
        <Setter Property="HorizontalContentAlignment" Value="Left" />
        <Setter Property="VerticalContentAlignment" Value="Center" />
    </Style>
</Application.Resources>

from...

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/42cd1554-de7a


2

我刚遇到了相同类型的错误:

System.Windows.Data Error: 4 : 找不到引用'相对源查找, 祖先类型='System.Windows.Controls.ItemsControl', 祖先级别='1''的绑定源。 BindingExpression:Path=HorizontalContentAlignment; DataItem=null; 目标元素为'ListBoxItem' (Name=''); 目标属性为'HorizontalContentAlignment' (类型为'HorizontalAlignment')

这是在执行以下绑定时发生的:

<ListBox ItemsSource="{Binding Path=MyListProperty}"  />

关于我的数据上下文对象的这个属性:

public IList<ListBoxItem> MyListProperty{ get; set;}

经过一些实验,我发现当项目数量超过 ListBox 的可见高度时(例如出现垂直滚动条),才会触发该错误。因此我立刻考虑了虚拟化并尝试了以下方法:

<ListBox ItemsSource="{Binding Path=MyListProperty}" VirtualizingStackPanel.IsVirtualizing="False" />

这对我解决了问题。虽然我更喜欢保持虚拟化开启状态,但我没有花费更多时间深入研究它。我的应用程序有点复杂,有多个级别的网格、停靠面板等,以及一些异步方法调用。我无法在一个更简单的应用程序中重现这个问题。


这也解决了我的问题。我猜绑定在项目完全加载之前就触发了。在我看来,这是一个错误,因此我已经向MSDN报告了它(尽管我肯定不是第一个),并发布了这个解决方法。 - William

1
根据 MSDN 上的 数据模板概述,应该使用 DataTemplates 作为 ItemTemplate 来定义数据的呈现方式,而 Style 则应该用作 ItemContainerStyle 来仅对生成的容器(如 ListBoxItem)进行样式设置。
然而,看起来你正在尝试使用后者来完成前者的工作。我无法在没有更多代码的情况下重现你的情况,但我怀疑在容器样式中进行数据绑定可能会对假定的视觉/逻辑树造成影响。
我还不禁想到,基于项目信息创建自定义布局需要创建自定义 Panel。让自定义 Panel 布置项目比让项目使用一堆 IValueConverters 自行布置要好得多。

1
如果你想完全替换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 包装器。如果需要,您可以使用以下模板进行替换:

<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值,那么可以删除一些以提高性能。


1

另一个对我有效的解决方法/变通方法是通过在类或顶级窗口的构造函数中将数据绑定源开关级别设置为关键来抑制这些错误(实际上,更适当地称它们为警告) -

#if DEBUG     
    System.Diagnostics.PresentationTraceSources.DataBindingSource.Switch.Level =
        System.Diagnostics.SourceLevels.Critical;
#endif

参考:如何抑制System.Windows.Data错误警告消息


1
在这里发布了关于此的博客 - http://weblogs.asp.net/akjoshi/archive/2011/11/30/resolving-un-harmful-binding-errors-in-wpf.aspx - akjoshi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接