I've got a ListBox control and I'm presenting a fixed number of ListBoxItem objects in a grid layout. So I've set my ItemsPanelTemplate to be a Grid.
I'm accessing the Grid from code behind to configure the RowDefinitions and ColumnDefinitions.
So far it's all working as I expect. I've got some custom IValueConverter implementations for returning the Grid.Row and Grid.Column that each ListBoxItem should appear in.
However I get weird binding errors sometimes, and I can't figure out exactly why they're happening, or even if they're in my code.
Here's the error I get:
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')
Can anybody explain what's going on?
Oh, and, here's my 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>
The binding problem comes from the default style for ListBoxItem. By default when applying styles to elements WPF looks for the default styles and applies each property that is not specifically set in the custom style from the default style. Refer to this great blog post By Ian Griffiths for more details on this behavior.
Back to our problem. Here is the default style for 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>
Note that I have removed the ControlTemplate to make it compact (I have used StyleSnooper - to retrieve the style). You can see that there is a binding with a relative source set to ancestor with type ItemsControl. So in your case the ListBoxItems that are created when binding did not find their ItemsControl. Can you provide more info with what is the ItemsSource for your ListBox?
P.S.: One way to remove the errors is to create new setters for HorizontalContentAlignment and VerticalContentAlignment in your custom Style.
Setting OverridesDefaultStyle
to True
in your ItemContainerStyle
will also fix these problems.
<Style TargetType="ListBoxItem">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<!-- set the rest of your setters, including Template, here -->
</Style>
This is a common problem with ListBoxItem
s and other ephemeral *Item
containers. They are created asynchronously/on the fly, while the ItemsControl
is loaded/rendered. You have to attach to ListBox.ItemContainerGenerator
's StatusChanged
event and wait for the Status to become ItemsGenerated
before trying to access them.
This is an amalgam of the other answers here, but for me, I had to apply the Setter
in two places to solve the error, although this was when using a custom VirtualizingWrapPanel
If I remove either one of the below Setter
declarations, my errors reappear.
<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>
I don't really have the time to investigate further at the moment, but I suspect it's related to the default style that JTango mentions in his answer - I'm not really customising my template to a huge degree.
I think there's more mileage to be had out of the other answers, but I thought I'd post this on the off chance it helps someone in the same boat.
David Schmitt's answer looks like it might describe the root cause.
I had the same problem as you and I just wanted to share what was my solution. I have tried all options from this post but the last one was the best for me - thx Chris.
So my code:
<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>
I have also discovered that this bug do not appear when custom ItemsPanelTemplate
do not exists.
I just encountered the same type of error:
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')
This happened while doing a binding like this:
<ListBox ItemsSource="{Binding Path=MyListProperty}" />
To this property on my data context object:
public IList<ListBoxItem> MyListProperty{ get; set;}
After some experimenting I discovered that the error was only triggered when the number of items exceeded the visible height of my ListBox (e.g. when vertical scrollbars appear). So I immediately thought about virtualization and tried this:
<ListBox ItemsSource="{Binding Path=MyListProperty}" VirtualizingStackPanel.IsVirtualizing="False" />
This solved the problem for me. Although I would prefer to keep virtualization turned on I did not use any more time to dive into it. My application is a bit on the complex side with mulitiple levels of grids, dock panels etc. and some asynch method calls. I was not able to reproduce the problem in a simpler application.
Another workaround/solution that worked for me was to suppress these errors (actually, it seems more appropriate to call them warnings) by setting the data binding source switch level as critical in constructor of the class or a top level window -
#if DEBUG
System.Diagnostics.PresentationTraceSources.DataBindingSource.Switch.Level =
System.Diagnostics.SourceLevels.Critical;
#endif
Ref.: How to suppress the System.Windows.Data Error warning message
According to the Data Templating Overview on MSDN, DataTemplates
should be used as the ItemTemplate
to define how the data is presented, while a Style
would be used as the ItemContainerStyle
to style just the generated container, such as ListBoxItem
.
However, it appears that you are trying to use the latter to do the job of the former. I can't recreate your situation without much more code, but I suspect that doing databinding in the container style could be throwing a wrench in the assumed visual/logical tree.
I also can't help but think that a custom layout of items based on the item's information calls for creating a custom Panel
. It's probably better for the custom Panel
to layout the items than for the items to lay themselves out with a Rube Goldberg assortment of IValueConverters
.
If you want to completely replace the ListBoxItem
template such that no selection is visible (perhaps you want the look of ItemsControl
with the grouping/etc behaviour of ListBox
) then you can use this style:
<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>
This template also excludes the standard Border
wrapper. If you need that, you can use replace the template with this:
<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>
If you don't need all these TemplateBinding
values then you can remove some for performance.
This worked for me. Put this in your Application.xaml file.
<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
Simply creating a default style for the type "ComboBoxItem" doesn't work, because it it overwritten by the ComboBox's default "ItemContainerStyle". To really get rid of this, you need to change the default "ItemContainerStyle" for ComboBoxes, like this:
<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>
I started running into this problem, even though my ListBox had both a Style and an ItemContainerStyle set - and these named styles had already defined HorizontalContentAlignment. I was using CheckBox controls to turn on/off live filtering on my ListBox and this seemed to be causing the items to pull instead from the default style instead of my assigned styles. Most errors would occur the first time the live filtering kicked in, but thereafter it would continue to throw 2 errors on each change. I find it interesting that exactly 2 records in my collection were empty and thus had nothing to display in the item. So this seems to have contibuted. I plan to create default data to be displayed when a record is empty.
Carter's suggestion worked for me. Adding a separate "default" style with no key and a TargetType="ListBoxItem" that defined the HorizontalContentAlignment property solved the problem. I didn't even need to set the OverridesDefaultStyle property for it.
来源:https://stackoverflow.com/questions/160391/listbox-with-grid-as-itemspaneltemplate-produces-weird-binding-errors