如何将样式传播到DataTemplate中的超链接?

7
我正在尝试使用祖先的资源中的样式对象在Hyperlink上设置Foreground颜色,但没有任何效果。我甚至使用了更改超链接前景色而不失去悬停颜色中的BasedOn提示,但没有任何区别 - 我仍然得到一个蓝色的超链接,在悬停时变成红色。
这是我的控件的XAML,包括一个ItemsControl,其项目使用超链接显示:
<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>

ItemsControl中的项目将使用以下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>

值得注意的是,我不希望直接在 DataTemplate 的 Hyperlink 上设置不同的颜色。这是因为该模板将被许多不同的 ItemsControl 对象使用,其中大部分将在白色背景上使用标准的超链接颜色(请注意,上面的 XAML 中有一个红色背景)。简而言之,我不希望 DataTemplate 知道它所使用的控件的任何信息。模板控件的样式应该只过滤到它。那么,有人能告诉我为什么样式没有过滤下来以及我该怎么做才能解决它吗?谢谢。更新:由于我无法使 Pavlo 的答案在我的生产应用程序中起作用,因此我后来在一个单独的测试应用程序中尝试了它。该应用程序是一个 WinForms 应用程序,主窗体除了包含一个 ElementHost 外,什么都没有,ElementHost 本身包含一个简单的 WPF 用户控件。以下是它的 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>

如上所述,这会生成一个InvalidOperationException,指出“只能基于目标类型为基本类型'IFrameworkInputElement'的样式”。
可以通过在UserControl.Resources元素内部的Style定义中立即放置TargetType =“Hyperlink”来解决此问题。然而,虽然消息正在显示,但它们的链接部分仍具有默认的蓝色超链接样式:
简而言之,它没有起作用,因此我希望得到其他建议/更正。 :(
更新2: 由于Pavlo提供了另一种解决方案,现在它确实起作用了。 :)
1个回答

8

在进行了一些谷歌搜索后,我发现了这篇文章:http://www.11011.net/archives/000692.html

正如它所描述的那样,事实证明没有从Control(例如TextBlockHyperlink)派生的元素不会在DataTemplate边界之外查找隐式样式。

同样,正如文章所说,可能的解决方法是显式指定样式关键字。 在您的情况下,可能是这样的:

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

接下来,您可以为 Hyperlink 添加一个隐式样式,只需在 DataTemplate 资源中引用我们的命名样式即可:

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

由于数据模板可以在不同的位置使用,因此父容器可能没有定义关键字为“MyDefaultHyperlinkStyle”的样式。在这种情况下,将抛出异常,说明找不到资源“MyDefaultHyperlinkStyle”。为解决此问题,您可以在App.xaml中的某个地方定义一个具有该关键字的样式,该样式仅继承默认样式:

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

更新:

您在更新中提供的代码将无法工作,因为静态资源的性质,这意味着在日期模板中以下资源引用...

BasedOn="{StaticResource MyDefaultHyperlinkStyle}"

...将始终指向以下资源(即默认样式):

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

静态资源引用在编译时解析,因此使用树中最接近的资源。

你可能会想使用DynamicResource,但不幸的是它不支持BasedOn属性。

但是,Foreground属性支持动态资源,因此我们可以使用与样式内部刷子相同的技巧。这里是修改后的测试用户控件,使用动态刷子:

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

它的工作效果符合预期,即StackPanel内的所有链接都将是黄色/白色,而在外部则是蓝色。

希望这可以帮助您。


@Pavlo:不,是我放进去的。 :-/ - Mal Ross
@Mal - 刚刚在 WinForms 项目中尝试了 ElementHost 内部。它完美地工作了。请检查您的代码。 - Pavlo Glazkov
嗨,Pavlo,我已经将你的代码复制粘贴到一个非常简单的测试应用程序中,但它仍然无法工作。是不是你自己的测试应用程序和上面的代码有什么区别?顺便说一下,看一下我原来问题的更新,以了解我正在使用的XAML。感谢你的帮助。 - Mal Ross
啊,真遗憾。我需要不同的“ItemsControl”具有不同的超链接样式,但仍然使用相同的“DataTemplate”。听起来像是我正在尝试做一些在框架代码中受限制的不可能的事情。我想我可以随时更改我的ViewModel类(上面的我的代码中的“ImportMessage”)以包含有关所需颜色的信息,然后只需绑定到相关属性即可。虽然不理想,但我想这很简单。感谢您所有的帮助-这很有启发性。 - Mal Ross
@Pavlo - 顺便说一句,如果你想修改你的答案并说我不能仅使用XAML实现我想要的功能(但是请保留其他部分的答案),我会将其标记为被接受的答案。 - Mal Ross
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接