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

Posted

技术标签:

【中文标题】将 Grid 用作 ItemsPanelTemplate 的 ListBox 会产生奇怪的绑定错误【英文标题】:ListBox with Grid as ItemsPanelTemplate produces weird binding errors 【发布时间】:2010-09-14 16:40:02 【问题描述】:

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

【问题讨论】:

我在运行时过滤列表时遇到了同样的错误。 【参考方案1】:

根据 MSDN 上的 Data Templating Overview,DataTemplates 应该用作 ItemTemplate 来定义数据的呈现方式,而 Style 将用作 ItemContainerStyle 来设置生成容器的样式,如ListBoxItem

但是,您似乎正在尝试使用后者来完成前者的工作。如果没有更多代码,我无法重现您的情况,但我怀疑以容器样式进行数据绑定可能会在假定的视觉/逻辑树中造成麻烦。

我也忍不住认为,基于项目信息的项目自定义布局需要创建自定义Panel。使用自定义 Panel 来布置项目可能比使用 IValueConverters 的 Rube Goldberg 分类来布置项目更好。

【讨论】:

【参考方案2】:

绑定问题来自于 ListBoxItem 的默认样式。默认情况下,当将样式应用于元素时,WPF 会查找默认样式并应用默认样式中未在自定义样式中专门设置的每个属性。有关此行为的更多详细信息,请参阅 Ian Griffiths 的 this great blog post。

回到我们的问题。这是 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 的信息?

P.S.:消除错误的一种方法是在您的自定义样式中为 Horizo​​ntalContentAlignment 和 VerticalContentAlignment 创建新的设置器。

【讨论】:

+1 指向 Ian Griffith 帖子的指针。这是毫无疑问的,是我读过的关于如何设计元素的最佳描述之一。 另外,在我的自定义样式中有一个 Horizo​​ntalContentAlignment 的设置器,对我来说似乎没有什么不同(虽然这是一个 ComboBoxItem)。 +1 表示整个答案,应该标记为正确答案,对我来说非常有效 我尝试为我的 ListBoxItem 样式设置 Horizo​​ntalContentAlignment、VerticalContentAlignment、Horizo​​ntalAlignment 和 VerticalAlignment,但失败了。我认为这与 ListBoxItem 和 ControlTemplate 的滚动查看器之间的交互有关。【参考方案3】:

这是一个带有ListBoxItems 和其他临时*Item 容器的common problem。它们是异步/动态创建的,而 ItemsControl 是加载/渲染的。您必须附加到ListBox.ItemContainerGeneratorStatusChanged 事件并等待状态变为ItemsGenerated,然后再尝试访问它们。

【讨论】:

你能详细说明一下吗?我认为这是我的问题。我正在尝试通过样式将 IsSelected 属性绑定到我的 ListBoxItem,但由于此异常,当 ListBox.SelectionMode=Extended 时,抛出的组选择不会按预期切换。我将如何拦截 IsSelected Item 之间的通信并让它等待 StatusChanged 事件完成触发?【参考方案4】:

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

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

【讨论】:

是的,但这也导致我的项目无法正确呈现。确保如果你这样做,你有足够的样式指定你不需要任何默认的(见上面@ligaz的答案) @JTango 有帮助;我是为我的 CustromTreeViewItem 对象做的,我没有更多像描述的例外 这也解决了我遇到的一个问题。在我们的控件中,我们完全重新设置它们的样式,因此我们不希望使用默认样式。甚至不知道 Style 的这个属性。谢谢! 这会隐藏我的列表项。要解决此问题,请将BasedOn="StaticResource x:Type ListBoxItem" 添加到您的style,紧跟在TargetType="ListBoxItem" 之后。我使用的是自定义VirtualizingWrapPanel【参考方案5】:

如果您想完全替换 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 值,则可以删除一些以提高性能。

【讨论】:

【参考方案6】:

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

System.Windows.Data 错误:4: 无法通过参考找到绑定源 'RelativeSource FindAncestor,AncestorType='System.Windows.Controls.ItemsControl',AncestorLevel='1''。 BindingExpression:Path=Horizo​​ntalContentAlignment; 数据项=空;目标元素是'ListBoxItem'(名称=''); 目标属性是“Horizo​​ntalContentAlignment”(类型“Horizo​​ntalAlignment”)

