Search code examples
wpfxamlfindancestor

Setting style based on existence of an ancestor type


I have 2 sets of TextBlocks some of them are in an ItemControl and some of them are not.

I want to make a style (just based on type) which sets the background of the TextBlock if its ancestor is an ItemControl.

I can do it using the following code but my problem is that on the log (and output window) a data biding error message is displayed because of the TextBlocks which do not have ItemControl as their ancestor.

Is there a better way to do this task and avoid this error message?

<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();
    }
}

Error message:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'NoTarget' (type 'Object')


Solution

  • According to @makc's response I solved the problem this way:

    <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>  
    

    Converter:

    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();
        }
    }
    

    Helper class for finding the parent of specific type:
    Note: This helper find any kind of parent in logical or visual tree. for example in my case ItemsControl is a parent in the logical tree, and it can be a grandparent.

    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;
        }
    }