Click here to Skip to main content
15,891,529 members
Articles / Desktop Programming / WTL

WTL for MFC Programmers, Part X - Implementing a Drag and Drop Source

Rate me:
Please Sign up or sign in to vote.
4.95/5 (40 votes)
16 Jun 200618 min read 196.7K   2.8K   107  
A tutorial on using drag and drop in your WTL application.
// WTLCabViewView.cpp : implementation of the CWTLCabViewView class
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "resource.h"
#include "WTLCabViewView.h"

/////////////////////////////////////////////////////////////////////////////
// Construction

CWTLCabViewView::CWTLCabViewView() : m_nSortedCol(-1), m_bSortAscending(true)
{
}


/////////////////////////////////////////////////////////////////////////////
// Message handlers

BOOL CWTLCabViewView::PreTranslateMessage ( MSG* pMsg )
{
    return FALSE;
}

void CWTLCabViewView::OnDestroy()
{
    // Destroy the state image list. The other image lists shouldn't be destroyed
    // since they are copies of the system image list. Destroying those on Win 9x
    // is a Bad Thing to do.
    if ( m_imlState )
        m_imlState.Destroy();

    SetMsgHandled(false);
}


/////////////////////////////////////////////////////////////////////////////
// Notify handlers

LRESULT CWTLCabViewView::OnColumnClick ( NMHDR* phdr )
{
CWaitCursor w;
int nCol = ((NMLISTVIEW*) phdr)->iSubItem;

    // If the user clicked the column that is already sorted, reverse the sort
    // direction. Otherwise, go back to ascending order.
    if ( nCol == m_nSortedCol )
        m_bSortAscending = !m_bSortAscending;
    else
        m_bSortAscending = true;

    if ( g_bXPOrLater )
        {
        HDITEM hdi = { HDI_FORMAT };
        CHeaderCtrl wndHdr = GetHeader();

        // Remove the sort arrow indicator from the previously-sorted column.
        if ( -1 != m_nSortedCol )
            {
            wndHdr.GetItem ( m_nSortedCol, &hdi );
            hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
            wndHdr.SetItem ( m_nSortedCol, &hdi );
            }

        // Add the sort arrow to the new sorted column.
        hdi.mask = HDI_FORMAT;
        wndHdr.GetItem ( nCol, &hdi );
        hdi.fmt |= m_bSortAscending ? HDF_SORTUP : HDF_SORTDOWN;
        wndHdr.SetItem ( nCol, &hdi );
        }

    // Store the column being sorted, and do the sort
    m_nSortedCol = nCol;

    SortItems ( SortCallback, (LPARAM)(DWORD_PTR) this );

    // Have the list ctrl indicate the sorted column by changing its color.
    if ( g_bXPOrLater )
        SetSelectedColumn ( nCol );

    return 0;   // retval ignored
}

LRESULT CWTLCabViewView::OnMarqueeBegin ( NMHDR* phdr )
{
    // Don't allow the marquee if the list is empty.
    return (GetItemCount() == 0);
}

LRESULT CWTLCabViewView::OnKeyDown ( NMHDR* phdr )
{
NMLVKEYDOWN* pnmlv = (NMLVKEYDOWN*) phdr;

    // Select all items if the user presses ^A
    if ( 'A' == pnmlv->wVKey && (GetKeyState(VK_CONTROL) & 0x8000) )
        SetItemState ( -1, LVIS_SELECTED, LVIS_SELECTED );

    return 0;   // retval ignored
}

LRESULT CWTLCabViewView::OnDeleteItem ( NMHDR* phdr )
{
NMLISTVIEW* pnmlv = (NMLISTVIEW*) phdr;

    if ( 0 != pnmlv->lParam )
        delete (CCompressedFileInfo*) pnmlv->lParam;

    return 0;   // retval ignored
}


/////////////////////////////////////////////////////////////////////////////
// Operations

