195 Stimmen

Wie erhalte ich einen relativen Pfad aus einem absoluten Pfad?

In meinen Anwendungen gibt es einen Teil, der den Dateipfad anzeigt, der vom Benutzer über OpenFileDialog geladen wurde. Es nimmt zu viel Platz ein, um den gesamten Pfad anzuzeigen, aber ich möchte nicht nur den Dateinamen anzeigen, da dieser zweideutig sein könnte. Ich würde es also vorziehen, den Dateipfad relativ zum Assembly/Exe-Verzeichnis anzuzeigen.

Zum Beispiel befindet sich die Baugruppe unter C:\Program Files\Dummy Folder\MyProgram und die Datei unter C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat dann möchte ich, dass es Folgendes anzeigt .\Data\datafile1.dat . Wenn die Datei in C:\Program Files\Dummy Folder\datafile1.dat dann würde ich wollen ..\datafile1.dat . Befindet sich die Datei jedoch im Stammverzeichnis oder in einem Verzeichnis unterhalb des Stammverzeichnisses, so wird der vollständige Pfad angezeigt.

Welche Lösung würden Sie empfehlen? Regex?

Grundsätzlich möchte ich nützliche Informationen zum Dateipfad anzeigen, ohne zu viel Platz auf dem Bildschirm einzunehmen.

EDIT: Nur um ein wenig mehr Klarheit zu schaffen. Der Zweck dieser Lösung ist es, dem Benutzer oder mir selbst zu helfen, zu wissen, welche Datei ich zuletzt geladen habe und aus welchem Verzeichnis sie ungefähr stammt. Ich verwende ein schreibgeschütztes Textfeld, um den Pfad anzuzeigen. Meistens ist der Dateipfad viel länger als der Anzeigebereich des Textfelds. Der Pfad soll informativ sein, aber nicht so wichtig, dass er mehr Platz auf dem Bildschirm einnimmt.

Der Kommentar von Alex Brault war gut, der von Jonathan Leffler auch. Die von DavidK bereitgestellte Win32-Funktion hilft nur bei einem Teil des Problems, nicht bei der Gesamtheit, aber trotzdem danke. Was die Lösung von James Newton-King betrifft, so werde ich sie später ausprobieren, wenn ich Zeit habe.

2 Stimmen

211voto

.NET Core 2.0 hat Path.GetRelativePath sonst verwenden Sie dies.

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

35 Stimmen

Nach vielen Versuchen hat diese Methode bei mir am besten funktioniert. Sie müssen bedenken, dass Uri einen Ordner, der nicht mit einem Pfadseparator endet, wie eine Datei behandelt (verwenden Sie c: \foo\bar\ anstelle von c: \foo\bar wenn bar ein Ordner ist).

2 Stimmen

Es wird Schrägstriche in Schrägstriche umwandeln, richtig? Ansonsten funktioniert es bei mir einwandfrei!

6 Stimmen

Eine allgemeine Lösung für das Problem der Schrägstriche ist die Verwendung von return relativeUri.ToString().Replace('/',Path.DirectorySeparatorCh‌​ar);

55voto

Muhammad Rehan Saeed Punkte 31864

.NET Core 2.0 Antwort

.NET Core 2.0 hat Pfad.GetRelativePath die wie folgt verwendet werden kann:

var relativePath = Path.GetRelativePath(
    @"C:\Program Files\Dummy Folder\MyProgram",
    @"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat");

Im obigen Beispiel ist die relativePath Variable ist gleich Data\datafile1.dat .

Alternative .NET-Antwort

Die Lösung von @Dave funktioniert nicht, wenn die Dateipfade nicht mit einem Schrägstrich enden ( / ), was passieren kann, wenn der Pfad ein Verzeichnispfad ist. Meine Lösung behebt dieses Problem und macht außerdem Gebrauch von der Uri.UriSchemeFile Konstante statt harter Kodierung "FILE" .

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

Windows Interop Antwort

Es gibt eine Windows-API namens PathRelativePathToA die verwendet werden kann, um einen relativen Pfad zu finden. Bitte beachten Sie, dass die Datei- oder Verzeichnispfade, die Sie an die Funktion übergeben, existieren müssen, damit sie funktioniert.

