Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Customize the My Places Bar in Common Dialogs with TweakJS

0.00/5 (No votes)
27 Dec 2005 1  
Customize Open/Save dialogs in Win 2K/XP and MS Office.

Contents

Introduction

As part of their Power Toys for Windows, Microsoft offers a cool tool called TweakUI. This tool allows you, the humble Windows XP user, to change some of XPs default settings without modifying the registry directly. This frees you from the fear and tremble that often accompanies registry modification by placing a lot of useful settings at your fingertips, rather than deeply nested in the registry bowels.

One of the tweaks that I've found particularly useful is the Places Bar page in TweakUI. This page provides the ability to set the folders that are shown in My Places Bar, the bar that is displayed in many open/save dialogs throughout Windows. Customizing this bar lets me get to the folders I save files in most often, saving a few clicks every time I save a file.

However, Microsoft Office uses its own Open/Save dialogs, and doesn't pay attention to TweakUI's modifications.

Enter TweakJS. TweakJS is similar to the Places Bar page in TweakUI, but with two significant differences:

  1. It customizes My Places Bar for both Microsoft Office and the common dialogs.
  2. It allows the creation of a set of shortcuts to the folders chosen.

Disclaimer

Although I have made every effort to make this code run safely and correctly, there is always a risk when you run any software that modifies the registry. Like the whimsical disclaimer featured on Mozilla's website, there is no guarantee that TweakJS won't fry your processor, insult your mother, or cause you to break out in a nasty rash. I take no responsibility for these or any other problems that this software may cause, though I will do my best to fix any problems reported. If this scares you, you probably shouldn't run TweakJS.

You will most likely need administrator access to take advantage of TweakJS, due to the registry modifications.

Usage

TweakJS allows you to select up to five folders that will appear in the My Places Bar. Once the folders have been selected in TweakJS, they will appear in any Open/Save dialog that shows My Places Bar.

The Links option creates shortcuts to the folders you've selected to add to the My Places Bar. Why would you do such a thing? I do it because it allows me to create a toolbar that can be placed on the taskbar, allowing easy access to the directories I've chosen (see the screenshot below):

Note that in the screenshot, the first link is titled Customize Links. This link is added automatically. It points to the folder all the shortcuts are stored in, making it easy to get to that folder and add more shortcuts as and when necessary.

To make this toolbar visible:

  1. Right-click on the taskbar.
  2. Select Toolbars | New Toolbar.
  3. Browse for the output folder you chose in TweakJS.
  4. You will probably need to size the toolbar by sliding it up next to the system tray.
  5. It may be necessary to unlock the toolbar (right-click, uncheck Lock the Taskbar) before you are able to move/size your new toolbar.

Background

In order to configure the common dialogs within Microsoft Office, I consulted Microsoft's knowledge base. Article 20501 documents the registry changes that are necessary to customize the My Places Bar in MS Office 2000's Open/Save dialogs. The registry settings are found in the key: HKCU\Software\Microsoft\Office\9.0\Common\OpenFind\Places.

The number 9.0 makes these settings Office 2000-specific. The number is 10.0 for Office XP, and 11.0 for Office 2003. I don't have access to Office XP or 2003, so I was unable to test this code with those programs.

The settings that are modified by TweakUI are stored in a different format, and are located here: HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\comdlg32\PlacesBar. These settings can obviously be modified manually, but using TweakJS is much simpler and faster.

After writing this code, I found two applications that also allow customizing the My Places Bar, one for the common Windows dialogs, and another for Microsoft Office dialogs. The programs are located here. I haven't used them, so I can't vouch for them. These programs offer the additional feature of being able to save and recall lists of directories, which may be very useful if you need access to different sets of directories depending on which project you're working on, or even what type of work you are doing. If there is enough demand for such a feature, I will consider implementing it in TweakJS. I still feel that TweakJS is a worthwhile program, as it allows the user to customize both MS Office's and Windows' common dialogs at once.

Removal

