Click here to Skip to main content
Click here to Skip to main content

Windows 7 Goodies in C++: Jump Lists

By , 19 May 2009
 
// MainDlg.cpp : implementation of the CMainDlg class
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "SimpleAlyViewer.h"
#include "MainDlg.h"
#include "aboutdlg.h"


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

BOOL CMainDlg::PreTranslateMessage ( MSG* pMsg )
{
    return CWindow::IsDialogMessage ( pMsg );
}

BOOL CMainDlg::OnInitDialog ( HWND hwndFocus, LPARAM lParam )
{
    // center the dialog on the screen
    CenterWindow();

    // set icons
HICON hIcon = AtlLoadIconImage ( IDR_MAINFRAME, LR_DEFAULTCOLOR, 32, 32 );
HICON hIconSmall = AtlLoadIconImage ( IDR_MAINFRAME, LR_DEFAULTCOLOR, 16, 16 );

    SetIcon(hIcon, TRUE);
    SetIcon(hIconSmall, FALSE);

    // register object for message filtering and idle updates
CMessageLoop* pLoop = _Module.GetMessageLoop();

    ATLASSERT(pLoop != NULL);
    pLoop->AddMessageFilter(this);

    DoDataExchange();

    // Check the command line. If the first argument is a path to an .aly file,
    // open it.
    if ( __argc > 1 )
        {
        LPCTSTR szArg = __targv[1];

        if ( PathMatchSpec ( szArg, _T("*.aly") ) && PathFileExists ( szArg ) )
            ViewAlyFile ( szArg );
        }

    // Let the system set the focus.
    return TRUE;
}

void CMainDlg::OnDestroy()
{
    // unregister message filtering and idle updates
CMessageLoop* pLoop = _Module.GetMessageLoop();

    ATLASSERT(pLoop != NULL);
    pLoop->RemoveMessageFilter(this);
}

void CMainDlg::OnDropFiles ( HDROP hDrop )
{
TCHAR szFilePath[MAX_PATH] = {0};

    // Get the full path of the first dragged file, and check that it's any .aly file.
    if ( DragQueryFile ( hDrop, 0, szFilePath, _countof(szFilePath) ) > 0 &&
         PathMatchSpec ( szFilePath, _T("*.aly") ))
        {
        ViewAlyFile ( szFilePath );
        }

    DragFinish ( hDrop );
}

void CMainDlg::OnCancel ( UINT nCode, int nID, HWND hwndCtrl )
{
    DestroyWindow();
    PostQuitMessage ( nID );
}

void CMainDlg::OnAppAbout ( UINT nCode, int nID, HWND hwndCtrl )
{
CAboutDlg dlg;

    dlg.DoModal ( m_hWnd );
}

void CMainDlg::OnOpenAlyFile ( UINT uCode, int nID, HWND hwndCtl )
{
const COMDLG_FILTERSPEC aFilters[] =
{
    { L"Aly test files", L"*.aly" },
    { L"All files", L"*.*" }
};
HRESULT hr;
CString sSelectedFilePath;
CShellFileOpenDialog dlg ( L"test1.aly",
                           FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST,
                           L".aly", aFilters, _countof(aFilters) );

    if ( IDOK != dlg.DoModal ( m_hWnd ) )
        return;

    hr = dlg.GetFilePath ( sSelectedFilePath );

    // Show the selected file as the current file. The file open dialog automatically
    // calls SHAddToRecentDocs for us because we didn't use the FOS_DONTADDTORECENT
    // flag. As a result, the file shows up in our jump list.
    if ( SUCCEEDED(hr) )
        ViewAlyFile ( sSelectedFilePath );
}

void CMainDlg::OnRegisterAsHandler ( UINT uCode, int nID, HWND hwndCtl )
{
CString sCaption ( LPCTSTR(IDR_MAINFRAME) );

    // Set this app as the one associated with *.aly files.
    if ( RegisterAsHandler() )
        {
        // That succeeded, so tell the shell that a file association has changed.
        SHChangeNotify ( SHCNE_ASSOCCHANGED, SHCNF_IDLIST, 0, 0 );

        // Create a couple of test files for you to use with this app.
        CreateTestFiles();

        MessageBox ( _T("The file association was created successfully."),
                     sCaption, MB_ICONINFORMATION );

        }
    else
        MessageBox ( _T("Error creating the file association"),
                     sCaption, MB_ICONERROR );
}

void CMainDlg::OnUnregisterAsHandler ( UINT uCode, int nID, HWND hwndCtl )
{
CString sCaption ( LPCTSTR(IDR_MAINFRAME) );

    // Remove the association with *.aly files.
    if ( UnregisterAsHandler() )
        {
        // Delete the test files that were created in OnRegisterAsHandler().
        DeleteTestFiles();

        // Tell the shell that a file association has changed.
        SHChangeNotify ( SHCNE_ASSOCCHANGED, SHCNF_IDLIST, 0, 0 );

        MessageBox ( _T("The file association was removed successfully."),
                     sCaption, MB_ICONINFORMATION );
        }
    else
        MessageBox ( _T("Error removing the file association"),
                     sCaption, MB_ICONERROR );
}


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

