How can I use C# GUI component in my MFC application.

G

Guest

Hi Guys,

Would you be able to help me using C# GUI (with user interface component) in
my MFC application. I have used managed extension, COM-interops, etc but
problem is this C# component has user interface. how should have get window
handle from managed windows?

Thanks in advance.
Sushil
 
N

Nishant Sivakumar

Hello Sushil,

Here's some stuff I had written on this topic for my book (Extending MFC
Applications with the .NET Framework co-authored with Tom Archer) - but it
never made it to the book as we found that COM techniques were available
that were better suited to doing it. But personally, I've always felt that
the technique I used (crude subclassing) was actually simpler to implement.
Of course with VC 8, you won't need to do all this as MFC has classes to
interact with Windows Forms smoothly.

[*****snip*****]

Using a .NET control in an MFC dialog

Well, we have seen how Windows Forms makes GUI development slightly easier
for us, and how we can easily develop custom Forms controls. But obviously
if we cannot use them in our existing MFC based applications, it's not going
to be very useful for us. Let's see how we can use a .NET Windows Forms
control in an MFC application. In fact let's use the same
Centigrade-Fahrenheit conversion control that we developed in the previous
section.

First create a MFC dialog based application and add Managed Extensions
support to it by setting the required project properties. Now add
System.Windows.Forms , System.Drawing and FirstComposite (this is our custom
C-F-C control) to the Project Reference list. Basically what we do is to
write a wrapper class for the custom .NET control we developed. The wrapper
class will have a CWnd member variable and we use the CWnd::Attach function
to attach the control to the CWnd object, and we get the HWND of the user
control using the Handle property. Things will be clearer when you examine
the code :-

//Header file
#pragma once

using namespace FirstComposite;

class CControlWrapper
{
public:
CControlWrapper(void);
~CControlWrapper(void);
BOOL Create(CWnd* pWnd, int x, int y, int cx, int cy);
private:
CWnd m_wnd;
gcroot<FirstCompositeControl*> m_control;
};

//Implementation file
#include "StdAfx.h"
#include ".\controlwrapper.h"
#using <mscorlib.dll>
#using <system.dll>

CControlWrapper::CControlWrapper(void)
{

}

CControlWrapper::~CControlWrapper(void)
{
m_wnd.Detach();
}

BOOL CControlWrapper::Create(CWnd *pWnd, int x,
int y, int cx, int cy)
{
BOOL suc = FALSE;

m_control = new FirstComposite::FirstCompositeControl();
m_control->Location = System::Drawing::point(x, y);
m_control->Name = S"WrappedUserControl";
m_control->Size = System::Drawing::Size(cx, cy);

if( m_wnd.Attach((HWND)m_control->Handle.ToPointer()) )
suc = TRUE;

m_wnd.SetParent(pWnd);

return suc;
}

As you can see, all the action is in the Create method, where we instantiate
and create our .NET custom Forms control. We get the HWND of the control
using the Handle property and attach it to the CWnd member object using the
CWnd::Attach method. And then we call the SetParent method to associate the
control with our dialog window. As you can see, we have called Detach on the
CWnd object in the class destructor, else when the CWnd object's destructor
gets called, the call to DestroyWindow will fail.

Add a CControlWrapper member variable to the dialog class, and add the
Create call in the OnInitDialog :-

m_control.Create(this,20,20,200,200);

Compile and run the program, and you'll see the .NET control we created
earlier looking nice and snappy inside our MFC dialog. In fact, you can
embed any .NET Forms controls in your MFC program, including the native .NET
controls like TextBox, ListBox etc. In the above control we don't need to
access any methods or properties of the wrapped control, but sometimes we
might need to make calls to the control methods, as in the case of our
colored list box which we created earlier. For such controls we need to
write wrapper methods and properties for all functions and properties that
we need to expose to the calling program. Let's now write a similar program
as above to use the colored list box control in an MFC dialog program. Just
follow similar steps as above, make sure you reference the colored list box
DLL, and create the wrapper class as follows :-

//Header file
#pragma once

class CControlWrapper
{
public:
CControlWrapper(void);
~CControlWrapper(void);
BOOL Create(CWnd* pWnd, int x, int y, int cx, int cy);
void Clear();
void Add(CString str);
void GenerateWinner();
private:
CWnd m_wnd;
gcroot<ColorListBox::ColorListBoxControl*> m_control;
};

