如何从绝对路径中得到相对路径

时间:2022-09-07 22:51:24

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 to display only the filename as it might be ambiguous. So I would prefer to show the file path relative to the assembly/exe directory.

我的应用程序中有一个部分显示用户通过OpenFileDialog加载的文件路径。它占用了太多的空间来显示整个路径,但是我不想只显示文件名,因为它可能不明确。因此,我希望显示相对于程序集/exe目录的文件路径。

For example, the assembly resides at "C:\Program Files\Dummy Folder\MyProgram" and the file at "C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat" then I would like it to show ".\Data\datafile1.dat". If the file is in "C:\Program Files\Dummy Folder\datafile1.dat", then I would want "..\datafile1.dat". But if the file is at the root directory or 1 directory below root, then display the full path.

例如,程序集驻留在“C:\程序文件\虚拟文件夹\MyProgram”和文件“C:\程序文件\虚拟文件夹\MyProgram\Data\datafile1”中。“那么我想让它显示”\Data\datafile1.dat“。如果文件在“C:\程序文件\虚拟文件夹\datafile1”中。那么我想要". \datafile1.dat"。但如果文件位于根目录或根下的一个目录,则显示完整的路径。

What solution would you recommend? Regex?

你有什么建议吗?正则表达式?

Basically I want to display useful file path info without taking too much screen space.

基本上,我想要显示有用的文件路径信息而不占用太多的屏幕空间。

EDIT: Just to clarify a little bit more. The purpose of this solution is to help user or myself knowing which file did I loaded last and roughly from which directory was it from. I'm using a readonly textbox to display the path. Most of the time, the file path is much longer than the display space of the textbox. The path is supposed to be informative but not important enough as to take up more screen space.

编辑:再澄清一点。这个解决方案的目的是帮助用户或我自己知道我最后一次加载哪个文件,大概是从哪个目录加载的。我使用一个只读文本框来显示路径。大多数时候,文件路径要比文本框的显示空间长得多。这条路径应该是信息丰富的,但不够重要,不能占用更多的屏幕空间。

Alex Brault comment was good, so is Jonathan Leffler. The Win32 function provided by DavidK only help with part of the problem, not the whole of it, but thanks anyway. As for James Newton-King solution, I'll give it a try later when I'm free.

Alex Brault说得很好,Jonathan Leffler也是如此。由DavidK提供的Win32功能只帮助解决部分问题,而不是整个问题,但是还是要谢谢你。至于詹姆斯·牛顿-金的解决方案,等我有空的时候再试一试。

21 个解决方案

#1


166  

/// <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;
}

#2


48  

A bit late to the question, but I just needed this feature as well. I agree with DavidK that since there is a built-in API function that provides this, you should use it. Here's a managed wrapper for it:

这个问题有点晚了,但是我也需要这个特性。我同意DavidK的观点,因为有一个内置的API函数提供这个功能,所以您应该使用它。这里有一个托管包装:

public static string GetRelativePath(string fromPath, string toPath)
{
    int fromAttr = GetPathAttribute(fromPath);
    int toAttr = GetPathAttribute(toPath);

    StringBuilder path = new StringBuilder(260); // MAX_PATH
    if(PathRelativePathTo(
        path,
        fromPath,
        fromAttr,
        toPath,
        toAttr) == 0)
    {
        throw new ArgumentException("Paths must have a common prefix");
    }
    return path.ToString();
}

private static int GetPathAttribute(string path)
{
    DirectoryInfo di = new DirectoryInfo(path);
    if (di.Exists)
    {
        return FILE_ATTRIBUTE_DIRECTORY;
    }

    FileInfo fi = new FileInfo(path);
    if(fi.Exists)
    {
        return FILE_ATTRIBUTE_NORMAL;
    }

    throw new FileNotFoundException();
}

private const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
private const int FILE_ATTRIBUTE_NORMAL = 0x80;

[DllImport("shlwapi.dll", SetLastError = true)]
private static extern int PathRelativePathTo(StringBuilder pszPath, 
    string pszFrom, int dwAttrFrom, string pszTo, int dwAttrTo);

#3


24  

There is a Win32 (C++) function in shlwapi.dll that does exactly what you want: PathRelativePathTo()

shlwapi中有一个Win32 (c++)函数。dll文件:PathRelativePathTo()

I'm not aware of any way to access this from .NET other than to P/Invoke it, though.

我不知道除了P/调用它之外还有什么方法可以从。net访问它。

#4


