virtual list control scrolling is painfully slow

So you create a LVS_OWNERDATA (virtual) list-view control and set its style to LVS_REPORT. Btw, isn't a rectangle of (0,0,20,20) too small? But I see you are using the same coordinates for other controls as well, so I guess you re-arrange them later (code not shown here).

The function processing the LVN_GETDISPINFO notification must be declared in your class's message-map, to associate the function to the message:

BEGIN_MESSAGE_MAP(CAlbumView, ...)
    .
    .
    ON_NOTIFY(LVN_GETDISPINFO, IDC_LISTCTRL, &CAlbumView::OnLvnGetdispinfoListCtlr)
END_MESSAGE_MAP()

This is what wizard-generated code would look like, if you didn't subclass from CMFCListControl and instead simply used CMFCListControl as is. But you create the control yourself, so please disregard this remark if you have correctly made the aforementioned declaration in the child class's message-map and you found that the function is actually called (debug or trace).

I think the reason why your code doesn't work is because you copy the items' texts to a buffer supposedly pointed by the pszText instead of setting the pszText pointer. Here is a excerpt from the documentation about the LV_ITEM structure:

pszText
If the structure specifies item attributes, pszText is a pointer to a null-terminated string containing the item text. When responding to an LVN_GETDISPINFO notification, be sure that this pointer remains valid until after the next notification has been received.
Also:
cchTextMax
This member is only used when the structure receives item attributes. ... It is read-only during LVN_GETDISPINFO and other LVN_ notifications.

And the example in the LVN_GETDISPINFO documentation does exactly this.

So, your code could be changed as shown below:

    void CAlbumListCtrl::OnLvnGetdispinfo(NMHDR *pNMHDR, LRESULT *pResult)
    {
        NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
        LVITEM &Item = (pDispInfo)->item;
        CString csTemp;
        static TCHAR _szItemId[20]; // Persistent buffer
        
        ASSERT(m_pLibrary);
        if (!m_pLibrary)
            AfxThrowMemoryException();
        
        const CImageEntry *pEntry = m_pLibrary->GetImageEntryAt((size_t)Item.iItem);
        
        if (Item.mask & LVIF_TEXT) //Item/subItem text
        {
            switch (Item.iSubItem)
            {
            case 0: //fill in ID
                csTemp.Format(_T("%ld"), pEntry->GetItemId());
                 _tcscpy_s(_szItemId, csTemp);
                Item.pszText = _szItemId;
                break;
            case 1: //fill in sub item 1 text
                Item.pszText = pEntry->GetItemName().c_str();
                break;
            case 2: //fill in sub item 2 text
                Item.pszText = pEntry->GetPathName().c_str();
                break;
            case 3: //fill in sub item 3 text
                Item.pszText = m_pLibrary->GetImageEntryType(pEntry).c_str();
                break;
            default:
                break;
            }
        }
        
        *pResult = 0;
    }

Notes:

  • I only modified this code in a text editor, it's not actually tested. It may need some minor fixes to even compile.
  • The InsertItem() and DeleteAllItems() calls (along with that browsing of the list) must be removed, as the control does not really store any content, instead it requests the data of the items being displayed, through the LVN_GETDISPINFO message (referenced by the item's index - iItem).