Developer Blog
Articles about Using Microsoft Developer Tools

Ensuring a Path Exists

Monday, May 03, 2010 9:26 AM by jonwood

NOTE: This article has been updated and moved to: Ensuring a Path Exists.

I was recently working on a C++/MFC application that needed to backup a bunch of files. Copying files is easy enough, but I had to create the target folder if it didn't exist. Creating a folder is also easy. However, CreateDirectory() will not create intermediate directories.

That is, if I need to create the directory D:\Documents\Financials, CreateDirectory() will fail if D:\Documents does not already exist. To build this directory, I must first create D:\Documents before I can create D:\Documents\Financials. Clearly, I needed a routine that will ensure a path of any depth exists, and will create any missing folders.

Note that SHCreateDirectory() is documented as doing just this. However, the documentation also states this function "might be altered or unavailable in subsequent versions of Windows." I didn't really care for the sound of that so I decided to write my own routine. Although it required a little thought, the routine I came up with with reasonably simple and quite short.

Listing one shows my EnsurePathExists() routine. It takes a path string, and will determine if it exists. If it does not exist, the routine will create it. It returns a Boolean value, which is false if a portion of the directory could not be created.

// Ensures the given path exists, creating it if needed
bool EnsurePathExists(LPCTSTR lpszPath)
{
  CString sPath;
  // Nothing to do if path already exists
  if (DirectoryExists(lpszPath))
    return true;
  // Ignore trailing backslash
  int nLen = _tcslen(lpszPath);
  if (lpszPath[nLen - 1] == '\\')
    nLen--;
  // Skip past drive specifier
  int nCurrLen = 0;
  if (nLen >= 3 && lpszPath[1] == ':' && lpszPath[2] == '\\')
    nCurrLen = 2;
  // We can't create root so skip past any root specifier
  while (lpszPath[nCurrLen] == '\\')
    nCurrLen++;
  // Test each component of this path, creating directories as needed
  while (nCurrLen < nLen)
  {
    // Parse next path compenent
    LPCTSTR psz = _tcschr(lpszPath + nCurrLen, '\\');
    if (psz != NULL)
      nCurrLen = (int)(psz - lpszPath);
    else
      nCurrLen = nLen;
    // Ensure this path exists
    sPath.SetString(lpszPath, nCurrLen);
    if (!DirectoryExists(sPath))
      if (!::CreateDirectory(sPath, NULL))
        return false;
    // Skip over current backslash
    if (lpszPath[nCurrLen] != '\0')
      nCurrLen++;
  }
  return true;
}
// Returns true if the specified path exists and is a directory
bool DirectoryExists(LPCTSTR lpszPath)
{
  DWORD dw = ::GetFileAttributes(lpszPath);
  return (dw != INVALID_FILE_ATTRIBUTES &&
    (dw & FILE_ATTRIBUTE_DIRECTORY) != 0);
}

Listing 1: EnsurePathExists() function.

The code processes a single component, or layer, of the path at a time, either ensuring it exists or creating it if it does not. It uses my helper routine DirectoryExists() to determine if each component exists. If it does not, that component is created using CreateDirectory().

To save time, EnsurePathExists() starts by testing for the existence of the entire path. If it already exists, the function simply returns.

That's about all there is to it. Most of the details are just in making sure each component is properly parsed and tested. The code skips the root folder because you cannot create a root folder. The code also strips multiple leading backslashes, as might be seen in paths that refer to network locations.

Reading and Writing CSV Files in MFC

Thursday, January 21, 2010 6:39 PM by jonwood

NOTE: This article has been updated and moved to: Reading and Writing CSV Files in MFC.

I recently had the need to import and export CSV files in an MFC application. A CSV (Comma-Delimited Values) file is a plain-text file where each row contains one or more fields, separated by commas.

CSV files are probably best known for their use by Microsoft Excel. CSV files provide a convenient format for sharing spreadsheet data between applications, particularly when you consider having your application work directly with native Excel files would be a very complex task.

