wpf WPF中的下划线标签,使用样式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1484072/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 21:02:24  来源:igfitidea点击:

Underline Label in WPF, using styles

wpfxamlstyleslabel

提问by Boris

I have the following style:

我有以下风格:

<Style x:Key="ActionLabelStyle" TargetType="{x:Type Label}">
    <Setter Property="Margin" Value="10,3" />
    <Setter Property="Padding" Value="0" />
    <Setter Property="TextBlock.TextWrapping" Value="Wrap" />
    <Setter Property="FontFamily" Value="Calibri" />
    <Style.Triggers>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsMouseOver" Value="True" />
                <Condition Property="IsEnabled" Value="True" />
            </MultiTrigger.Conditions>
            <Setter Property="Background" Value="Red" />
            <Setter Property="TextBlock.TextDecorations" Value="Underline" />
        </MultiTrigger>
    </Style.Triggers>
</Style>

So basically, I want to have a label which is underlined when it is enabled and the mouse cursor is over it. The part of this style which is not working is the <Setter Property="TextBlock.TextDecorations" Value="Underline" />. Now, what am I doing wrong here? Thanks for all the help.

所以基本上,我想要一个标签,当它被启用并且鼠标光标在它上面时它带有下划线。这种样式中不起作用的部分是<Setter Property="TextBlock.TextDecorations" Value="Underline" />. 现在,我在这里做错了什么?感谢所有的帮助。

回答by Jerry Bullard

This is actually much more difficult than it appears. In WPF, a Label is not a TextBlock. It derives from ContentControl and can therefore host other, non-text controls in its Content collection.

这实际上比看起来要困难得多。在 WPF 中,标签不是文本块。它派生自 ContentControl,因此可以在其 Content 集合中承载其他非文本控件。

However, you can specify a string as the content as in the example below. Internally, a TextBlock will be constructed to host the text for you.

但是,您可以指定一个字符串作为内容,如下例所示。在内部,将构造一个 TextBlock 来为您托管文本。

<Label Content="Test!"/>

This internally translates to:

这在内部转换为:

    <Label>
        <Label.Content>
            <TextBlock>
                Test!
            </TextBlock>
        </Label.Content>
    </Label>

The simple solution to this would be for the TextDecorations property of a TextBlock to be an attached property. For example, FontSize is designed this way, so the following works:

对此的简单解决方案是将 TextBlock 的 TextDecorations 属性作为附加属性。例如, FontSize 就是这样设计的,所以下面的工作:

    <Label TextBlock.FontSize="24">
        <Label.Content>
            <TextBlock>
                Test!
            </TextBlock>
        </Label.Content>
    </Label>

The TextBlock.FontSize attached property can be applied anywhere in the visual tree and will override the default value for that property on any TextBlock descendant in the tree. However, the TextDecorations property is not designed this way.

TextBlock.FontSize 附加属性可以应用于可视化树中的任何位置,并将覆盖树中任何 TextBlock 后代上该属性的默认值。但是,TextDecorations 属性不是这样设计的。

This leaves you with at least a few options.

这让您至少有几个选择。

  1. Use color, border, cursor, etc., instead of underlined text because this is 100% easier to implement.
  2. Change the way you are doing this to apply the Style to the TextBlock instead.
  3. Go to the trouble to create your own attached propertyand the control template to respect it.
  4. Do something like the following to nest the style for TextBlocks that appear as children of your style:
  1. 使用颜色、边框、光标等,而不是带下划线的文本,因为这 100% 更容易实现。
  2. 更改您执行此操作的方式以将样式应用于 TextBlock。
  3. 麻烦去创建自己的附加属性和控件模板来尊重它。
  4. 执行以下操作以嵌套显示为您样式的子项的 TextBlock 的样式:

FYI, this is the ugliest thing I've done in WPF so far, but it works!

仅供参考,这是迄今为止我在 WPF 中所做的最丑陋的事情,但它有效!

    <Style x:Key="ActionLabelStyle" TargetType="{x:Type Label}">
        <Setter Property="Margin" Value="10,3" />
        <Setter Property="Padding" Value="0" />
        <Setter Property="TextBlock.TextWrapping" Value="Wrap" />
        <Setter Property="FontFamily" Value="Calibri" />
        <Style.Triggers>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsMouseOver" Value="True" />
                    <Condition Property="IsEnabled" Value="True" />
                </MultiTrigger.Conditions>
                <Setter Property="Background" Value="Red" />
            </MultiTrigger>
        </Style.Triggers>
        <Style.Resources>
            <Style TargetType="TextBlock">
                <Style.Triggers>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Label}, Path=IsMouseOver}" Value="True" />
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsEnabled}" Value="True" />
                        </MultiDataTrigger.Conditions>
                        <Setter Property="TextDecorations" Value="Underline"/>
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
        </Style.Resources>
    </Style>

