Настройка стиля на основе существования типа предка
У меня есть 2 набора текстовых блоков, некоторые из которых находятся в элементе управления контентом, а некоторые нет, я хочу создать стиль (только на основе типа), который устанавливает фон текстового блока, если его предком является ItemControl.
Я могу сделать это с помощью следующего кода, но проблема в том, что в журнале (и окне вывода) будет отображаться сообщение об ошибке привязки данных из-за текстовых блоков, которые не имеют Itemcontrol в качестве ancestore. Есть ли лучший способ сделать эту задачу и избежать этого сообщения об ошибке?
<Grid>
<Grid.Resources>
<local:HasAncestorConverter x:Key="HasAncestorConverter" />
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}, Converter={StaticResource HasAncestorConverter}}" Value="True">
<Setter Property="Background"
Value="{Binding Tag,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<StackPanel>
<TextBlock Text="Out of ItemControl" />
<ItemsControl Tag="Blue" >
<TextBlock Text="Inside of ItemControl" />
</ItemsControl>
</StackPanel>
</Grid>
Convertor:
class HasAncestorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Сообщение об ошибке:
Ошибка System.Windows.Data: 4: Не удается найти источник для привязки со ссылкой 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=; DataItem= NULL; целевым элементом является TextBlock (Name=''); Свойство target - "NoTarget" (тип "Объект").
4 ответа
Согласно ответу @makc, я решил проблему следующим образом:
<Grid>
<Grid.Resources>
<local:HasAncestorConverter x:Key="HasAncestorConverter" />
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource HasAncestorConverter},
ConverterParameter={x:Type ItemsControl}}"
Value="True">
<Setter Property="Background"
Value="{Binding Tag, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<StackPanel>
<TextBlock Text="Out of ItemsControl" />
<ItemsControl Tag="Blue">
<TextBlock Text="Inside of ItemsControl" />
</ItemsControl>
</StackPanel>
</Grid>
Преобразователь:
class HasAncestorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter
, System.Globalization.CultureInfo culture)
{
object parent = null;
if (value != null && parameter != null &&
parameter is Type && value is DependencyObject)
{
var control = value as DependencyObject;
Type t = parameter as Type;
parent = ParentFinder.FindParent(control, t);
}
return parent != null;
}
public object ConvertBack(object value, Type targetType, object parameter
, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Вспомогательный класс для поиска родителя определенного типа:
Примечание. Этот помощник находит любого родителя в логическом или визуальном дереве. например, в моем случае ItemsControl является родителем в логическом дереве и может быть дедушкой.
class ParentFinder
{
public static object FindParent(DependencyObject child, Type parentType)
{
object parent = null;
var logicalParent = LogicalTreeHelper.GetParent(child);
var visualParent = VisualTreeHelper.GetParent(child);
if (!(logicalParent == null && visualParent == null))
{
if (logicalParent != null && logicalParent.GetType() == parentType)
parent = logicalParent;
else if (visualParent != null && visualParent.GetType() == parentType)
parent = visualParent;
else
{
if (visualParent != null)
parent = FindParent(visualParent, parentType);
if (parent == null && logicalParent != null)
parent = FindParent(logicalParent, parentType);
}
}
return parent;
}
}
Я думаю, что решение @Xameli - это то, что вы на самом деле ищете...
но если вы просто должны сделать это в стиле, то вы можете достичь этого с помощью VisualTreeHelper
как это:
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}, Converter={StaticResource HasAncestorConverter}}" Value="True">
<Setter Property="Background"
Value="{Binding Tag,RelativeSource={RelativeSource Self}}" />
</DataTrigger>
</Style.Triggers>
конвертер:
class HasAncestorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//you probably will have to look a few levels up
var parent = VisualTreeHelper.GetParent(value) as ItemsControl;
return item != null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Использование DataTemplate
для предметов в ItemsControl
,
<ItemsControl ....
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding }"
Background="{Binding Tag,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<sys:String>Inside of ItemControl</String>
</ItemsControl>
Сохраните стиль, который у вас есть, если он вам нужен для других сеттеров, просто удалите триггер.
Вы можете работать с FallbackValue или TargetNullValue
Проверьте эту ссылку:
http://dontcodetired.com/blog/post/FallbackValue-TargetNullValue-StringFormat-in-Silverlight-4.aspx