The first time TweakJS runs, it backs up the current Common Dialog settings in Windows and MS Office. The registry keys that will be modified are exported to two files, Office.reg for MS Office settings, and Windows.reg for Windows settings. The following commands are run to create these files (the second command varies depending on the version of Office detected, or if Office is detected at all):

[Editor comment: Line breaks used to avoid scrolling.]

Regedit /e Windows.reg HKCU\Software\Microsoft\
         Windows\CurrentVersion\Policies\comdlg32\PlacesBar 
Regedit /e Office.reg "HKCU\Software\Microsoft\Office\9.0\
                                            Common\OpenFind

When the Remove button is pressed, TweakJS creates a registry script (remove.reg) to delete all the registry keys that were modified by TweakJS:

[Editor comment: Line breaks used to avoid scrolling.]

[-HKEY_CURRENT_USER\Software\Microsoft\Office\9.0\
                             Common\Open Find\Places]
[-HKEY_CURRENT_USER\Software\Microsoft\Windows\
                    CurrentVersion\Policies\comdlg32]
[-HKEY_CURRENT_USER\Software\TweakJS]

It first runs the script it created, removing all the changes TweakJS has made, and then merges the two backup files TweakJS created the first time it ran. It also opens the directory TweakJS is running from, in case you want to delete the executable.

Overview of the code

TweakJS is a fairly simple program, consisting of:

  1. User interface code.
  2. Code to read from and write to the registry.
  3. Code to detect the installed version of MS Office.
  4. Code to write shortcut files out.

User interface code

The combo boxes in TweakJS emulate their TweakUI counterparts. The class CBrowseCombo encapsulates their functionality. CBrowseCombo is a combobox that allows the user to browse for a folder to be added to the combobox. As its name suggests, it is derived from CComboBox. Making CBrowseCombo a derived class saves the dialog class, CTweakJSDlg, from having to deal with initializing it or handling its browse behavior. Since there are five of them, it also eliminates a bunch of redundant code.

A CBrowseCombo box in action.

Each of the default locations in the combo box is named by getting the display name for its CSIDL from the shell:

bool CBrowseCombo::GetDisplayName(UINT csidl, 
                              CString& sDisplayName)
{
    // A pointer to the shell's IMalloc interface

    IMalloc * pShellMalloc = NULL; 
    // A pointer to the parent folder object's 

    // IShellFolder interface.      

    IShellFolder *psfParent; 
    // The item's PIDL.            

    LPITEMIDLIST pidlItem = NULL; 
    // The item's PIDL relative to the parent folder.       

    LPITEMIDLIST pidlRelative = NULL;   
    // The structure for strings returned from 

    // IShellFolder. 

    STRRET str;                           
    // The display name of this folder

    TCHAR szDisplayName[MAX_PATH]= _T("");
    HRESULT hr = SHGetMalloc(&pShellMalloc);
    
    bool bReturn = false;
    if (SUCCEEDED(hr))
    {
        hr = SHGetSpecialFolderLocation(NULL, 
                              csidl, &pidlItem);
        if (SUCCEEDED(hr))
        {
            hr = SHBindToParent(pidlItem, 
                    IID_IShellFolder, (void**)&psfParent, 
                    (LPCITEMIDLIST*)&pidlRelative);
            if (SUCCEEDED(hr))
            {
                // Retrieve the display name

                memset(&str, 0, sizeof(str));
                hr = psfParent->GetDisplayNameOf(
                          pidlRelative, SHGDN_NORMAL, &str);
                if (SUCCEEDED(hr))
                {
                    StrRetToBuf(&str, pidlItem, 
                                   szDisplayName, MAX_PATH);
                    sDisplayName = szDisplayName;
                    bReturn = true;
                }

                psfParent->Release();
            }
        }
        
        // Clean up allocated memory

        if (pidlItem)
            pShellMalloc->Free(pidlItem);
        
        pShellMalloc->Release();
    }    
    return bReturn;
}