var relativePath = PathExtended.GetRelativePath(
    @"C:\Program Files\Dummy Folder\MyProgram",
    @"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat");

public static class PathExtended
{
    private const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
    private const int FILE_ATTRIBUTE_NORMAL = 0x80;
    private const int MaximumPath = 260;

    public static string GetRelativePath(string fromPath, string toPath)
    {
        var fromAttribute = GetPathAttribute(fromPath);
        var toAttribute = GetPathAttribute(toPath);

        var stringBuilder = new StringBuilder(MaximumPath);
        if (PathRelativePathTo(
            stringBuilder,
            fromPath,
            fromAttribute,
            toPath,
            toAttribute) == 0)
        {
            throw new ArgumentException("Paths must have a common prefix.");
        }

        return stringBuilder.ToString();
    }

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

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

        throw new FileNotFoundException(
            "A file or directory with the specified path was not found.",
            path);
    }

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

1 Stimmen

Funktioniert viel eher so, wie man es erwarten würde - ich empfehle dies anstelle der am höchsten bewerteten Antwort.

0 Stimmen

Da Sie davon ausgehen AltDirectorySeparatorChar eine Möglichkeit ist, sollte nicht AppendDirectorySeparatorChar auch darauf überprüfen?

0 Stimmen

Außerdem kann eine Datei ohne Erweiterung sein, und obwohl dies in den meisten Fällen bequemer ist, können Sie diesen Fall nicht angeben. Fügen Sie vielleicht eine Prüfung hinzu, ob der Dateisystemeintrag existiert, und wenn ja, prüfen Sie, ob es eine Datei oder ein Ordner ist. Wenn er nicht existiert, bleibt es bei dieser Logik. Oder vielleicht sogar eine Möglichkeit in der Signatur hinzufügen, um anzugeben, ob eine Datei oder ein Verzeichnis angegeben wurde (z.B. 2 Boolesche Werte).

53voto

ctacke Punkte 65813

Die Frage kommt etwas spät, aber ich brauchte diese Funktion auch gerade. Ich stimme mit DavidK überein, dass, da es eine integrierte API-Funktion das dies bietet, sollten Sie es verwenden. Hier ist ein verwalteter Wrapper für sie:

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

4 Stimmen

Ich würde keine Ausnahme auslösen, wenn die Datei oder der Pfad nicht existiert, da dies ein völlig legaler Fall sein könnte.

2 Stimmen

Was würde GetPathAttributes dann zurückgeben? Es gibt keine Flagge für "Datei existiert nicht", so dass ich nicht sehen, eine praktikable Option außer zu werfen, sonst der Aufrufer erhält fehlerhafte Informationen.

2 Stimmen

Beachten Sie, dass PathRelativePathTo FALSE zurückgibt, wenn kein relativer Pfad erstellt werden konnte. In diesem Fall sollten Sie entweder String.Empty zurückgeben oder eine Exception auslösen.

25voto

DavidK Punkte 3834

Es gibt eine Win32 (C++) Funktion in shlwapi.dll, die genau das tut, was Sie wollen: PathRelativePathTo()

Mir ist keine Möglichkeit bekannt, von .NET aus darauf zuzugreifen, außer über P/Invoke.

0 Stimmen

Diese Funktion vereinfacht nur einen Teil des Problems.

3 Stimmen

Bei welchem Teil hilft es nicht? Lesen Sie den ursprünglichen Beitrag sieht es für mich wie PathRelativePathTo() tut, was Sie wollten, aber das ist wahrscheinlich, weil ich etwas falsch interpretiert haben ...

3 Stimmen

Funktioniert tadellos. Siehe pinvoke.net/default.aspx/shlwapi.PathRelativePathTo zur Einrichtung des P/Invoke.

14voto

Ray Punkte 178277

Wenn Sie Folgendes verwenden .NET Core 2.0, Path.GetRelativePath() zur Verfügung, die diese spezielle Funktionalität bietet:

        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 

Andernfalls empfehlen wir für das vollständige .NET-Framework (ab v4.7) die Verwendung einer der anderen vorgeschlagenen Antworten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X