André Pontes
André Pontes

Reputation: 467

Function to shrink file path to be more human readable

Is there any function in c# to shink a file path ?

Input: "c:\users\Windows\Downloaded Program Files\Folder\Inside\example\file.txt"

Output: "c:\users\...\example\file.txt"

Upvotes: 9

Views: 5639

Answers (9)

Elmue
Elmue

Reputation: 8138

Nearly all answers here shorten the path string by counting characters. But this approach ignores the width of each character.

These are 30 'W' characters:

WWWWWWWWWWWWWWWWWWWWWWWWWWWWWW

These are 30 'i' characters:

iiiiiiiiiiiiiiiiiiiiiiiiiiiiii

As you see, counting characters is not really useful.

And there is no need to write your own code because the Windows API has this functionaly since Windows 95. The name of this functionality is "Path Ellipsis". The Windows API DrawTextW() has a flag DT_PATH_ELLIPSIS which does exactly this. In the .NET framwork this is available (without the need to use PInvoke) in the TextRenderer class.

There are 2 ways how this can be used:


1.) Drawing the path directly into a Label:

public class PathLabel : Label
{
    protected override void OnPaint(PaintEventArgs e)
    {
        if (AutoSize)
            throw new Exception("You must set "+Name+".AutoSize = false in VS " 
                              + "Designer and assign a fix width to the PathLabel.");

        Color c_Fore = Enabled ? ForeColor : SystemColors.GrayText;
        TextRenderer.DrawText(e.Graphics, Text, Font, ClientRectangle, c_Fore, 
                              BackColor, TextFormatFlags.PathEllipsis);
    }
}

This label requires you to turn AutoEllipsis off in Visual Studio Designer and assign a fix width to the Label (the maximum width that your path should occupy).

You even see the truncated path in Visual Studio Designer.

I entered a long path which does not fit into the label:

C:\WINDOWS\Installer{40BF1E83-20EB-11D8-97C5-0009C5020658}\ARPPRODUCTICON.exe

Even in Visual Studio Designer it is displayed like this:

Label with Path Ellipsis in C#


2.) Shorten the path without drawing it on the screen:

public static String ShortenPath(String s_Path, Font i_Font, int s32_Width)
{
    TextRenderer.MeasureText(s_Path, i_Font, new Size(s32_Width, 100), 
                             TextFormatFlags.PathEllipsis | TextFormatFlags.ModifyString);

    // Windows inserts a '\0' character into the string instead of shortening the string
    int s32_Nul = s_Path.IndexOf((Char)0);
    if (s32_Nul > 0)
        s_Path = s_Path.Substring(0, s32_Nul);
    return s_Path;
}

The flag TextFormatFlags.ModifyString inserts a '\0' character into the string. It is very unusual that a string is modified in C#. Normally strings are unmutable. This is because the underlying API DrawTextW() works this way. But as the string is only shortened and never will become longer there is no risk of a buffer overflow.

The following code

String s_Text = @"C:\WINDOWS\Installer{40BF1E83-20EB-11D8-97C5-0009C5020658}\ARPPRODUCTICON.exe";
s_Text = ShortenPath(s_Text, new Font("Arial", 12), 500);

will result in "C:\WINDOWS\Installer{40BF1E83-20EB-1...\ARPPRODUCTICON.exe"

Upvotes: 1

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112334

If you want, do insert ellipsis dependent on the length of the path string, then use this code:

TextRenderer.MeasureText(path, Font, 
    new System.Drawing.Size(Width, 0),
    TextFormatFlags.PathEllipsis | TextFormatFlags.ModifyString);

It will modify path in-place.

EDIT: Be careful with this method. It breaks the rule, saying that strings in .NET are immutable. In fact, the first parameter of the MeasureText method is not a ref parameter, which means that no new string can be returned. Instead, the existing string is altered. It would be careful to work on a copy created with

string temp = String.Copy(path);

Upvotes: 5

David Carrigan
David Carrigan

Reputation: 783

I was just faced with this issue as long paths were becoming a complete eye sore. Here is what I tossed together real quick (mind the sloppiness) but it gets the job done.

private string ShortenPath(string path, int maxLength)
{
    int pathLength = path.Length;

    string[] parts;
    parts = label1.Text.Split('\\');

    int startIndex = (parts.Length - 1) / 2;
    int index = startIndex;

    string output = "";
    output = String.Join("\\", parts, 0, parts.Length);

    decimal step = 0;
    int lean = 1;

    do
    {
        parts[index] = "...";

        output = String.Join("\\", parts, 0, parts.Length);

        step = step + 0.5M;
        lean = lean * -1;

        index = startIndex + ((int)step * lean);
    }
    while (output.Length >= maxLength && index != -1);

    return output;
}

Results

EDIT

Below is an update with Merlin2001's corrections.

