Setting style based on existence of an ancestor ty

2019-02-23 05:39发布

I have 2 sets of textblocks some of them are in an itemcontrol and some of them are not, I want to make an style (just based on type) which sets the background of the textblock if its ancestor is an ItemControl.
I can do it by the following code but the problem is that on the log (and output window) a data biding error message will be displayed because of the textblocks which do not have Itemcontrol as ancestore. Is there any 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')

4条回答
唯我独甜
2楼-- · 2019-02-23 06:21

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

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;
    }
}
查看更多
劫难
3楼-- · 2019-02-23 06:24

You can work with FallbackValue or TargetNullValue

Check this link out:

http://dontcodetired.com/blog/post/FallbackValue-TargetNullValue-StringFormat-in-Silverlight-4.aspx

查看更多
做自己的国王
4楼-- · 2019-02-23 06:31

Use DataTemplate for the items in 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>

Keep the style that you have if you need it for other setters, just remove the trigger.

查看更多
相关推荐>>
5楼-- · 2019-02-23 06:35

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();
    }
}
查看更多
登录 后发表回答