Introduction
Often enough I've had to display a file path in a ListView/GridView, but if the size of the
column isn't sufficient, the file part (and some of the directories are clipped).
Using TextBlock.TextTrimming
doesn't help, it'll simply add an ellipsis to the end of the string. So how do we display a long file path in a
GridViewColumn
effectively?
Background
I started off thinking this would be a simple job for a Converter. Simply feed in the
Path
and TextBlock
itself, perform some magic using the
FormattedText
class,
and voila! Turns out it wasn't as simple as that.
The problem is basically that TextBlock.ActualWidth
isn't a DependencyProperty
, and the effect is, that when
the Size
of the TextBlock
changes, the Converter doesn't recalculate. OK... scrap that.
Then I thought to myself, well... I'll just derive from TextBlock
, create a
DependencyProperty
called Path
, hook SizeChanged
, and voila!
Well... it nearly worked.
The SizeChanged
event was called when the GridViewColumn
Width increased but not when it decreased. Um, what?
So.. with a little help from nguyentrucdn on MSDN
(http://social.msdn.microsoft.com/Forums/en/wpf/thread/8a00e43d-7091-49e7-b57c-86fc0951c4d0),
I discovered that the TextBlock
won't actually post SizeChanged
events in a
GridViewColumn
when its Width
decreases... but it's Container will
So... I thought to myself, well, I'll wrap the TextBlock
in a
Grid
in the XAML, and in the Loaded handler of the TextBlock
I'll grab the parent container
and hook its SizeChanged
event and feed its ActualWidth
into the trimming algorithm and voila! Got it!
Using the code
Usage is very simple, but with one caveat. You must place the PathTrimmingTextBlock
in a
Grid
or other Container. It'll throw an InvalidOperationException
if you don't. Otherwise, it's simply...
<ListView ItemsSource="{Binding }" HorizontalContentAlignment="Stretch">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Filename">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<WpfApplication307:PathTrimmingTextBlock FontSize="40"
Path="{Binding}"></WpfApplication307:PathTrimmingTextBlock>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Just remember you'll have to reference the correct xmlns, not WpfApplication307.
The ellipsis algorithm was simple enough. Pass in the required width and full path, split the directory from the filename, remove the last character of the directory
until FormattedText
tells you its Width is less than Directory + "..." + Filename, and set the
Text
to that. My implementation looks like...
string GetTrimmedPath(double width)
{
string filename = System.IO.Path.GetFileName(Path);
string directory = System.IO.Path.GetDirectoryName(Path);
FormattedText formatted;
bool widthOK = false;
bool changedWidth = false;
do
{
formatted = new FormattedText(
"{0}...\\{1}".FormatWith(directory, filename),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
FontFamily.GetTypefaces().First(),
FontSize,
Foreground
);
widthOK = formatted.Width < width;
if (!widthOK)
{
changedWidth = true;
directory = directory.Substring(0, directory.Length - 1);
if (directory.Length == 0) return "...\\" + filename;
}
} while (!widthOK);
if (!changedWidth)
{
return Path;
}
return "{0}...{1}".FormatWith(directory, filename);
}
Points of Interest
That a TextBlock
won't post SizeChanged
events when its Width decreases... at least if it's a
GridViewColumn
!
I should also note, you don't have to use this in a
GridViewColumn
, it'll work anywhere really
History
- 28/9/2012 - Posted article.