Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to display abbreviated path names in .NET

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.

like image 268
chuckhlogan Avatar asked Nov 19 '09 15:11

chuckhlogan


People also ask

What does \\ mean in Windows path?

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 \\ .

How do I get the filename from the file path?

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);

How do I find the file path in Visual Studio?

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.

Does path include filename?

A path name can be up to 1023 characters long, including all directory names, file names, and separating slashes.


2 Answers

Display Abbreviated Paths using 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);
}

Alternative Ways to Compact a Path:

There are home grown solutions to compact paths as well as some WinApi calls one can use.

PathCompactPathEx - Compacting a String Based on Desired String Length in Characters

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();
}

Other Solutions

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:

  • Coding Horror: - Shortening Long File Paths
  • CodeProject: - Auto Ellipsis
like image 84
Sam Avatar answered Oct 16 '22 02:10

Sam


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

like image 32
Torben Warberg Rohde Avatar answered Oct 16 '22 04:10

Torben Warberg Rohde