Search code examples
windowstextformattingstring-formattingwindows-explorer

Transforming file sizes to text representation


I am building on online file manager. One of the columns it displays is the file size, but this is always a high number of bytes. I would like to display the file size as does Windows Explorer, with a smaller number and the appropriate unit, e.g. 5 MB instead of 5000000.

It isn't at all hard for me to do this, but I was wondering of Windows rather had a built in function to do this. Is there something already, or must I roll my own?


Solution

  • Here are two variants (they need Windows Vista) in C#:

    ...
    Console.WriteLine(FormatByteSize(1031023120)); // 983 MB
    Console.WriteLine(FormatByteSize2(1031023120, true)); // 1 006 859 KB
    ...
    

    Note the benefit (or an inconvenient depending on how you see it) of using Windows is you will get localized version (if any), using the Shell/OS culture.

    public static string FormatByteSize2(long size, bool alwaysKb = false)
    {
        // Here, we use Windows Shell's size column definition and formatting
        // note although System.Size is defined as a UInt64, formatting doesn't support more than long.MaxValue...
        PSGetPropertyKeyFromName("System.Size", out var pk);
        var pv = new PROPVARIANT(size);
        var sb = new StringBuilder(128);
        const int PDFF_ALWAYSKB = 4;
        PSFormatForDisplay(ref pk, pv, alwaysKb ? PDFF_ALWAYSKB : 0, sb, sb.Capacity);
        return sb.ToString();
    }
    
    public static string FormatByteSize(long size)
    {
        // Here, we use use a Windows Shell API (probably the sames algorithm underneath)
        // It's much simpler, we only need to declare one StrFormatByteSizeW API
        var sb = new StringBuilder(128);
        StrFormatByteSizeW(size, sb, sb.Capacity);
        return sb.ToString();
    }
    
    [DllImport("shlwapi", CharSet = CharSet.Unicode)]
    private static extern IntPtr StrFormatByteSizeW(long qdw, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszBuf, int cchBuf);
    
    [DllImport("propsys", CharSet = CharSet.Unicode)]
    private static extern int PSFormatForDisplay(
        ref PROPERTYKEY propkey,
        PROPVARIANT pv,
        int pdfFlags,
        [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszBuf, int cchBuf);
    
    [DllImport("propsys", CharSet = CharSet.Unicode)]
    private static extern int PSGetPropertyKeyFromName([MarshalAs(UnmanagedType.LPWStr)] string pszName, out PROPERTYKEY ppropkey);
    
    [StructLayout(LayoutKind.Sequential)]
    private struct PROPERTYKEY
    {
        public Guid fmtid;
        public int pid;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    private class PROPVARIANT
    {
        // note this version of PROPVARIANT is far from being suited for all purposes...
        public short vt;
        short wReserved1;
        short wReserved2;
        short wReserved3;
        public long val;
    
        const short VT_UI8 = 21;
    
        public PROPVARIANT(long ul)
        {
            wReserved3 = wReserved2 = wReserved1 = 0;
            val = ul;
            vt = VT_UI8;
        }
    }