Modern C# Interop with MFC

A modern way to use pure .Net Components and Controls in MFC applications

Part 3.

Completiamo l'applicazione MFC aggiungendo i gestori degli Eventi dei componenti e controls .Net nel sorgente MFC.



.Net Events
Genericamente parlando, nel .Net Framework un evento è un messaggio che un oggetto "manda" per segnalare che è avvenuto un "qualcosa" che era atteso succedesse. Questo "qualcosa" può accadere a causa dell'interazione di un utente (es: click su un bottone) oppure per altre cause generate internamente durante il funzionamento del programma, (es: il programma ha concluso il download di un file da internet).

L'oggetto che genera un evento, (event sender), notifica che l'evento è avvenuto ad un qualche altro oggetto in "ascolto" (event receiver); quest'ultimo ha il compito di "gestire" (handle) l'evento, eseguendo a sua volta, una qualche azione.



Event and Delegate
Un oggetto che genera un evento (event sender) non sa quale altro oggetto o metodo, successivamente, gestirà l'evento stesso, per cui la comunicazione tra il sender ed il receiver è indiretta ed avviene tramite una sorta di puntatore a funzione.
In ambiente .Net non esistono puntatori a funzione come in C/C++, ma esiste un tipo di classe, Delegate, che è l'intermediario tra sender e receiver.
Qui sotto la dichiarazione di un Delegate in C#:

public delegate void MyControlEventHandler(object sender, MyCustomEventArgs e);  //Delegate

public void MyControlDoSomething(object sender, MyCustomEventArgs e) {...} // EventHandler method

Nella dichiarazione qui sopra, possiamo notare che per il Delegate la sintassi è somigliante ad un metodo C#, tranne la keyword delegate.
Normalmente i delegates vengono dichiarati con due parametri:

Nota: MyCustomEventArgs può essere effettivamente un oggetto custom (creato quindi dallo sviluppatore) quanto un oggetto predefinito di tipo System.EventArgs.
Un EventArgs creato dallo sviluppatore (es: MyCustomEventArgs) ha senso qualora si debbano passare dei dati (es: una struct etc.) al metodo che riceverà un certo evento; in tutti gli altri casi è sufficiente la versione base EventArgs.



Continuiamo l'applicazione MFC
Ora che sappiamo cosa si intende con event e delegate in .Net, possiamo riprendere l'applicazione MFC che è stata iniziata nella parte 2 di questo post, nella quale implementeremo, oltre ai restanti components e controls della libreria CFComponentN18.dll, i relativi eventi.

Nota : In realtà è possibile creare nella classe proxy (vedi Part 2) un solo UserControl ed utilizzarlo come una Windows Form. Sarebbe sufficiente mettere tutti i componenti del CFComponentsN18.dll nello UserControl e gestire quindi gli eventi dei vari componenti da esso, tuttavia noi vogliamo gestire gli eventi .Net dal nostro codice C++ nel progetto MFC.
Inoltre dovremo ovviare al problema del BindingContext (vedi Part 2), pertanto, a tale scopo, creeremo nella classe proxy, (vedi sempre Part 2), 8 UserControls.

Riprendendiamo il progetto MFC creato nella Part 2).
In Visual Studio apriamo la dialog MFC da Resource View; sulla dialog MFC aggiungeremo (drag&drop dalla Toolbox MFC) come segnaposti 8 GroupBox (ogni segnaposto deve essere di tipo static control).
Andremo quindi settare i relativi ID secondo lo schema introdotto nella parte 2.
Nota: 8 segnaposti in quanto nell'assembly CFComponentsN18.dll son presenti 1 component e 7 controls.

Nella dialog MFC infine ci servirà un Button ed un Edit Contol (entrambi componenti MFC).


Settiamo gli ID di ogni static control (io ho usato 8 GroupBox) della dialog MFC con un id significativo:


Implementiamo gli 8 UserControls nella ProxyClassLibrary

Ogni UserControl conterrà un singolo controllo dell'assembly CFComponentN18.dll.

MFC and C# MFC and C# MFC and C#


I nomi degli UserControls di questo esempio:

Il risultato dovrebbe essere un qualcosa del genere:

MFC and C#


Fatto ciò, dichiariamo nella classe della dialog MFC 8 nuovi membri ( i nostri 8 .Net controls).

//............

