ListBox с Grid as ItemsPanelTemplate вызывает странные ошибки привязки
У меня есть элемент управления ListBox, и я представляю фиксированное количество объектов ListBoxItem в сетке. Поэтому я установил мой ItemsPanelTemplate в качестве сетки.
Я обращаюсь к Grid из кода позади, чтобы настроить RowDefinitions и ColumnDefinitions.
Пока все работает так, как я ожидаю. У меня есть несколько пользовательских реализаций IValueConverter для возврата Grid.Row и Grid.Column, в которых должен отображаться каждый ListBoxItem.
Однако иногда я получаю странные ошибки связывания и не могу точно понять, почему они происходят, или даже если они есть в моем коде.
Вот ошибка, которую я получаю:
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>
12 ответов
Проблема привязки происходит от стиля по умолчанию для ListBoxItem. По умолчанию при применении стилей к элементам WPF ищет стили по умолчанию и применяет каждое свойство, которое специально не установлено в пользовательском стиле, из стиля по умолчанию. Обратитесь к этому великому сообщению в блоге Иана Гриффитса для более подробной информации об этом поведении.
Вернемся к нашей проблеме. Вот стиль по умолчанию для 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. Можете ли вы предоставить больше информации о том, что представляет собой ItemsSource для вашего ListBox?
PS: Одним из способов устранения ошибок является создание новых сеттеров для HorizontalContentAlignment и VerticalContentAlignment в вашем пользовательском стиле.
Настройка OverridesDefaultStyle
в True
в вашем ItemContainerStyle
также исправит эти проблемы.
<Style TargetType="ListBoxItem">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<!-- set the rest of your setters, including Template, here -->
</Style>
Это смесь других ответов здесь, но я должен был применить 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 упоминает в своем ответе - я не очень настраиваю свой шаблон в значительной степени.
Я думаю, что из других ответов будет больше пробега, но я решил опубликовать это на случай, если это поможет кому-то в той же лодке.
Ответ Дэвида Шмитта выглядит так, как будто он описывает основную причину.
Это общая проблема с ListBoxItem
с и другие эфемерные *Item
контейнеры. Они создаются асинхронно / на лету, в то время как ItemsControl
загружается / отображается Вы должны приложить к ListBox.ItemContainerGenerator
"s StatusChanged
событие и ждать, пока статус станет ItemsGenerated
прежде чем пытаться получить к ним доступ.
У меня была та же проблема, что и у вас, и я просто хотел поделиться своим решением. Я перепробовал все варианты из этого поста, но последний был лучшим для меня - спасибо Крис.
Итак, мой код:
<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
не существует
Это сработало для меня. Поместите это в свой файл 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
Я только что столкнулся с такой же ошибкой:
Ошибка System.Windows.Data: 4: Не удается найти источник для привязки со ссылкой 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=HorizontalContentAlignment; DataItem= NULL; целевым элементом является ListBoxItem (Name=''); Свойство target - "HorizontalContentAlignment" (тип "HorizontalAlignment")
Это произошло во время выполнения привязки:
<ListBox ItemsSource="{Binding Path=MyListProperty}" />
Для этого свойства в моем объекте контекста данных:
public IList<ListBoxItem> MyListProperty{ get; set;}
После некоторых экспериментов я обнаружил, что ошибка возникает только тогда, когда количество элементов превышает видимую высоту моего ListBox (например, когда появляются вертикальные полосы прокрутки). Поэтому я сразу подумал о виртуализации и попробовал это:
<ListBox ItemsSource="{Binding Path=MyListProperty}" VirtualizingStackPanel.IsVirtualizing="False" />
Это решило проблему для меня. Хотя я бы предпочел оставить виртуализацию включенной, я больше не использовал время, чтобы погрузиться в нее. Мое приложение немного сложное, с несколькими уровнями сетки, док-панелями и т. Д. И некоторыми асинхронными вызовами методов. Я не смог воспроизвести проблему в более простом приложении.
Другой обходной путь / решение, которое работало для меня, состояло в том, чтобы подавить эти ошибки (фактически, кажется, более уместно называть их предупреждениями), устанавливая уровень переключателя источника привязки данных как критический в конструкторе класса или окне верхнего уровня -
#if DEBUG
System.Diagnostics.PresentationTraceSources.DataBindingSource.Switch.Level =
System.Diagnostics.SourceLevels.Critical;
#endif
Ссылка: Как подавить предупреждение об ошибке System.Windows.Data Error.
Согласно Обзору шаблонов данных на MSDN, DataTemplates
следует использовать как ItemTemplate
определить, как данные представлены, а Style
будет использоваться в качестве ItemContainerStyle
стилизовать только сгенерированный контейнер, такой как ListBoxItem
,
Тем не менее, похоже, что вы пытаетесь использовать последний, чтобы сделать работу первого. Я не могу воссоздать вашу ситуацию без гораздо большего количества кода, но я подозреваю, что выполнение привязки данных в стиле контейнера может привести к появлению гаечного ключа в предполагаемом визуальном / логическом дереве.
Я также не могу не думать, что пользовательский макет элементов на основе информации об элементе требует создания собственного Panel
, Это, вероятно, лучше для обычая Panel
чтобы расположить предметы, чем для предметов, чтобы выложить себя с ассортиментом Рубе Голдберг IValueConverters
,
Если вы хотите полностью заменить ListBoxItem
шаблон такой, что выбор не виден (возможно, вы хотите, чтобы внешний вид ItemsControl
с поведением группировки / etc 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
значения, то вы можете удалить некоторые для производительности.
Я начал сталкиваться с этой проблемой, даже несмотря на то, что в моем ListBox был установлен и Style, и ItemContainerStyle - и эти именованные стили уже определили HorizontalContentAlignment. Я использовал элементы управления CheckBox, чтобы включить / отключить фильтрацию в реальном времени на моем ListBox, и это, казалось, заставляло элементы тянуться вместо стиля по умолчанию вместо моих назначенных стилей. Большинство ошибок происходит в первый раз, когда включается фильтрация в реальном времени, но после этого она будет продолжать генерировать 2 ошибки при каждом изменении. Интересно, что в моей коллекции ровно 2 записи были пустыми, и поэтому в них ничего не отображалось. Так что это, кажется, продолжалось. Я планирую создать данные по умолчанию, которые будут отображаться, когда запись пуста.
Предложение Картера сработало для меня. Добавление отдельного стиля "по умолчанию" без ключа и TargetType="ListBoxItem", который определил свойство HorizontalContentAlignment, решило проблему. Мне даже не нужно было устанавливать для него свойство OverridesDefaultStyle.
Простое создание стиля по умолчанию для типа "ComboBoxItem" не работает, поскольку оно перезаписывается стандартным ComboBox "ItemContainerStyle". Чтобы по-настоящему избавиться от этого, вам нужно изменить "ItemContainerStyle" по умолчанию для ComboBoxes, например так:
<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>