Jul 16
The Microsoft .NET Framework is quite comprehensive, but occasionally an obvious function slips through the cracks and you have to use InteropServices to access the Windows API.
One such obvious miss is the ability to truncate a file path. If you are drawing text and know the font and desired output size, you can use the WinForms TextRenderer class. But to truncate a file path to a specific number of characters, you need the "Shell Lightweight Utility Library" function PathCompactPathEx:
using System.Runtime.InteropServices; [DllImport( "shlwapi.dll" )] static extern bool PathCompactPathEx( [Out] StringBuilder pszOut, string szPath, int cchMax, int dwFlags ); static string TruncatePath( string path, int length ) { StringBuilder sb = new StringBuilder(); PathCompactPathEx( sb, path, length, 0 ); return sb.ToString(); }
For example, here is a file path truncated to 40 characters:
C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe
C:\Program Files\Microso…\gacutil.exe
Popularity: 15% [?]
Related posts:

Hello Timm,
It seems you underestimate the .NET framework a little bit.
Have you ever tried this method TextRenderer.MeasureText?
You can find a detailed description on the MSDN. I reckon it does exactly the trick you mentioned above.
Cheers,
Michael
Hi Michael, thanks for commenting. I should have been more clear. If you are trying to restrict to a size on the screen with a given font, then you can use TextRenderer.MeasureText with TextFormatFlags.PathEllipsis. But if you need to restrict a file path to a specific number of characters, then you need PathCompactPathEx as described in this article.
Hi, although the interop version looks like the viable solution to my problem, I find it un".NET" way to code. Now, is there an alternate System.Web class for TextRenderer so that I don't have to reference System.Windows.Forms namespace on my ASPX pages/ web controls? I'm trying to build a custom datagrid with resizable column widths that require this functionality. Any advice will really help me, thanks.
Hi Chris,
The .NET Framework is essentially a collection of classes and methods that wrap interop functions, so it's certainly not "un-.NET" for you to use interop to access functionality that the .NET designers overlooked. Interop does not introduce any unsafe code, and you're simply using the tools you need to get the job done.
I'm unaware of a System.Web equivalent of the TextRenderer class. Could you perhaps use the Graphics class DrawString method with a StringFormat argument and StringTrimming enumeration?
Thanks for the info. I used the following to truncate a pathname with ellipsis to fit into a textbox. This method truncates the name based on the size of a textbox, not a fixed number of characters.
string fullName = @"C:\Users\XY3\Documents\Dates.exe";
// Change copy of fullname to ellipsis truncated form
// and display it
string truncName = fullName.Trim();
TextRenderer.MeasureText(truncName, textBoxTruncName.Font, new Size(textBoxTruncName.Width, textBoxTruncName.Height), TextFormatFlags.ModifyString | TextFormatFlags.PathEllipsis);
textBoxTruncName.Text = truncName;
This has been a useful post for me. I see your aim, timm, which is fitting text into a defined number of characters instead of a defined space.
To solve the problem of fitting into a defined space, I am most intrigued by Gary Montante's post (and which Michael seems to suggest), as it seems not to involve the drawText or on paint approaches I have seen elsewhere.
I am quite new to C# and .NET, though an old C programmer, so I am thrown a bit by something very basic: It appears TextRenderer,MeaureText is not only measuring the text, but is actually modifying it (or replacing the text which truncName was pointing to). I don't see this documented in the MS help for MeasureText, though Michael's post alludes to it. Does anyone have a reference to that behavior?
Thanks,
Mike
Hi, Mike,
Take a look at
http://www.codeproject.com/KB/vb/NewPathCompactPath.aspx
which has some explanation of TextFormatFlags.ModifyString.
Sorry, but I don't remember exactly how/where I stumbled onto the solution. My usual fact finding mode is to Google suggestive words, read some, search some more, and experiment with code when/if I see something that seems relevant.
I, too, was an old Fortran, Assembler, C/C++ programmer (since the late 1970s) and high school/community college instructor until a friend introduced me to C#. Well, better late than never! At least I got to see what heaven could be like while I'm still alive!
Gary
P.S. I recommend learning C# in a guided manner. The C# language has changed since I formally learned it, so I'm not the best source for a recommendation on new textbooks. But, in the past, I found the Microsoft C# Language document (e.g., http://download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/csharp%20language%20specification.doc) quite good for the language specifics. However, for creating Forms programs, a textbook like "Murach's C#"; "Windows Forms Programming in C#" (Chris Sells), or "Microsoft Visual C#.NET Step by Step" (John Sharp, Jon Jagger) to be very useful. Once you got the hang of it, you might like a guide to find quicky info like Microsoft's "C# Programmer's Cookbook". Happy programming…
Gary,
Thanks. I think that codeproject link is about as explicit as it gets about the behavior of MeasureText. As it says, "it's relatively undocumented".
I found a suggestion by a Craig Murphy similar to yours at http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/1acc1d42-416e-4a10-9112-11018e611d95/.
And thanks for the comments on learning C#. I kind of spent the 90's avoiding C++, so I am long overdue for a rollover in my knowledge base.
Relative to our offline e-mail exchange, and perhaps for the comment and benefit of others, I had adapted this approach to work with a filename obtained via openFileDialog.Filename. Being a noob, I think I have misunderstood how strings are handled in C#. For it appears that the transformation to the truncated path appears not only in truncString but in openFileDialog.Filename. The sign of trouble was an obscure (to me) exception after calling MeasureText. The VS2008 debugger shows '((System.Windows.Forms.FileDialog)(openFileDialogBMP)).FileName' threw an exception of type 'System.ArgumentException' for the Filename value in the watch window. My working theory, though I've not proved it yet, is that the ellipsis gets into the Filename field (due to my misunderstanding of string references) and that is illegal in a filename.
Anyway, I hope to figure this out fully and will report the result here.
Mike
Here is a sample class that encapsulates an ellipsis textbox.
Sample usage:
EllipsisTextBox txtBox = new EllipsisTextBox();
txtBox.EllipsisType =
EllipsisTextBox.EllipsisLocation.Path;
txtBox.Text =
@"C:\directory\subdirectory\filename.ext";
Depending on the width of the textbox, it will display something like "C:\di…\filename.ext".
However,
string boo = txtBox.Text; yields
boo == @"C:\directory\subdirectory\filename.ext", the original string, NOT the truncated string.
The sample textbox can be dragged onto a form, and assigned EllipsisType in the Properties window.
The sample also demonstrates a "bug" with String.Trim().
=================================================
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace MyProject
{
///
/// Creates a TextBox with built-in ellipsis control.
/// (1) Specify EllipsisTextBox.EllipsisType
/// (e.g., = EllipsisLocation.Path)
/// (2) Put text into the textbox (e.g., EllipsisTextBox.Text = @"c:\directory\file.txt")
/// The displayed text is the original text modified by the EllipsisType.
/// (e.g., "…\file.txt" if that's what fills the small textbox)
/// The string returned by EllipsisTextBox.Text is the original text,
/// which may differ from what is showing in the textbox.
///
public partial class EllipsisTextBox : System.Windows.Forms.TextBox
{
private string fullText;
private string ellipsisText;
private EllipsisLocation ellipsisLocation = EllipsisLocation.None;
private TextFormatFlags ellipsisTextFormatFlag = 0;
public enum EllipsisLocation { Path, Word, End, None };
///
/// Define where ellipsis appears
///
/// TextFormatFlags.PathEllipsis,TextFormatFlags.EndEllipsis,TextFormatFlags.WordEllipsis
///
[System.ComponentModel.Description("Define where ellipsis appears in the string")]
[System.ComponentModel.DefaultValue(EllipsisLocation.None)]
public EllipsisLocation EllipsisType
{
get { return ellipsisLocation; }
set {
ellipsisLocation = value;
switch (value)
{
case EllipsisLocation.End:
ellipsisTextFormatFlag = TextFormatFlags.EndEllipsis; break;
case EllipsisLocation.None:
ellipsisTextFormatFlag = 0; break;
case EllipsisLocation.Path:
ellipsisTextFormatFlag = TextFormatFlags.PathEllipsis; break;
case EllipsisLocation.Word:
ellipsisTextFormatFlag = TextFormatFlags.WordEllipsis; break;
default:
ellipsisTextFormatFlag = 0;
ellipsisLocation = EllipsisLocation.None;
break;
}
}
}
///
/// Modify/Get text of an EllipseTextBox.
/// Get: returns the original, unmodified assigned text.
/// Set: saves a copy of the new text, but displays
/// the new text as modified by EllipsisType.
///
public new string Text
{
get
{
return fullText;
}
set
{
// Ensure we get a trimmed copy of the original string,
// NOT a reference to the original string.
// NOTE: seems to be a "bug" in .Trim() – returns a reference
// to the original string if .Trim() does not need to modify
// the original string, otherwise, it returns a reference to a new string.
// We could use…
// string truncText = (new StringBuilder(value)).ToString();
// but drop the below to see the .Trim() anomaly!
fullText = (value + " ").Trim(); //want a new copy, not a reference to value!
// Change copy of fullText to ellipsis truncated form
//truncText = (new StringBuilder(value)).ToString();
ellipsisText = (fullText + " ").Trim(); //want a new copy to modify, else fullText would be modified, too!
TextRenderer.MeasureText(ellipsisText, this.Font, new Size(this.Width, this.Height), TextFormatFlags.ModifyString | ellipsisTextFormatFlag);
base.Text = ellipsisText;
}
}
public EllipsisTextBox() : base()
{
fullText = "";
base.Text = "";
}
}
}
@Gary Montante Thanks; here is the functionized version:
using System.Drawing;
using System.Windows.Forms;
public string TruncateFileName( string fileName, TextBox textBox )
{
string truncName = String.Copy(fileName);
TextRenderer.MeasureText(
truncName,
textBox.Font,
new Size(textBox.Width, textBox.Height),
TextFormatFlags.ModifyString | TextFormatFlags.PathEllipsis);
return truncName;
}