private:
	
	CWinFormsControl<ProxyClassLibrary::CfComponent> m_cfComponent;
	CWinFormsControl<ProxyClassLibrary::TxtCognome> m_txtCognome;
	CWinFormsControl<ProxyClassLibrary::TxtNome> m_txtNome;
	CWinFormsControl<ProxyClassLibrary::CmbSesso> m_cmbSesso;
	CWinFormsControl<ProxyClassLibrary::DtNascitaPicker> m_dtNascitaPicker;
	CWinFormsControl<ProxyClassLibrary::CmbRegioni> m_cmbRegioni;
	CWinFormsControl<ProxyClassLibrary::CmbProvince> m_cmbProvince;
	CWinFormsControl<ProxyClassLibrary::CmbComuni> m_cmbComuni;



Quindi nel file di implementazione della dialog MFC individuiamo la funzione DoDataExchange(CDataExchange* pDX) e subito dopo la riga CDialogEx::DoDataExchange(pDX); aggiungiamo le righe:

DDX_ManagedControl(pDX, IDC_CFCOMPONENT, m_cfComponent);
DDX_ManagedControl(pDX, IDC_TXT_COGNOME, m_txtCognome);
DDX_ManagedControl(pDX, IDC_TXT_NOME, m_txtNome);
DDX_ManagedControl(pDX, IDC_CTRL_CMB_SEX, m_cmbSesso);
DDX_ManagedControl(pDX, IDC_NASCITA_DTPICKER, m_dtNascitaPicker);
DDX_ManagedControl(pDX, IDC_CTRL_CMB_REGIONI, m_cmbRegioni);
DDX_ManagedControl(pDX, IDC_CTRL_CMB_PROVINCE, m_cmbProvince);
DDX_ManagedControl(pDX, IDC_CTRL_CMB_COMUNI, m_cmbComuni);


Eseguiamo l'applicazione ed otterremo questo:

MFC and C#



Gestire eventi .Net in MFC
Selezionando una regione italiana nel controllo RegioniDropDownList il controllo ProvinceDropDownList deve aggiornarsi dinamicamente mostrando l'elenco delle province italiane appartenenti all regione selezionata e di conseguenza, la stessa cosa avverrà nel controllo ComuniDropDownList selezionando una provincia nel controllo ProvinceDropDownList.

Tuttavia in questa fase, eseguendo l'applicazione MFC, viene popolato solo il controllo RegioniDropDownList e selezionando in questo una regione italiana, i due controlli collegati in cascata non faranno assolutamente nulla se non mostrare un laconico invito, all'utente, a selezionare qualcosa che non esiste!
Vedi immagini qui sotto:

MFC and C# MFC and C#


Questo è normale in quanto i 3 controlli vengono sincronizzati al verificarsi dei due eventi xxxSelectedValueChanged:


Affinchè il tutto funzioni correttamente, noi dovremo intercettare i 2 eventi, lato MFC, e scrivere in essi 1 riga di codice (indicheremo al controllo corrente, l'ID dell'elemento correntemente selzionato), tutto il resto, connessione al database, queries etc. verrà gestito internamente dai vari componenti .Net.

In pratica non dovremo fare altro che aggiungere delle funzioni membro alla classe della dialog che abbiano la stessa identica sintassi dei delegates C# dei controlli .Net dell'assembly CFComponentN18.dll, dichiarando inoltre (sempre nella stessa classe) che un certo metodo, è un metodo event handler.
Nella fase di inizializzazione dell'applicazione MFC, con l'operatore +=, assegneremo il nostro metodo all' event handler C#.
Ricapitolando:


Per far ciò, MFC usa delle macro:

Prima di procedere è necessario quindi aggiungere un paio di file #include che consentono di utilizzare tali macro.

#include <vcclr.h>
#include <msclr\event.h>

Aggiungiamo nel file header della classe MFC una sezione public e le prime due macro:

//......

public:

// delegate map  
BEGIN_DELEGATE_MAP(CMFCCSharpDemoDlg)

EVENT_DELEGATE_ENTRY(OnRegioniSelectedValueChanged, System::Object^, 
                      CFComponentN18::RegioniDropDownList::RegioniDropDownList::RegioneSelectedEventArgs ^)

EVENT_DELEGATE_ENTRY(OnProvinceSelectedValueChanged, System::Object^, 
                      CFComponentN18::ProvinceDropDownList::ProvinceDropDownList::ProvinciaSelectedEventArgs ^)

