Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to propagate styles to Hyperlinks inside a DataTemplate?

I'm try to set the Foreground colour on a Hyperlink using a Style object in an ancestor's Resources, but it's not having any effect. I even used the BasedOn tip from Changing Hyperlink foreground without losing hover color, but it's not made any difference - I still get a blue hyperlink that is red on hover.

Here's the XAML for my controls, including an ItemsControl whose items are shown using a hyperlink:

<StackPanel Background="Red" TextElement.Foreground="White">
  <StackPanel.Resources>
    <Style TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
      <Setter Property="Foreground" Value="Yellow"/>
      <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <Setter Property="Foreground" Value="White"/>
        </Trigger>
      </Style.Triggers>
    </Style>
  </StackPanel.Resources>
  <TextBlock>Data import errors</TextBlock>
  <ItemsControl ItemsSource="{Binding Errors}"/>
</StackPanel>

And the items in the ItemsControl are picking up the following DataTemplate:

<DataTemplate DataType="{x:Type Importer:ConversionDetailsMessage}">
  <TextBlock>
    <Run Text="{Binding Message, Mode=OneTime}"/>
    <Hyperlink Command="Common:ImportDataCommands.ExplainConversionMessage" CommandParameter="{Binding}">
      <Run Text="{Binding HelpLink.Item2, Mode=OneTime}"/>
    </Hyperlink>
  </TextBlock>
</DataTemplate>

It's worth noting, too, that I don't want to just set the different colours directly on the Hyperlink in the DataTemplate. This is because the template will be used by a number of different ItemsControl objects, most of which will be on a white background and so can use the standard hyperlink colours. (Note that the one in the XAML above, however, has a red background.)

In short, I don't want the DataTemplate to have to know anything about the control in which it's being used. The styles for the template's controls should just filter down to it.

So... can anyone tell me why the style's not filtering down and what I can do to fix it?

Thanks.

Update:
Since I couldn't get Pavlo's answer to work in my production app, I've since tried it in a separate test app. The app is a WinForms app, with the main form containing nothing but an ElementHost, which itself contains a simple WPF usercontrol. Here's its XAML:

<UserControl x:Class="DataTemplateStyling.StylingView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:DataTemplateStyling="clr-namespace:DataTemplateStyling"
             x:Name="root" Loaded="StylingViewLoaded">

  <UserControl.Resources>
    <Style x:Key="MyDefaultHyperlinkStyle" BasedOn="{StaticResource {x:Type Hyperlink}}"/>

    <DataTemplate DataType="{x:Type DataTemplateStyling:ImportMessage}">
      <DataTemplate.Resources>
        <Style TargetType="{x:Type Hyperlink}"
               BasedOn="{StaticResource MyDefaultHyperlinkStyle}"/>
      </DataTemplate.Resources>
      <TextBlock>
        <Run Text="{Binding Message, Mode=OneTime}"/>
        <Hyperlink NavigateUri="{Binding HelpLink.Item1}">
          <Run Text="{Binding HelpLink.Item2, Mode=OneTime}"/>
        </Hyperlink>
      </TextBlock>
    </DataTemplate>
  </UserControl.Resources>

  <Grid DataContext="{Binding ElementName=root}">
    <StackPanel Background="Red" TextElement.Foreground="White">
      <StackPanel.Resources>
        <Style x:Key="MyDefaultHyperlinkStyle" TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
          <Setter Property="Foreground" Value="Yellow"/>
          <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
              <Setter Property="Foreground" Value="White"/>
            </Trigger>
          </Style.Triggers>
        </Style>
      </StackPanel.Resources>
      <TextBlock>Data import errors</TextBlock>
      <ItemsControl ItemsSource="{Binding Messages}"/>
    </StackPanel>
  </Grid>
</UserControl>

As it stands above, this generates an InvalidOperationException, stating "Can only base on a Style with target type that is base type 'IFrameworkInputElement'."

That can be fixed by putting TargetType="Hyperlink" on the Style definition immediately inside the UserControl.Resources element. However, while the messages are being shown, the link part of them still has the default blue hyperlink styling:

Blue hyperlinks persist

In short, it's not working, so I'd welcome any other suggestions/corrections. :(

Update 2:
Thanks to an alternative solution from Pavlo, it now is working. :)

like image 366
Mal Ross Avatar asked Mar 03 '11 14:03

Mal Ross


1 Answers

After some googling I ran into this post: http://www.11011.net/archives/000692.html.

As it described there, it turns out that elements that are not derived from Control (like TextBlock and Hyperlink) do not look for implicit styles outside the DataTemplate boundary.

Again, as the article says, the possible workaround is to specify the style key explicitly. In your case it might be something like this:

