Подчеркивать ярлык в 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" />
            <Setter Property="TextBlock.TextDecorations" Value="Underline" />
        </MultiTrigger>
    </Style.Triggers>
</Style>

В принципе, я хочу иметь метку, которая подчеркнута, когда она включена, и курсор мыши над ней. Часть этого стиля, который не работает, - это <Setter Property="TextBlock.TextDecorations" Value="Underline" />. Теперь, что я делаю неправильно здесь? Спасибо за помощь.

Ответ 1

Это намного сложнее, чем кажется. В WPF метка не является TextBlock. Он происходит из ContentControl и поэтому может размещать другие, нетекстовые элементы управления в его коллекции содержимого.

Однако вы можете указать строку как содержимое, как в приведенном ниже примере. Внутренне, TextBlock будет построен для размещения текста для вас.

<Label Content="Test!"/>

Это внутренне переводит на:

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

Простым решением для этого было бы свойство TextDecorations TextBlock быть присоединенным свойством. Например, FontSize разработан таким образом, поэтому следующие работы:

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

Вложенное свойство TextBlock.FontSize можно применять в любом месте визуального дерева и переопределить значение по умолчанию для этого свойства для любого потомка TextBlock в дереве. Однако свойство TextDecorations не создано таким образом.

Это оставляет вам хотя бы несколько вариантов.

  • Используйте цвет, границу, курсор и т.д. вместо подчеркнутого текста, потому что это на 100% проще реализовать.
  • Измените способ, которым вы это делаете, чтобы применить стиль к TextBlock вместо этого.
  • Постарайтесь создать ваше собственное прикрепленное свойство и шаблон управления, чтобы его уважать.
  • Сделайте что-то вроде следующего, чтобы вложить стиль для TextBlocks, которые отображаются как дети вашего стиля:

FYI, это самая уродливая вещь, которую я сделал в 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>

Это работает, потому что оно переопределяет стиль по умолчанию любого TextBlock под меткой этого стиля. Затем он использует MultiDataTrigger, чтобы разрешить относительную привязку к ярлыку, чтобы проверить, является ли его свойство IsMouseOver True. Тьфу.

Edit:

Обратите внимание, что это работает, только если вы явно создаете TextBlock. Я был некорректным, когда я разместил это, потому что я уже загрязнил свой тестовый ярлык. Бу. Спасибо, Анвака, за это.

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

Это работает, но если вам нужно идти на эту проблему, вы просто слишком много работаете. Либо кто-то опубликует что-то более умное, или, как вы сказали, мой вариант 1 выглядит довольно хорошо прямо сейчас.

Ответ 2

Я думаю, проблема в том, что TextBlock.TextDecorations не определен на Label.

Вы можете использовать этот подход, если вы с удовольствием используете TextBlock, а не Label.

Ответ 3

В ответ на вопрос Джерри, чтобы избежать необходимости добавлять TextBlock в шаблон каждый раз, когда вы можете позволить этому стилю сделать это, добавив свойство Setter в стиль:

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

Затем ваш ярлык вернется к:

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

Спасибо Джерри!

Андрей.

Ответ 4

Просто добавьте способ обхода в микс. В настоящее время я использую код С#, и он работает достаточно хорошо. Я просто захватываю события 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);
}

Класс WPFHelper просто перечисляет все дочерние объекты DependencyObject, а ForEach - это метод расширения, который выполняет только действие внутри выражения лямбда для каждого элемента.

Ответ 5

Старый вопрос, но поскольку я просто сражался с этим, это мой метод. Хотя он просто использует триггер, а не MultiTrigger

Для 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>

Для С# Codebehind:

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 - это bool, который установлен в конструкторе false, а FindVisualChild получен из здесь.