END_DELEGATE_MAP()


La macro EVENT_DELEGATE_ENTRY ha 3 argomenti:


Dichiariamo quindi le funzioni C++ che assegneremo all'event handler dei rispettivi controlli .Net


void OnRegioniSelectedValueChanged(System::Object ^obj,
		CFComponentN18::RegioniDropDownList::RegioniDropDownList::RegioneSelectedEventArgs ^e);

void OnProvinceSelectedValueChanged(System::Object ^obj,
		CFComponentN18::ProvinceDropDownList::ProvinceDropDownList::ProvinciaSelectedEventArgs ^e);


Apriamo il file di implementazione della dialog MFC ed implementiamo le funzioni OnRegioniSelecteValueChanged e OnProvinceSelectedValueChanged:

void CMFCCSharpDemoDlg::OnRegioniSelectedValueChanged(System::Object ^ obj, CFComponentN18::RegioniDropDownList::RegioniDropDownList::RegioneSelectedEventArgs ^ e)
{
	m_cmbProvince.GetControl()->ProvinceDropDownList->UpdateData(e->ItemValue);
}


void CMFCCSharpDemoDlg::OnProvinceSelectedValueChanged(System::Object ^ obj, CFComponentN18::ProvinceDropDownList::ProvinceDropDownList::ProvinciaSelectedEventArgs ^ e)
{
	m_cmbComuni.GetControl()->ComuniDropDownList->UpdateData(e->ItemValue);
}


Nota: come si può notare la sintassi delle funzioni è identica al prototipo di delegate ed il linguaggio è managed C++.


Nello stesso file di implementazione individuiamo la funzione OnInitDialog() ed aggiungiamo il codice per assegnare gli event handlers ed inizializzare i nostri componenti .Net;


//.......
// TODO: Add extra initialization here

	m_cfComponent.GetControl()->CFComponent->CognomeTextBox = m_txtCognome.GetControl()->CognomeTextBox;
	m_cfComponent.GetControl()->CFComponent->NomeTextBox = m_txtNome.GetControl()->NomeTextBox;
	m_cfComponent.GetControl()->CFComponent->SessoDropDownList = m_cmbSesso.GetControl()->SessoDropDownList;
	m_cfComponent.GetControl()->CFComponent->DataNascitaPicker = m_dtNascitaPicker.GetControl()->DataNascitaPicker;
	m_cfComponent.GetControl()->CFComponent->RegioniDropDownList = m_cmbRegioni.GetControl()->RegioniDropDownList;
	m_cfComponent.GetControl()->CFComponent->ProvinceDropDownList = m_cmbProvince.GetControl()->ProvinceDropDownList;
	m_cfComponent.GetControl()->CFComponent->ComuniDropDownList = m_cmbComuni.GetControl()->ComuniDropDownList;


m_cmbRegioni.GetControl()->RegioniDropDownList->RegioneSelectedValueChanged += 
MAKE_DELEGATE(CFComponentN18::RegioniDropDownList::RegioniDropDownList::RegioneSelectedEventHandler, 
                  OnRegioniSelectedValueChanged);
	
m_cmbProvince.GetControl()->ProvinceDropDownList->ProvinciaSelectedValueChanged += 
MAKE_DELEGATE(CFComponentN18::ProvinceDropDownList::ProvinceDropDownList::ProvinciaSelectedEventHandler, 
                  OnProvinceSelectedValueChanged);



Mandiamo in esecuzione l'applicazione e finalmente i controlli fanno ciò che devono!

MFC and C#


Nel file header della classe della dialog MFC aggiungiamo una variabile membro riferita al controllo Edit Text di tipo CEdit

CEdit m_editCtrl;
MFC and C#



Nel file di implementazione della dialog MFC implementiamo il button click del Button "Calculate";

MFC and C# MFC and C#

void CMFCCSharpDemoDlg::OnBnClickedButtonCalculate()
{
	// TODO: Add your control notification handler code here

	CString szCFCode = m_cfComponent.GetControl()->CFComponent->GetCodiceFiscale;
	
	m_editCtrl.SetWindowTextW(szCFCode);
}



Eseguiamo l'applicazione, riempiamo i campi e godiamoci il risultato!

MFC and C#




Downloads tutorial Part 3:





Giuseppe Pischedda 2018


Se il post ti è utile puoi fare una donazione all'autore, l'importo è a tua libera scelta.

Grazie