private string ShortenPath(string path, int maxLength)
{
    int pathLength = path.Length;

    string[] parts;
    parts = path.Split('\\');

    int startIndex = (parts.Length - 1) / 2;
    int index = startIndex;

    String output = "";
    output = String.Join("\\", parts, 0, parts.Length);

    decimal step = 0;
    int lean = 1;

    while (output.Length >= maxLength && index != 0 && index != -1)
    {
        parts[index] = "...";

        output = String.Join("\\", parts, 0, parts.Length);

        step = step + 0.5M;
        lean = lean * -1;

        index = startIndex + ((int)step * lean);
    }
    // result can be longer than maxLength
    return output.Substring(0, Math.Min(maxLength, output.Length));  
}

Upvotes: 1

Major
Major

Reputation: 6658

If you want to write you own solution to this problem, use build in classes like: FileInfo, Directory, etc... which makes it less error prone.

The following code produces "VS style" shortened path like: "C:\...\Folder\File.ext".

public static class PathFormatter
{
    public static string ShrinkPath(string absolutePath, int limit, string spacer = "…")
    {
        if (string.IsNullOrWhiteSpace(absolutePath))
        {
            return string.Empty;
        }
        if (absolutePath.Length <= limit)
        {
            return absolutePath;
        }

        var parts = new List<string>();

        var fi = new FileInfo(absolutePath);
        string drive = Path.GetPathRoot(fi.FullName);

        parts.Add(drive.TrimEnd('\\'));
        parts.Add(spacer);
        parts.Add(fi.Name);

        var ret = string.Join("\\", parts);
        var dir = fi.Directory;

        while (ret.Length < limit && dir != null)
        {
            if (ret.Length + dir.Name.Length > limit)
            {
                break;
            }

            parts.Insert(2, dir.Name);

            dir = dir.Parent;
            ret = string.Join("\\", parts);
        }

        return ret;
    }
}

Upvotes: 0

Mariano Arce
Mariano Arce

Reputation: 11

    private string ShrinkPath(string path, int maxLength)
    {
        var parts = path.Split('\\');
        var output = String.Join("\\", parts, 0, parts.Length);
        var endIndex = (parts.Length - 1);
        var startIndex = endIndex / 2;
        var index = startIndex;
        var step = 0;

        while (output.Length >= maxLength && index != 0 && index != endIndex)
        {
            parts[index] = "...";
            output = String.Join("\\", parts, 0, parts.Length);
            if (step >= 0) step++;
            step = (step * -1);
            index = startIndex + step;
        }
        return output;
    }

Upvotes: 1

Daniele
Daniele

Reputation: 236

Nasreddine answer was nearly correct. Just specify StringBuilder size, in your case:

[DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
static extern bool PathCompactPathEx(
                       [Out] StringBuilder pszOut, 
                       string szPath, 
                       int cchMax, 
                       int dwFlags);

static string PathShortener(string path, int length)
{
    StringBuilder sb = new StringBuilder(length + 1);
    PathCompactPathEx(sb, path, length, 0);
    return sb.ToString();
}

Upvotes: 10

Christoph Fink
Christoph Fink

Reputation: 23103

You could use something like:

public string ShrinkPath(string path, int maxLength)
{
    List<string> parts = new List<string>(path.Split('\\'));

    string start = parts[0] + @"\" + parts[1];
    parts.RemoveAt(1);
    parts.RemoveAt(0);

    string end = parts[parts.Count-1];
    parts.RemoveAt(parts.Count-1);

    parts.Insert(0, "...");
    while(parts.Count > 1 && 
      start.Length + end.Length + parts.Sum(p=>p.Length) + parts.Count > maxLength)
        parts.RemoveAt(parts.Count-1);

    string mid = "";
    parts.ForEach(p => mid += p + @"\");

    return start+mid+end;
}

Or just use Olivers solution, which is much easier ;-).

Upvotes: 2

Nasreddine
Nasreddine

Reputation: 37808

Jeff Atwood posted a solution to this on his blog and here it is :

[DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
static extern bool PathCompactPathEx([Out] StringBuilder pszOut, string szPath, int cchMax, int dwFlags);

static string PathShortener(string path, int length)
{
    StringBuilder sb = new StringBuilder();
    PathCompactPathEx(sb, path, length, 0);
    return sb.ToString();
}

It uses the unmanaged function PathCompactPathEx to achieve what you want.

Upvotes: 6

Orn Kristjansson
Orn Kristjansson

Reputation: 3485

That looks less human readable to me. Anyway, I don't think there is such a function. split it on the \ character and just keep the first two slots and the last two slots and you have it.

Something like this, although that code is not very elegant

  string[] splits = path.Split('\\');
  Console.WriteLine( splits[0] + "\\" + splits[1] + "\\...\\" + splits[splits.Length - 2] + "\\" +  splits[splits.Length - 1]);

Upvotes: 6

Related Questions