//Implementation file
#include "StdAfx.h"
#include ".\controlwrapper.h"
#using <mscorlib.dll>

CControlWrapper::CControlWrapper(void)
{
}

CControlWrapper::~CControlWrapper(void)
{
m_wnd.Detach();
}

BOOL CControlWrapper::Create(CWnd* pWnd, int x, int y, int cx, int cy)
{
BOOL suc = FALSE;

m_control = new ColorListBox::ColorListBoxControl();
m_control->Location = System::Drawing::point(x, y);
m_control->Name = S"WrappedUserControl";
m_control->Size = System::Drawing::Size(cx, cy);

if( m_wnd.Attach((HWND)m_control->Handle.ToPointer()) )
suc = TRUE;

m_wnd.SetParent(pWnd);

return suc;
}

void CControlWrapper::Clear()
{
m_control->Items->Clear();
}

void CControlWrapper::Add(CString str)
{
m_control->Items->Add((System::String*)str);
}

void CControlWrapper::GenerateWinner()
{
m_control->GenerateWinner();
}

As you can see, we have written methods in the wrapper class that internally
call the corresponding methods in the inner class. Notice how we have used a
CString as the argument, but in the method we convert it to a String*. This
makes it easier for MFC code to call this function. And now calling those
methods for a calling program would be as easy as the code snippet below :-

void CMFCListBoxTestDlg::OnBnClickedButton1()
{
m_control.Clear();
m_control.Add(S"Goran Ivanisevic");
m_control.Add(S"Andre Agassi");
m_control.Add(S"Pete Sampras");
m_control.Add(S"Gabriela Sabatini");
m_control.Add(S"Mary Joe Fernandez");
m_control.Add(S"Venus Williams");
m_control.Add(S"Boris Becker");
m_control.Add(S"Leander Paes");

m_control.GenerateWinner();
}

The only disadvantage is that for every function that you need to expose,
you will have to write a corresponding wrapper function. But usually you'll
find that you only need to expose a few functions, and thus you need to
write wrapper functions only for those methods.

