8

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

Stu
  • 30,392
  • 6
  • 14
  • 33
Fred Jand
  • 699
  • 7
  • 25

4 Answers4

4

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;
    }
}
Toni
  • 1,555
  • 4
  • 15
  • 23
Fred Jand
  • 699
  • 7
  • 25
3

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();
    }
}
makc
  • 2,569
  • 1
  • 18
  • 28
  • Thanks, this gave me the idea to find the solution. By the way, ItemsControl shall be find by checking the Logical Tree. – Fred Jand Oct 29 '13 at 18:28
1

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.

XAMeLi
  • 6,189
  • 2
  • 22
  • 29
  • Thanks but this is not what I want. I simplified the problem to be able to explain it. In my case it is a control and the style is default style and shall be set in “Generic.xaml” and it will be pass to the user of the control in a package. So if user puts this control in an ItemControl then control shall have different style rather than placing it outside of an ItemControl. So I do not have access to the code that uses this control, and the user of the control shall not be responsible to applying default style. – Fred Jand Oct 29 '13 at 15:49
0

You can work with FallbackValue or TargetNullValue

Check this link out:

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

dev hedgehog
  • 8,698
  • 3
  • 28
  • 55
  • It can be another way to set the value but in this case You will still get the error in the output because the null value is not the main issue the issue is that ancestor does not exist, I want to avoid the error there. – Fred Jand Oct 28 '13 at 22:45
  • I havent tested out if the error is always gonna be thrown but according to msdn it shouldnt be the case. FallbackValue means the value that will be used when none found. So I dont think an error is been thrown when on no found value the FallbackValue is gonna be used. – dev hedgehog Oct 29 '13 at 06:51