我的应用程序中有一部分显示用户通过OpenFileDialog加载的文件路径.它占用了太多空间来显示整个路径,但我不想只显示文件名,因为它可能不明确.所以我更喜欢显示相对于assembly/exe目录的文件路径.
例如,程序集位于"C:\ Program Files\Dummy Folder\MyProgram",文件位于"C:\ Program Files\Dummy Folder\MyProgram\Data\datafile1.dat",然后我希望它显示".\DATA\datafile1.dat".如果文件位于"C:\ Program Files\Dummy Folder\datafile1.dat"中,那么我想要"..\datafile1.dat".但是,如果文件位于根目录下或根目录下的1个目录中,则显示完整路径.
你会推荐什么解决方案?正则表达式?
基本上我想显示有用的文件路径信息而不占用太多屏幕空间.
编辑:只是为了澄清一点.此解决方案的目的是帮助用户或我自己知道我最后加载了哪个文件,大致来自哪个目录.我正在使用只读文本框来显示路径.大多数情况下,文件路径比文本框的显示空间长得多.该路径应该是提供信息的,但不足以占用更多的屏幕空间.
Alex Brault的评论很好,Jonathan Leffler也是如此.DavidK提供的Win32功能只能帮助解决部分问题,而不是整个问题,但无论如何都要感谢.至于James Newton-King的解决方案,我稍后会在我自由时试一试.
////// Creates a relative path from one file or folder to another. /// /// Contains the directory that defines the start of the relative path. /// Contains the path that defines the endpoint of the relative path. ///The relative path from the start directory to the end path or ///toPath if the paths are not related./// /// 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; }
这个问题有点晚了,但我也需要这个功能.我同意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);
shlwapi.dll中有一个Win32(C++)函数,可以完全按照您的要求执行: PathRelativePathTo()
不过,除了P/Invoke之外,我不知道有什么方法可以从.NET访问它.
如果路径是目录路径,当文件路径不以'/'结尾时,@ Dave的解决方案不起作用.该解决方案解决了该问题,并且还使用Uri.UriSchemeFile
常量而不是硬编码"FILE"
.
这里提供的许多其他解决方案都使用字符串操作,但没有提供关于它们的可靠性的保证或指示,例如单元测试的数量等.总的来说,我建议使用Uri.MakeRelativeUri
是最安全的纯.NET选项,而最好的选择是@ ctacke的Windows互操作示例.
////// Creates a relative path from one file or folder to another. /// /// Contains the directory that defines the start of the relative path. /// Contains the path that defines the endpoint of the relative path. ///The relative path from the start directory to the end path. ////// or is null ./// 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; }
如果您使用的是.NET Core 2.0,Path.GetRelativePath()
则可以使用此特定功能:
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
否则,对于.NET完整框架(从v4.7开始),建议使用其他建议的答案之一.
我过去曾经用过这个.
////// Creates a relative path from one file /// or folder to another. /// /// /// Contains the directory that defines the /// start of the relative path. /// /// /// Contains the path that defines the /// endpoint of the relative path. /// ////// The relative path from the start /// directory to the end path. /// ///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 relativePath = new List (); 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; }
正如Alex Brault指出的那样,特别是在Windows上,绝对路径(带有驱动器号和所有内容)是明确的,通常更好.
您的OpenFileDialog不应该使用常规的树浏览器结构吗?
要获得一些术语,RefDir是您要指定路径的相对目录; 该AbsName是要映射的绝对路径名; 并且RelPath是生成的相对路径.
选择匹配的第一个选项:
如果您有不同的驱动器号,则没有从RefDir到AbsName的相对路径; 你必须使用AbsName.
如果AbsName位于RefDir的子目录中或者是RefDir中的文件,则只需从AbsName的开头删除RefDir即可创建RelPath; 可选地预先添加"./"(或".\",因为您在Windows上).
找到RefDir和AbsName的最长公共前缀(其中D:\ Abc\Def和D:\ Abc\Default共享D:\ Abc作为最长的公共前缀;它必须是名称组件的映射,而不是简单的最长公共子); 称之为LCP.从AbsName和RefDir中删除LCP.对于(RefDir - LCP)中剩下的每个路径组件,将".."添加到(AbsName - LCP)以产生RelPath.
为了说明最后一条规则(当然,到目前为止最复杂的规则),从以下开始:
RefDir = D:\Abc\Def\Ghi AbsName = D:\Abc\Default\Karma\Crucible
然后
LCP = D:\Abc (RefDir - LCP) = Def\Ghi (Absname - LCP) = Default\Karma\Crucible RelPath = ..\..\Default\Karma\Crucible
在我打字的时候,DavidK提出了一个答案,表明你不是第一个需要这个功能的人,并且有一个标准的功能来完成这项工作. 用它. 但是,从第一原则开始思考你的方式也没有坏处.
除了Unix系统不支持驱动器号(因此所有内容始终位于同一根目录下,因此第一个子弹无关紧要),可以在Unix上使用相同的技术.