WPF XAML show ellipsis if text does not fit in TextBlock
Asked Answered
G

2

5

I would like to show ellipsis if my filename and path do not fit in a TextBlock.

For example, if my file name and path are like:

C:\examples\example\folderA\folderB\folderC\myfilename.txt

I would like to show it in my TextBlock like this:

C:\examples...myfilename.txt

UPDATE: I know I can use TextTrimming to set ellipsis at the end. But I need a way to set ellipsis in the middle somewhere.

Ganglion answered 26/5, 2016 at 16:20 Comment(7)
I know this can be done at the end of the string, but I don't think there is any built-in way to do this at some arbitrary point in the middle.Equivocation
I know how to add ellipsis at the end. TextTrimming can handle that. What I need is ellipsis in the middle. Thank you both.Ganglion
Measuring the text isn't that hard to do, but deciding the right number of characters to show and where to split the text is a little harder. I would start by messing around with an IValueConverter (or IMultiValueConverter) that measures if the text fits in the box. If not, convert text into something that fits and contains your rules. Perhaps use Path.GetFileName(..) to obtain just the file name part, remove it from the full string, and try to trim the remaining string to fit the size for the TextBox.Pestilence
@Pestilence So, this must be customized. I'll think how to do it, thanks Rachel. I thought there could be a simple way to do it in xaml. Much appreciated.Ganglion
@dbnex14 You might be able to do it with XAML only, but it would be tricky. Basically you would have two properties, one for FileName and one for FilePath. They would both go in TextBoxes next to each other, with TextTrimming turned on for the FilePath box. And then you'd have to find a way to style them to hide the border between the boxes and make them overlap a little bit. And likely, you would actually have a 3rd TextBox that goes on top of everything that contains the full string, and would only be visible when Editing.Pestilence
cont. So while editing, user sees a single textbox containing full file path, but as soon as focus is lost, the template changes to the two textboxes bound to path/name and that overlap a little bit. Since the Path/Name boxes would be the read-only template, you could probably even do it with a single property too if you want to write converters for converting the full string to either the FilePath or FileName. And at that point, I would just make this thing a custom UserControl with a property for FileName, so it can be reused :)Pestilence
@Pestilence OK, thanks. I'll think about it.Ganglion
P
7

Ok, I was interested if this could be done with just basic XAML, and no messing around with measuring or drawing, so started messing around. I don't have time to finish this today, but I thought I'd share as a starting point for you if you'd like to take it and clean it the remaining issue.

The XAML looks like this so far :

<Window.Resources>
    <local:FileNameConverter x:Key="FileNameConverter" />
    <local:FilePathConverter x:Key="FilePathConverter" />

    <Style x:Key="CustomFileText" TargetType="{x:Type TextBox}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding IsKeyboardFocused, RelativeSource={RelativeSource Self}}" Value="false">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="TextBox">
                            <Grid>
                                <TextBox IsHitTestVisible="False" /> <!-- for Border -->
                                <DockPanel>
                                    <TextBlock Text="{TemplateBinding Text, Converter={StaticResource FileNameConverter}}" DockPanel.Dock="Right" Margin="-3,3,4,3" />
                                    <TextBlock Text="{TemplateBinding Text, Converter={StaticResource FilePathConverter}}" TextTrimming="CharacterEllipsis" Margin="4,3,0,3" />
                                </DockPanel>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

<StackPanel Margin="10">
    <TextBox Text="C:\Program Files\Some Directory\SomeFile.txt" Style="{StaticResource CustomFileText}"/>
    <TextBox Margin="0,10" />
</StackPanel>

The end result is that if the TextBox has focus for editing, it displays as a normal TextBox :

enter image description here

But once the user moves their focus elsewhere on the form, it splits up into two separate TextBlocks that uses converters to parse the Directory and FileName. TextTrimming is used on the Directory to give the effect you were describing in your question :

enter image description here

The main problem with this is when resizing, the extra space is added between the two textboxes.

enter image description here

There's two options I can think of here :

  1. Make the template based on a Trigger of some kind that says "only use this template if TextBox.DesiredSize.Width > TextBox.ActualWidth"

  2. Change the XAML in the template so the Name TextBox somehow says "take up all the space you need as your minimum size. If there is extra space, assign it to this box too", while the Directory TextBox says "take up all the space you can, but do not grow bigger than your contents". I am not sure the best way to do this, but I imagine it has something to do with either a different panel, some properties on TextBox I can't think of right now, or some custom converter/binding to limit size.

I am guessing #1 will be easier to implement, but I don't have time to figure it out now. Hope this gives you a good starting point though, and good luck with it! :)

Oh, and converters were just very basic. You'd probably want to add more safeguards, but here's what I was using for testing :

public class FileNameConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null && value is string)
            return System.IO.Path.GetFileName((string)value);

        return null;
    }

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

public class FilePathConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null && value is string)
            return System.IO.Path.GetDirectoryName((string)value);

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
Pestilence answered 26/5, 2016 at 21:26 Comment(2)
Thanks Rachel, I'll take a look at this. Much appreciated. Will let you know how far I get but at the moment trying to deal with the issue with my form resizing. I have updated it with screenshots. If you can shed some lights, that would be greatly appreciated. The issue is #37465577Ganglion
@dbnex14 Oh I didn't realize that question was you too :)Pestilence
M
1

I modified XAML provided by Rachel to fix the extra space in the path appearing on window sizing.

    <Window.Resources>
    <local:FileNameConverter x:Key="FileNameConverter" />
    <local:FilePathConverter x:Key="FilePathConverter" />

    <Style x:Key="CustomFileText" TargetType="{x:Type TextBox}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding IsKeyboardFocused, RelativeSource={RelativeSource Self}}" Value="false">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="TextBox">
                            <Grid>
                                <TextBox Grid.ColumnSpan="2" IsHitTestVisible="False"/>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition MaxWidth="{Binding ActualWidth, ElementName=PART_DirMaxWidth}"/>
                                        <ColumnDefinition Width="Auto"/>
                                    </Grid.ColumnDefinitions>
                                    <Canvas x:Name="PART_Canvas">
                                        <TextBlock x:Name="PART_DirMaxWidth" Margin="0" Padding="0" Text="{TemplateBinding Text, Converter={StaticResource FilePathConverter}}" Visibility="Hidden"/>
                                        <TextBlock Width="{Binding ActualWidth, ElementName=PART_Canvas}" Margin="0" Padding="0" Text="{TemplateBinding Text, Converter={StaticResource FilePathConverter}}" TextTrimming="CharacterEllipsis" Background="Transparent"/>
                                    </Canvas>
                                    <StackPanel Grid.Column="1" Orientation="Horizontal">
                                        <TextBlock Margin="0" Padding="0" Text="\" Background="Transparent"/>
                                        <TextBlock Margin="0" Padding="0" Text="{TemplateBinding Text, Converter={StaticResource FileNameConverter}}" Background="Transparent"/>
                                    </StackPanel>
                                </Grid>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
Mating answered 19/9, 2017 at 21:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.