The CSV file format is not complex. Mostly, it just requires some simple parsing. One trick is when a data field contains a comma. Since commas are used to delimit fields, this would cause problems and so such fields are enclosed in double quotes. And since double quotes have special meaning, we have problems if a data field contains a double quote and so such fields are also enclosed in double quotes, and pairs of double quotes are interpreted to mean a single double quote in the data.

My header file is shown in Listing 1 and my source file is shown in listing 2.

#pragma once
#include "afx.h"
class CCSVFile : public CStdioFile
{
public:
  enum Mode { modeRead, modeWrite };
  CCSVFile(LPCTSTR lpszFilename, Mode mode = modeRead);
  ~CCSVFile(void);
  bool ReadData(CStringArray &arr);
  void WriteData(CStringArray &arr);
#ifdef _DEBUG
  Mode m_nMode;
#endif
};

Listing 1: CSVFile.h

The class is very simple: it only contains two methods (besides the constructor and destructor).

Note that it is up to the caller to ensure that only the ReadData() method is called when the constructor specified read mode, and only the WriteData() method is called when the constructor specified write mode. To help enforce this, the code asserts if it is not the case when _DEBUG is defined.

#include "StdAfx.h"
#include "CSVFile.h"
CCSVFile::CCSVFile(LPCTSTR lpszFilename, Mode mode)
  : CStdioFile(lpszFilename, (mode == modeRead) ?
    CFile::modeRead|CFile::shareDenyWrite|CFile::typeText   
    :
    CFile::modeWrite|CFile::shareDenyWrite|CFile::modeCreate|CFile::typeText)
{
#ifdef _DEBUG
  m_nMode = mode;
#endif
}
CCSVFile::~CCSVFile(void)
{
}
bool CCSVFile::ReadData(CStringArray &arr)
{
  // Verify correct mode in debug build
  ASSERT(m_nMode == modeRead);
  // Read next line
  CString sLine;
  if (!ReadString(sLine))
    return false;
  LPCTSTR p = sLine;
  int nValue = 0;
  // Parse values in this line
  while (*p != '\0')
  {
    CString s;  // String to hold this value
    if (*p == '"')
    {
      // Bump past opening quote
      p++;
      // Parse quoted value
      while (*p != '\0')
      {
        // Test for quote character
        if (*p == '"')
        {
          // Found one quote
          p++;
          // If pair of quotes, keep one
          // Else interpret as end of value
          if (*p != '"')
          {
            p++;
            break;
          }
        }
        // Add this character to value
        s.AppendChar(*p++);
      }
    }
    else
    {
      // Parse unquoted value
      while (*p != '\0' && *p != ',')
      {
        s.AppendChar(*p++);
      }
      // Advance to next character (if not already end of string)
      if (*p != '\0')
        p++;
    }
    // Add this string to value array
    if (nValue < arr.GetCount())
      arr[nValue] = s;
    else
      arr.Add(s);
    nValue++;
  }
  // Trim off any unused array values
  if (arr.GetCount() > nValue)
    arr.RemoveAt(nValue, arr.GetCount() - nValue);
  // We return true if ReadString() succeeded--even if no values
  return true;
}
void CCSVFile::WriteData(CStringArray &arr)
{
  static TCHAR chQuote = '"';
  static TCHAR chComma = ',';
  // Verify correct mode in debug build
  ASSERT(m_nMode == modeWrite);
  // Loop through each string in array
  for (int i = 0; i < arr.GetCount(); i++)
  {
    // Separate this value from previous
    if (i > 0)
      WriteString(_T(","));
    // We need special handling if string contains
    // comma or double quote
    bool bComma = (arr[i].Find(chComma) != -1);
    bool bQuote = (arr[i].Find(chQuote) != -1);
    if (bComma || bQuote)
    {
      Write(&chQuote, sizeof(TCHAR));
      if (bQuote)
      {
        for (int j = 0; j < arr[i].GetLength(); i++)
        {
          // Pairs of quotes interpreted as single quote
          if (arr[i][j] == chQuote)
            Write(&chQuote, sizeof(TCHAR));
          TCHAR ch = arr[i][j];
          Write(&ch, sizeof(TCHAR));
        }
      }
      else
      {
        WriteString(arr[i]);
      }
      Write(&chQuote, sizeof(TCHAR));
    }
    else
    {
      WriteString(arr[i]);
    }
  }
  WriteString(_T("\n"));
}