This works because it is overriding the default style of any TextBlock beneath a Label of this style. It then uses a MultiDataTrigger to allow relative binding back up to the Label to check if its IsMouseOver property is True. Yuck.

这是有效的,因为它覆盖了此样式标签下的任何 TextBlock 的默认样式。然后它使用 MultiDataTrigger 允许相对绑定返回到 Label 以检查其 IsMouseOver 属性是否为 True。哎呀。

Edit:

编辑:

Note that this only works if you explicitly create the TextBlock. I was incorrect when I posted this because I had already dirtied up my test Label. Boo. Thanks, Anvaka, for pointing this out.

请注意,这仅在您明确创建 TextBlock 时才有效。当我发布这个时我是不正确的,因为我已经弄脏了我的测试标签。嘘。感谢 Anvaka 指出这一点。

    <Label Style="{StaticResource ActionLabelStyle}">
        <TextBlock>Test!</TextBlock>
    </Label>

This works, but if you have to go to this trouble, you're just working too hard. Either someone will post something more clever, or as you said, my option 1 is looking pretty good right now.

这行得通,但如果你不得不遇到这个麻烦,那你就是工作太努力了。要么有人会发布更聪明的东西,要么就像你说的,我的选项 1 现在看起来不错。

回答by Reddog

Further to Jerry's answer, in order to avoid having to add the TextBlock into the template each time you can let the style do this too by adding the Setter property into the style:

进一步 Jerry 的回答,为了避免每次都必须将 TextBlock 添加到模板中,您可以通过将 Setter 属性添加到样式中来让样式执行此操作:

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type Label}">
            <TextBlock>
                <ContentPresenter />
            </TextBlock>
        </ControlTemplate>
    </Setter.Value>
</Setter>

Then your Label is back to:

然后你的标签又回到了:

<Label Content="Test!" Style="{StaticResource ActionLabelStyle}" />

Thanks Jerry!

谢谢杰瑞!

Andrew.

安德鲁。

回答by Drew Noakes

I think the issue is that TextBlock.TextDecorationsis not defined on Label.

我认为问题TextBlock.TextDecorations是没有在Label.

You can use this approachif you're happy to use a TextBlockrather than a Label.

如果您乐于使用 a而不是 ,则可以使用这种方法TextBlockLabel

回答by MikeKulls

Just to add my workaround to the mix. I am currently using C# code and it works well enough. I just trap the MouseLeave and MouseEnter events and show the underline there.

只是为了将我的解决方法添加到组合中。我目前正在使用 C# 代码,它运行良好。我只是捕获 MouseLeave 和 MouseEnter 事件并在那里显示下划线。

void Control_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
    WPFHelper.EnumerateChildren<TextBlock>(this, true).ForEach(c => c.TextDecorations = null);
}

void Control_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
    WPFHelper.EnumerateChildren<TextBlock>(this, true).ForEach(c => c.TextDecorations = TextDecorations.Underline);
}

The WPFHelper class simply enumerates all the children of an DependencyObject and ForEach is an extension method that just does executes the action inside the lambda expression for each item.

WPFHelper 类简单地枚举了 DependencyObject 的所有子项,而 ForEach 是一个扩展方法,它只是为每个项目执行 lambda 表达式中的操作。

回答by Matthew Barthel

An old question, but since I just fought with this, heres my method. Though it just uses a Trigger as opposed to a MultiTrigger

一个老问题,但由于我刚刚与这个问题斗争,这是我的方法。虽然它只是使用触发器而不是多触发器

For XAML:

对于 XAML:

<Label Content="This text is for testing purposes only.">
    <Style TargetType="{x:Type ContentPresenter}">
        <Style.Resources>
            <Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
                <Style.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="TextDecorations" Value="Underline" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </Style.Resources>
    </Style>
</Label>

For C# Codebehind:

对于 C# 代码隐藏:

public override void OnApplyTemplate()
{
    if( !hasInitialized )
    {
        var tbUnderStyle = new Style( typeof(TextBlock), (Style)FindResource(typeof(TextBlock)) );
        var tbUnderSetter = new Setter( TextBlock.TextDecorationsProperty, TextDecorations.Underline );
        var tbUnderTrigger = new Trigger() { Property = Label.IsMouseOverProperty, Value = true };
        tbUnderTrigger.Setters.Add( tbUnderSetter );
        var contentPresenter = FindVisualChild<ContentPresenter>( this );
        contentPresenter.Resources.Add( typeof(TextBlock), tbUnderStyle );

        hasInitialized = true;
    }
}

hasInitialized being a bool that is set to false in the constructor, and FindVisualChild sourced from here.

hasInitialized 是一个在构造函数中设置为 false 的 bool,FindVisualChild 来自here