void CWTLCabViewView::Init()
{
    InitColumns();
    InitImageLists();
    SetExtendedListViewStyle ( LVS_EX_HEADERDRAGDROP, LVS_EX_HEADERDRAGDROP );

    // On XP, set some additional properties of the list ctrl.
    if ( g_bXPOrLater )
        {
        // Turning on LVS_EX_DOUBLEBUFFER also enables the transparent
        // selection marquee.
        SetExtendedListViewStyle ( LVS_EX_DOUBLEBUFFER, LVS_EX_DOUBLEBUFFER );

        // Each tile will have 2 additional lines (3 lines total).
        LVTILEVIEWINFO lvtvi = { sizeof(LVTILEVIEWINFO), LVTVIM_COLUMNS };

        lvtvi.cLines = 2;
        lvtvi.dwFlags = LVTVIF_AUTOSIZE;
        SetTileViewInfo ( &lvtvi );
        }
}

void CWTLCabViewView::Clear()
{
    DeleteAllItems();

    if ( -1 != m_nSortedCol )
        {
        if ( g_bXPOrLater )
            {
            // Remove the sort arrow indicator from the sorted column.
            HDITEM hdi = { HDI_FORMAT };
            CHeaderCtrl wndHdr = GetHeader();

            wndHdr.GetItem ( m_nSortedCol, &hdi );
            hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
            wndHdr.SetItem ( m_nSortedCol, &hdi );

            // Remove the sorted column color from the list.
            SetSelectedColumn(-1);
            }

        m_nSortedCol = -1;
        m_bSortAscending = true;
        }
}

void CWTLCabViewView::SetViewMode ( int nMode )
{
    ATLASSERT(nMode >= LV_VIEW_ICON && nMode <= LV_VIEW_TILE);

    if ( g_bXPOrLater )
        {
        if ( LV_VIEW_TILE == nMode )
            SetImageList ( m_imlTiles, LVSIL_NORMAL );
        else
            SetImageList ( m_imlLarge, LVSIL_NORMAL );

        SetView ( nMode );
        }
    else
        {
        DWORD dwViewStyle;

        ATLASSERT(LV_VIEW_TILE != nMode);

        switch ( nMode )
            {
            case LV_VIEW_ICON:      dwViewStyle = LVS_ICON; break;
            case LV_VIEW_SMALLICON: dwViewStyle = LVS_SMALLICON; break;
            case LV_VIEW_LIST:      dwViewStyle = LVS_LIST; break;
            case LV_VIEW_DETAILS:   dwViewStyle = LVS_REPORT; break;
            DEFAULT_UNREACHABLE;
            }

        ModifyStyle ( LVS_TYPEMASK, dwViewStyle );
        }
}

void CWTLCabViewView::AddFile (
    LPCTSTR szFilename, long cbyUncompressedSize, USHORT uDate,
    USHORT uTime, USHORT uAttribs )
{
int nIdx;
TCHAR szSize[64], szDate[64], szTime[64];
CString sDateTime, sAttrs;
FILETIME ft = {0}, ftLocal = {0};
SYSTEMTIME st = {0};

    // Get the file size with the size in KB or MB or whatever suffix.
    StrFormatByteSize ( cbyUncompressedSize, szSize, countof(szSize) );

    // Format the modified date/time using the user's locale settings.
    DosDateTimeToFileTime ( uDate, uTime, &ft );
    FileTimeToLocalFileTime ( &ft, &ftLocal );
    FileTimeToSystemTime ( &ftLocal, &st );

    GetDateFormat ( LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, szDate, countof(szDate) );
    GetTimeFormat ( LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, szTime, countof(szTime) );

    sDateTime.Format ( _T("%s %s"), szDate, szTime );

    // Make a string that shows the attributes.
    if ( uAttribs & _A_RDONLY )
        sAttrs += 'R';
    if ( uAttribs & _A_HIDDEN )
        sAttrs += 'H';
    if ( uAttribs & _A_SYSTEM )
        sAttrs += 'S';
    if ( uAttribs & _A_ARCH )
        sAttrs += 'A';

    // Get the file's type description and the index of its icon in the system imagelist.
SHFILEINFO info = {0};

    SHGetFileInfo ( szFilename, FILE_ATTRIBUTE_NORMAL, &info, sizeof(info),
                    SHGFI_USEFILEATTRIBUTES|SHGFI_TYPENAME|SHGFI_SYSICONINDEX );

    // Add a new list item for this file.
    nIdx = InsertItem ( GetItemCount(), szFilename, info.iIcon );
    SetItemText ( nIdx, 1, info.szTypeName );
    SetItemText ( nIdx, 2, szSize );
    SetItemText ( nIdx, 3, sDateTime );
    SetItemText ( nIdx, 4, sAttrs );

CCompressedFileInfo* pInfo = new CCompressedFileInfo;

    pInfo->sFilename = szFilename;
    pInfo->sFileType = info.szTypeName;
    pInfo->dwFileSize = cbyUncompressedSize;
    pInfo->ftDateTime = ft;
    pInfo->uAttribs = uAttribs;

    SetItemData ( nIdx, (DWORD_PTR) pInfo );

    // On XP+, set up the additional tile view text for the item.
    if ( g_bXPOrLater )
        {
        UINT aCols[] = { 1, 2 };
        LVTILEINFO lvti = { sizeof(LVTILEINFO), nIdx, countof(aCols), aCols };

        SetTileInfo ( &lvti );
        }
}