[/*****snip*****]

And here's some more stuff :-

[*****snip*****]
Getting Event notifications for the .NET control

One issue you'll soon realize when you use .NET controls in your MFC dialogs
or form views is that there is no direct way for you to get event
notifications for the control. Let's say you have a list box control (the
..NET version) on an MFC dialog and that you want to update a text box (the
MFC version) with the current selected item's text every time the list box
selection changes. Well, it can be done, as we'll demonstrate in this
section.

Let's create a MFC dialog based application and add support for Managed
Extensions to it. Now add a text box using the dialog editor. Now we'll add
a .NET Windows Forms list box to the dialog just as we had done in the
previous sections with our custom user controls. Here is the header file for
the class :-

#pragma once

#define WM_LISTBOXCHANGED WM_APP + 100

class MyListBox
{
public:
MyListBox(void);
~MyListBox(void);
BOOL Create(CWnd *pWnd, int x, int y, int cx, int cy);
private:
CWnd m_hWnd;
gcroot <System::Windows::Forms::ListBox*> m_listbox;
HWND m_parent;
};

__gc class EventHandlerClass
{
public:
static System::IntPtr m_hwnd;
static System::Void SelectedValChangedHandler(System::Object * sender,
System::EventArgs * e)
{
System::Windows::Forms::ListBox* listbox =
static_cast<System::Windows::Forms::ListBox*>( sender );
CString s = (CString)listbox->Text;
SendMessage((HWND)m_hwnd.ToPointer(),WM_LISTBOXCHANGED,
(WPARAM)(LPCTSTR)s,0);
}
};

Most of it is similar to what we did in the previous sections, but you'll
notice that I have added an __gc class called EventHandlerClass. This class
is used as a proxy for the events generated by the .NET control. Basically
we have a static event handler in the __gc class which we register as an
event handler for the .NET control; and thus whenever the event occurs, we
have an access point to the event. Now the next difficulty would be in
passing on this event information to the dialog window where it should be
handled. That's where we use the age old trusted windows message technique.
We have a user-defined message called WM_LISTBOXCHANGED and we simply post
this message to the handle of the parent window (which in this case will be
the dialog window) and pass the currently selected string in the list box as
the WPARAM parameter. And here is the implementation file for the class :-

#include "StdAfx.h"
#include ".\mylistbox.h"
#using <mscorlib.dll>

MyListBox::MyListBox(void)
{
m_parent = NULL;
}

MyListBox::~MyListBox(void)
{
m_hWnd.Detach();
}

BOOL MyListBox::Create(CWnd* pWnd, int x, int y, int cx, int cy)
{
BOOL suc = FALSE;

m_listbox = new System::Windows::Forms::ListBox();
m_listbox->Location = System::Drawing::point(x, y);
m_listbox->Name = S"WrappedUserControl";
m_listbox->Size = System::Drawing::Size(cx, cy);

m_listbox->Items->Add(S"Apples");
m_listbox->Items->Add(S"Bananas");
m_listbox->Items->Add(S"Oranges");

m_listbox->SelectedIndexChanged += new System::EventHandler(NULL,
EventHandlerClass::SelectedValChangedHandler);

if( m_hWnd.Attach((HWND)m_listbox->Handle.ToPointer()) )
suc = TRUE;

m_hWnd.SetParent(pWnd);
m_parent = pWnd->m_hWnd;
EventHandlerClass::m_hwnd = m_parent;

return suc;
}

Well, there isn't much new here compared with what we did in the previous
sections, except that we have registered the event handler from the __gc
class and also saved the parent window's HWND so that we can send the user
defined message to the parent window. In the MFC dialog all we need to do is
add an entry to the message map as follows, and then write the message
handler function :-

BEGIN_MESSAGE_MAP(CGetEventsTestDlg, CDialog)
. . .
ON_MESSAGE( WM_LISTBOXCHANGED, OnListBoxChanged )
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

.. . .

LRESULT CGetEventsTestDlg::OnListBoxChanged( WPARAM wParam, LPARAM )
{
LPCTSTR str = (LPCTSTR) wParam;
m_edit.SetWindowText(str);
return 0;
}

Okay, so that is fine now. The MFC program can now get notifications from
the user control. Of course the only issue is that you will have to write
user defined messages and handlers for every event that you want to get
notified for. But this can be solved by having virtual functions in the
control wrapper class, where each virtual function represents an on_event
sort of handler method. So all the MFC caller program has to do is to derive
a class from our wrapper class and override these virtual functions to put
their own code. But then that might be even more contrived a method when
there are only a few events that the caller is interested in, and thus as is
always the case in programming, it's up to the user to decide which
technique he or she chooses.

[/*****snip*****]
 
G

Guest

Hi Nishant,

Thanks for your help and I really appreciate your time. I will use it and
let you know how it turned out in my application.

Again Thanks
Sushi Srivastava

Nishant Sivakumar said:
Hello Sushil,

Here's some stuff I had written on this topic for my book (Extending MFC
Applications with the .NET Framework co-authored with Tom Archer) - but it
never made it to the book as we found that COM techniques were available
that were better suited to doing it. But personally, I've always felt that
the technique I used (crude subclassing) was actually simpler to implement.
Of course with VC 8, you won't need to do all this as MFC has classes to
interact with Windows Forms smoothly.

[*****snip*****]

Using a .NET control in an MFC dialog

Well, we have seen how Windows Forms makes GUI development slightly easier
for us, and how we can easily develop custom Forms controls. But obviously
if we cannot use them in our existing MFC based applications, it's not going
to be very useful for us. Let's see how we can use a .NET Windows Forms
control in an MFC application. In fact let's use the same
Centigrade-Fahrenheit conversion control that we developed in the previous
section.

First create a MFC dialog based application and add Managed Extensions
support to it by setting the required project properties. Now add
System.Windows.Forms , System.Drawing and FirstComposite (this is our custom
C-F-C control) to the Project Reference list. Basically what we do is to
write a wrapper class for the custom .NET control we developed. The wrapper
class will have a CWnd member variable and we use the CWnd::Attach function
to attach the control to the CWnd object, and we get the HWND of the user
control using the Handle property. Things will be clearer when you examine
the code :-

//Header file
#pragma once

using namespace FirstComposite;

class CControlWrapper
{
public:
CControlWrapper(void);
~CControlWrapper(void);
BOOL Create(CWnd* pWnd, int x, int y, int cx, int cy);
private:
CWnd m_wnd;
gcroot<FirstCompositeControl*> m_control;
};

//Implementation file
#include "StdAfx.h"
#include ".\controlwrapper.h"
#using <mscorlib.dll>
#using <system.dll>

CControlWrapper::CControlWrapper(void)
{

}

CControlWrapper::~CControlWrapper(void)
{
m_wnd.Detach();
}

BOOL CControlWrapper::Create(CWnd *pWnd, int x,
int y, int cx, int cy)
{
BOOL suc = FALSE;

m_control = new FirstComposite::FirstCompositeControl();
m_control->Location = System::Drawing::point(x, y);
m_control->Name = S"WrappedUserControl";
m_control->Size = System::Drawing::Size(cx, cy);

if( m_wnd.Attach((HWND)m_control->Handle.ToPointer()) )
suc = TRUE;

m_wnd.SetParent(pWnd);

return suc;
}

As you can see, all the action is in the Create method, where we instantiate
and create our .NET custom Forms control. We get the HWND of the control
using the Handle property and attach it to the CWnd member object using the
CWnd::Attach method. And then we call the SetParent method to associate the
control with our dialog window. As you can see, we have called Detach on the
CWnd object in the class destructor, else when the CWnd object's destructor
gets called, the call to DestroyWindow will fail.

Add a CControlWrapper member variable to the dialog class, and add the
Create call in the OnInitDialog :-

m_control.Create(this,20,20,200,200);

Compile and run the program, and you'll see the .NET control we created
earlier looking nice and snappy inside our MFC dialog. In fact, you can
embed any .NET Forms controls in your MFC program, including the native .NET
controls like TextBox, ListBox etc. In the above control we don't need to
access any methods or properties of the wrapped control, but sometimes we
might need to make calls to the control methods, as in the case of our
colored list box which we created earlier. For such controls we need to
write wrapper methods and properties for all functions and properties that
we need to expose to the calling program. Let's now write a similar program
as above to use the colored list box control in an MFC dialog program. Just
follow similar steps as above, make sure you reference the colored list box
DLL, and create the wrapper class as follows :-

//Header file
#pragma once

class CControlWrapper
{
public:
CControlWrapper(void);
~CControlWrapper(void);
BOOL Create(CWnd* pWnd, int x, int y, int cx, int cy);
void Clear();
void Add(CString str);
void GenerateWinner();
private:
CWnd m_wnd;
gcroot<ColorListBox::ColorListBoxControl*> m_control;
};

//Implementation file
#include "StdAfx.h"
#include ".\controlwrapper.h"
#using <mscorlib.dll>

CControlWrapper::CControlWrapper(void)
{
}

CControlWrapper::~CControlWrapper(void)
{
m_wnd.Detach();
}

BOOL CControlWrapper::Create(CWnd* pWnd, int x, int y, int cx, int cy)
{
BOOL suc = FALSE;

m_control = new ColorListBox::ColorListBoxControl();
m_control->Location = System::Drawing::point(x, y);
m_control->Name = S"WrappedUserControl";
m_control->Size = System::Drawing::Size(cx, cy);

if( m_wnd.Attach((HWND)m_control->Handle.ToPointer()) )
suc = TRUE;

m_wnd.SetParent(pWnd);

return suc;
}

void CControlWrapper::Clear()
{
m_control->Items->Clear();
}

void CControlWrapper::Add(CString str)
{
m_control->Items->Add((System::String*)str);
}

void CControlWrapper::GenerateWinner()
{
m_control->GenerateWinner();
}

As you can see, we have written methods in the wrapper class that internally
call the corresponding methods in the inner class. Notice how we have used a
CString as the argument, but in the method we convert it to a String*. This
makes it easier for MFC code to call this function. And now calling those
methods for a calling program would be as easy as the code snippet below :-

void CMFCListBoxTestDlg::OnBnClickedButton1()
{
m_control.Clear();
m_control.Add(S"Goran Ivanisevic");
m_control.Add(S"Andre Agassi");
m_control.Add(S"Pete Sampras");
m_control.Add(S"Gabriela Sabatini");
m_control.Add(S"Mary Joe Fernandez");
m_control.Add(S"Venus Williams");
m_control.Add(S"Boris Becker");
m_control.Add(S"Leander Paes");

m_control.GenerateWinner();
}

The only disadvantage is that for every function that you need to expose,
you will have to write a corresponding wrapper function. But usually you'll
find that you only need to expose a few functions, and thus you need to
write wrapper functions only for those methods.

[/*****snip*****]

And here's some more stuff :-

[*****snip*****]
Getting Event notifications for the .NET control

One issue you'll soon realize when you use .NET controls in your MFC dialogs
or form views is that there is no direct way for you to get event
notifications for the control. Let's say you have a list box control (the
..NET version) on an MFC dialog and that you want to update a text box (the
MFC version) with the current selected item's text every time the list box
selection changes. Well, it can be done, as we'll demonstrate in this
section.

Let's create a MFC dialog based application and add support for Managed
Extensions to it. Now add a text box using the dialog editor. Now we'll add
a .NET Windows Forms list box to the dialog just as we had done in the
previous sections with our custom user controls. Here is the header file for
the class :-

#pragma once

#define WM_LISTBOXCHANGED WM_APP + 100

class MyListBox
{
public:
MyListBox(void);
~MyListBox(void);
BOOL Create(CWnd *pWnd, int x, int y, int cx, int cy);
private:
CWnd m_hWnd;
gcroot <System::Windows::Forms::ListBox*> m_listbox;
HWND m_parent;
};

__gc class EventHandlerClass
{
public:
static System::IntPtr m_hwnd;
static System::Void SelectedValChangedHandler(System::Object * sender,
System::EventArgs * e)
{
System::Windows::Forms::ListBox* listbox =
static_cast<System::Windows::Forms::ListBox*>( sender );
CString s = (CString)listbox->Text;
SendMessage((HWND)m_hwnd.ToPointer(),WM_LISTBOXCHANGED,
(WPARAM)(LPCTSTR)s,0);
}
};

Most of it is similar to what we did in the previous sections, but you'll
notice that I have added an __gc class called EventHandlerClass. This class
is used as a proxy for the events generated by the .NET control. Basically
we have a static event handler in the __gc class which we register as an
event handler for the .NET control; and thus whenever the event occurs, we
have an access point to the event. Now the next difficulty would be in
passing on this event information to the dialog window where it should be
handled. That's where we use the age old trusted windows message technique.
We have a user-defined message called WM_LISTBOXCHANGED and we simply post
this message to the handle of the parent window (which in this case will be
the dialog window) and pass the currently selected string in the list box as
the WPARAM parameter. And here is the implementation file for the class :-

#include "StdAfx.h"
#include ".\mylistbox.h"
#using <mscorlib.dll>

MyListBox::MyListBox(void)
{
m_parent = NULL;
}

MyListBox::~MyListBox(void)
{
m_hWnd.Detach();
}

BOOL MyListBox::Create(CWnd* pWnd, int x, int y, int cx, int cy)
{
BOOL suc = FALSE;

m_listbox = new System::Windows::Forms::ListBox();
m_listbox->Location = System::Drawing::point(x, y);
m_listbox->Name = S"WrappedUserControl";
m_listbox->Size = System::Drawing::Size(cx, cy);

m_listbox->Items->Add(S"Apples");
m_listbox->Items->Add(S"Bananas");
m_listbox->Items->Add(S"Oranges");

m_listbox->SelectedIndexChanged += new System::EventHandler(NULL,
EventHandlerClass::SelectedValChangedHandler);

if( m_hWnd.Attach((HWND)m_listbox->Handle.ToPointer()) )
suc = TRUE;

m_hWnd.SetParent(pWnd);
m_parent = pWnd->m_hWnd;
EventHandlerClass::m_hwnd = m_parent;

return suc;
 
E

Eric Duperier

Hi Nishant, Sushil

Thanks for this great technique on a problem i was also facing. I'm using
VC7 and don't want to use COM for this project.

I used your technique and it works perfectly well for my custom c# controls
which inherits from Windows.Forms.Control

If my control inherits from Windows.Forms.UserControl, the control appears
fine, then it often "freezes" the whole dialog on some user input (seems
like an infinite loop on a windows message)

The only difference i see in my code is that the main app and parent dialog
are not compiled with Managed Extentions, but load a dll with Managed
Extentions which holds the wrapper;

Does anybody see a reason or an issue ? (some style or properties to set ?)

Thanks in advance,
Eric
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top