winapi

Sort ListView with ULARGE_INTEGER-based Size/Date columns


I'm displaying a File ListView which has the columns Size and Date. The WIN32_FIND_DATA structure used to find files specifies (1) Size as nFileSizeHigh/nFileSizeLow, and (2) Date via FILETIME as dwLowDateTime/dwHighDateTime.

These are meant to be combined into ULARGE_INTEGER as e.g.

uLargeInteger.LowPart = low;
uLargeInteger.HighPart = high;
// Final value
ULONGLONG final = uLargeInteger.QuadPart;

Once added into the ListView these are strings. (1) The Size is goes into LVITEM's pszText as std::towstring(sizeULastInteger.QuadPart, (2) The Date via SHFormatDate().

The ListView will be sorted on these columns.

  1. For the size, I can test std::stoi(wstr) (assuming there won't be any int issues in modern environments, Windows 10+)
  2. For the Date, obtained with SHFormatDate, is there an easy way to convert back into FILETIME or a number? I can either maintain a parallel data struct with the underlying ULARGE_INTEGER, or use C++'s date/time libraries but that assumes a local pattern.

Note: The only answers available on Google on this are for WPF and C#.


Solution

  • Yeah, I wouldn't bother trying to reverse SHFormatDate() — that's strictly for display, and also it is locale-dependent. Same with the file size strings — once you format it, you've lost any real ability to sort on it numerically.

    The right way is to keep the actual sort key (e.g. ULARGE_INTEGER for size, FILETIME for date) and stash it in the LVITEM::lParam. then in your compare function you just cast lParam back and compare the raw values. something like:

    struct file_item_data {
        ULONGLONG size;
        FILETIME date;
    };
    
    // when inserting
    auto* data = new file_item_data{ size_ull, file_time };
    LVITEM item = {};
    item.mask = LVIF_TEXT | LVIF_PARAM;
    item.iItem = index;
    item.pszText = formatted_size_string;
    item.lParam = reinterpret_cast<LPARAM>(data);
    ListView_InsertItem(hwnd_listview, &item);
    ListView_SetItemText(hwnd_listview, index, 1, formatted_date_string); // column 1 = date
    

    and then for sorting:

    int CALLBACK compare(LPARAM l1, LPARAM l2, LPARAM col) {
        auto* a = reinterpret_cast<file_item_data*>(l1);
        auto* b = reinterpret_cast<file_item_data*>(l2);
        if (col == 0) return a->size < b->size ? -1 : (a->size > b->size ? 1 : 0);
        if (col == 1) return CompareFileTime(&a->date, &b->date);
        return 0;
    }
    

    You call ListView_SortItems(..., compare, col_index) when the user clicks a header. if you're toggling ascending/descending, pass that in the hiword of the sort param.

    Don't forget to free the file_item_data* structs when you're done — or just track them in a vector and clean them up all at once on exit.