There\'s a part in my apps that displays the file path loaded by the user through OpenFileDialog. It\'s taking up too much space to display the whole path, but I don\'t want
Way with Uri not worked on linux/macOS systems. Path '/var/www/root' can't be converted to Uri. More universal way - do all by hands.
public static string MakeRelativePath(string fromPath, string toPath, string sep = "/")
{
var fromParts = fromPath.Split(new[] { '/', '\\'},
StringSplitOptions.RemoveEmptyEntries);
var toParts = toPath.Split(new[] { '/', '\\'},
StringSplitOptions.RemoveEmptyEntries);
var matchedParts = fromParts
.Zip(toParts, (x, y) => string.Compare(x, y, true) == 0)
.TakeWhile(x => x).Count();
return string.Join("", Enumerable.Range(0, fromParts.Length - matchedParts)
.Select(x => ".." + sep)) +
string.Join(sep, toParts.Skip(matchedParts));
}
PS: i use "/" as a default value of separator instead of Path.DirectorySeparatorChar, because result of this method used as uri in my app.
I'm using this:
public static class StringExtensions
{
/// <summary>
/// Creates a relative path from one file or folder to another.
/// </summary>
/// <param name="absPath">Absolute path.</param>
/// <param name="relTo">Directory that defines the start of the relative path.</param>
/// <returns>The relative path from the start directory to the end path.</returns>
public static string MakeRelativePath(this string absPath, string relTo)
{
string[] absParts = absPath.Split(Path.DirectorySeparatorChar);
string[] relParts = relTo.Split(Path.DirectorySeparatorChar);
// Get the shortest of the two paths
int len = absParts.Length < relParts.Length
? absParts.Length : relParts.Length;
// Use to determine where in the loop we exited
int lastCommonRoot = -1;
int index;
// Find common root
for (index = 0; index < len; index++)
{
if (absParts[index].Equals(relParts[index], StringComparison.OrdinalIgnoreCase))
lastCommonRoot = index;
else
break;
}
// If we didn't find a common prefix then throw
if (lastCommonRoot == -1)
throw new ArgumentException("The path of the two files doesn't have any common base.");
// Build up the relative path
var relativePath = new StringBuilder();
// Add on the ..
for (index = lastCommonRoot + 1; index < relParts.Length; index++)
{
relativePath.Append("..");
relativePath.Append(Path.DirectorySeparatorChar);
}
// Add on the folders
for (index = lastCommonRoot + 1; index < absParts.Length - 1; index++)
{
relativePath.Append(absParts[index]);
relativePath.Append(Path.DirectorySeparatorChar);
}
relativePath.Append(absParts[absParts.Length - 1]);
return relativePath.ToString();
}
}
.NET Core 2.0 has Path.GetRelativePath
, else, use this.
/// <summary>
/// Creates a relative path from one file or folder to another.
/// </summary>
/// <param name="fromPath">Contains the directory that defines the start of the relative path.</param>
/// <param name="toPath">Contains the path that defines the endpoint of the relative path.</param>
/// <returns>The relative path from the start directory to the end path or <c>toPath</c> if the paths are not related.</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="UriFormatException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public static String MakeRelativePath(String fromPath, String toPath)
{
if (String.IsNullOrEmpty(fromPath)) throw new ArgumentNullException("fromPath");
if (String.IsNullOrEmpty(toPath)) throw new ArgumentNullException("toPath");
Uri fromUri = new Uri(fromPath);
Uri toUri = new Uri(toPath);
if (fromUri.Scheme != toUri.Scheme) { return toPath; } // path can't be made relative.
Uri relativeUri = fromUri.MakeRelativeUri(toUri);
String relativePath = Uri.UnescapeDataString(relativeUri.ToString());
if (toUri.Scheme.Equals("file", StringComparison.InvariantCultureIgnoreCase))
{
relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
}
return relativePath;
}
This should work:
private string rel(string path) {
string[] cwd = new Regex(@"[\\]").Split(Directory.GetCurrentDirectory());
string[] fp = new Regex(@"[\\]").Split(path);
int common = 0;
for (int n = 0; n < fp.Length; n++) {
if (n < cwd.Length && n < fp.Length && cwd[n] == fp[n]) {
common++;
}
}
if (common > 0) {
List<string> rp = new List<string>();
for (int n = 0; n < (cwd.Length - common); n++) {
rp.Add("..");
}
for (int n = common; n < fp.Length; n++) {
rp.Add(fp[n]);
}
return String.Join("/", rp.ToArray());
} else {
return String.Join("/", fp);
}
}
You want to use the CommonPath
method of this RelativePath
class. Once you have the common path, just strip it out of the path you want to display.
Namespace IO.Path
Public NotInheritable Class RelativePath
Private Declare Function PathRelativePathTo Lib "shlwapi" Alias "PathRelativePathToA" ( _
ByVal pszPath As String, _
ByVal pszFrom As String, _
ByVal dwAttrFrom As Integer, _
ByVal pszTo As String, _
ByVal dwAttrTo As Integer) As Integer
Private Declare Function PathCanonicalize Lib "shlwapi" Alias "PathCanonicalizeA" ( _
ByVal pszBuf As String, _
ByVal pszPath As String) As Integer
Private Const FILE_ATTRIBUTE_DIRECTORY As Short = &H10S
Private Const MAX_PATH As Short = 260
Private _path As String
Private _isDirectory As Boolean
#Region " Constructors "
Public Sub New()
End Sub
Public Sub New(ByVal path As String)
_path = path
End Sub
Public Sub New(ByVal path As String, ByVal isDirectory As Boolean)
_path = path
_isDirectory = isDirectory
End Sub
#End Region
Private Shared Function StripNulls(ByVal value As String) As String
StripNulls = value
If (InStr(value, vbNullChar) > 0) Then
StripNulls = Left(value, InStr(value, vbNullChar) - 1)
End If
End Function
Private Shared Function TrimCurrentDirectory(ByVal path As String) As String
TrimCurrentDirectory = path
If Len(path) >= 2 And Left(path, 2) = ".\" Then
TrimCurrentDirectory = Mid(path, 3)
End If
End Function
''' <summary>
''' 3. conforming to general principles: conforming to accepted principles or standard practice
''' </summary>
Public Shared Function Canonicalize(ByVal path As String) As String
Dim sPath As String
sPath = New String(Chr(0), MAX_PATH)
If PathCanonicalize(sPath, path) = 0 Then
Canonicalize = vbNullString
Else
Canonicalize = StripNulls(sPath)
End If
End Function
''' <summary>
''' Returns the most common path between two paths.
''' </summary>
''' <remarks>
''' <para>returns the path that is common between two paths</para>
''' <para>c:\FolderA\FolderB\FolderC</para>
''' c:\FolderA\FolderD\FolderE\File.Ext
'''
''' results in:
''' c:\FolderA\
''' </remarks>
Public Shared Function CommonPath(ByVal path1 As String, ByVal path2 As String) As String
'returns the path that is common between two paths
'
' c:\FolderA\FolderB\FolderC
' c:\FolderA\FolderD\FolderE\File.Ext
'
' results in:
' c:\FolderA\
Dim sResult As String = String.Empty
Dim iPos1, iPos2 As Integer
path1 = Canonicalize(path1)
path2 = Canonicalize(path2)
Do
If Left(path1, iPos1) = Left(path2, iPos2) Then
sResult = Left(path1, iPos1)
End If
iPos1 = InStr(iPos1 + 1, path1, "\")
iPos2 = InStr(iPos2 + 1, path1, "\")
Loop While Left(path1, iPos1) = Left(path2, iPos2)
Return sResult
End Function
Public Function CommonPath(ByVal path As String) As String
Return CommonPath(_path, path)
End Function
Public Shared Function RelativePathTo(ByVal source As String, ByVal isSourceDirectory As Boolean, ByVal target As String, ByVal isTargetDirectory As Boolean) As String
'DEVLIB
' 05/23/05 1:47PM - Fixed call to PathRelativePathTo, iTargetAttribute is now passed to dwAttrTo instead of IsTargetDirectory.
' For Visual Basic 6.0, the fix does not change testing results,
' because when the Boolean IsTargetDirectory is converted to the Long dwAttrTo it happens to contain FILE_ATTRIBUTE_DIRECTORY,
'
Dim sRelativePath As String
Dim iSourceAttribute, iTargetAttribute As Integer
sRelativePath = New String(Chr(0), MAX_PATH)
source = Canonicalize(source)
target = Canonicalize(target)
If isSourceDirectory Then
iSourceAttribute = FILE_ATTRIBUTE_DIRECTORY
End If
If isTargetDirectory Then
iTargetAttribute = FILE_ATTRIBUTE_DIRECTORY
End If
If PathRelativePathTo(sRelativePath, source, iSourceAttribute, target, iTargetAttribute) = 0 Then
RelativePathTo = vbNullString
Else
RelativePathTo = TrimCurrentDirectory(StripNulls(sRelativePath))
End If
End Function
Public Function RelativePath(ByVal target As String) As String
Return RelativePathTo(_path, _isDirectory, target, False)
End Function
End Class
End Namespace
If you have a readonly text box, could you not not make it a label and set AutoEllipsis=true?
alternatively there are posts with code for generating the autoellipsis yourself: (this does it for a grid, you would need to pass i the width for the text box instead. It isn't quite right as it hacks off a bit more than is necessary, and I haven;t got around to finding where the calculation is incorrect. it would be easy enough to modify to remove the first part of the directory rather than the last if you desire.
Private Function AddEllipsisPath(ByVal text As String, ByVal colIndex As Integer, ByVal grid As DataGridView) As String
'Get the size with the column's width
Dim colWidth As Integer = grid.Columns(colIndex).Width
'Calculate the dimensions of the text with the current font
Dim textSize As SizeF = MeasureString(text, grid.Font)
Dim rawText As String = text
Dim FileNameLen As Integer = text.Length - text.LastIndexOf("\")
Dim ReplaceWith As String = "\..."
Do While textSize.Width > colWidth
' Trim to make room for the ellipsis
Dim LastFolder As Integer = rawText.LastIndexOf("\", rawText.Length - FileNameLen - 1)
If LastFolder < 0 Then
Exit Do
End If
rawText = rawText.Substring(0, LastFolder) + ReplaceWith + rawText.Substring(rawText.Length - FileNameLen)
If ReplaceWith.Length > 0 Then
FileNameLen += 4
ReplaceWith = ""
End If
textSize = MeasureString(rawText, grid.Font)
Loop
Return rawText
End Function
Private Function MeasureString(ByVal text As String, ByVal fontInfo As Font) As SizeF
Dim size As SizeF
Dim emSize As Single = fontInfo.Size
If emSize = 0 Then emSize = 12
Dim stringFont As New Font(fontInfo.Name, emSize)
Dim bmp As New Bitmap(1000, 100)
Dim g As Graphics = Graphics.FromImage(bmp)
size = g.MeasureString(text, stringFont)
g.Dispose()
Return size
End Function