Listing 2: CSVFile.cpp

There are many ways to go about parsing text. I was more comfortable manually stepping through the text, character-by-character and so that’s what the code does.

As implied by the names, ReadData() is used to read to a CSV file while WriteData() is used to write to one. Each line of data is stored in a CStringArray[], which is passed by reference to both functions. The caller must call these methods once for each line. When calling ReadData(), false is returned when the end of the file is reached.

As mentioned, the code is fairly simple but I’ve actually found I’ve used this code on several occasions and even ported it to C#. Perhaps you will find it useful as well.

Tags:  
Categories:   C++/MFC
Actions:   E-mail | del.icio.us | Permalink | Comments (2) | Comment RSSRSS comment feed

Implement Accelerators in a Dialog-Based Application

Tuesday, June 09, 2009 6:57 AM by jonwood

NOTE: This article has been updated and moved to: Implement Accelerators in an MFC Dialog Box.

One of the options you have when creating an MFC application is to make your application dialog-based. This is one of the options in the MFC Wizards.

Dialog-based applications are simply an application where the main window is a dialog box. Applications that need to display a bunch of controls in the main window are considerably easier to implement as a dialog-based application.

In addition, you can also add a menu to your dialog box, which can complete a sophisticated dialog-based application. A menu can be added by simple choosing the menu resource ID in the properties window for the dialog box. But if your menu has accelerators (hotkeys) you’ll notice that they don’t work. In this article, I’ll describe getting accelerators to work in a dialog box.

The discussion below assumes you have created a dialog box with a menu, that you’ve also created an accelerator resource with hotkeys for one or more commands in your dialog box menu, and that you’ve defined handlers for those commands.

In your main application class (not the dialog box class), define the following member variable.

HACCEL m_hAccelTable;

Listing 1: Add variable to main application class.

Next, initialize this variable in your main application class’s InitInstance() method. You may want to initialize it after the basic MFC initialization code but be sure to initialize it before the main dialog box is displayed. Again, this is in your main application class and not in your dialog box class.

BOOL CMyApp::InitInstance()
{
  // ...
  m_hAccelTable = LoadAccelerators(AfxGetInstanceHandle(),
    MAKEINTRESOURCE(IDR_ACCELERATOR1));
  // ...
}

Listing 2: Initialize variable in InitInstance().

Note that this code assumes the ID of your accelerator is IDR_ACCELERATOR1. You’ll need to change it if you are using a different resource ID.

Next, override ProcessMessageFilter in your main application class. Again, this is in your main application class and not in your dialog box class.

BOOL CMyApp::ProcessMessageFilter(int code, LPMSG lpMsg)
{
  if (code >= 0 && m_pMainWnd && m_hAccelTable)
  {
    if (::TranslateAccelerator(m_pMainWnd->m_hWnd, m_hAccelTable, lpMsg))
      return TRUE;
  }
  return CWinApp::ProcessMessageFilter(code, lpMsg);
}

Listing 3: Override ProcessMessageFilter().

And that’s all there is to it. Assuming you have the menu and accelerator resources implemented and associated with your dialog box, and you have handlers for the commands, the accelerators should now work.

I’m not sure why this change is even necessary and couldn’t easily find out. At first glance, it sure seems like this trivial amount of code should be implemented in the base MFC classes. But this approach is simple and should work for any dialog box in your application (that includes a menu and accelerator resource).

