Click here to Skip to main content
15,895,462 members
Articles / Desktop Programming / WTL

Windows 7 Goodies in C++: Jump Lists

Rate me:
Please Sign up or sign in to vote.
4.90/5 (42 votes)
19 May 2009CPOL13 min read 121.7K   1.8K   87  
An intro to using jump lists with your Windows 7 applications
// MainDlg.cpp : implementation of the CMainDlg class
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "NaiveAlyViewer.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 );
}


/////////////////////////////////////////////////////////////////////////////
// Command handlers

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 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;
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 );

    // 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 { LPCTSTR szKey, szValue, szData; } aEntries[] =
{
    { _T("software\\classes\\.aly"), NULL, _T("alyfile") },
    { _T("software\\classes\\alyfile"), NULL, _T("ALY test file type") },
    { _T("software\\classes\\alyfile\\DefaultIcon"), NULL, sIconPath },
    { _T("software\\classes\\alyfile\\shell\\open\\command"), NULL, sCommandLine }
};

    for ( int i = 0; i < _countof(aEntries); i++ )
        {
        if ( !WriteRegString ( HKEY_CURRENT_USER, aEntries[i].szKey, 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, _T("alyfile") );

    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.";
            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.
    SHAddToRecentDocs ( SHARD_PATH, szFilePath );
}

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


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