<StackPanel Background="Red" TextElement.Foreground="White">
  <StackPanel.Resources>
    <Style x:Key="MyDefaultHyperlinkStyle" TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
      <Setter Property="Foreground" Value="Yellow"/>
      <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <Setter Property="Foreground" Value="White"/>
        </Trigger>
      </Style.Triggers>
    </Style>
  </StackPanel.Resources>
  <TextBlock>Data import errors</TextBlock>
  <ItemsControl ItemsSource="{Binding Errors}"/>
</StackPanel>

Then, you can add an implicit style for Hyperlink that just references our named style in the DataTemplate resources:

<DataTemplate DataType="{x:Type Importer:ConversionDetailsMessage}">
  <DataTemplate.Resources>
    <Style TargetType="{x:Type Hyperlink}"
           BasedOn="{StaticResource MyDefaultHyperlinkStyle}"/>
  </DataTemplate.Resources>
  <TextBlock>
    <Run Text="{Binding Message, Mode=OneTime}"/>
    <Hyperlink Command="Common:ImportDataCommands.ExplainConversionMessage" CommandParameter="{Binding}">
      <Run Text="{Binding HelpLink.Item2, Mode=OneTime}"/>
    </Hyperlink>
  </TextBlock>
</DataTemplate>

And because the data template can be used in different places, there is a possibility that parent container doesn't define a style with key "MyDefaultHyperlinkStyle". In this case an exception will be thrown saying that resource "MyDefaultHyperlinkStyle" cannot be found. To address this, you can define a style with such key that will only inherit default style somewhere in App.xaml:

<Style x:Key="MyDefaultHyperlinkStyle"
       BasedOn="{StaticResource {x:Type Hyperlink}}/>

Update:

The code you included in your update will not work because of the nature of static resources, which means that the following resource reference in date template...

BasedOn="{StaticResource MyDefaultHyperlinkStyle}"

... will always point to the following resource (which is the default style):

<Style x:Key="MyDefaultHyperlinkStyle" BasedOn="{StaticResource {x:Type Hyperlink}}"/>

Static resource references are resolved at compile time, therefore the closest resource in the tree is used.

You might be tempted to use DynamicResource, but unfortunately it is not supported with the BasedOn property.

BUT, Foreground property supports dynamic resources, so we can use the same trick with brushes we use inside our style. Here is your test user control modified to use dynamic brushes:

<UserControl x:Class="DataTemplateStyling.StylingView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:DataTemplateStyling="clr-namespace:DataTemplateStyling"
             x:Name="root"
             Loaded="StylingViewLoaded">

    <UserControl.Resources>
        <SolidColorBrush x:Key="HyperlinkForeground"
                         Color="Blue" />

        <SolidColorBrush x:Key="HyperlinkHoverForeground"
                         Color="Gray" />

        <Style x:Key="MyDefaultHyperlinkStyle"
               TargetType="Hyperlink"
               BasedOn="{StaticResource {x:Type Hyperlink}}">
            <Setter Property="Foreground"
                    Value="{DynamicResource HyperlinkForeground}" />
            <Style.Triggers>
                <Trigger Property="IsMouseOver"
                         Value="True">
                    <Setter Property="Foreground"
                            Value="{DynamicResource HyperlinkHoverForeground}" />
                </Trigger>
            </Style.Triggers>
        </Style>

        <DataTemplate DataType="{x:Type DataTemplateStyling:ImportMessage}">
            <DataTemplate.Resources>
                <Style TargetType="{x:Type Hyperlink}"
                       BasedOn="{StaticResource MyDefaultHyperlinkStyle}" />
            </DataTemplate.Resources>
            <TextBlock>
                <Run Text="{Binding Message, Mode=OneTime}" />
                <Hyperlink NavigateUri="{Binding HelpLink.Item1}">
                    <Run Text="{Binding HelpLink.Item2, Mode=OneTime}" />
                </Hyperlink>
            </TextBlock>
        </DataTemplate>
    </UserControl.Resources>

    <Grid DataContext="{Binding ElementName=root}">
        <StackPanel Background="Red"
                    TextElement.Foreground="White">
            <StackPanel.Resources>
                <SolidColorBrush x:Key="HyperlinkForeground"
                                 Color="Yellow" />

                <SolidColorBrush x:Key="HyperlinkHoverForeground"
                                 Color="White" />
            </StackPanel.Resources>
            <TextBlock>Data import errors</TextBlock>
            <ItemsControl ItemsSource="{Binding Messages}" />
        </StackPanel>
    </Grid>
</UserControl>

It works as expected, i.e. all links inside StackPanel will be Yellow/White, while outside they will be blue.

Hope this helps.

like image 67
Pavlo Glazkov Avatar answered Nov 19 '22 03:11

Pavlo Glazkov