Categories:   C++/MFC
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

What are Kilobytes, Megabytes, Gigabytes, Etc?

Friday, April 03, 2009 5:16 AM by jonwood

Okay, we are all familiar with kilobytes, megabytes, gigabytes and the like. Most of us know that a kilobyte is approximately a thousand bytes. And that is accurate enough for most purposes. But what if you need to need to deal with these numbers a bit more accurately?

A kilobyte is exactly 1,024 bytes. So why not 1,000 bytes? Computers use binary to generate numbers. So memory addresses are naturally a power of two. 1,000 is not a power-of-two number, but 1,024 is.

Just as a million is a thousand times a thousand, a megabyte is a kilobyte times a kilobyte, or 1,048,576 bytes. Again, close enough to a million for many purposes but not exactly a million.

Here are the values for some power-of-two numbers.

Number Abbreviation Value
1 Kilobyte KB 1,024 Bytes
1 Megabyte MB 1,048,576 Bytes
1 Gigabyte GB 1,073,741,824 Bytes
1 Terabyte TB 1,099,511,627,776 Bytes
1 Petabyte PB 1,125,899,906,842,624 Bytes
1 Exabyte EB 1,152,921,504,606,846,976 Bytes

    I was working on an MFC application recently and needed to define some large numbers that I felt would be more efficient if the numbers were a power of two. I ended up defining the following C++ macros.

    #define KB(n) (((UINT64)0x400)*((UINT64)(n))) 
    
    #define MB(n) (((UINT64)0x100000)*((UINT64)(n))) 
    
    #define GB(n) (((UINT64)0x40000000)*((UINT64)(n))) 
    
    #define TB(n) (((UINT64)0x10000000000)*((UINT64)(n))) 
    
    #define PB(n) (((UINT64)0x4000000000000)*((UINT64)(n))) 
    
    #define EB(n) (((UINT64)0x1000000000000000)*((UINT64)(n)))

    Macros to make it easy to declare large power-of-two numbers.

    These macros make it easy to exactly declare a large number. For example, if I want to define four gigabytes, I can simply say GB(4).

    Nothing complicated here. But if you need to make use of these numbers, it’s nice to have a table of actual values along with some handy helper macros.

    Tags:  
    Categories:   General | C++/MFC
    Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

    SQLite

    Saturday, December 13, 2008 9:09 AM by jonwood

    MFC programmers wanting to store data to disk have a couple of choices.

    Although there are a variety of third-party options available, the choice generally comes down to a home-grown solution that includes code to access raw disk files, or using a major database server.

    Home grown solutions can be anything from fprintf() to complex data-handling systems. By coding it yourself, you can add just the code you need and have it work exactly how you want. Of course, it can also be a lot of work, especially if you need something more sophisticated.

    Major database servers make it easier to efficiently handle large amounts of data. And, using SQL (structured query language), you have a lot of flexibility in how your application can extract that data. However, this can complicate distribution and installation of your application if it requires access to a bunch of additional modules or perhaps even a database connection.

    I was recently writing an application for a client and our needs seemed to fall somewhere between these two choices. We didn’t want the coding and distribution overhead associated with a complete database server, but we did want some flexibility on extracting the data in order to be able to produce a number of reports.

    For this project, we decided to go with SQLite. SQLite is a complete SQL database engine but works quite different than those you may be familiar with. For starters, SQLite is completely free of charge. And, although this means no support, there are some forums you can access and I’ve found support there to be pretty good.

    Second, the library comes as raw source code. Although you could compile it into a DLL and call the DLL, we simply included the source code in our project. They provide a combined source code so all we had to do was include the one file and a header file and we were good to go.

    Of course, SQLite does not run as a separate process as a database server would. It is simply a library of subroutines in our application (which we developed a class library around). But we didn’t need a database server. And because the database is now part of our application, we don’t need to worry about distribution issues, installation problems, version incompatibilities, or anything else of that nature. And we have full SQL support when querying our data.

    SQLite has a few oddities. The main one is that it does not enforce referential integrity. If you delete a row that is referenced by a foreign key in another table, you now have an invalid reference. It is possible to use triggers to catch this and perform, for example, a cascading delete. For our purposes, this hasn’t really been an issue.

    Another thing is the way it stores data. You can specify a column to be an integer data type, but it doesn’t mean that’s how it will be stored. You can easily assign a string to a field in that column. It will correctly sort those strings based on their integer values. You just don’t know how the actual data will be stored. This has never been an issue for us either.

    All in all, I think SQLite is totally cool. I now have all the power of SQL in a stand-alone application. And, being free, the price cannot be beat. You’d be surprised how many applications are using it as well. From desktop applications to cell phones, it contains thoroughly tested code. Depending on your needs, perhaps SQLite is appropriate for your next project too.

    Editing Ribbon Bar Images

    Saturday, December 13, 2008 8:37 AM by jonwood

    If you’ve installed the Visual C++ 2008 Feature Pack or upgraded to Visual Studio 2008 Service Pack 1, you may already be working with the new ribbon bar control.

    This control provides many new user-interface elements and cool new images. However, if you are like many people, you may have wondered how you can change those images. Even with all its powerful resource editing features, the current version of Visual Studio does not support editing the type of images used by the ribbon bar. You can open them in Visual Studio, but they will appear with strange black areas and will not be editable.

    Ribbon Bar Images

    So what’s different about the images used in the ribbon bar compared to those images used in icons, cursors, and regular bitmaps? Two things: The color depth and the alpha channel. Ribbon bar images use 32 bits per pixel to specify color values. This allows for images with a much greater color depth.

    The alpha channel is a value that specifies the transparency of a pixel. Color values have historically used a byte each to specify red, green, and blue color values. In a 32-bit integer, this leaves a fourth byte available for the alpha channel. This means that we can specify the transparency of a given pixel with the same resolution we specify any of the other color components.

    The alpha channel is great for smoothing the edges of images. Sometimes, bitmap edges can look jagged when dark outlines have awkward correlation with the underlying pixels. These edges can be smoothed somewhat by adding gray pixels to smooth the transition from black image pixels to white background pixels. But what happens if the background is black? The alpha channel allows you to fade the edge of an image into any background color. The result is much smoother outlines and transparent areas regardless of the background.

    IconWorkshop

    So if you can’t use Visual Studio, you’ll need another tool. Fortunately, Axialis has made arrangements with Microsoft to provide a free version of their IconWorkshop. This is a great little program. In fact, I registered the full version quite some time ago.

    IconWorkshop has a recently added feature that displays an image strip bitmap as a collection of separate images, similar to how the toolbar editor does in Visual Studio. This makes it very easy to work with image strips for ribbon bars. I should point out that most of the ribbon images I put together these days are either some of those provided with Visual Studio, created using high-end graphics programs, or even imported from photos. IconWorkshop is great for importing various image types into a single image strip, even if you don’t use it to actually create the original images.

    Application Button

    There are two primary types of ribbon images that you’ll want to change. The first is the application or “jewel”. This is the image that appears in the round button near the upper left corner of the main window. By default, CMainFrame::InitializeRibbon() initializes this button to use the bitmap resource with the ID IDB_MAIN.

    // Init main button:
    
    m_MainButton.SetImage(IDB_MAIN);
    
    m_MainButton.SetText(_T("\nf"));
    
    m_MainButton.SetToolTipText(strTemp);

    Default initialization of ribbon application button

    Note that when you load main.bmp (IDB_MAIN) in IconWorkshop, it will appear as an image strip with a single image. Don’t let that throw you. Just edit the image and save it and you’ll have a new application button image.

    Command Images

    The other primary type of images are for the actual ribbon commands. As described before, these images are stored as image strips where a single bitmap contains a number of different images. The image strip is set when a ribbon bar category, or tab, is created. Two image strips are specified: one for large images and one for small images. For example, the default code specifies the image strip bitmaps with the IDs IDB_WRITESMALL and IDB_WRITELARGE when creating the Home category. If you open either of these bitmaps, you can see the images they contain.

    // Add "Home" category with "Clipboard" panel:
    
    bNameValid = strTemp.LoadString(IDS_RIBBON_HOME);
    
    ASSERT(bNameValid);
    
    CMFCRibbonCategory* pCategoryHome = m_wndRibbonBar.AddCategory(strTemp,
    
      IDB_WRITESMALL, IDB_WRITELARGE);
    

    Default initialization of category image strip

    When a ribbon element such as CMFCRibbonButton is created, you specify the zero-based index for the image to be used with zero specifying the left-most image. You can specify two indexes, one for the index into the small image strip, and one for the index into the large image strip. If you only specify the index for the small image, then that element will only use the small image.

    Conclusion

    So there are a few new things to learn here and a few more tool that you’ll need.

    Although the ribbon bar can seem a bit awkward to experienced users accustomed to using toolbars and pull-down menus, we’re told that research shows the ribbon bar layout to be much easier for most users to learn.

    When done well, ribbon bars can look great and support some really cool images. This is definitely something to get on top of if you are designing modern interfaces in MFC.

    Advanced Message Map Techniques

    Tuesday, November 25, 2008 9:37 AM by jonwood

    NOTE: This article has been updated and moved to: Advanced Message Map Techniques.

    Experienced MFC developers will already be familiar with message maps. A message map uses macros to create a table of message or command IDs and the methods that will handle those messages in a particular class. When a command or message is routed to an instance of that class, MFC scans the table for an entry that corresponds to the current message. If the entry is found, the corresponding method is called.

    BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
    
        ON_COMMAND(ID_MYCMD, OnMyCommand)
    
        // Additional entries can go here
    
    END_MESSAGE_MAP( )

    A message map

    When you add a C++ event handler in Visual Studio, the Wizards automatically add entries to the message map. So, normally, you don’t need to think much about them. The two types of entries that the Wizards will add automatically are regular message and command handlers, and update handlers, which are used to update the state of a command’s button or menu.

    However, there are some advanced message map techniques that are not directly supported by the Wizards. These techniques require you to edit the message map manually.

    Command Range Handlers

    The first technique I’ll discuss is using command range handlers. Command range handlers allow you to specify a single method that will handle a range of command IDs. For example, you may have a number of commands for setting a color used by your application. In this case, it may be easier to write one method that processes all of these commands since the response to each command will be very similar.

    To accomplish this, you can use the ON_COMMAND_RANGE macro. The arguments to this macro are the starting command ID, the ending command ID, and the method handler. The method will be called for each command in the specified range.

    // In class header
    
    afx_msg void OnColorCommand(UINT nID);
    
    // In message map
    
    BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
    
      ON_COMMAND_RANGE(ID_FIRST, ID_LAST, OnColorCommand)
    
    END_MESSAGE_MAP()
    
    // In class implementation
    
    void CMyDoc::OnColorCommand(UINT nID)
    
    {
    
      switch (nID)
    
      {
    
      case ID_FIRST:
    
        // Handle this command
    
        break;
    
      case ID_LAST:
    
        // Handle this command
    
        break;
    
      // Handle any additional commands
    
      // ...
    
      }
    
    }

    Command Range Handler

    In addition, you can use the ON_UPDATE_COMMAND_UI_RANGE macro to specify a range update handler. This allows you to control the command state from a single update handler. This macro takes the same arguments as ON_COMMAND_RANGE and the handler method takes an additional argument, which is a pointer to a CCmdUI object. You can use this argument to control the state of the corresponding menu/button command.

    User Message Handlers

    In some cases, it may be necessary to define your own message and then add a handler for that message. This can be accomplished using the ON_MESSAGE macro.

    // In class header
    
    #define WM_CUSTOMMESSAGE (WM_USER + 100)
    
    afx_msg LRESULT OnCustomMessage(WPARAM wParam, LPARAM lParam);
    
    // In message map
    
    BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
    
      ON_MESSAGE(WM_CUSTOMMESSAGE, OnCustomMessage)
    
    END_MESSAGE_MAP()
    
    // In class implementation
    
    LRESULT CMyDoc::OnCustomMessage(WPARAM wParam, LPARAM lParam)
    
    {
    
      return 0;
    
    }

    User Message Handler

    Registered Message Handlers

    Sometimes you need to handle a user message, but you don’t know the value of that message until run time. For example, you may be using RegisterWindowMessage() to obtain the message value to ensure different instances of your application will be using the same value.

    This can be accomplished using the ON_REGISTERED_MESSAGE macro. Instead of taking an explicit command ID, this macro references an integer variable that will hold the ID value at run time.

    // In class header
    
    UINT m_nRegMsg;
    
    afx_msg LRESULT OnRegisteredMessage(WPARAM wParam, LPARAM lParam);
    
    // In message map
    
    BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
    
      ON_REGISTERED_MESSAGE(m_nRegMsg, OnRegisteredMessage)
    
    END_MESSAGE_MAP()
    
    // In class implementation
    
    UINT CMyDoc::m_nRegMsg = RegisterWindowMessage(_T("MyRegMsg"));
    
    LRESULT CMyDoc::OnRegisteredMessage(WPARAM wParam, LPARAM lParam)
    
    {
    
      return 0;
    
    }

    Registered Message Handler

    Conclusion

    MFC defines a few additional message map macros that I have not covered here. However, you probably won’t run into many cases where you need them. As I mentioned before, most of the time the Wizards will add these entries automatically. But understanding the techniques I’ve described above will add some helpful techniques to your bag of tricks.

    Tags:  
    Categories:   C++/MFC
    Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

    MFC Ribbon Bar Doesn't Implement ID_CONTEXT_HELP

    Tuesday, November 25, 2008 2:48 AM by jonwood

    Overview

    One of the predefined command IDs historically supported by MFC is ID_CONTEXT_HELP. When this command is selected, the mouse pointer changes to an arrow with a question mark and, when you click on a control or window, MFC displays help associated with the item clicked.

    However, as of Visual Studio 2008 SP1, this command does not work for the ribbon bar. The command is still implemented but it does not obtain the correct value and so help fails to load.

    Details

    CMFCToolBar derives from CBasePane, which derives from CWnd. The handler for ID_CONTEXT_HELP ends up calling CBasePane::OnHelpHitTest() to obtain the ID of the element that was clicked. To accomplish this, CBasePane::OnHelpHitTest() calls CWnd::OnToolHitTest(), which is virtual.

    Classes like CMFCToolBar override OnToolHitTest() to return the ID of the button at the specified location. But CMFCRibbonBar does not override OnToolHitTest() and so CWnd::OnToolHitTest() tries to find the child window at the specified point. Since ribbon elements are not actual windows, CWnd::OnToolHitTest() finds no child windows and returns -1. As a result, CBasePane::OnHelpHitTest() simply returns the ID of the ribbon window itself, which is useless here.

    Resolution

    Someone from Microsoft confirmed with me this issue and said it was not supported because it is not supported in the Microsoft Office 2007 applications.

    In all fairness, the ribbon bar has a fairly sophisticated tooltip system than can be used to display brief help for any element by simply hovering the mouse over that element. This makes the ID_CONTEXT_HELP command far less important than it may have been in the past. Still, I ended up wasting a bit of time trying to determine why I couldn't get ID_CONTEXT_HELP to work with the ribbon bar. The best fix may simply be to avoid its use in MFC applications that have a ribbon bar.

    Tags:   ,
    Categories:   C++/MFC
    Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed