将文件大小转换为文本表示
Transforming file sizes to text representation
我正在构建在线文件管理器。它显示的列之一是文件大小,但这总是很大的字节数。我想像 Windows Explorer 一样显示文件大小,使用较小的数字和适当的单位,例如5 MB
而不是 5000000
。
这对我来说一点也不难,但我想知道 Windows 而不是有一个内置函数来执行此操作。已经有东西了吗,还是我必须自己动手?
我看到 3 个变体:
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;
这是 C# 中的两个变体(它们需要 Windows Vista):
...
Console.WriteLine(FormatByteSize(1031023120)); // 983 MB
Console.WriteLine(FormatByteSize2(1031023120, true)); // 1 006 859 KB
...
请注意使用 Windows 的好处(或不便之处,具体取决于您的看法)是您将获得使用 Shell/OS 文化的本地化版本(如果有的话)。
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;
}
}
我正在构建在线文件管理器。它显示的列之一是文件大小,但这总是很大的字节数。我想像 Windows Explorer 一样显示文件大小,使用较小的数字和适当的单位,例如5 MB
而不是 5000000
。
这对我来说一点也不难,但我想知道 Windows 而不是有一个内置函数来执行此操作。已经有东西了吗,还是我必须自己动手?
我看到 3 个变体:
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;
这是 C# 中的两个变体(它们需要 Windows Vista):
...
Console.WriteLine(FormatByteSize(1031023120)); // 983 MB
Console.WriteLine(FormatByteSize2(1031023120, true)); // 1 006 859 KB
...
请注意使用 Windows 的好处(或不便之处,具体取决于您的看法)是您将获得使用 Shell/OS 文化的本地化版本(如果有的话)。
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;
}
}