17  

@Dave's solution does not work when the file paths do not end with a '/' if the path is a directory path. This solution fixes that problem and also makes use of the Uri.UriSchemeFile constant instead of hard coding "FILE".

如果路径是目录路径,那么当文件路径以'/'结尾时,@Dave的解决方案就不起作用。这个解决方案解决了这个问题,并使用了Uri。UriSchemeFile常量代替了硬编码“FILE”。

Many of the other solutions provided here use string manipulation but provide no guarantees or indications as to how reliable they are, such as number of unit tests etc. So overall I would suggest that using Uri.MakeRelativeUri is the safest pure .NET option, while the best alternative is @ctacke's Windows interop example.

这里提供的许多其他解决方案都使用字符串操作,但不提供关于它们的可靠性的保证或指示,比如单元测试的数量等等。所以总的来说,我建议使用Uri。MakeRelativeUri是最安全的纯。net选项,而最好的替代方案是@ctacke的Windows interop示例。

/// <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.</returns>
/// <exception cref="ArgumentNullException"><paramref name="fromPath"/> or <paramref name="toPath"/> is <c>null</c>.</exception>
/// <exception cref="UriFormatException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public static string GetRelativePath(string fromPath, string toPath)
{
    if (string.IsNullOrEmpty(fromPath))
    {
        throw new ArgumentNullException("fromPath");
    }

    if (string.IsNullOrEmpty(toPath))
    {
        throw new ArgumentNullException("toPath");
    }

    Uri fromUri = new Uri(AppendDirectorySeparatorChar(fromPath));
    Uri toUri = new Uri(AppendDirectorySeparatorChar(toPath));

    if (fromUri.Scheme != toUri.Scheme)
    {
        return toPath;
    }

    Uri relativeUri = fromUri.MakeRelativeUri(toUri);
    string relativePath = Uri.UnescapeDataString(relativeUri.ToString());

    if (string.Equals(toUri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
    {
        relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
    }

    return relativePath;
}

private static string AppendDirectorySeparatorChar(string path)
{
    // Append a slash only if the path is a directory and does not have a slash.
    if (!Path.HasExtension(path) &&
        !path.EndsWith(Path.DirectorySeparatorChar.ToString()))
    {
        return path + Path.DirectorySeparatorChar;
    }

    return path;
}

#5


9  

I have used this in the past.

我以前用过这个。

/// <summary>
/// Creates a relative path from one file
/// or folder to another.
/// </summary>
/// <param name="fromDirectory">
/// 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.
/// </returns>
/// <exception cref="ArgumentNullException"></exception>
public static string MakeRelative(string fromDirectory, string toPath)
{
  if (fromDirectory == null)
    throw new ArgumentNullException("fromDirectory");

  if (toPath == null)
    throw new ArgumentNullException("toPath");

  bool isRooted = (Path.IsPathRooted(fromDirectory) && Path.IsPathRooted(toPath));

  if (isRooted)
  {
    bool isDifferentRoot = (string.Compare(Path.GetPathRoot(fromDirectory), Path.GetPathRoot(toPath), true) != 0);

    if (isDifferentRoot)
      return toPath;
  }

  List<string> relativePath = new List<string>();
  string[] fromDirectories = fromDirectory.Split(Path.DirectorySeparatorChar);

  string[] toDirectories = toPath.Split(Path.DirectorySeparatorChar);

  int length = Math.Min(fromDirectories.Length, toDirectories.Length);

  int lastCommonRoot = -1;

  // find common root
  for (int x = 0; x < length; x++)
  {
    if (string.Compare(fromDirectories[x], toDirectories[x], true) != 0)
      break;

    lastCommonRoot = x;
  }

  if (lastCommonRoot == -1)
    return toPath;

  // add relative folders in from path
  for (int x = lastCommonRoot + 1; x < fromDirectories.Length; x++)
  {
    if (fromDirectories[x].Length > 0)
      relativePath.Add("..");
  }

  // add to folders to path
  for (int x = lastCommonRoot + 1; x < toDirectories.Length; x++)
  {
    relativePath.Add(toDirectories[x]);
  }

  // create relative path
  string[] relativeParts = new string[relativePath.Count];
  relativePath.CopyTo(relativeParts, 0);

  string newPath = string.Join(Path.DirectorySeparatorChar.ToString(), relativeParts);

  return newPath;
}

#6


5  

As Alex Brault points out, especially on Windows, the absolute path (with drive letter and all) is unambiguous and often better.

正如Alex Brault指出的,尤其是在Windows上,绝对路径(带有驱动器字母和所有)是明确的,而且通常更好。

Shouldn't your OpenFileDialog use a regular tree-browser structure?

您的OpenFileDialog不应该使用常规的树浏览器结构吗?

To get some nomenclature in place, the RefDir is the directory relative to which you want to specify the path; the AbsName is the absolute path name that you want to map; and the RelPath is the resulting relative path.

为了获得一些命名空间,RefDir是您想要指定路径的目录;AbsName是要映射的绝对路径名;而RelPath是相对路径。

Take the first of these options that matches:

选择第一个匹配的选项:

  • If you have different drive letters, there is no relative path from RefDir to AbsName; you must use the AbsName.
  • 如果有不同的驱动器号,则从RefDir到AbsName没有相关路径;你必须使用潜名。
  • If the AbsName is in a sub-directory of RefDir or is a file within RefDir then simply remove the RefDir from the start of AbsName to create RelPath; optionally prepend "./" (or ".\" since you are on Windows).
  • 如果AbsName位于RefDir的子目录中,或者是RefDir中的文件,那么只需从AbsName开头删除RefDir,创建RelPath;可以选择前置”。(或“/”。因为你在Windows上)。
  • Find the longest common prefix of RefDir and AbsName (where D:\Abc\Def and D:\Abc\Default share D:\Abc as the longest common prefix; it has to be a mapping of name components, not a simple longest common substring); call it LCP. Remove LCP from AbsName and RefDir. For each path component left in (RefDir - LCP), prepend "..\" to (AbsName - LCP) to yield RelPath.
  • 查找RefDir和AbsName的最长公共前缀(其中D:\Abc\Def和D:\Abc默认共享D:\Abc为最长公共前缀;它必须是名称组件的映射,而不是简单的最长公共子字符串);称之为连结控制协定。从AbsName和RefDir中删除LCP。对于(RefDir - LCP)中剩下的每个路径组件,“.. .”“to(潜行- LCP)来产生RelPath。

To illustrate the last rule (which is, of course, by far the most complex), start with:

为了说明最后一条规则(当然,这是迄今为止最复杂的一条规则),请从以下几点开始:

RefDir = D:\Abc\Def\Ghi
AbsName = D:\Abc\Default\Karma\Crucible

Then

然后

LCP = D:\Abc
(RefDir - LCP) = Def\Ghi
(Absname - LCP) = Default\Karma\Crucible
RelPath = ..\..\Default\Karma\Crucible

While I was typing, DavidK produced an answer which suggests that you are not the first to need this feature and that there is a standard function to do this job. Use it. But there's no harm in being able to think your way through from first principles, either.

在我打字的时候,DavidK给出了一个答案,这表明你不是第一个需要这个功能的人,并且有一个标准的功能来完成这项工作。使用它。但是从基本原则出发思考问题也没有坏处。

Except that Unix systems do not support drive letters (so everything is always located under the same root directory, and the first bullet therefore is irrelevant), the same technique could be used on Unix.

除了Unix系统不支持驱动器字母(因此所有内容总是位于相同的根目录下,因此第一个项目是不相关的)之外,在Unix上也可以使用相同的技术。

#7


4  

It's a long way around, but System.Uri class has a method named MakeRelativeUri. Maybe you could use that. It's a shame really that System.IO.Path doesn't have this.

这是一段很长的路,但是系统。Uri类有一个名为MakeRelativeUri的方法。也许你可以用这个。真可惜那个系统。没有这个路径。

#8


2  

If you're sure that your absolute path 2 is always relative to absolute path, just remove the first N characters from path2, where N is the length of path1.

如果你确定你的绝对路径2总是相对于绝对路径,只需从路径2中删除前N个字符,其中N是路径1的长度。

#9


2  

If you know that toPath is contained by fromPath then you can keep it simple. I'll leave out the asserts for brevity.

如果你知道toPath是由fromPath包含的,那么你可以保持简单。我将省略简短的断言。

public static string MakeRelativePath(string fromPath, string toPath)
{
    // use Path.GetFullPath to canonicalise the paths (deal with multiple directory seperators, etc)
    return Path.GetFullPath(toPath).Substring(Path.GetFullPath(fromPath).Length + 1);
}

#10


2  

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.

您希望使用相对论路径类的CommonPath方法。一旦有了公共路径,就把它从想要显示的路径中去掉。

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

#11


2  

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

#12


2  

Use:

使用:

RelPath = AbsPath.Replace(ApplicationPath, ".")

#13


1  

I'd split both of your paths at the directory level. From there, find the point of divergence and work your way back to the assembly folder, prepending a '../' everytime you pass a directory.

我把你的路径都分割到目录层。从那里,找到发散点,然后回到程序集文件夹,在a '.. ./'每次你通过一个目录。

Keep in mind however, that an absolute path works everywhere and is usually easier to read than a relative one. I personally wouldn't show an user a relative path unless it was absolutely necessary.

但是请记住,绝对路径在任何地方都适用,而且通常比相对路径更容易阅读。我个人不会向用户显示相对路径,除非它是绝对必要的。

#14


1  

The function that uses URI returned "almost" relative path. It included directory that directly contains the file which relative path I wanted to get.

使用URI的函数返回“几乎”的相对路径。它包括直接包含我想要获得的相对路径的文件的目录。

Some time ago I wrote a simple function that returns relative path of folder or file, and even if it's on another drive, it includes the drive letter as well.

不久前,我编写了一个简单的函数,它返回文件夹或文件的相对路径,即使它在另一个驱动器上,它也包含驱动器字母。

Please take a look:

请看看:

    public static string GetRelativePath(string BasePath, string AbsolutePath)
    {
        char Separator = Path.DirectorySeparatorChar;
        if (string.IsNullOrWhiteSpace(BasePath)) BasePath = Directory.GetCurrentDirectory();
        var ReturnPath = "";
        var CommonPart = "";
        var BasePathFolders = BasePath.Split(Separator);
        var AbsolutePathFolders = AbsolutePath.Split(Separator);
        var i = 0;
        while (i < BasePathFolders.Length & i < AbsolutePathFolders.Length)
        {
            if (BasePathFolders[i].ToLower() == AbsolutePathFolders[i].ToLower())
            {
                CommonPart += BasePathFolders[i] + Separator;
            }
            else
            {
                break;
            }
            i += 1;
        }
        if (CommonPart.Length > 0)
        {
            var parents = BasePath.Substring(CommonPart.Length - 1).Split(Separator);
            foreach (var ParentDir in parents)
            {
                if (!string.IsNullOrEmpty(ParentDir))
                    ReturnPath += ".." + Separator;
            }
        }
        ReturnPath += AbsolutePath.Substring(CommonPart.Length);
        return ReturnPath;
    }

#15


1  

If you have a readonly text box, could you not not make it a label and set AutoEllipsis=true?

如果您有一个只读文本框,难道您不能将它设置为一个标签并设置自动省略号=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.

另一种方法是使用代码生成自动省略号:(对于网格,您需要将文本框的宽度传递给i。这并不完全正确,因为它删去了一些不必要的东西,而且我还没有找到计算错误的地方。如果您愿意,可以很容易地修改以删除目录的第一部分,而不是最后一部分。

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

#16


0  

    public static string ToRelativePath(string filePath, string refPath)
    {
        var pathNormalized = Path.GetFullPath(filePath);

        var refNormalized = Path.GetFullPath(refPath);
        refNormalized = refNormalized.TrimEnd('\\', '/');

        if (!pathNormalized.StartsWith(refNormalized))
            throw new ArgumentException();
        var res = pathNormalized.Substring(refNormalized.Length + 1);
        return res;
    }

#17


0  

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

#18


0  

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.

在linux/macOS系统上无法使用Uri。路径'/var/www/root'不能转换为Uri。更普遍的方法是:全凭双手。

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.

PS:我用“/”作为分隔符的默认值,而不是路径。DirectorySeparatorChar,因为这个方法在我的应用程序中用作uri。

#19


0  

here's mine:

这是我的:

public static string RelativePathTo(this System.IO.DirectoryInfo @this, string to)
{
    var rgFrom = @this.FullName.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
    var rgTo = to.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
    var cSame = rgFrom.TakeWhile((p, i) => i < rgTo.Length && string.Equals(p, rgTo[i])).Count();

    return Path.Combine(
        Enumerable.Range(0, rgFrom.Length - cSame)
        .Select(_ => "..")
        .Concat(rgTo.Skip(cSame))
        .ToArray()
    );
}

#20


0  

Play with something like:

玩类似:

private String GetRelativePath(Int32 level, String directory, out String errorMessage) {
        if (level < 0 || level > 5) {
            errorMessage = "Find some more smart input data";
            return String.Empty;
        }
        // ==========================
        while (level != 0) {
            directory = Path.GetDirectoryName(directory);
            level -= 1;
        }
        // ==========================
        errorMessage = String.Empty;
        return directory;
    }

And test it

和测试它

[Test]
    public void RelativeDirectoryPathTest() {
        var relativePath =
            GetRelativePath(3, AppDomain.CurrentDomain.BaseDirectory, out var errorMessage);
        Console.WriteLine(relativePath);
        if (String.IsNullOrEmpty(errorMessage) == false) {
            Console.WriteLine(errorMessage);
            Assert.Fail("Can not find relative path");
        }
    }

#21


0  

If you are using .NET Core 2.0, Path.GetRelativePath() is available providing this specific functionality:

如果您正在使用。net Core 2.0,可以使用path . get相对论路径()提供以下特定功能:

        var relativeTo = @"C:\Program Files\Dummy Folder\MyProgram";
        var path = @"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat";

        string relativePath = System.IO.Path.GetRelativePath(relativeTo, path);

        System.Console.WriteLine(relativePath);
        // output --> Data\datafile1.dat 

Otherwise, for .NET full framework (as of v4.7) recommend using one of the other suggested answers.

否则,对于。net完整框架(如v4.7),建议使用其他建议的答案之一。

#1


166  

/// <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;
}

#2


48  

A bit late to the question, but I just needed this feature as well. I agree with DavidK that since there is a built-in API function that provides this, you should use it. Here's a managed wrapper for it:

这个问题有点晚了,但是我也需要这个特性。我同意DavidK的观点,因为有一个内置的API函数提供这个功能,所以您应该使用它。这里有一个托管包装:

public static string GetRelativePath(string fromPath, string toPath)
{
    int fromAttr = GetPathAttribute(fromPath);
    int toAttr = GetPathAttribute(toPath);

    StringBuilder path = new StringBuilder(260); // MAX_PATH
    if(PathRelativePathTo(
        path,
        fromPath,
        fromAttr,
        toPath,
        toAttr) == 0)
    {
        throw new ArgumentException("Paths must have a common prefix");
    }
    return path.ToString();
}

private static int GetPathAttribute(string path)
{
    DirectoryInfo di = new DirectoryInfo(path);
    if (di.Exists)
    {
        return FILE_ATTRIBUTE_DIRECTORY;
    }

    FileInfo fi = new FileInfo(path);
    if(fi.Exists)
    {
        return FILE_ATTRIBUTE_NORMAL;
    }

    throw new FileNotFoundException();
}

private const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
private const int FILE_ATTRIBUTE_NORMAL = 0x80;

[DllImport("shlwapi.dll", SetLastError = true)]
private static extern int PathRelativePathTo(StringBuilder pszPath, 
    string pszFrom, int dwAttrFrom, string pszTo, int dwAttrTo);

#3


24  

There is a Win32 (C++) function in shlwapi.dll that does exactly what you want: PathRelativePathTo()

shlwapi中有一个Win32 (c++)函数。dll文件:PathRelativePathTo()

I'm not aware of any way to access this from .NET other than to P/Invoke it, though.

我不知道除了P/调用它之外还有什么方法可以从。net访问它。

#4


17  

@Dave's solution does not work when the file paths do not end with a '/' if the path is a directory path. This solution fixes that problem and also makes use of the Uri.UriSchemeFile constant instead of hard coding "FILE".

如果路径是目录路径,那么当文件路径以'/'结尾时,@Dave的解决方案就不起作用。这个解决方案解决了这个问题,并使用了Uri。UriSchemeFile常量代替了硬编码“FILE”。

Many of the other solutions provided here use string manipulation but provide no guarantees or indications as to how reliable they are, such as number of unit tests etc. So overall I would suggest that using Uri.MakeRelativeUri is the safest pure .NET option, while the best alternative is @ctacke's Windows interop example.

这里提供的许多其他解决方案都使用字符串操作,但不提供关于它们的可靠性的保证或指示,比如单元测试的数量等等。所以总的来说,我建议使用Uri。MakeRelativeUri是最安全的纯。net选项,而最好的替代方案是@ctacke的Windows interop示例。

/// <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.</returns>
/// <exception cref="ArgumentNullException"><paramref name="fromPath"/> or <paramref name="toPath"/> is <c>null</c>.</exception>
/// <exception cref="UriFormatException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public static string GetRelativePath(string fromPath, string toPath)
{
    if (string.IsNullOrEmpty(fromPath))
    {
        throw new ArgumentNullException("fromPath");
    }

    if (string.IsNullOrEmpty(toPath))
    {
        throw new ArgumentNullException("toPath");
    }

    Uri fromUri = new Uri(AppendDirectorySeparatorChar(fromPath));
    Uri toUri = new Uri(AppendDirectorySeparatorChar(toPath));

    if (fromUri.Scheme != toUri.Scheme)
    {
        return toPath;
    }

    Uri relativeUri = fromUri.MakeRelativeUri(toUri);
    string relativePath = Uri.UnescapeDataString(relativeUri.ToString());

    if (string.Equals(toUri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
    {
        relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
    }

    return relativePath;
}

private static string AppendDirectorySeparatorChar(string path)
{
    // Append a slash only if the path is a directory and does not have a slash.
    if (!Path.HasExtension(path) &&
        !path.EndsWith(Path.DirectorySeparatorChar.ToString()))
    {
        return path + Path.DirectorySeparatorChar;
    }

    return path;
}

#5


9  

I have used this in the past.

我以前用过这个。

/// <summary>
/// Creates a relative path from one file
/// or folder to another.
/// </summary>
/// <param name="fromDirectory">
/// 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.
/// </returns>
/// <exception cref="ArgumentNullException"></exception>
public static string MakeRelative(string fromDirectory, string toPath)
{
  if (fromDirectory == null)
    throw new ArgumentNullException("fromDirectory");

  if (toPath == null)
    throw new ArgumentNullException("toPath");

  bool isRooted = (Path.IsPathRooted(fromDirectory) && Path.IsPathRooted(toPath));

  if (isRooted)
  {
    bool isDifferentRoot = (string.Compare(Path.GetPathRoot(fromDirectory), Path.GetPathRoot(toPath), true) != 0);

    if (isDifferentRoot)
      return toPath;
  }

  List<string> relativePath = new List<string>();
  string[] fromDirectories = fromDirectory.Split(Path.DirectorySeparatorChar);

  string[] toDirectories = toPath.Split(Path.DirectorySeparatorChar);

  int length = Math.Min(fromDirectories.Length, toDirectories.Length);

  int lastCommonRoot = -1;

  // find common root
  for (int x = 0; x < length; x++)
  {
    if (string.Compare(fromDirectories[x], toDirectories[x], true) != 0)
      break;

    lastCommonRoot = x;
  }

  if (lastCommonRoot == -1)
    return toPath;

  // add relative folders in from path
  for (int x = lastCommonRoot + 1; x < fromDirectories.Length; x++)
  {
    if (fromDirectories[x].Length > 0)
      relativePath.Add("..");
  }

  // add to folders to path
  for (int x = lastCommonRoot + 1; x < toDirectories.Length; x++)
  {
    relativePath.Add(toDirectories[x]);
  }

  // create relative path
  string[] relativeParts = new string[relativePath.Count];
  relativePath.CopyTo(relativeParts, 0);

  string newPath = string.Join(Path.DirectorySeparatorChar.ToString(), relativeParts);

  return newPath;
}

#6


5  

As Alex Brault points out, especially on Windows, the absolute path (with drive letter and all) is unambiguous and often better.

正如Alex Brault指出的,尤其是在Windows上,绝对路径(带有驱动器字母和所有)是明确的,而且通常更好。

Shouldn't your OpenFileDialog use a regular tree-browser structure?

您的OpenFileDialog不应该使用常规的树浏览器结构吗?

To get some nomenclature in place, the RefDir is the directory relative to which you want to specify the path; the AbsName is the absolute path name that you want to map; and the RelPath is the resulting relative path.

为了获得一些命名空间,RefDir是您想要指定路径的目录;AbsName是要映射的绝对路径名;而RelPath是相对路径。

Take the first of these options that matches:

选择第一个匹配的选项:

  • If you have different drive letters, there is no relative path from RefDir to AbsName; you must use the AbsName.
  • 如果有不同的驱动器号,则从RefDir到AbsName没有相关路径;你必须使用潜名。
  • If the AbsName is in a sub-directory of RefDir or is a file within RefDir then simply remove the RefDir from the start of AbsName to create RelPath; optionally prepend "./" (or ".\" since you are on Windows).
  • 如果AbsName位于RefDir的子目录中,或者是RefDir中的文件,那么只需从AbsName开头删除RefDir,创建RelPath;可以选择前置”。(或“/”。因为你在Windows上)。
  • Find the longest common prefix of RefDir and AbsName (where D:\Abc\Def and D:\Abc\Default share D:\Abc as the longest common prefix; it has to be a mapping of name components, not a simple longest common substring); call it LCP. Remove LCP from AbsName and RefDir. For each path component left in (RefDir - LCP), prepend "..\" to (AbsName - LCP) to yield RelPath.
  • 查找RefDir和AbsName的最长公共前缀(其中D:\Abc\Def和D:\Abc默认共享D:\Abc为最长公共前缀;它必须是名称组件的映射,而不是简单的最长公共子字符串);称之为连结控制协定。从AbsName和RefDir中删除LCP。对于(RefDir - LCP)中剩下的每个路径组件,“.. .”“to(潜行- LCP)来产生RelPath。

To illustrate the last rule (which is, of course, by far the most complex), start with:

为了说明最后一条规则(当然,这是迄今为止最复杂的一条规则),请从以下几点开始:

RefDir = D:\Abc\Def\Ghi
AbsName = D:\Abc\Default\Karma\Crucible

Then

然后

LCP = D:\Abc
(RefDir - LCP) = Def\Ghi
(Absname - LCP) = Default\Karma\Crucible
RelPath = ..\..\Default\Karma\Crucible

While I was typing, DavidK produced an answer which suggests that you are not the first to need this feature and that there is a standard function to do this job. Use it. But there's no harm in being able to think your way through from first principles, either.

在我打字的时候,DavidK给出了一个答案,这表明你不是第一个需要这个功能的人,并且有一个标准的功能来完成这项工作。使用它。但是从基本原则出发思考问题也没有坏处。

Except that Unix systems do not support drive letters (so everything is always located under the same root directory, and the first bullet therefore is irrelevant), the same technique could be used on Unix.

除了Unix系统不支持驱动器字母(因此所有内容总是位于相同的根目录下,因此第一个项目是不相关的)之外,在Unix上也可以使用相同的技术。

#7


4  

It's a long way around, but System.Uri class has a method named MakeRelativeUri. Maybe you could use that. It's a shame really that System.IO.Path doesn't have this.

这是一段很长的路,但是系统。Uri类有一个名为MakeRelativeUri的方法。也许你可以用这个。真可惜那个系统。没有这个路径。

#8


2  

If you're sure that your absolute path 2 is always relative to absolute path, just remove the first N characters from path2, where N is the length of path1.

如果你确定你的绝对路径2总是相对于绝对路径,只需从路径2中删除前N个字符,其中N是路径1的长度。

#9


2  

If you know that toPath is contained by fromPath then you can keep it simple. I'll leave out the asserts for brevity.

如果你知道toPath是由fromPath包含的,那么你可以保持简单。我将省略简短的断言。

public static string MakeRelativePath(string fromPath, string toPath)
{
    // use Path.GetFullPath to canonicalise the paths (deal with multiple directory seperators, etc)
    return Path.GetFullPath(toPath).Substring(Path.GetFullPath(fromPath).Length + 1);
}

#10


2  

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.

您希望使用相对论路径类的CommonPath方法。一旦有了公共路径,就把它从想要显示的路径中去掉。

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

#11


2  

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

#12


2  

Use:

使用:

RelPath = AbsPath.Replace(ApplicationPath, ".")

#13


1  

I'd split both of your paths at the directory level. From there, find the point of divergence and work your way back to the assembly folder, prepending a '../' everytime you pass a directory.

我把你的路径都分割到目录层。从那里,找到发散点,然后回到程序集文件夹,在a '.. ./'每次你通过一个目录。

Keep in mind however, that an absolute path works everywhere and is usually easier to read than a relative one. I personally wouldn't show an user a relative path unless it was absolutely necessary.

但是请记住,绝对路径在任何地方都适用,而且通常比相对路径更容易阅读。我个人不会向用户显示相对路径,除非它是绝对必要的。

#14


1  

The function that uses URI returned "almost" relative path. It included directory that directly contains the file which relative path I wanted to get.

使用URI的函数返回“几乎”的相对路径。它包括直接包含我想要获得的相对路径的文件的目录。

Some time ago I wrote a simple function that returns relative path of folder or file, and even if it's on another drive, it includes the drive letter as well.

不久前,我编写了一个简单的函数,它返回文件夹或文件的相对路径,即使它在另一个驱动器上,它也包含驱动器字母。

Please take a look:

请看看:

    public static string GetRelativePath(string BasePath, string AbsolutePath)
    {
        char Separator = Path.DirectorySeparatorChar;
        if (string.IsNullOrWhiteSpace(BasePath)) BasePath = Directory.GetCurrentDirectory();
        var ReturnPath = "";
        var CommonPart = "";
        var BasePathFolders = BasePath.Split(Separator);
        var AbsolutePathFolders = AbsolutePath.Split(Separator);
        var i = 0;
        while (i < BasePathFolders.Length & i < AbsolutePathFolders.Length)
        {
            if (BasePathFolders[i].ToLower() == AbsolutePathFolders[i].ToLower())
            {
                CommonPart += BasePathFolders[i] + Separator;
            }
            else
            {
                break;
            }
            i += 1;
        }
        if (CommonPart.Length > 0)
        {
            var parents = BasePath.Substring(CommonPart.Length - 1).Split(Separator);
            foreach (var ParentDir in parents)
            {
                if (!string.IsNullOrEmpty(ParentDir))
                    ReturnPath += ".." + Separator;
            }
        }
        ReturnPath += AbsolutePath.Substring(CommonPart.Length);
        return ReturnPath;
    }

#15


1  

If you have a readonly text box, could you not not make it a label and set AutoEllipsis=true?

如果您有一个只读文本框,难道您不能将它设置为一个标签并设置自动省略号=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.

另一种方法是使用代码生成自动省略号:(对于网格,您需要将文本框的宽度传递给i。这并不完全正确,因为它删去了一些不必要的东西,而且我还没有找到计算错误的地方。如果您愿意,可以很容易地修改以删除目录的第一部分,而不是最后一部分。

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

#16


0  

    public static string ToRelativePath(string filePath, string refPath)
    {
        var pathNormalized = Path.GetFullPath(filePath);

        var refNormalized = Path.GetFullPath(refPath);
        refNormalized = refNormalized.TrimEnd('\\', '/');

        if (!pathNormalized.StartsWith(refNormalized))
            throw new ArgumentException();
        var res = pathNormalized.Substring(refNormalized.Length + 1);
        return res;
    }

#17


0  

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

#18


0  

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.

在linux/macOS系统上无法使用Uri。路径'/var/www/root'不能转换为Uri。更普遍的方法是:全凭双手。

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.

PS:我用“/”作为分隔符的默认值,而不是路径。DirectorySeparatorChar,因为这个方法在我的应用程序中用作uri。

#19


0  

here's mine:

这是我的:

public static string RelativePathTo(this System.IO.DirectoryInfo @this, string to)
{
    var rgFrom = @this.FullName.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
    var rgTo = to.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
    var cSame = rgFrom.TakeWhile((p, i) => i < rgTo.Length && string.Equals(p, rgTo[i])).Count();

    return Path.Combine(
        Enumerable.Range(0, rgFrom.Length - cSame)
        .Select(_ => "..")
        .Concat(rgTo.Skip(cSame))
        .ToArray()
    );
}

#20


0  

Play with something like:

玩类似:

private String GetRelativePath(Int32 level, String directory, out String errorMessage) {
        if (level < 0 || level > 5) {
            errorMessage = "Find some more smart input data";
            return String.Empty;
        }
        // ==========================
        while (level != 0) {
            directory = Path.GetDirectoryName(directory);
            level -= 1;
        }
        // ==========================
        errorMessage = String.Empty;
        return directory;
    }

And test it

和测试它

[Test]
    public void RelativeDirectoryPathTest() {
        var relativePath =
            GetRelativePath(3, AppDomain.CurrentDomain.BaseDirectory, out var errorMessage);
        Console.WriteLine(relativePath);
        if (String.IsNullOrEmpty(errorMessage) == false) {
            Console.WriteLine(errorMessage);
            Assert.Fail("Can not find relative path");
        }
    }

#21


0  

If you are using .NET Core 2.0, Path.GetRelativePath() is available providing this specific functionality:

如果您正在使用。net Core 2.0,可以使用path . get相对论路径()提供以下特定功能:

        var relativeTo = @"C:\Program Files\Dummy Folder\MyProgram";
        var path = @"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat";

        string relativePath = System.IO.Path.GetRelativePath(relativeTo, path);

        System.Console.WriteLine(relativePath);
        // output --> Data\datafile1.dat 

Otherwise, for .NET full framework (as of v4.7) recommend using one of the other suggested answers.

否则,对于。net完整框架(如v4.7),建议使用其他建议的答案之一。