左边的文本修剪

22

有没有办法在TextBlock上指定文本修剪从左侧开始?

我已经完成了三种情况中的两种(第三种是我需要的):

  1. 常规修剪

    <TextBlock 
        VerticalAlignment="Center" 
        Width="80" 
        TextTrimming="WordEllipsis"
        Text="A very long text that requires trimming" />
    
    // Result: "A very long te..."
    
  2. 左侧修剪

  3. <TextBlock 
        VerticalAlignment="Center" 
        Width="80" 
        FlowDirection="RightToLeft"
        TextTrimming="WordEllipsis"
        Text="A very long text that requires trimming." />
    
    // Result: "...A very long te"
    
  4. 看到文本末尾时向左消除空格

  5. // Desired result: "...uires trimming"
    

有人知道这是否可能吗?谢谢。


我认为你需要使用TextTrimming="CharacterEllipsis"而不是WordEllipsis。 - Justin
4个回答

7

如果您不关心省略号,只想在文本被截断时看到末尾而不是开头,可以将TextBlock包裹在另一个容器中,并将其HorizontalAlignment设置为Right。这样将会按照您的要求进行截断,但没有省略号。

<Grid>
    <TextBlock Text="Really long text to cutoff." HorizontalAlignment="Right"/>
</Grid>

5
您无法直接实现此功能,但我可以想到两种可能的解决方案:
1)为TextBlock创建一个附加属性,比如LeftTrimmingText。然后,您需要设置该属性而不是Text属性。例如:
  <TextBlock my:TextBlockHelper.LeftTrimmingText="A very long text that requires trimming." />

附加属性将计算实际可以显示的字符数,然后相应地设置TextBlock的Text属性。
2)创建自己的类来包装TextBlock,并添加自己的属性以处理所需的逻辑。
我认为第一种选项更容易。

不幸的是,第一种选择并不容易。Silverlight没有公开的文本渲染/测量API。你能做的最好的事情就是检测何时发生了修剪。请参阅此博客文章:http://www.scottlogic.co.uk/blog/colin/2011/01/showing-tooltips-on-trimmed-textblock-silverlight/ - ColinE
2
测量API是TextBlock本身。基本上,您可以在代码中创建一个新的临时TextBlock,并不断添加字符,直到ActualWidth变得大于您想要的宽度。该临时TextBlock不需要被渲染甚至不需要出现在可视树中。 - RobSiklos

4
这种样式可以达到效果。诀窍是重新定义标签的控件模板。然后将内容放在剪辑画布内,并对其进行右对齐。内容的最小宽度是画布的宽度,因此如果有足够的空间,内容文本将左对齐,当被剪切时则右对齐。
如果内容的宽度大于画布,则激活省略号。
<Style x:Key="LeftEllipsesLabelStyle"
       TargetType="{x:Type Label}">
    <Setter Property="Foreground"
            Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
    <Setter Property="Background"
            Value="Transparent" />
    <Setter Property="Padding"
            Value="5" />
    <Setter Property="HorizontalContentAlignment"
            Value="Left" />
    <Setter Property="VerticalContentAlignment"
            Value="Top" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Label}">
                <Grid >
                    <Grid.Resources>
                        <LinearGradientBrush x:Key="HeaderBackgroundOpacityMask" StartPoint="0,0" EndPoint="1,0">
                            <GradientStop Color="Black"  Offset="0"/>
                            <GradientStop Color="Black" Offset="0.5"/>
                            <GradientStop Color="Transparent" Offset="1"/>
                        </LinearGradientBrush>
                    </Grid.Resources>

                    <Canvas x:Name="Canvas" 
                            ClipToBounds="True" 
                            DockPanel.Dock="Top"  
                            Height="{Binding ElementName=Content, Path=ActualHeight}">

                        <Border 
                            BorderBrush="{TemplateBinding BorderBrush}"
                            Canvas.Right="0"
                            Canvas.ZIndex="0"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            Background="{TemplateBinding Background}"
                            Padding="{TemplateBinding Padding}"
                            MinWidth="{Binding ElementName=Canvas, Path=ActualWidth}"
                            SnapsToDevicePixels="true"
                            x:Name="Content"
                        >
                            <ContentPresenter
                                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                Content="{Binding RelativeSource={RelativeSource AncestorType=Label}, Path=Content}"
                                RecognizesAccessKey="True"
                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            >
                                <ContentPresenter.Resources>
                                    <Style TargetType="TextBlock">
                                        <Setter Property="FontSize" Value="{Binding FontSize, RelativeSource={RelativeSource AncestorType={x:Type Label}}}"/> 
                                        <Setter Property="FontWeight" Value="{Binding FontWeight, RelativeSource={RelativeSource AncestorType={x:Type Label}}}"/> 
                                        <Setter Property="FontStyle" Value="{Binding FontStyle, RelativeSource={RelativeSource AncestorType={x:Type Label}}}"/> 
                                        <Setter Property="FontFamily" Value="{Binding FontFamily, RelativeSource={RelativeSource AncestorType={x:Type Label}}}"/> 
                                    </Style>
                                </ContentPresenter.Resources>

                            </ContentPresenter>
                        </Border>
                        <Label 
                            x:Name="Ellipses" 
                            Canvas.Left="0" 
                            Canvas.ZIndex="10"
                            FontWeight="{TemplateBinding FontWeight}"
                            FontSize="{TemplateBinding FontSize}"
                            FontFamily="{TemplateBinding FontFamily}"
                            FontStyle="{TemplateBinding FontStyle}"
                            VerticalContentAlignment="Center" 
                            OpacityMask="{StaticResource HeaderBackgroundOpacityMask}" 
                            Background="{TemplateBinding Background}"
                            Foreground="RoyalBlue"
                            Height="{Binding ElementName=Content, Path=ActualHeight}" 
                            Content="...&#160;&#160;&#160;">
                            <Label.Resources>
                                <Style TargetType="Label">
                                    <Style.Triggers>
                                        <DataTrigger Value="true">
                                            <DataTrigger.Binding>
                                                <MultiBinding Converter="{StaticResource GteConverter}">
                                                    <Binding ElementName="Canvas" Path="ActualWidth"/>
                                                    <Binding ElementName="Content" Path="ActualWidth"/>
                                                </MultiBinding>
                                            </DataTrigger.Binding>
                                            <Setter Property="Visibility" Value="Hidden"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Label.Resources>

                        </Label>
                    </Canvas>

                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsEnabled"
                             Value="false">
                        <Setter Property="Foreground"
                                Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