这发生在进行这样的绑定时:

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

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

public IList<ListBoxItem> MyListProperty get; set;

经过一些实验,我发现只有当项目数超过我的 ListBox 的可见高度时才会触发错误(例如,当垂直滚动条出现时)。 所以我立即想到了虚拟化并尝试了这个:

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

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

【讨论】:

这也解决了我的问题。我猜在项目完全加载之前绑定会触发。在我看来,这是一个错误,因此我已将其提交给 MSDN(尽管我确定我不是第一个)并将其发布为一种解决方法。【参考方案7】:

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

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

参考:How to suppress the System.Windows.Data Error warning message

【讨论】:

在此发布博客 - weblogs.asp.net/akjoshi/archive/2011/11/30/…【参考方案8】:

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

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

来自...

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

【讨论】:

【参考方案9】:

简单地为类型“ComboBoxItem”创建一个默认样式是行不通的,因为它被 ComboBox 的默认“ItemContainerStyle”覆盖。要真正摆脱这种情况,您需要更改 ComboBoxes 的默认“ItemContainerStyle”,如下所示:

<Style TargetType="ComboBox">
    <Setter Property="ItemContainerStyle">
        <Setter.Value>                
            <Style TargetType="ComboBoxItem">
                <Setter Property="HorizontalContentAlignment" Value="Left" />
                <Setter Property="VerticalContentAlignment" Value="Center" />
            </Style>
        </Setter.Value>
    </Setter>
</Style>

【讨论】:

【参考方案10】:

这是此处其他答案的混合体,但对我来说,我必须在两个地方应用 Setter 来解决错误,尽管这是在使用自定义 VirtualizingWrapPanel

如果我删除以下任一 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,但它在某种程度上是“不同的”,使问题略有不同。在Style 下为Style 添加BasedOn="StaticResource x:Type ListBoxItem" &lt;Setter Property="OverridesDefaultStyle" Value="True"/&gt;ListView.ItemContainerStyle 也有帮助。 好提示,似乎有一些微妙之处需要理解。【参考方案11】:

我开始遇到这个问题,即使我的 ListBox 同时设置了 Style 和 ItemContainerStyle - 这些命名的样式已经定义了 Horizo​​ntalContentAlignment。我正在使用 CheckBox 控件打开/关闭我的 ListBox 上的实时过滤,这似乎导致项目从默认样式而不是我分配的样式中提取。大多数错误会在第一次启动实时过滤时发生,但此后它会继续在每次更改时抛出 2 个错误。我发现有趣的是,我的收藏中恰好有 2 条记录是空的,因此项目中没有任何内容可显示。所以这似乎有所贡献。我计划创建一个记录为空时显示的默认数据。

卡特的建议对我有用。添加一个没有键的单独“默认”样式和定义 Horizo​​ntalContentAlignment 属性的 TargetType="ListBoxItem" 解决了这个问题。我什至不需要为它设置 OverridesDefaultStyle 属性。

【讨论】:

【参考方案12】:

我和你有同样的问题,我只是想分享一下我的解决方案。 我已经尝试了这篇文章中的所有选项,但最后一个对我来说是最好的——谢谢克里斯。

所以我的代码:

<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 不存在时不会出现此错误。

【讨论】:

以上是关于将 Grid 用作 ItemsPanelTemplate 的 ListBox 会产生奇怪的绑定错误的主要内容,如果未能解决你的问题,请参考以下文章

调参-网格搜索(Grid Search)

Vue组件用作插槽的子项时未定义的属性

将多个复杂图组合为单个图形中的面板

Xaml技术:浅谈Grid.ColumnDefinitions和Grid.RowDefinitions属性

WPF 动态插入grid 将Grid中的一个空白布局,用同样大小的一个Grid插入,怎么实现

AG-GRID 无法将图标按钮添加到我的 ag-grid 中的 ag-grid 和多选复选框的每一行