void CWTLCabViewView::AddPartialFile (
    LPCTSTR szFilename, LPCTSTR szStartingCabName, LPCTSTR szStartingDiskName )
{
int nIdx;
SHFILEINFO info = {0};
CString sSplitFileText;

    sSplitFileText.Format ( IDS_FROM_PREV_CAB, szStartingCabName );

    // Get the file's type description and the index of its icon in the system imagelist.
    SHGetFileInfo ( szFilename, FILE_ATTRIBUTE_NORMAL, &info, sizeof(info),
                    SHGFI_USEFILEATTRIBUTES|SHGFI_TYPENAME|SHGFI_SYSICONINDEX );

    // Add a new list item for this file.
    nIdx = InsertItem ( GetItemCount(), szFilename, info.iIcon );
    SetItemText ( nIdx, 1, info.szTypeName );
    SetItemText ( nIdx, 5, sSplitFileText );

CCompressedFileInfo* pInfo = new CCompressedFileInfo;

    pInfo->sFilename = szFilename;
    pInfo->sFileType = info.szTypeName;
    pInfo->location = CCompressedFileInfo::from_prev_cab;
    pInfo->sOtherCabName = szStartingCabName;
    pInfo->bExtractable = false;

    SetItemData ( nIdx, (DWORD_PTR) pInfo );

    // Set the stage image to the icon that shows the file begins in
    // an earlier CAB.
    SetItemState ( nIdx, INDEXTOSTATEIMAGEMASK(1), LVIS_STATEIMAGEMASK );

    // On XP+, set up the additional tile view text for the item.
    if ( g_bXPOrLater )
        {
        UINT aCols[] = { 5 };
        LVTILEINFO lvti = { sizeof(LVTILEINFO), nIdx, countof(aCols), aCols };

        SetTileInfo ( &lvti );
        }
}

void CWTLCabViewView::UpdateContinuedFile ( const CDraggedFileInfo& info )
{
CString sSplitFileText;

    // Update the Split File column to show what CAB this file is continued in.
    sSplitFileText.Format ( IDS_TO_NEXT_CAB, (LPCTSTR) info.sCabName );

    SetItemText ( info.nListIdx, 5, sSplitFileText );

    // Update the item's data to keep track of the fact that the file is
    // continued in another CAB.
CCompressedFileInfo* pInfo;

    pInfo = (CCompressedFileInfo*) GetItemData ( info.nListIdx );
    ATLASSERT(NULL != pInfo);

    pInfo->location = CCompressedFileInfo::to_next_cab;
    pInfo->sOtherCabName = info.sCabName;

    // Set the stage image to the icon that shows the file continues in another CAB.
    SetItemState ( info.nListIdx, INDEXTOSTATEIMAGEMASK(2), LVIS_STATEIMAGEMASK );

    if ( info.bCabMissing )
        {
        // Set a flag indicating that the file can't be extracted, and set the
        // LVIS_CUT style so the icon is grayed out.
        pInfo->bExtractable = false;
        SetItemState ( info.nListIdx, LVIS_CUT, LVIS_CUT );
        }
}

