Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 819
Fred Jand Avatar asked Oct 28 '13 21:10

Fred Jand


2 Answers

I think @Xameli solution is what you are actually looking for...
but if you simply must do it in a style then you can achieve it using VisualTreeHelper like that:

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

the converter:

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();
    }
}
like image 117
makc Avatar answered Oct 26 '22 12:10

makc


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;
    }
}
like image 23
Fred Jand Avatar answered Oct 26 '22 13:10

Fred Jand