Настройка стиля на основе существования типа предка

У меня есть 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

Другие вопросы по тегам