bool CWTLCabViewView::GetDraggedFileInfo ( std::vector<CDraggedFileInfo>& vec )
{
int nIdx = -1;

    while ( (nIdx = GetNextItem ( nIdx, LVNI_SELECTED )) != -1 )
        {
        CCompressedFileInfo* pInfo;

        pInfo = (CCompressedFileInfo*) GetItemData ( nIdx );
        ATLASSERT(NULL != pInfo);

        if ( pInfo->bExtractable )
            vec.push_back ( CDraggedFileInfo ( pInfo->sFilename, nIdx ) );
        else
            ATLTRACE("Skipping partial/unavailable file <%s>\n", (LPCTSTR) pInfo->sFilename);
        }

    return !vec.empty();
}


/////////////////////////////////////////////////////////////////////////////
// Other methods

void CWTLCabViewView::InitColumns()
{
    InsertColumn ( 0, _S(IDS_COLHDR_NAME), LVCFMT_LEFT, 100, 0 );
    InsertColumn ( 1, _S(IDS_COLHDR_TYPE), LVCFMT_LEFT, 100, 1 );
    InsertColumn ( 2, _S(IDS_COLHDR_SIZE), LVCFMT_LEFT, 150, 2 );
    InsertColumn ( 3, _S(IDS_COLHDR_DATE), LVCFMT_LEFT, 100, 3 );
    InsertColumn ( 4, _S(IDS_COLHDR_ATTRS), LVCFMT_LEFT, 100, 4 );
    InsertColumn ( 5, _S(IDS_COLHDR_SPLIT), LVCFMT_LEFT, 100, 5 );
}

void CWTLCabViewView::InitImageLists()
{
SHFILEINFO info = {0};

    // Get the large/small system image lists
    m_imlSmall = (HIMAGELIST) SHGetFileInfo ( _T("alyson.com"), FILE_ATTRIBUTE_NORMAL,
                                &info, sizeof(info),
                                SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_SMALLICON|SHGFI_SYSICONINDEX );

    m_imlLarge = (HIMAGELIST) SHGetFileInfo ( _T("willow.com"), FILE_ATTRIBUTE_NORMAL,
                                &info, sizeof(info),
                                SHGFI_USEFILEATTRIBUTES|SHGFI_ICON|SHGFI_SYSICONINDEX );

    SetImageList ( m_imlLarge, LVSIL_NORMAL );
    SetImageList ( m_imlSmall, LVSIL_SMALL );

    // Create a state image list
HICON hiPrev = AtlLoadIconImage ( IDI_PREV_CAB, LR_DEFAULTCOLOR, 16, 16 );
HICON hiNext = AtlLoadIconImage ( IDI_NEXT_CAB, LR_DEFAULTCOLOR, 16, 16 );

    m_imlState.Create ( 16, 16, ILC_COLOR8|ILC_MASK, 2, 1 );

    m_imlState.AddIcon ( hiPrev );
    m_imlState.AddIcon ( hiNext );

    DestroyIcon ( hiPrev );
    DestroyIcon ( hiNext );

    SetImageList ( m_imlState, LVSIL_STATE );

    // On pre-XP, we're done.
    if ( !g_bXPOrLater )
        return;

    // Get the 48x48 system image list. 
HRESULT hr, (WINAPI* pfnGetImageList)(int, REFIID, void**) = NULL;
HMODULE hmod = GetModuleHandle ( _T("shell32") );

    if ( NULL != hmod )
        (FARPROC&) pfnGetImageList = GetProcAddress ( hmod, "SHGetImageList" );

    if ( NULL == pfnGetImageList )
        (FARPROC&) pfnGetImageList = GetProcAddress ( hmod, MAKEINTRESOURCEA(727) );  // see Q316931

    if ( NULL == pfnGetImageList )
        {
        // Couldn't get the address of SHGetImageList(), so just use the 32x32 imglist.
        m_imlTiles = m_imlLarge;
        return;
        }

    hr = pfnGetImageList ( SHIL_EXTRALARGE, IID_IImageList, (void**) &m_TileIml );

    if ( FAILED(hr) )
        {
        // Couldn't get the 48x48 image list, so fall back to using the 32x32 one.
        m_imlTiles = m_imlLarge;
        return;
        }

    // HIMAGELIST and IImageList* are interchangeable, so this cast is kosher.
    m_imlTiles = (HIMAGELIST)(IImageList*) m_TileIml;
}

