MFC and .NET: Handling .NET Events

In a previous article, I explained and illustrated how to use .NET Delegates and Events types from C++ Managed Extensions. This week's column covers some standard problems you'll face when attempting to use events from a mixed-mode (MFC and Managed Extensions) application.

Figure 1: Demo application illustrating the subscribing to and catching of the event that is fired when a file is deleted from disk

Let's say that you have an MFC application with the /CLR flag set (the "Use Managed Extensions" option in the Project Settings). You then want to subscribe to the FileSystemWatcher::Deleted event so that your application is notified each time a file is deleted in the specified path.

However, if you attempt to specify an event handler within one of your MFC classes (such as the application, view, or dialog class) and then attempt to instantiate the delegate using that class, you'll receive a compiler error stating that you can't instantiate a delegate from a "non-member function or a member of an unmanaged class". Therefore, you'll need to create a managed class (__gc) wrapper for the event handler similar to the following:

#using <system.dll>
using namespace System::IO;

...

__gc class FileWatcherEvents
{
  public: static void OnDeleted (Object* source, FileSystemEventArgs* e)
  {
    AfxMessageBox("A file was deleted!");
  }
};

Note that I made the member method static so that I don't need to instantiate the FileWatcherEvents class. Obviously, if you're going to have instance data associated with the class (and specifically this method), you'll need to make this an instance method.

I then can subscribe to the event as follows:

#pragma push_macro("new")
#undef new
  FileSystemWatcher* pWatcher = new FileSystemWatcher("d:\\");
  pWatcher->EnableRaisingEvents=true;
  pWatcher->Deleted += new FileSystemEventHandler(
            NULL, FileWatcherEvents::OnDeleted);
#pragma pop_macro("new")

You can plug this code directly into a mixed-mode application and a message box will appear any time a file is deleted from the D: root folder (specified in the FileSystemWatcher constructor). However, chances are you'll want to do something other than display a message to this effect. Therefore, the next task is figuring out how to communicate between the managed object and the desired MFC object (such as the dialog object in the download demo).

One obvious way of handling this problem would be to pass the MFC object to the handling wrapper object. However, that would couple the two classes a little too tightly for my taste. Another—more generic—way would be to pass the HWND of the client window to the wrapper object. Now, we're onto something! That would at least decouple the wrapper from a specific MFC class. We could take that notion a step further and create a base event-handling class from which all of our event-handling wrappers would derive. Then, we could instantiate each derived class with the HWND of the client window, as well as a message ID that the wrapper could use in sending a message to signal that an event had been raised. Here's an example of such a base class:

__gc class EventBase
{
public:
  EventBase(HWND subscriberWindow, UINT messageID)
  {
    this->subscriberWindow = subscriberWindow;
    this->messageID = messageID;
  }
  protected:
    HWND subscriberWindow;
    UINT messageID;
  protected:
    void SendEventInfo(String* info)
  {
    const __wchar_t __pin * path = PtrToStringChars(info);
    ::SendMessage(subscriberWindow, messageID, (WPARAM)path, NULL);
  }
};

Note that the base class has a SendEventInfo method that allows all derived classes to send a string back to the client. (As I explained in the article, "Converting Between MFC/C++ and .NET Types," the __pin keyword and PtrToStringChars functions are used to convert a managed String object to an unmanaged char array.) Obviously, you can supply other methods if you wish to send more complex types to the client.

Now, you can derive each of your event-handling wrapper classes from the EventBase class. Using our FileSystemWatcher example, we could define the following class to handle the FileSystemWatcher::Deleted event:

__gc class FileWatcherEvents : public EventBase
{
public:
  FileWatcherEvents(FileSystemWatcher* watcher,
                    HWND subscriberWindow, UINT messageID)
  : EventBase(subscriberWindow, messageID)
  {
    watcher->EnableRaisingEvents=true;
  }

public: 
  void OnDeleted (Object* source, FileSystemEventArgs* e)
  {
    SendEventInfo(e->FullPath);
  }
};

Note how the FileSystemWatcher-specific code, such as which event to subscribe to (Deleted) and the setting of the EnableRaisingEvents property, are placed in this derived class.

The client code then would instantiate the event-handler wrapper as follows:

#pragma push_macro("new")
#undef new
  FileSystemWatcher* pWatcher = 
    new FileSystemWatcher("d:\\");

  FileWatcherEvents* fileEvents = 
    new FileWatcherEvents(pWatcher, GetSafeHwnd(), WM_FILE_DELETED);

  pWatcher->Deleted += 
    new FileSystemEventHandler(fileEvents, 
                               FileWatcherEvents::OnDeleted); 
#pragma pop_macro("new")

At this point, the FileWatcherEvents will automatically send a message to the client window (as specified in its constructor) when a file is deleted from the D: root folder.

You need only define the message ID and implement a handler. Here's an example from the article's download demo:

#define WM_FILE_DELETED (WM_USER + 1)

...

BEGIN_MESSAGE_MAP(CHandlingEventsInMFCDlg, CDialog)
  ON_MESSAGE(WM_FILE_DELETED, OnFileDeleted)
  //}}AFX_MSG_MAP
END_MESSAGE_MAP()

...

LRESULT CHandlingEventsInMFCDlg::OnFileDeleted(WPARAM wp, LPARAM)
{
  const __wchar_t * path = (const __wchar_t *)wp; 

  int idx = m_lstEvents.InsertItem(0, _T("Deleted"));
  m_lstEvents.SetItemText(idx, 1, (CString)path);

  return 0L;
}

As you can see, the handler simply converts the WPARAM first to its original unmanaged char array form and then casts it to a CString object so that it can be displayed in the demo's list control.

Easier with Practice

While not terribly intuitive at first blush, once you get the hang of handling events in a managed object and then communicating the desired information between that managed object and your unmanaged MFC objects, you'll see that subscribing and dealing with events in mixed-mode applications becomes easy. In addition, while my first swing at a generic base class is far from perfect, it should get you started in the right direction to making this task more generic for your programming needs.



About the Author

Tom Archer - MSFT

I am a Program Manager and Content Strategist for the Microsoft MSDN Online team managing the Windows Vista and Visual C++ developer centers. Before being employed at Microsoft, I was awarded MVP status for the Visual C++ product. A 20+ year veteran of programming with various languages - C++, C, Assembler, RPG III/400, PL/I, etc. - I've also written many technical books (Inside C#, Extending MFC Applications with the .NET Framework, Visual C++.NET Bible, etc.) and 100+ online articles.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

  • Live Event Date: April 22, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Database professionals — whether developers or DBAs — can often save valuable time by learning to get the most from their new or existing productivity tools. Whether you're responsible for managing database projects, performing database health checks and reporting, analyzing code, or measuring software engineering metrics, it's likely you're not taking advantage of some of the lesser-known features of Toad from Dell. Attend this live …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds