4

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?

ProfK
  • 49,207
  • 121
  • 399
  • 775
  • Denis' answer is thorough. Can you explain what extra attention do you need? – Simon Mourier Mar 09 '18 at 13:17
  • @SimonMourier Denis's answer is readable, but in a language I don't know and littered with references to types, structures, and procedures defined outside the code in his answer. – ProfK Mar 11 '18 at 08:32
  • 1
    It's using the functions provided by the win32 API (as provided by microsoft). For example, see https://msdn.microsoft.com/en-us/library/windows/desktop/bb759975(v=vs.85).aspx. – Peter Brittain Mar 11 '18 at 22:27
  • what language do you need? – Simon Mourier Mar 13 '18 at 09:57
  • @SimonMourier C# would be ideal. I can read most languages, including that in the answer, but there are a real lot of undefined symbols (constant and method names esp.) in the answer I'm not sure where to find. – ProfK Mar 14 '18 at 03:18
  • The complexity of using the Windows built-in function is similar to that of using a [roll-your-own .NET file size format solution](https://stackoverflow.com/q/14488796/43452) – Stobor Mar 16 '18 at 03:38
  • Or a [file size formatter that works with String.format()](https://stackoverflow.com/q/128618/43452) – Stobor Mar 16 '18 at 03:40

2 Answers2

4

I see 3 variants:

function FormatFileSize(const ASize: UInt64; AKbMode: Boolean): UnicodeString;
var
  PS: IPropertySystem;
  PD: IPropertyDescription;
  PV: TPropVariant;
  Flags: DWORD;
  Display: PWideChar;
  PUI: IPropertyUI;
begin
  Result := '';

  // Variant 1
  if Succeeded(CoCreateInstance(CLSID_IPropertySystem, nil, CLSCTX_INPROC_SERVER, IPropertySystem, PS)) then
    begin
      if Succeeded(PS.GetPropertyDescription(PKEY_Size, IPropertyDescription, PD)) then
        begin
          PV.vt := VT_UI8;
          PV.uhVal.QuadPart := ASize;
          if AKbMode then Flags := PDFF_ALWAYSKB
                     else Flags := PDFF_DEFAULT;
          if Succeeded(PD.FormatForDisplay(PV, Flags, Display)) then
             begin
               Result := Display;
               CoTaskMemFree(Display);
             end;
          PD := nil;
        end;
      PS := nil;
    end;
  if Result <> '' then Exit;

  // Variant 2 - Windows XP mode, can be replaced with Variant 3
  if Succeeded(CoCreateInstance(CLSID_PropertiesUI, nil, CLSCTX_INPROC_SERVER, IPropertyUI, PUI)) then
    begin
      PV.vt := VT_UI8;
      PV.uhVal.QuadPart := ASize;
      SetLength(Result, 100);
      if Succeeded(PUI.FormatForDisplay(PKEY_Size.fmtid, PKEY_Size.pid, PV, PUIFFDF_DEFAULT, PWideChar(Result), Length(Result) + 1)) then
        Result := PWideChar(Result)
      else
        Result := '';
      PUI := nil;
    end;
  if Result <> '' then Exit;

  // Variant 3
  SetLength(Result, 100);
  if AKbMode then
    Result := StrFormatKBSizeW(ASize, PWideChar(Result), Length(Result))
  else
    Result := StrFormatByteSizeW(ASize, PWideChar(Result), Length(Result));
end;
Denis Anisimov
  • 3,297
  • 1
  • 10
  • 18
  • @SimonMourier Very funny situation :) Google knows everything about every "undefined symbols (constant and method names esp.)" and I cannot understand the problem. If you want to rewrite the answer with using of c# - I don`t mind. – Denis Anisimov Mar 14 '18 at 14:17
  • @DenisAnisimov Just one example is, `CLSID_IPropertySystem`. The first three Google hits are SO questions, including this one. Thank you for your effort though. – ProfK Mar 17 '18 at 02:56
  • @ProfK How to find CLSID_IPropertySystem: goggle for IPropertySystem, first link in the search result will be https://msdn.microsoft.com/en-us/library/windows/desktop/bb761437(v=vs.85).aspx , on this page you will see that IPropertySystem is declared in Propsys.h. Find this file in Windows SDK and open it. Inside it you can find CLSID_PropertySystem declaration. My bad that I used CLSID_**I**PropertySystem but this fact ho relation to principle of search. – Denis Anisimov Mar 17 '18 at 03:52
1

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;
    }
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298