bool CMainDlg::ShowCategory ( KNOWNDESTCATEGORY category )
{
HRESULT hr;
CComPtr<ICustomDestinationList> pDestList;

    // Create an ICustomDestinationList interface.
    hr = pDestList.CoCreateInstance ( CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER );

    if ( FAILED(hr) )
        return false;

    // Tell the jump list our AppID.
    hr = pDestList->SetAppID ( g_wszAppID );

    if ( FAILED(hr) )
        return false;

UINT cMaxSlots;
CComPtr<IObjectArray> pRemovedItems;

    // Create a new empty jump list. The output parameters aren't important here
    // since they are only relevant if you're adding custom items to the list.
    hr = pDestList->BeginList ( &cMaxSlots, IID_PPV_ARGS(&pRemovedItems) );

    if ( FAILED(hr) )
        return false;

    // Add a category to the list.
    hr = pDestList->AppendKnownCategory ( category );

    if ( FAILED(hr) )
        return false;

    // Save the new list.
    return SUCCEEDED( pDestList->CommitList() );
}

void CMainDlg::OnShowRecentCategory ( UINT uCode, int nID, HWND hwndCtl )
{
CString sCaption ( LPCTSTR(IDR_MAINFRAME) );

    if ( ShowCategory ( KDC_RECENT ) )
        MessageBox ( _T("The jump list now shows the Recent category."),
                     sCaption, MB_ICONINFORMATION );
    else
        MessageBox ( _T("Error changing the jump list"), sCaption, MB_ICONERROR );
}

void CMainDlg::OnShowFrequentCategory ( UINT uCode, int nID, HWND hwndCtl )
{
CString sCaption ( LPCTSTR(IDR_MAINFRAME) );

    if ( ShowCategory ( KDC_FREQUENT ) )
        MessageBox ( _T("The jump list now shows the Frequent category."),
                     sCaption, MB_ICONINFORMATION );
    else
        MessageBox ( _T("Error changing the jump list"), sCaption, MB_ICONERROR );
}

bool CMainDlg::ClearJumpList()
{
HRESULT hr;
CComPtr<IApplicationDestinations> pDests;

    // Create an IApplicationDestinations interface.
    hr = pDests.CoCreateInstance ( CLSID_ApplicationDestinations, NULL,
                                   CLSCTX_INPROC_SERVER );

    if ( FAILED(hr) )
        return false;

    // Tell the jump list our AppID.
    hr = pDests->SetAppID ( g_wszAppID );

    if ( FAILED(hr) )
        return false;

    // Clear the list of files.
    return SUCCEEDED( pDests->RemoveAllDestinations() );
}

void CMainDlg::OnClearJumpList ( UINT uCode, int nID, HWND hwndCtl )
{
CString sCaption ( LPCTSTR(IDR_MAINFRAME) );

    if ( ClearJumpList() )
        MessageBox ( _T("The jump list was cleared successfully."),
                     sCaption, MB_ICONINFORMATION );
    else
        MessageBox ( _T("Error clearing the jump list"), sCaption, MB_ICONERROR );
}

bool CMainDlg::WriteRegString ( HKEY hkeyParent, LPCTSTR szSubkey, LPCTSTR szValueName,
                                LPCTSTR szData, HANDLE hTransaction )
{
HKEY hKey;
CRegKey reg;
LONG lRet;

    lRet = RegCreateKeyTransacted (
               hkeyParent, szSubkey, 0, REG_NONE,
               REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL,
               hTransaction, NULL );

    if ( ERROR_SUCCESS != lRet )
        return false;

    reg.Attach ( hKey );

    lRet = reg.SetStringValue ( szValueName, szData );

    return ERROR_SUCCESS == lRet;
}

// This function registers this app as the default handler for *.aly files.
// The association is written to HKEY_CURRENT_USER instead of HKEY_CLASSES_ROOT
// so we can do this without requiring elevation. The jump list would work just
// as well if we did write to HKEY_CLASSES_ROOT, or if we had an installer that
// wrote the association there.
bool CMainDlg::RegisterAsHandler()
{
CHandle hTransaction;
CString sIconPath, sCommandLine, sProgIDKey;
TCHAR szModulePath[MAX_PATH] = {0};

    GetModuleFileName ( NULL, szModulePath, _countof(szModulePath) );
    sIconPath.Format ( _T("\"%s\",-%d"), szModulePath, IDI_ALY_FILETYPE );

    sCommandLine.Format ( _T("\"%s\" \"%%1\""), szModulePath );
    sProgIDKey.Format ( _T("software\\classes\\%s"), g_wszProgID );

    // Create a transaction for these registry changes.
    hTransaction.Attach ( CreateTransaction ( NULL, 0, TRANSACTION_DO_NOT_PROMOTE,
                          0, 0, 0, NULL ) );

    if ( hTransaction == NULL )
        return false;

struct { CString sKey; LPCTSTR szValue, szData; } aEntries[] =
{
    { _T("software\\classes\\.aly"), NULL, g_wszProgID },
    { _T("software\\classes\\.aly\\OpenWithProgIDs"), g_wszProgID, _T("") },
    { sProgIDKey, _T("FriendlyTypeName"), _T("ALY test file type") },
    { sProgIDKey, _T("AppUserModelID"), g_wszAppID },
    { sProgIDKey + _T("\\DefaultIcon"), NULL, sIconPath },
    { sProgIDKey + _T("\\CurVer"), NULL, g_wszProgID },
    { sProgIDKey + _T("\\shell\\open\\command"), NULL, sCommandLine }
};

    for ( int i = 0; i < _countof(aEntries); i++ )
        {
        if ( !WriteRegString ( HKEY_CURRENT_USER, aEntries[i].sKey, aEntries[i].szValue,
                               aEntries[i].szData, hTransaction ) )
            return false;
        }

    return 0 != CommitTransaction ( hTransaction );
}

// Remove the file association that was created in RegisterAsHandler().
bool CMainDlg::UnregisterAsHandler()
{
CHandle hTransaction;
LONG lRet;
HKEY hKey;
CRegKey rk;

    // Create a transaction for these registry changes.
    hTransaction.Attach ( CreateTransaction ( NULL, 0, TRANSACTION_DO_NOT_PROMOTE,
                          0, 0, 0, NULL ) );

    if ( hTransaction == NULL )
        return false;

    // Delete the .aly key.
    lRet = RegOpenKeyTransacted (
               HKEY_CURRENT_USER, _T("software\\classes"), 0,
               DELETE | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE,
               &hKey, hTransaction, NULL );

    if ( ERROR_SUCCESS != lRet )
        return false;

    rk.Attach ( hKey );

    lRet = RegDeleteTree ( rk, _T(".aly") );

    if ( ERROR_SUCCESS != lRet )
        return false;

    // Delete the alyfile key.
    lRet = RegDeleteTree ( rk, g_wszProgID );

    if ( ERROR_SUCCESS != lRet )
        return false;

    return CommitTransaction ( hTransaction ) != 0;
}

LPWSTR g_awszTestFileNames[] = { L"test1.aly", L"willow.aly", L"buffy.aly" };

// Create some test files in the user's My Documents directory.
void CMainDlg::CreateTestFiles()
{
HRESULT hr;
LPWSTR pwszMyDocsPath = NULL;

    hr = SHGetKnownFolderPath ( FOLDERID_Documents, 0, NULL, &pwszMyDocsPath );

    if ( FAILED(hr) )
        return;

    for ( int i = 0; i < _countof(g_awszTestFileNames); i++ )
        {
        WCHAR wszTestFilePath[MAX_PATH] = {0};
        CHandle hFile;

        PathCombineW ( wszTestFilePath, pwszMyDocsPath, g_awszTestFileNames[i] );

        hFile.Attach ( CreateFileW ( wszTestFilePath, GENERIC_WRITE, 0, NULL,
                                     CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL ) );

        if ( hFile != NULL )
            {
            LPCSTR szMsg = "Just a boring ol' test file, part deux.";
            DWORD cbyToWrite = strlen ( szMsg ), cbyWritten = 0;

            WriteFile ( hFile, szMsg, cbyToWrite, &cbyWritten, NULL );
            }
        }

    CoTaskMemFree ( pwszMyDocsPath );
}

// Delete the test files that were created in CreateTestFiles().
void CMainDlg::DeleteTestFiles()
{
HRESULT hr;
LPWSTR pwszMyDocsPath = NULL;

    hr = SHGetKnownFolderPath ( FOLDERID_Documents, 0, NULL, &pwszMyDocsPath );

    if ( FAILED(hr) )
        return;

    for ( int i = 0; i < _countof(g_awszTestFileNames); i++ )
        {
        WCHAR wszTestFilePath[MAX_PATH] = {0};

        PathCombineW ( wszTestFilePath, pwszMyDocsPath, g_awszTestFileNames[i] );
        DeleteFileW ( wszTestFilePath );
        }

    CoTaskMemFree ( pwszMyDocsPath );
}

void CMainDlg::ViewAlyFile ( LPCTSTR szFilePath )
{
    // Since this app doesn't do anything with its files, there isn't much here.
    // In a more substantal app, this would be where you open and parse your
    // documents.
    // We'll just check that the file exists, and bail out if not.
    if ( !PathFileExists ( szFilePath ) )
        return;

    // Show the file as the current file.
    m_cCurrFilePath.SetWindowText ( szFilePath );

    // Add it to the recent files list. This also makes Win 7 add it to the
    // app's jump list.
HRESULT hr;
SHARDAPPIDINFO info;
CComPtr<IShellItem> pItem;

    hr = SHCreateItemFromParsingName ( szFilePath, NULL, IID_PPV_ARGS(&pItem) );

    if ( SUCCEEDED(hr) )
        {
        info.psi = pItem;
        info.pszAppID = g_wszAppID;

        SHAddToRecentDocs ( SHARD_APPIDINFO, &info );
        }
}

By viewing downloads associated with this article you agree to the Terms of use 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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Michael Dunn
Software Developer (Senior) VMware
United States United States
Member
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.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 19 May 2009
Article Copyright 2009 by Michael Dunn
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid