ListBox with Grid as ItemsPanelTemplate produces weird binding errors

25,573

Solution 1

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.

Solution 2

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>

Solution 3

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.

Solution 4

This is a common problem with ListBoxItems 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.

Solution 5

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.

Share:
25,573
Tushee
Author by

Tushee

Full-stack Software Architect and Developer in C#, .NET, Javascript, Typescript, AngularJS, Angular .NET, JADE, related web technologies. SOreadytohelp

Updated on December 18, 2020

Comments

  • Tushee
    Tushee over 3 years

    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>
    

  • Drew Noakes
    Drew Noakes almost 15 years
    True, but it also caused my item to not present itself properly. Make sure that if you do this, you have enough of the style specified that you don't need anything from the default (see @ligaz's answer above)
  • paranoidduck
    paranoidduck about 14 years
    @JTango it helps; I did it for my CustromTreeViewItem object and I have no more exceptions like those described
  • cplotts
    cplotts almost 14 years
    +1 for the pointer to Ian Griffith's post. That is hands-down, one of the best descriptions on how elements get styled ... that I've ever read.
  • cplotts
    cplotts almost 14 years
    Also, having a setter for HorizontalContentAlignment in my custom Style, did NOT seem to make a difference for me (this is for a ComboBoxItem though).
  • David
    David over 13 years
    +1 for the whole answer, should be marked as the right answer, worked perfectly for me
  • Esben von Buchwald
    Esben von Buchwald about 13 years
    This solved an issue I had as well. In our controls we are completely re-styling them so we didn't want the default style used anyway. Didn't even know about this property of Style. Thank you!
  • akjoshi
    akjoshi over 12 years
  • William
    William over 11 years
    I tried setting HorizontalContentAlignment, VerticalContentAlignment, HorizontalAlignment, and VerticalAlignment for my ListBoxItem style and it failed. I think it has to do with the interaction between the ListBoxItem and the ControlTemplate's scrollviewer.
  • William
    William over 11 years
    Can you elaborate on this? I think this is my issue. I'm trying to bind IsSelected property to my ListBoxItem via styling, but because of this exception thrown group selections are not toggled as expected when ListBox.SelectionMode=Extended. How would I go about intercepting the communication between the IsSelected Item and have it wait for the StatusChanged event to finish firing?
  • William
    William over 11 years
    This solves my problem too. I guess the bindings fire before the item is completly loaded. In my opinion, this is a bug, and as such I have filed it with MSDN (though I'm sure I'm not the first) and posted this as a workaround.
  • Meow Cat 2012
    Meow Cat 2012 over 4 years
    This hides my list items. To solve, add BasedOn="{StaticResource {x:Type ListBoxItem}}" to your style, right after TargetType="ListBoxItem". I was using a custom VirtualizingWrapPanel.
  • Meow Cat 2012
    Meow Cat 2012 over 4 years
    We should have been using the same VirtualizingWrapPanel and it is "different" somehow and makes the problem slightly variated. Adding BasedOn="{StaticResource {x:Type ListBoxItem}}" for Style and <Setter Property="OverridesDefaultStyle" Value="True"/> under Style, of ListView.ItemContainerStyle also helps.
  • Chris
    Chris over 4 years
    Good tip, seems like there are some subtleties to understand.
  • Mike Williams
    Mike Williams over 2 years
    Thanks this worked for us at Chem4Word