The item data for each of the default locations shown in CBrowseCombo is set to its CSIDL. The three extra items, Path (if present), Browse, and the other, have item data values of -1, -2, and -3, respectively. This allows the dialog to handle these cases specially.

Browsing for a folder uses the shell dialog code that's probably present everywhere by now:

bool CUtility::Browse(HWND hOwner, CString &sFolder)
{
    BROWSEINFO bi;
    ZeroMemory(&bi, sizeof(BROWSEINFO));
    
    bi.hwndOwner = hOwner;
    bi.ulFlags   = BIF_RETURNONLYFSDIRS;
    
    LPITEMIDLIST pidl = SHBrowseForFolder(&bi);
    
    bool bRet = false;
    
    TCHAR szFolder[MAX_PATH*2];
    szFolder[0] = _T('\0');
    
    if (pidl)
    {
        if (SHGetPathFromIDList(pidl, szFolder))
        {
            bRet = true;
            sFolder = szFolder;
        }
        
        IMalloc *pMalloc = NULL; 
        if (SUCCEEDED(SHGetMalloc(&pMalloc)) && pMalloc) 
        {  
            pMalloc->Free(pidl);  
            pMalloc->Release(); 
        }
    }
    
    return bRet;
}

Registry code

TweakJS uses Robert Pittenger's CRegistry class to read from and write to the registry. The version included here incorporates PEK's changes to add Unicode support to ReadString and WriteString. I changed WriteDword to specify REG_DWORD instead of REG_BINARY, and added a useful EnumerateKeys function, shown here:

void CRegistry::EnumerateKeys(CStringArray &saReturn)
{
    //SetKey MUST be called beforehand.

    ASSERT(m_strCurrentPath.GetLength() > 0);

    //Set up return array.

    saReturn.RemoveAll();

    HKEY hKey;
    if (::RegOpenKeyEx(m_hRootKey, LPCTSTR(m_strCurrentPath), 
                   0, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS) 
        return;
    //Get some sizes to make life easier.

    DWORD dwNumKeys, dwMaxNameLength, dwSize;
    LONG lResult = RegQueryInfoKey(hKey, 0, 0, 0, 
                   &dwNumKeys, &dwMaxNameLength, 0, 
                   0, 0, 0, 0, 0);
    
    //Include room for a NULL in the buffer length

    dwMaxNameLength++;

    TCHAR *pBuffer = new TCHAR[dwMaxNameLength];
    for(unsigned int i=0; i < dwNumKeys; i++)
    {
        dwSize = dwMaxNameLength;
        lResult = RegEnumKeyEx(hKey, i, pBuffer, 
                                 &dwSize, 0,0,0,0);
        if(lResult == ERROR_SUCCESS)
            saReturn.Add(pBuffer);
    }
    delete[] pBuffer;
}

CRegistry is called upon to write to three different locations: TweakJS's registry location, where its settings are saved, the location for common Windows dialogs, and the location for MS Office. As was noted earlier, the formats differ for MS Office and Windows. I opted to save TweakJS's settings using the same format that Windows does, as I found it to be more appealing.

Detecting Microsoft Office

It would be nice to query the version of MS Office by checking the registry to find the path of the executable registered to the .doc file extension, and then using FileVersionEx to retrieve the version. I opted for the lazy way out, simply looking for the registry key for each of Office 97, 2000, XP, and 2003.

[Editor comment: Line breaks used to avoid scrolling.]

float fVer = 8.0f;
bool bFound = false;
CString sOfficeRoot;
while (!bFound && fVer < 12.0f)
{
    sOfficeRoot.Format(_T("Software\\Microsoft\\
         Office\\%1.1f\\Common\\Open Find\\Places"), fVer);
    bFound = 
      m_pRegistry->KeyExists(sOfficeRoot, HKEY_CURRENT_USER);
    fVer++;
}

//Display the installed version of office detected to the user.

m_sOfficeVer = _T("None");
if (bFound)
{
    int iVer = fVer - 1.0f;
    switch (iVer)
    {
    case 8:
        m_sOfficeVer = 
          _T("Office 97");//Unlikely/Impossible to run into this.

        break;
    case 9:
        m_sOfficeVer = _T("Office 2000");
        break;
    case 10:
        m_sOfficeVer = _T("Office XP");
        break;
    case 11:
        m_sOfficeVer = _T("Office 2003");
        break;
    default:
        ASSERT(FALSE);
    }
}

Shortcuts

The shortcut code was provided via PJ Naughter's easy-to-use CShellLink class.

Points of interest

As is usually the case, despite this project's trivial appearance, there are several interesting points that are presented here:

  • Some special folders don't show up with their expected names in MS Office. For example, the folder that is displayed as Shared Documents is actually a folder named "Documents" in C:\Documents and Settings\All Users. To retrieve the name that is displayed to the user, I used the SHGetFileInfo shell call and specified SHGFI_DISPLAYNAME.
  • Until I worked on TweakJS, I used to encounter the REG_EXPAND_SZ data specifier while editing the registry. While investigating the behavior of TweakUI, I noticed that it writes certain strings out as REG_EXPAND_SZ to the registry. Using PathUnExpandEnvStrings (documentation here), it is possible to convert a path in the form C:\Documents and Settings\User\Folder to %USERPROFILE%\Folder. PathUnExpandEnvStrings works for several other locations that are stored in the environment variables, such as the C:\Windows, and C:\Program Files, even returning the location of the system drive (in the examples that I've shown, it's C:\) as %SystemDrive%. The inverse of this function is ExpandEnvironmentStrings. It is worth noting that Microsoft Office reads REG_SZ (regular strings) from the registry, and does not support the use of REG_EXPAND_SZ. Note: Platform SDK is required to build TweakJS because of its use of ExpandEnvironmentStrings and PathUnExpandEnvStrings.
  • Although building in Unicode is a fairly standard practice, I haven't made a habit of doing it. I found that in addition to making the code Unicode-compliant, the following steps were necessary to add Unicode build configurations to my project:
    1. Choose Configurations from the Build menu.
    2. Add "Unicode Debug" - Copy Win32 Debug.
    3. Add "Unicode Release" - Copy Win32 Release.
    4. Click OK.
    5. Choose Settings from the Project menu.
    6. Go to the General tab.
    7. For "Win32 Unicode Debug", set Intermediate Files and Output Files to DebugU.
    8. For "Win32 Unicode Release", set Intermediate Files and Output Files to ReleaseU.
    9. Go to the C++ tab.
    10. Choose "Preprocessor" from the combo-box.
    11. For "Win32 Unicode Debug" and "Win32 Unicode Release", add _UNICODE and UNICODE to the list of preprocessor variables.
    12. Go to the Link tab.
    13. Choose "Output" from the combo-box.
    14. Enter wWinMainCRTStartup in the Entry Point Symbol box.

Caveats

Users of MS Office 2002/XP and 2003 can easily update their My Places Bar through the bar itself. See Microsoft's Knowledge Base article KB 826214.

The astute reader will notice that My Computer and Network Neighborhood don't appear in TweakJS's combo boxes. This is because they are virtual folders that don't resolve to a path, and hence cannot be displayed by MS Office. Even setting the path string to the CLSID of the folder (::{20d04fe0-3aea-1069-a2d8-08002b30309d} for My Computer and ::{208D2C60-3AEA-1069-A2D7-08002B30309D} for My Network Places) didn't work. If you haven't experienced the thrill of opening the Run dialog (Start | Run ...) and typing in one of those strings, give it a shot. Rather than displaying these options and have them work incorrectly in MS Office, I opted not to offer them. If you know of a workaround, please contact me. I'll update the code and credit you for the same.

Acknowledgements

CodeProject's rich collection of articles has been very useful to me over the years. I offer this, my first article, to the CP community, in return for all I've learned from my fellow CPians. Special thanks go to the CP members whose code I have used here, and to Marc Clifton, for his Guide to Writing Articles for Code Project.

History

  • Version 1.0 - Initial release.

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