这里有几个实用类:
GteConverter
<c:GteConverter x:Key="GteConverter"/>

这是

public class RelationalValueConverter : IMultiValueConverter
{
    public enum RelationsEnum
    {
        Gt,Lt,Gte,Lte,Eq,Neq
    }

    public RelationsEnum Relations { get; protected set; }

    public RelationalValueConverter(RelationsEnum relations)
    {
        Relations = relations;
    }

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if(values.Length!=2)
            throw new ArgumentException(@"Must have two parameters", "values");

        var v0 = values[0] as IComparable;
        var v1 = values[1] as IComparable;

        if(v0==null || v1==null)
            throw new ArgumentException(@"Must arguments must be IComparible", "values");

        var r = v0.CompareTo(v1);

        switch (Relations)
        {
            case RelationsEnum.Gt:
                return r > 0;
                break;
            case RelationsEnum.Lt:
                return r < 0;
                break;
            case RelationsEnum.Gte:
                return r >= 0;
                break;
            case RelationsEnum.Lte:
                return r <= 0;
                break;
            case RelationsEnum.Eq:
                return r == 0;
                break;
            case RelationsEnum.Neq:
                return r != 0;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

并且

public class GtConverter : RelationalValueConverter
{
    public GtConverter() : base(RelationsEnum.Gt) { }
}
public class GteConverter : RelationalValueConverter
{
    public GteConverter() : base(RelationsEnum.Gte) { }
}
public class LtConverter : RelationalValueConverter
{
    public LtConverter() : base(RelationsEnum.Lt) { }
}
public class LteConverter : RelationalValueConverter
{
    public LteConverter() : base(RelationsEnum.Lte) { }
}
public class EqConverter : RelationalValueConverter
{
    public EqConverter() : base(RelationsEnum.Eq) { }
}
public class NeqConverter : RelationalValueConverter
{
    public NeqConverter() : base(RelationsEnum.Neq) { }
}

这是它的工作原理。 enter image description here enter image description here enter image description here

由于某些原因,标签的OpacityMask对我来说无法正常工作,但是我通过使用Background属性来获得所需的效果 - 很棒的解决方案,谢谢! - Wolfshead
看起来是最干净的解决方案。但是代码对我不起作用,它根本没有显示任何文本。 - U. Bulle
你有使用它的示例吗?我对WPF和XAML都是新手。我遇到了一个关于命名空间前缀“c”未定义和类型“c:GteConverter”未找到的错误。我猜我要么没有把所有东西放在正确的位置/文件中,要么就是我漏掉了什么。 - Chris
嗨,Chris。我已经一年多没有使用XAML了,现在它又开始像黑魔法一样让我感到困惑。然而,上面的代码有点假设你知道如何处理XAML命名空间。如果不知道,阅读https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/xaml-namespaces-and-namespace-mapping-for-wpf-xaml可能会有所帮助。如果还是不行,请提出另一个问题,并尽可能提供完整的信息,那么最新的技术人员可能会提供帮助。 - bradgonesurfing
嗯,无论如何感谢原帖。我想我正在弄清楚它。当代码实际编译时,Visual Studio 显示的错误可能会消失,这让我感到困惑。 - Chris

0

我不知道这是否是一个打字错误,但是您在“期望结果”的末尾缺少了“句号”。我假设您不需要它。由于您知道应该显示多少个字符,因此可以获取整个字符串的子字符串并将其显示出来。例如,

string origText = "A very long text that requires trimming.";

//15 because the first three characters are replaced
const int MAXCHARACTERS = 15;

//MAXCHARACTERS - 1 because you don't want the full stop
string sub = origText.SubString(origText.Length-MAXCHARACTERS, MAXCHARACTERS-1);

string finalString = "..." + sub;
textBlock.Text = finalString;

如果您事先不知道需要多少个字符,则可以进行计算来确定它。在您的示例中,80 的宽度结果为 17 个字符,如果宽度发生变化,则可以使用该比率。


5
请不要忘记,字符宽度取决于字体。 - RobSiklos
是的,我假设 OP 事先知道字体。如果不知道,那么 OP 可以使用此方法根据字体确定文本宽度:http://stackoverflow.com/questions/913053/how-do-you-determine-the-width-of-the-text-in-a-wpf-treeviewitem-at-run-time - keyboardP
1
在你的例子中,宽度为80会产生17个字符,如果宽度改变,你可以使用该比率。每个字符都可以有(通常有)其独立的宽度。从一个样本字符串中使用比率不会产生任何精确的结果。 - O. R. Mapper

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