I have a fixed-length field I'd like to display path information in. I thought in .NET there is a method that will abbreviate long path names to fit in fixed length fields by inserting ellipsis, for example "......\myfile.txt". I can't for the life of me find this method.
Universal naming convention (UNC) paths, which are used to access network resources, have the following format: A server or host name, which is prefaced by \\ .
To extract filename from the file, we use “GetFileName()” method of “Path” class. This method is used to get the file name and extension of the specified path string. The returned value is null if the file path is null. Syntax: public static string GetFileName (string path);
Ctrl+Click or Double Right Click to Open Containing Folder, Right click to Copy Full Path. This lightweight extension lets you display the full path of the file at bottom of Visual Studio's Editor.
A path name can be up to 1023 characters long, including all directory names, file names, and separating slashes.
TextRenderer
.MeasureText
I had the same dilemma as the OP in that I needed a solution for showing an abbreviated path without too much hassle so that my UI could easily show the main parts of a path. The final solution: Use TextRenderer
's MeasureText
method like so:
public static string GetCompactedString(
string stringToCompact, Font font, int maxWidth)
{
// Copy the string passed in since this string will be
// modified in the TextRenderer's MeasureText method
string compactedString = string.Copy(stringToCompact);
var maxSize = new Size(maxWidth, 0);
var formattingOptions = TextFormatFlags.PathEllipsis
| TextFormatFlags.ModifyString;
TextRenderer.MeasureText(compactedString, font, maxSize, formattingOptions);
return compactedString;
}
Important: Passing in a formatting option of TextFormatFlags.ModifyString
actually causes the MeasureText
method to alter the string argument (compactedString
) to be a compacted string. This seems very weird since no explicit ref
or out
method parameter keyword is specified and strings are immutable. However, its definitely the case. I assume the string's pointer was updated via unsafe code to the new compacted string.
As another lessor note, since I want the compacted string to compact a path, I also used the TextFormatFlags.PathEllipsis
formatting option.
I found it handy to make a small extension method on control so that any control (like TextBox's and Label's) can set their text to a compacted path:
public static void SetTextAsCompactedPath(this Control control, string path)
{
control.Text = GetCompactedString(path, control.Font, control.Width);
}
There are home grown solutions to compact paths as well as some WinApi calls one can use.
You can use pinvoke to call PathCompactPathEx
to compact a string based on the number of characters you wish to restrict it to. Note that this does not take into account font and how wide the string will appear on a screen.
Code Source PInvoke: http://www.pinvoke.net/default.aspx/shlwapi.pathcompactpathex
[DllImport("shlwapi.dll", CharSet=CharSet.Auto)]
static extern bool PathCompactPathEx(
[Out] StringBuilder pszOut, string szPath, int cchMax, int dwFlags);
public static string CompactPath(string longPathName, int wantedLength)
{
// NOTE: You need to create the builder with the
// required capacity before calling function.
// See http://msdn.microsoft.com/en-us/library/aa446536.aspx
StringBuilder sb = new StringBuilder(wantedLength + 1);
PathCompactPathEx(sb, longPathName, wantedLength + 1, 0);
return sb.ToString();
}
There are more solutions out there as well that involve regular expressions and smart parsing of paths to determine where to put ellipsis. These are a few I saw:
In case someone is looking for the same thing. I cooked up this function:
/// <summary>
/// Shortens a file path to the specified length
/// </summary>
/// <param name="path">The file path to shorten</param>
/// <param name="maxLength">The max length of the output path (including the ellipsis if inserted)</param>
/// <returns>The path with some of the middle directory paths replaced with an ellipsis (or the entire path if it is already shorter than maxLength)</returns>
/// <remarks>
/// Shortens the path by removing some of the "middle directories" in the path and inserting an ellipsis. If the filename and root path (drive letter or UNC server name) in itself exceeds the maxLength, the filename will be cut to fit.
/// UNC-paths and relative paths are also supported.
/// The inserted ellipsis is not a true ellipsis char, but a string of three dots.
/// </remarks>
/// <example>
/// ShortenPath(@"c:\websites\myproject\www_myproj\App_Data\themegafile.txt", 50)
/// Result: "c:\websites\myproject\...\App_Data\themegafile.txt"
///
/// ShortenPath(@"c:\websites\myproject\www_myproj\App_Data\theextremelylongfilename_morelength.txt", 30)
/// Result: "c:\...gfilename_morelength.txt"
///
/// ShortenPath(@"\\myserver\theshare\myproject\www_myproj\App_Data\theextremelylongfilename_morelength.txt", 30)
/// Result: "\\myserver\...e_morelength.txt"
///
/// ShortenPath(@"\\myserver\theshare\myproject\www_myproj\App_Data\themegafile.txt", 50)
/// Result: "\\myserver\theshare\...\App_Data\themegafile.txt"
///
/// ShortenPath(@"\\192.168.1.178\theshare\myproject\www_myproj\App_Data\themegafile.txt", 50)
/// Result: "\\192.168.1.178\theshare\...\themegafile.txt"
///
/// ShortenPath(@"\theshare\myproject\www_myproj\App_Data\", 30)
/// Result: "\theshare\...\App_Data\"
///
/// ShortenPath(@"\theshare\myproject\www_myproj\App_Data\themegafile.txt", 35)
/// Result: "\theshare\...\themegafile.txt"
/// </example>
public static string ShortenPath(string path, int maxLength)
{
string ellipsisChars = "...";
char dirSeperatorChar = Path.DirectorySeparatorChar;
string directorySeperator = dirSeperatorChar.ToString();
//simple guards
if (path.Length <= maxLength)
{
return path;
}
int ellipsisLength = ellipsisChars.Length;
if (maxLength <= ellipsisLength)
{
return ellipsisChars;
}
//alternate between taking a section from the start (firstPart) or the path and the end (lastPart)
bool isFirstPartsTurn = true; //drive letter has first priority, so start with that and see what else there is room for
//vars for accumulating the first and last parts of the final shortened path
string firstPart = "";
string lastPart = "";
//keeping track of how many first/last parts have already been added to the shortened path
int firstPartsUsed = 0;
int lastPartsUsed = 0;
string[] pathParts = path.Split(dirSeperatorChar);
for (int i = 0; i < pathParts.Length; i++)
{
if (isFirstPartsTurn)
{
string partToAdd = pathParts[firstPartsUsed] + directorySeperator;
if ((firstPart.Length + lastPart.Length + partToAdd.Length + ellipsisLength) > maxLength)
{
break;
}
firstPart = firstPart + partToAdd;
if (partToAdd == directorySeperator)
{
//this is most likely the first part of and UNC or relative path
//do not switch to lastpart, as these are not "true" directory seperators
//otherwise "\\myserver\theshare\outproject\www_project\file.txt" becomes "\\...\www_project\file.txt" instead of the intended "\\myserver\...\file.txt")
}
else
{
isFirstPartsTurn = false;
}
firstPartsUsed++;
}
else
{
int index = pathParts.Length - lastPartsUsed - 1; //-1 because of length vs. zero-based indexing
string partToAdd = directorySeperator + pathParts[index];
if ((firstPart.Length + lastPart.Length + partToAdd.Length + ellipsisLength) > maxLength)
{
break;
}
lastPart = partToAdd + lastPart;
if (partToAdd == directorySeperator)
{
//this is most likely the last part of a relative path (e.g. "\websites\myproject\www_myproj\App_Data\")
//do not proceed to processing firstPart yet
}
else
{
isFirstPartsTurn = true;
}
lastPartsUsed++;
}
}
if (lastPart == "")
{
//the filename (and root path) in itself was longer than maxLength, shorten it
lastPart = pathParts[pathParts.Length - 1];//"pathParts[pathParts.Length -1]" is the equivalent of "Path.GetFileName(pathToShorten)"
lastPart = lastPart.Substring(lastPart.Length + ellipsisLength + firstPart.Length - maxLength, maxLength - ellipsisLength - firstPart.Length);
}
return firstPart + ellipsisChars + lastPart;
}
Originial post with (a little) background here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With