int CALLBACK CWTLCabViewView::SortCallback (
    LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort )
{
    ATLASSERT(NULL != lParam1);
    ATLASSERT(NULL != lParam2);
    ATLASSERT(NULL != lParamSort);

CCompressedFileInfo& info1 = *(CCompressedFileInfo*) lParam1;
CCompressedFileInfo& info2 = *(CCompressedFileInfo*) lParam2;
CWTLCabViewView* pThis = (CWTLCabViewView*) lParamSort;

    return pThis->SortCallback ( info1, info2 );
}

int CWTLCabViewView::SortCallback (
    const CCompressedFileInfo& info1, const CCompressedFileInfo& info2 ) const
{
int nRet = 0;
LPCTSTR sz1, sz2;

    ATLASSERT(-1 != m_nSortedCol);
    ATLASSERT(!info1.sFilename.IsEmpty());
    ATLASSERT(!info2.sFilename.IsEmpty());

    // Default to comparing the file names, unless something in the switch
    // changes these pointers.
    sz1 = info1.sFilename;
    sz2 = info2.sFilename;

    switch ( m_nSortedCol )
        {
        case 0:     // file name
        break;

        case 1:     // type description
            ATLASSERT(!info1.sFileType.IsEmpty());
            ATLASSERT(!info2.sFileType.IsEmpty());

            sz1 = info1.sFileType;
            sz2 = info2.sFileType;
        break;

        case 2:     // uncompressed size
            if ( info1.dwFileSize < info2.dwFileSize )
                nRet = -1;
            else if ( info1.dwFileSize > info2.dwFileSize )
                nRet = 1;
        break;

        case 3:     // modified date/time
            {
            LONG lRet = CompareFileTime ( &info1.ftDateTime, &info2.ftDateTime );

            if ( 0 != lRet )
                nRet = lRet;
            }
        break;

        case 4:     // file attributes
            if ( info1.uAttribs < info2.uAttribs )
                nRet = -1;
            else if ( info1.uAttribs > info2.uAttribs )
                nRet = 1;
        break;

        case 5:     // prev/next CAB name
            if ( info1.location < info2.location )
                nRet = -1;
            else if ( info1.location > info2.location )
                nRet = 1;
            else if ( info1.location != CCompressedFileInfo::in_this_cab )
                {
                ATLASSERT(!info1.sOtherCabName.IsEmpty());
                ATLASSERT(!info2.sOtherCabName.IsEmpty());

                nRet = lstrcmpi ( info1.sOtherCabName, info2.sOtherCabName );
                }
        break;

        DEFAULT_UNREACHABLE;
        }

    // If the primary comparison in the switch returned equality (nRet is still 0),
    // then compare sz1/sz2 so equal items will still be sorted by file names.
    if ( 0 == nRet )
        nRet = lstrcmpi ( sz1, sz2 );

    return m_bSortAscending ? nRet : -nRet;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) VMware
United States United States
Michael lives in sunny Mountain View, California. He started programming with an Apple //e in 4th grade, graduated from UCLA with a math degree in 1994, and immediately landed a job as a QA engineer at Symantec, working on the Norton AntiVirus team. He pretty much taught himself Windows and MFC programming, and in 1999 he designed and coded a new interface for Norton AntiVirus 2000.
Mike has been a a developer at Napster and at his own lil' startup, Zabersoft, a development company he co-founded with offices in Los Angeles and Odense, Denmark. Mike is now a senior engineer at VMware.

He also enjoys his hobbies of playing pinball, bike riding, photography, and Domion on Friday nights (current favorite combo: Village + double Pirate Ship). He would get his own snooker table too if they weren't so darn big! He is also sad that he's forgotten the languages he's studied: French, Mandarin Chinese, and Japanese.

Mike was a VC MVP from 2005 to 2009.

Comments and Discussions