Appunti MFC
Appunti MFC
Bibliografia:
“Programming Windows with MFC” di J. Prosise
“Programming Windows” di C. Petzold
Microsoft MSDN Library
1
In generale i normali programmi per ambienti tradizionali utilizzano una logica
procedurale, ovvero l’esecuzione del programma parte da un main e continua a seconda
del flusso dell’applicazione (cicli, chiamate a funzione, istruzioni if-then-else ecc.).
Le applicazioni Windows operano in maniera diversa, ovvero utilizzano un modello di
programmazione chiamato “event driven”; che consiste nel gestire la reazione del
programma di fronte ad eventi (es. la pressione del pulsante del mouse o la chiusura di
una finestra) che vengono segnalati all’applicazione tramite messaggi inviati dal sistema
operativo.
In sostanza un’applicazione Windows non ha un vero e proprio main, ma si limita ad
aprire una o piu’ finestre e a posizionarsi in un ciclo di attesa (loop dei messaggi),
aspettando che il sistema operativo invii messaggi all’applicazione in risposta ai vari
eventi che si possono verificare durante l’esecuzione del programma.
I messaggi in arrivo diretti ad una applicazione vengono accodati in attesa
dell’elaborazione e solitamente si continua l’esecuzione in questo modo fino all’arrivo di
un messaggio di richiesta di terminazione del programma (WM_QUIT), che solitamente
viene inviato quando l’utente preme il tasto ‘x’ su di una finestra, oppure seleziona la
voce ‘Esci’ dalla barra dei menu.
La complessita’ di sviluppo delle applicazioni Windows deriva pertanto dalla necessita’
di prevedere in fase di progetto tutti i possibili messaggi che dovranno essere gestiti e che
quindi richiederanno la scrittura di una routine di gestione apposita.
La libreria MFC semplifica questo processo prendendosi carico di gestire al suo interno
gran parte dei messaggi che l’applicazione riceve e lasciando quindi al programmatore
solo la scrittura della logica del programma.
Il seguente listato puo’ essere generato dallo Wizard di Visual Studio 6.0 selezionando
File->New e scegliendo un nuovo progetto di tipo Win32Application.
Nella finestra del wizard selezionare “Hello World Application” per generare
automaticamente il codice di una semplice applicazione Windows-SDK che stampa un
messaggio a video ed e’ dotata di un piccolo menu e di una finestra di informazioni.
2
(listato 2_1_1)
#include "stdafx.h"
#include "resource.h"
// Inizializzazione dell’applicazione
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
3
// LOOP dei messaggi
while (GetMessage(&msg, NULL, 0, 0))
{
// I messaggi vengono estratti dalla coda
// e passati alla funzione di callback
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
return RegisterClassEx(&wcex);
}
hInst = hInstance;
4
// dell’applicazione
hWnd = CreateWindow(szWindowClass, szTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL,
hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
return TRUE;
}
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Gestisce la selezione di una voce dal
// menu:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst,
(LPCTSTR)IDD_ABOUTBOX, hWnd,
(DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
5
return DefWindowProc(hWnd, message,
wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
RECT rt;
GetClientRect(hWnd, &rt);
// Visualizza il messaggio nella finestra
DrawText(hdc, szHello, strlen(szHello), &rt,
DT_CENTER);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
// si e’ ricevuto il messaggio di uscita
PostQuitMessage(0);
break;
default:
// I messaggi non gestiti vengono passati
// alla funzione di default
return DefWindowProc(hWnd, message, wParam,
lParam);
}
return 0;
}
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam)
== IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}
6
Il cuore di una applicazione Windows SDK e’ la funzione WinMain; al suo interno viene
richiamata RegisterClass per registrare la finestra principale dell’applicazione (la
registrazione consiste nel definire alcune importanti caratteristiche di una finestra come
l’indirizzo della routine di gestione dei messaggi, la presenza di icone e menu, i colori di
sfondo ecc.).
Una volta registrata la finestra dell’applicazione e’ possibile creare fisicamente la finestra
tramite la funzione CreateWindow che permette anche di definirne lo stile (posizione,
dimensione, presenza di scrollbar laterali e/o orizzontali ecc.).
La finestra, una volta creata, non e’ visibile fino a che non vengono chiamate le funzioni
ShowWindow e UpdateWindow.
Successivamente l’applicazione inizia il cosiddetto loop dei messaggi, ovvero si effettua
un ciclo while che chiama le funzioni GetMessage, TranslateMessage e
DispatchMessage le quali rispettivamente esaminano la coda dei messaggi e, se un
nuovo messaggio e’ presente, lo interpretano e lo inviano alla corrispondente funzione di
gestione (message handler).
Nel nostro esempio la funzione di gestione dei messaggi e’ la CALLBACK WndProc
che si occupa di implementare la reazione dell’applicazione ad ognuno dei messaggi
riconosciuti.
Ad esempio, in risposta al messaggio WM_PAINT (che viene inviato tutte le volte che
e’ necessario ridisegnare la finestra), la funzione WndProc provvede a visualizzare il
messaggio a video.
La funzione WndProc viene definita funzione di callback perche’ non viene chiamata
esplicitamente dal programma ma e’ chiamata direttamente dal loop dei messaggi ogni
volta che se ne presenti la necessita’ (ovvero e’ arrivato un nuovo messaggio).
I messaggi non gestiti direttamente dall’applicazione vengono passati alla funzione
DefWindowProc per segnalare al sistema operativo che il messaggio non viene gestito e
che quindi e’ sufficiente un trattamento di default da parte di Windows.
Il gestore del messaggio WM_DESTROY richiama la funzione PostQuitMessage per
inviare un messaggio WM_QUIT alla coda dei messaggi e causare in questo modo la
corretta terminazione dell’applicazione.
Il messaggio WM_DESTROY viene inviato ad una applicazione immediatamente prima
della chiusura della finestra principale ed e’ a cura del programmatore inviare il
messaggio WM_QUIT altrimenti l’applicazione non potrebbe terminare correttamente.
7
Introduzione a MFC
Lo scopo primario di MFC (Microsoft Foundation Classes) e’ quello di incapsulare
all’interno di classi (piu’ di 200 nella versione 6.0) le API di Windows e di fornire inoltre
un ambiente di programmazione Windows ad oggetti che aiuti il programmatore nel
complesso meccanismo di sviluppo delle applicazioni dotate di interfaccia grafica.
Le classi MFC forniscono un architettura di sviluppo che semplifica la gestione dei
compiti piu’ ripetitivi di ogni applicazione Windows come la gestione dei messaggi, ed
offre inoltre alcuni potenti strumenti per separare l’implementazione dei dati di un
programma dalla sua rappresentazione grafica raggiungendo un livello di astrazione
sensibilmente maggiore rispetto all’SDK (Architettura Document/View).
E’ da considerare inoltre che l’utilizzo di MFC e del linguaggio C++ aggiunge alle
applicazioni Windows tutti i benefici derivanti dalle tecniche OO come l’incapsulamento,
l’ereditarieta’ e le funzioni virtuali.
E’ possibile infatti ereditare da una qualsiasi classe MFC e creare in poco tempo potenti
elementi di interfaccia personalizzati ed avvalersi inoltre della libreria di template e
container messi a disposizione del programmatore da STL.
A questo punto e’ possibile aggiungere un nuovo file .CPP o .H (in alternativa .HPP) ed
iniziare a scrivere il codice.
8
(listato 2_1_2)
(Hello.h)
// classe principale applicazione MFC
class CMyApp : public CWinApp
{
public:
// il metodo InitInstance deve essere
// implementato in CMyApp
virtual BOOL InitInstance ();
};
protected:
// dichiarazione della mappa dei messaggi
// e delle funzioni di gestione dei msg
afx_msg void OnPaint ();
DECLARE_MESSAGE_MAP ()
};
(Hello.cpp)
// MFC richiede l’include di afxwin.h
#include <afxwin.h>
#include "Hello.h"
9
// mappa dei messaggi gestiti dall’applicazione
BEGIN_MESSAGE_MAP (CMainWindow, CFrameWnd)
ON_WM_PAINT ()
END_MESSAGE_MAP ()
Come si puo’ notare dal listato, una applicazione MFC non ha una funzione WinMain
come l’esempio del capitolo precedente realizzato mediante l’SDK.
Le funzionalita’ del WinMain sono svolte da un oggetto di tipo applicazione che deve
essere istanziato in maniera globale derivandolo dalla classe base CWinApp.
In ogni programma MFC deve esistere un solo oggetto di tipo applicazione e se si vuole
che l’applicazione abbia una finestra e’ necessario effettuare l’implementazione del
metodo InitInstance nella classe derivata (in questo caso CMyApp) per crearne una e poi
visualizzarla tramite i metodi ShowWindow e UpdateWindow.
La funzione virtuale InitInstance e’ utilizzata per effettuare qualsiasi operazione di
inizializzazione sia necessaria al momento della partenza dell’applicazione; la funzione
InitInstance deve sempre terminare con il return TRUE.
Da notare che la finestra dell’applicazione viene creata dinamicamente all’interno di
InitInstance tramite l’istruzione m_pMainWnd = new CMainWindow.
In questo modo viene creata una nuova istanza di un oggetto appartenente alla classe
CMainWindow che avevamo derivato (nel .h) dalla classe base CFrameWnd e ne
avevamo implementato il costruttore (l’istruzione Create).
10
La classe base CFrameWnd e’ una delle classi MFC che fornisce l’implementazione di
una generica finestra che puo’ essere contenitore per altre finestre o controlli, oppure
essere utilizzata come nell’esempio per potervi accedere direttamente.
Il metodo Create della classe CFrameWnd puo’ essere scritto in modo da modificare
l’apparenza della finestra dell’applicazione; ad esempio per dotare la window di una
scrollbar verticale e’ necessario specificare:
Quando si dichiara la mappa dei messaggi e’ necessario indicare sia la classe base che la
classe derivata di cui si dovranno gestire i messaggi ed inoltre e’ necessario che la classe
derivata contenga una funzione membro per ogni messaggio gestito (la CMainWindow
risponde all’evento ON_WM_PAINT dichiarato all’interno della mappa dei messaggi,
tramite la funzione OnPaint).
E’ necessario consultare la documentazione dell’MFC per il prototipo preciso della
funzione membro che deve rispondere ad un determinato messaggio dato che alcune
richiedono parametri in input come ad esempio:
11
WM_LBUTTONDOWN (Pulsante destro del mouse premuto)
Per illustrare gli argomenti di questo capitolo verra’ utilizzato come esempio il listato
2_2_1, nel quale si trova l’implementazione di una semplice versione del gioco del tris.
Oltre ad essere un poco piu’ complessa dell’esempio “Hello World” questa applicazione
ci permette di introdurre alcuni argomenti fondamentali come l’intercettazione del mouse
e l’aggiunta e la gestione dei menu.
Data la lunghezza del listato ne riporteremo solo alcuni brani commentati ed invitiamo gli
studenti ad esaminarlo con la massima attenzione autonomamente.
Il “Device Context”
Come abbiamo gia’ visto nel capitolo precedente per poter disegnare su di una finestra e’
necessario disporre di un dispositivo astratto chiamato “Device Context”; per modificare
le caratteristiche di cio’ che si disegna come ad esempio il colore o lo spessore del tratto,
e’ necessario avvalersi di alcune classi MFC come CPen e CBrush che devono essere
associate al device context corrente in questo modo:
Void CMainWindow::OnPaint()
{
// crea il device context
CPaintDC dc(this);
12
Dal questo brano di codice si comprende che la sequenza di passi per disegnare su di una
window e’ sempre composta da:
Le “Message Box”
Spesso e’ necessario comunicare all’utente di una applicazione tramite piccole finestre di
avvertenza o di segnalazione dette “message box”.
Una message box puo’ essere indipendente dalla finestra in cui si trova e solitamente
serve per comunicare errori, warning o informazioni all’utente (come ad esempio che la
partita di tris e’ terminata nel listato di esempio).
La message box si definisce “finestra modale” ovvero rimane a video sopra le altre
finestre fino a che l’utente non preme il pulsante (solitamente di OK) per confermare la
lettura del messaggio.
Il metodo PostNCDestroy
Nell’applicazione di esempio possiamo vedere che la classe che gestisce la finestra
principale dell’applicazione (CMainWindow) richiede l’implementazione della funzione
virtuale PostNCDestroy per distruggere esplicitamente l’oggetto di tipo finestra.
Questo avviene perche’ la classe CMainWindow non era stata derivata da CFrameWnd
come nell’esempio precedente, ma dalla classe base CWnd che ha caratteristiche diverse
e non effettua automaticamente il rilascio delle risorse allocate.
13
Virtual void PostNCDestroy
…
void CMainWindow::PostNcDestroy ()
{
delete this;
}
e nel .cpp avro’ l’implementazione delle varie routine di gestione degli eventi legati al
mouse:
14
// in questo caso l’implementazione della routine di
// gestione per l’evento double click del pulsante sinistro
void CMainWindow::OnLButtonDblClk (UINT nFlags, CPoint
point)
{
…
CClientDC dc (this);
if (dc.GetPixel (point) == RGB (0, 0, 0))
ResetGame ();
…
}
15
Selezionare dal menu Insert -> Resource e scegliere Menu premendo il tasto New.
A questo punto e’ possibile editare la struttura dei menu graficamente facendo doppio
click sulla casella vuota per inserire un nuovo elemento in un menu esistente oppure
crearne uno nuovo.
Una volta creata la struttura dei menu e’ necessario salvare il file di risorse (avra’
estensione .rc) e spostarlo tra i sorgenti del progetto per fare in modo che venga
ricompilato quando si effettua il Build dell’eseguibile.
Per indicare nel sorgente che si utilizza un file di risorse esterno e’ necessario includere
nel .h lo header file resource.h
#include <resource.h>
a questo punto dobbiamo, sempre nel .h, dichiarare il prototipo della funzione che verra’
richiamata per ogni voce di menu selezionata; ad esempio se abbiamo una voce ‘Exit’
all’interno del menu ‘File’ che e’ stata chiamata all’interno dell’editor di menu
ID_EXIT, e’ necessario aggiungere il prototipo della seguente funzione di gestione:
nel .cpp e’ necessario aggiungere alla mappa dei messaggi il collegamento alla funzione
OnExit( ) nel momento in cui viene ricevuto il messaggio che segnala che l’utente ha
selezionato la voce ‘Exit’ dalla barra dei menu:
void CMainWindow::OnExit()
{
// si fa semplicemente terminare l’applicazione
PostMessage(WM_CLOSE, 0, 0);
}
A questo punto rimane da collegare il menu che abbiamo creato alla finestra
dell’applicazione e per fare questo abbiamo varie alternative; una di queste consiste
nell’aggiungere le seguenti righe di codice all’interno del costruttore della classe
CMainWindow:
16
// Crea un nuovo oggetto di tipo menu
CMenu menu;
// il menu creato nel resource file viene associato
// all’oggetto menu creato
menu.LoadMenu(MAKEINTRESOURCE(IDR_MENU1));
SetMenu(&menu);
// si distacca il menu creato dall’oggetto menu in modo che
// non venga distrutto quando l’oggetto menu andra’ al di
// fuori dello spazio di visibilita’
menu.Detach();
Un metodo alternativo per collegare un menu ad una finestra consiste nel modificare
l’istruzione Create della finestra passandogli l’identificativo del menu:
Esempio:
// crea il menu di primo livello (es. File)
CMenu menuMain;
MenuMain.CreateMenu();
17
I controlli di base e le “dialog box”
I “controlli” possono essere considerati come i “mattoni” che servono a costruire
l’interfaccia di qualsiasi applicazione Windows.
In generale un controllo e’ uno speciale tipo di finestra che permette di interagire con
l’utente finale, sia in termini di input che di output; i piu’ comuni controlli utilizzati in
pressoche’ tutte le applicazioni Windows sono bottoni, label, textbox, listbox, combo
box e check box.
Naturalmente esistono molti altri controlli, ed altri se ne aggiungono via via che
l’interfaccia delle varie versioni di Windows viene migliorata ed evoluta; il
programmatore li puo’ utilizzare come oggetti preconfezionati (classi in MFC) tramite la
loro interfaccia senza preoccuparsi di come siano stati implementati.
- Nel .h della classe che implementa la finestra si aggiungono gli oggetti che si
vorranno visualizzare nella finestra sotto forma di oggetti membro di tipo
variabile a seconda della classe di oggetto (es. CButton m_myButton, CListbox
m_myListBox ecc.)
- Al momento della creazione della una finestra si creano anche i controlli che sono
contenuti nella finestra (chiamando i metodi create delle varie classi MFC che
implementano i controlli come CButton, CListbox ecc.).
- Nella mappa dei messaggi vanno aggiunti gli eventi generati dai controlli
utilizzati; ad esempio il controllo bottone genera un evento ON_BN_CLICKED
che viene generato al momento della pressione.
- Gli eventi dei controlli che si desidera gestire vanno associati alle corrispondenti
routine di gestione (es. OnButtonClicked).
- Le routine di gestione vanno implementate all’interno dell’applicazione.
Ogni controllo ha le sue caratteristiche, attributi ed eventi che puo’ generare con relativi
differenti prototipi delle routine di gestione; e’ chiaro quindi che e’ necessario leggere la
documentazione di ogni tipo di controllo per poterlo utilizzare correttamente all’interno
delle proprie applicazioni.
Nel listato 2_2_2 si trova un esempio di utilizzo di alcuni dei controlli principali (un
bottone, una listbox, una label ecc.) che vongono impiegati in una semplice applicazione
che visualizza in una listbox tutte le font di caratteri installate nel sistema.
Nel file .h vengono aggiunti come membri della classe finestra dell’applicazione i
controlli che si utilizzeranno in questo modo:
CStatic m_wndLBTitle;
CListBox m_wndListBox;
CButton m_wndCheckBox;
18
CButton m_wndGroupBox;
CStatic m_wndSampleText;
CButton m_wndPushButton;
Nella mappa dei messaggi della nostra applicazione d’esempio vediamo quali siano gli
eventi gestiti per ognuno dei controlli presenti nella finestra principale; per il bottone
gestiamo l’evento OnPushButtonClicked, per la checkbox gestiamo
OnCheckBoxClicked e per la listbox OnSelChange che viene attivato quando la
selezione di uno degli elementi della listbox cambia.
Naturalmente possono esserci dei controlli nella finestra dell’applicazione per cui non
gestiamo eventi; ad esempio il controllo label e’ utilizzato per visualizzare solo del testo
statico e quindi non abbiamo la necessita’ di gestirne alcun evento.
19
m_wndSampleText.Create ("", WS_CHILD | WS_VISIBLE |
SS_CENTER, rect, this, IDC_SAMPLE);
Da notare che il metodo Create e’ diverso per ogni controllo a seconda degli argomenti
necessari e che ognuno ha bisogna di una struttura di tipo CRect per definirne posizione
e dimensione all’interno della finestra.
void CMainWindow::OnPushButtonClicked ()
{
MessageBox ("Aspetto di essere implementata",
"Error", MB_ICONINFORMATION | MB_OK);
}
Una delle potenzialita’ piu’ interessanti di MFC e’ rappresentata dal fatto che e’ possibile
creare controlli “customizzati” ereditando dalle classi base dei controlli predefiniti in
modo da arricchirne le funzionalita’.
L’architettura MFC rende questa operazione sufficientemente semplice in quanto
permette di specificare un meccanismo di “reindirizzamento” dei messaggi dalla classe
base a quella derivata; ad esempio se ho derivato un controllo listbox proprietario in cui
20
voglio gestire il double click in maniera diversa da quella della listbox tradizionale e’
sufficientemente ereditare una nuova classe CMyListbox da CListBox e specificare
nella mappa dei messaggi che l’evento double click avra’ un trattamento speciale (che
naturalmente dovra essere implementato) rispetto al controllo base.
public:
CColorStatic ();
void SetTextColor (COLORREF clrText);
void SetBkColor (COLORREF clrBack);
protected:
afx_msg HBRUSH CtlColor (CDC* pDC, UINT nCtlColor);
DECLARE_MESSAGE_MAP ()
};
Dalla definizione della classe del nostro controllo custom si puo’ notare che abbiamo
aggiunto due nuovi metodi SetTextColor e SetBkColor; nella mappa dei messaggi del
nuovo controllo dovremo specificare solo quali sono gli eventi che vogliamo gestire in
maniera differente dal controllo base:
(common controls)
Le “Dialog Box”
Nelle applicazioni Windows di solito i controlli non vengono posizionati nella finestra
principale dell’applicazione, ma in speciali finestre progettate per la raccolta dell’input da
parte dell’utente dette “dialog box”.
21
Un esempio puo’ essere la finestra di salvataggio dei file che troviamo in tutte le
applicazioni Windows oppure la finestra in cui si definiscono le opzioni di stampa.
Per inserire una dialog box in una applicazione MFC e’ necessario ereditare una propria
classe dalla classe base CDialog, inserire i controlli all’interno della dialog, ed
eventualmente implementare alcune funzioni virtuali che gestiscono il comportamento
della dialog.
In sintesi i passi da seguire per aggiungere ad una applicazione una semplice dialog box
che richiede l’input di un campo test e di uno numerico (listato 2_2_4), sono questi:
- Sebbene sia possibile fare tutto manualmente e’ bene in questo caso avvalersi
dell’aiuto dell’ambiente di sviluppo per la creazione della struttura base della
dialog e quindi dal menu Insert selezionare New Form, scegliere come classe
base CDialog e specificare il nome; il Visual Studio provvedera’ a creare il file di
risorse, ed i file .cpp e .h per la nostra nuova dialog.
- A questo punto possiamo aprire il resource editor e modificare l’aspetto della
nostra dialog box aggiungendo i controlli di input necessari (da notare che i
bottoni OK e CANCEL con la relativa gestione base sono gia’ stati implementati
dallo Wizard.
- Una volta aggiunti i controlli di input graficamente e assegnato loro un nome (es.
IDC_EDITNAME) e’ necessario aggiungere nel .h tante variabili membro della
classe CMyDialog quanti sono i campi associati ai controlli di input.
- E’ necessario poi effettuare l’associazione tra le variabili membro ed i controlli di
input e definire se devono essere effettuati controlli di validazione sull’input
inserito; i due metodi possibili per queste operazioni sono illustrati
successivamente da alcuni brani di codice tratti dall’esempio 2_2_4.
- Una volta preparata la dialog box e’ possibile richiamarla dall’applicazione
istanziando un oggetto di tipo CMyDialog in maniera “modale” (la dialog non si
chiude fino a che non si preme Ok o Cancel e non si puo’ passare ad altre finestre)
oppure “non modale” la dialog puo’ esssere chiusa e trattata come le finestre
tradizionali.
Vediamo la definizione della classe CMyDialog tratta dal listato 2_2_4 con la
dichiarazione delle variabili membro che dovranno ospitare il risultato dell’input sui
controllli della dialog:
22
Per poter associare i controlli alle variabili membro appena dichiarate essitono due
metodi; il primo consiste nell’effettuare l’override delle funzioni virtuali OnInitDialog e
OnOk per associare direttamente al controllo (in questo caso IDC_EDITNOME) la
variabile membro (m_strNome) sia quando la dialog viene inizializzata, sia quando il
pulsante Ok viene premuto e quindi la dialog verra’ chiusa.
// nel .h (CMyDialog.h)
virtual BOOL OnInitDialog ();
virtual void OnOK();
void CMyDialog::OnOK()
{
GetDlgItemText(IDC_EDITNOME, m_strNome);
CDialog::OnOK();
}
23
CMyDialog dlg;
if (dlg.DoModal() == IDOK)
{
// l'utente ha premuto il pulsante OK nella dialog
// visualizziamo il risultato dell'input nella dialog
CString nome;
nome = dlg.m_strNome;
MessageBox(NULL,nome,"Messaggio",MB_OK);
}
Nonostante l’associazione tra controlli e variabili membro all’interno di una dialog box
possa essere lasciata ad MFC, e’ possibile comunque manipolare direttamente i controlli
presenti all’interno di una dialog ottenendone un puntatore e chiamandone i metodi
tramite questo puntatore:
si puo’ interagire con un controllo all’interno della dialog anche in altri modi, ad esempio
tramite il metodo DDX_Control della dialog che effettua il collegamento tra una
variabile di tipo CListBox ed un controllo ListBox all’interno della dialog:
Le “Common Dialogs”
Le “common dialogs” sono finestre di tipo dialog box che vengono usate praticamente in
ogni applicazione Windows (come la finestra “Save as …”) e che quindi sono state
“pacchettizzate” in un un unico controllo che al suo interno incorpora tutti gli altri
controlli costituenti e nasconde all’utilizzatore la complessita’ della loro
implementazione.
In sostanza e’ possibile, utilizzando le common dialogs, incorporare nelle proprie
applicazioni finestre complete di gestione di particolari tipi di input standard (salvataggio
dei file, settaggio della stampante, selezione di un colore ecc.) scrivendo pochissime
righe di codice che servono solo a personalizzare minimamente il contenuto di questi utili
oggetti preconfezionati.
24
Le classi di utilita’ MFC e la “serializzazione”
Un alternativa all’utilizzo della libreria STL (Standard Template Library) e’ quella di
utilizzare le collezioni di classi “container” e di utilita’ fornite da MFC; infatti all’interno
di MFC si trovano una serie di classi che mette a disposizione container, strutture dati ed
ua classe stringa (CString).
Dato che le funzionalita’ di queste classi rappresentano, sotto molti aspetti, un
sottoinsieme di quello che viene fornito da STL si consiglia per quanto possibile, di
utilizzare STL all’interno delle proprie applicazioni MFC (anche per non dover imparare
ad utilizzare tutta una nuova libreria di classi per fare piu’ o meno le stesse cose).
La “serializzazione”
Uno dei concetti piu’ importanti contenuti all’interno delle classi di utilita’ MFC e’
quello della serializzazione; questo procedimento consiste nel poter salvare e leggere da
disco i dati persistenti di una applicazione MFC in maniera semplice ed indipendente da
come i dati sono organizzati.
Il cuore del concetto di serializzazione consiste nella possibilita’ di rendere
“serializzabile” una classe, ovvero di poter avere a disposizione una serie di meccanismi
che permettono di memorizzare e rileggere da supporto magnetico i dati contenuti
all’interno di una classe semplicemente utilizzando gli operatori << e >> tramite
l’utilizzo di un oggetto appartenente alla classe CArchive.
Per rendere una classe “serializzabile” come prima cosa e’ necessario derivarla dalla
classe base CObject ed inserire all’interno della definizione della classe la macro
DECLARE_SERIAL; la classe serializzabile deve inoltre avere un metodo Serialize che
riceve in input un reference ad un oggetto di tipo CArchive che sara’ il vero e proprio
file su cui andremo a salvare il contenuto della classe Ctest:
protected:
CString m_strMsg;
public:
CTest() { }
void set_msg(CString s) { m_strMsg = s; }
CString get_msg() { return m_strMsg; }
void Serialize (CArchive& ar);
};
25
Nel .cpp in cui andremo ad implementare i metodi della nostra classe serializzabile e’
necessario specificare la macro IMPLEMENT_SERIAL e scrivere il metodo Serialize
in cui andremo a scrivere e leggere all’interno dell’oggetto CArchive i membri della
classe che vogliamo rendere persistenti:
IMPLEMENT_SERIAL(CTest, CObject, 1)
void CMy2_2_5App::OnSave()
{
CTest ct;
ct.set_msg("pino");
26
void CMy2_2_5App::OnLoad()
{
CTest ct;
MessageBox(NULL,ct.get_msg(),"Msg",MB_OK);
}
27
salvato dall’applicazione per poterlo caricare ed eseguire allo stesso tempo l’applicazione
(come succede quando si fa doppio click su di un file .doc di Word); e’ inoltre possibile
aprire direttamente un nuovo file semplicemente trascinandolo all’interno della finestra
dell’applicazione (drag & drop open).
28
Per creare lo scheletro di una applicazione SDI utilizzando il code wizard e’ necessario
seguire questi passi:
- Selezionare File -> New per creare un nuovo progetto e scegliere “MFC App
Wizard” come tipo di applicazione.
- A questo punto si apriranno alcune finestre in cui e’ possibile specificare che tipo
di scheletro vogliamo generare; nel nostro caso selezionare “Single Document” e
lasciare attivata la checkbox “Document/View Support”.
- Nella seconda finestra del wizard non selezionare alcun supporto per database.
- Nella terza finestra lasciare attivata la voce “None” e deselezionare anche la voce
“ActiveX Controls”.
- Nella quarta finestra selezionare le feature che si desidera includere
automaticamente nell’applicazione; per mantenere semplice il codice se non si
hanno esigenze particolari di stampa o di interfaccia deselezionare tutte le
opzioni.
- Nella quinta e ultima finestra del wizard lasciare tutte le opzioni di default.
- Nella sesta e ultima finestra e’ presente un riepilogo dei file .cpp e .h che verranno
generati automaticamente dal Visual Studio ed e’ possibile eventualmente
cambiare i nomi proposti per classi e sorgenti.
- Finalmente lo scheletro dell’applicazione viene generato, ed e’ ora possibile
intervenire nel codice prodotto per personalizzare il comportamento
dell’applicazione; da notare che viene generato anche il file di risorse (.rc) che
contiene tutti i menu, i messaggi a video, le icone e le dialog box
dell’applicazione.
La prima classe che troviamo e’ quella utilizzata per istanziare l’applicazione stessa;
l’oggetto applicazione viene creato in maniera globale nello stesso modo che avevamo
visto negli esempi precedenti (ad esempio CSquaresApp MyApp), ma troviamo una
differenza nell’implementazione del metodo InitInstance dell’oggetto CSquaresApp
dato che l’inizializzazione dell’architettura Document/View richiede una serie di
operazioni aggiuntive:
29
// anche CSquaresAPP e’ derivata da CWinApp
class CSquaresApp : public CWinApp
BOOL CSquaresApp::InitInstance()
{
// crea la chiave nel registro se non esistente
// e carica le impostazioni di default (gestito
// da MFC)
SetRegistryKey("SDISquares");
LoadStdProfileSettings();
30
La differenza piu’ importante sta nella creazione del template di tipo
CSingleDocTemplate, che in pratica comunica al compilatore che stiamo creando una
nuova applicazione di tipo Document/View e che quindi vogliamo utilizzare la struttura
finestra container / vista / documento.
protected:
COLORREF m_clrCurrentColor;
COLORREF m_clrGrid[4][4];
31
afx_msg void OnColorCyan();
afx_msg void OnColorBlue();
afx_msg void OnColorWhite();
afx_msg void OnUpdateColorRed(CCmdUI* pCmdUI);
afx_msg void OnUpdateColorYellow(CCmdUI* pCmdUI);
afx_msg void OnUpdateColorGreen(CCmdUI* pCmdUI);
afx_msg void OnUpdateColorCyan(CCmdUI* pCmdUI);
afx_msg void OnUpdateColorBlue(CCmdUI* pCmdUI);
afx_msg void OnUpdateColorWhite(CCmdUI* pCmdUI);
DECLARE_MESSAGE_MAP()
}
BOOL CSquaresDoc::OnNewDocument()
void CSquaresDoc::Serialize(CArchive& ar)
32
In genere, dato che la vista si occupa di dare una rappresentazione grafica ad un
documento, il metodo piu’ importante dell’oggetto CView e’ quello che risponde
all’evento OnDraw, ovvero alla richiesta di disegnare il contenuto della vista.
Questo metodo, fornito come implementazione della funzione virtuale OnDraw, e’
chiamato direttamente dal framework MFC tutte le volte che sia necessario ridisegnare il
contenuto della vista :
La classe CSquaresView, come gia’ detto, si deve occupare anche della gestione
dell’input (in questo caso della pressione del tasto sinistro del mouse che comporta la
colorazione di uno dei quadrati); e’ da notare che questo evento e’ gestito normalmente
tramite la mappa dei messaggi come avevami visto negli esempi dei capitoli precedenti.
L’unica particolarita’ consiste nel fatto che, come prima cosa, viene richiamato il metodo
OnLButtonDown della classe base CView (dato che anche la classe base deve essere
notificata dell’evento):
33
Le viste “scrollabili” ed altri tipi di viste
La vista che abbiamo utilizzato nel programma di esempio (derivata da CView) non
dispone di funzionalita’ di scroll del contenuto della finestra; se avessimo voluto disporre
di tale funzionalita’ avremmo dovuto derivare la nostra classe vista da un’altra classe
denominata CScrollView; la classe CscrollView si occupa di gestire la maggior parte
delle operazioni necessarie per gestire lo scorrimento del contenuto della vista tanto da
poter utilizzare una generica funzione OnDraw sia su di una vista scrollabile, sia su di
una vista fissa.
Esistono inoltre altri tipi di viste che e’ possibile utilizzare all’interno dell’architettura
Document/View, le piu’ importanti sono:
E’ possibile inoltre crearsi tipi personalizzati di viste per particolari esegenze applicative
semplicemente derivando una propria classe vista dalla classe base CCtrlView.
34
finestre che contengono le diverse viste e che possono essere ognuna
ridimensionata, dockata, minimizzata, chiusa ecc.
Le funzionalita’ di stampa
Implementare funzionalita’ di stampa in applicazioni Windows tradizionali (SDK) e’
sempre stato un compito di una certa difficolta’, che rendeva la progettazione dei report e
delle stampe una attivita’ piuttosto onerosa per il programmatore.
Tutto questo per i molti fattori che entrano in gioco quando si tratta di stampare in un
ambiente Windows, come la paginazione, il fatto di poter essere in grado di annullare una
stampa in corso ed anche perche’ spesso si tratta di stampare output grafico e non solo
testo.
Inoltre altro codice andava scritto se si voleva dotare le applicazioni anche della
funzionalita’di “Preview di stampa”.
L’architettura MFC attenua moltissimo la difficolta’ di implementazione di procedure di
stampa nelle applicazioni Windows, in particolare se utilizzano l’architettura
Document/View.
Infatti MFC mette a disposizione del programmatore un device context generico che puo’
essere rediretto facilmento su stampante, si prende cura della possibile interruzione della
stampa in corso e gestisce perfino in maniera molto semplice la funzionalita’ di preview.
Sebbene il metodo OnDraw visto nel capitolo 2.5 permetta di inviare l’output sia a video
che su stampante e’ spesso necessario utilizzare l’altro metodo chiamato OnPrint in
quanto comunque quando si stampa si devono gestire informazioni aggiuntive come
magari il numero di pagina o una intestazione.
I passi necessari per effettuare la stampa da una applicazione MFC document/view sono I
seguenti:
35
BOOL CMyView::OnPreparePrinting(CPrintInfo* pInfo)
{
pInfo->SetMaxPage(10);
return DoPreparePrinting(pInfo);
}
In questa forma minima la funzione OnPreparePrinting non fa altro che aprire la dialog
box relativa alle impostazioni di stampa e ritornare un puntatore ad un device context di
tipo stampante; e’ consigliato inoltre chiamare la funzione SetMaxPage per comunicare
al sottosistema di stampa il numero massimo di pagine.
Dato che il numero di pagine puo’ non essere noto a priori e spesso dipende dalla
dimensione del documento si puo’ omettere tale parametro nella OnPreparePrinting e
sfruttare l’evento successivo OnBeginPrinting per calcolare il numero effettivo di
pagine:
Una volta chiamate queste due funzioni di preparazione della stampa, per ogni pagina,
vengono chiamati i metodi:
36
l’override di queste due funzioni virtuali e’ necessario solamente nel caso in cui si voglia
stampare qualcosa oltre il contenuto di default (cio’ che viene stampato nel metodo
OnDraw), come ad esempio il numero di pagina oppure un’intestazione.
Se deve essere effettuata qualche operazione al termine della stampa puo’ essere utile
effettuare l’override del metodo OnEndPrint.
Questa e’ la mappa dei messaggi tratta dall’applicazione di esempio 2_2_7 che mostra
quanto poco codice sia necessario aggiungere per implementare la funzionalita’ di
preview di stampa:
ON_COMMAND(ID_FILE_PRINT_PREVIEW,CView::OnFilePrintPreview)
I Timer
Spesso capita di dover sviluppare applicazioni che devono eseguire operazioni non in
risposta a determinati input da parte dell’utente, ma ad intervalli regolari; un esempio
puo’ essere un’applicazione che effettua il monitoraggio delle risorse di un server ogni 5
minuti, oppure un programma che effettua la pulizia di alcuni file temporanei ogni n
secondi ecc.
In questi casi e’ necessario avere a disposizione un oggetto chiamato “timer” la cui
funzione e’ quella di “svegliare” un’applicazione una volta trascorso un certo intervallo
di tempo (solitamente espresso in millisecondi).
Per gestire i timer con MFC e’ necessario utilizzare due funzioni CWnd::SetTimer e
CWnd::KillTimer il cui significato e’ abbastanza ovvio.
Ogni volta che l’intervallo fissato e’ trascorso il timer deve comunicarlo all’applicazione
e per fare questo possono essere utilizzati due metodi:
37
- inviando ad una finestra specificata il messaggio WM_TIMER
- chiamando una funzione definita all’interno dell’applicazione detta “funzione di
callback”
Gestire il messaggio WM_TIMER probabilmente e’ il metodo piu’ semplice, ma se si
hanno timer multipli in un’applicazione e’ preferibile utilizzare il metodo della funzione
di callback.
NOTA BENE: Windows non e’ un sistema operativo “real time” quindi un timer
settato ad un intervallo di 500 millisecondi non mi da nessuna garanzia che scattera’
con la massima precisione ogni 500 millisecondi; diciamo piuttosto che la media,
dopo un centinaio di attivazioni, convergera’ verso tale valore.
38
// funzione che risponde al messaggio WM_TIMER
// da notare che input riceve l’identificativo del timer
Void CMainWindow::OnTimer(UINT nTimerId)
{
// operazioni da svolgere ogni secondo
}
39
// chiamata automaticamente durante il ciclo di idle
// lCount indica il numero di volte che la funzione
// di idle e’ stata chiamata dall’ultimo messaggio
// processato dall’applicazione
BOOL CMyApp::OnIdle (LONG lCount)
{
// ATTENZIONE se si omette la chiamata al metodo
// OnIdle di CWinApp si compromette il funzionamento
// di tutta l’applicazione
CWinApp::OnIdle(lCount)
// adesso possiamo eseguire le nostre operazioni in
// background
DoIdleProcessing();
Return TRUE;
}
Sebbene la tecnica dell’utilizzo del ciclo di idle possa apparire interessante e conveniente
nelle applicazioni moderne in genere si tende ad utilizzare la tecnica del “multithreading”
che vedremo nel prossimo capitolo; puo’ capitare comunque di utilizzare questa tecnica
se le operazioni da compiere in background sono semplici e non si vuole utilizzare risorse
inutilmente per creare un nuovo thread, oppure se la nostra applicazione deve girare
ancora in ambiente 16 bit (Windows 3.11 e precedenti) dove non esiste supporto per il
multithreading.
(WriteProfileString / GetProfileString)
(Ctime / Ctime::GetCurrentTime)
40
necessario gestire la sincronizzazione fra il task principale dell’applicazione ed i vari
sottothread per evitare tutta una serie di errori che possono scaturire dal parallelismo (ad
esempio il programma principale tenta di accedere ai risultati di una elaborazione quando
il thread che elabora non e’ ancora terminato, oppure le risorse necessarie ad un thread
per un’elaborazione sono “bloccate” da un altro ecc.).
Priorita’
Ad un thread puo’ essere utile assegnare una priorita’ maggiore rispetto ad un altro
perche’ l’elaborazione che sta svolgendo e’ di importanza critica per l’applicazione.
41
// alla pressione del comando start viene avviato il nuovo
// thread
void CSieveDlg::OnStart()
{
…
// al thread di calcolo viene passata una struttura
// contenente i parametri necessari per il calcolo
THREADPARMS* ptp = new THREADPARMS;
ptp->nMax = nMax;
ptp->hWnd = m_hWnd;
// oltre ai parametri viene passato il nome della
// funzione che implementa il thread vero e proprio
AfxBeginThread (ThreadFunc, ptp);
}
42
// il vero e proprio thread di calcolo
UINT ThreadFunc (LPVOID pParam)
{
// lettura dei parametri ricevuti
THREADPARMS* ptp = (THREADPARMS*) pParam;
int nMax = ptp->nMax;
HWND hWnd = ptp->hWnd;
delete ptp;
// si effettua il calcolo
int nCount = Sieve (nMax);
43
OLE DB
OLE DB e’ un set di interfacce che espongono dati da una varieta’ di fonti relazionali e
non relazionali, attraverso l’utilizzo della tecnologia COM.
Fornisce inoltre un’interfaccia verso i driver ODBC per garantire uniformita’ di supporto
e di accesso verso una vasta gamma di DBMS relazionali.
- I dati risiedono in un database relazionale per cui esiste un driver ODBC ma per
cui non e’ disponibile un provider OLE DB nativo; l’applicazione utilizza ADO
per parlare con l’OLE DB provider per ODBC il quale poi pensa ad utilizzare il
driver ODBC appropriato; il driver passa lo statement SQL al database il quale
restituisce i dati richiesti.
- I dati risiedono in in database Microsoft SQL Server per cui e’ disponibile un
provider OLE DB nativo; l’applicazione utilizza ADO per parlare direttamente
con l’OLE DB provider per SQL Server e nessun altro strato intermedio e’
necessario
- I dati risiedono in Microsoft Exchange, il quale ha un OLE DB provider ma che,
non essendo un DBMS relazionale, non ha un motore in grado di comprendere
statement SQL; l’applicazione utilizza ADO per parlare con l’OLE DB Provider
per Echange il quale si occupa di tradurre gli statement SQL in query che possano
essere comprese da Exchange
- I dati risiedono nel file system di Windows 2000 (NTFS) sotto forma di
documento; l’accesso avviene tramite un provider OLE DB nativo che si
44
appoggia al servizio di Microsoft Indexing Service il quale estre i dati richiesti dal
proprio repository.
Come si puo’ vedere da questi esempi, un’applicazione che utilizza ADO puo’ effettuare
interrogazioni su una vasta gamma di fonti dati (non necessariamente database
relazionali) senza effettuare cambiamenti di rilievo alla struttura dell’applicazione ed al
modo di utilizzare gli oggetti ADO; tutto questo e’ possibile grazie all’architettura OLE
DB.
45
Il seguente diagramma mostra graficamente la gerarchia degli oggetti che compongono
ADO:
Per poter utilizzare ADO all’interno di una applicazione C++ e’ necessario importare la
libreria ADO (msado15.dll) tramite l’utilizzo della nuova direttiva di preprocessore
import; inoltre per evitare collisioni tra ADO ed MFC e’ necessario specificare la
clausola no_namespace e rinominare la define EOF in EndOfFile.
#import
"c:\Program Files\CommonFiles\System\ADO\msado15.dll"
no_namespace rename("EOF", "EndOfFile")
Il primo passo per poter accedere ad un data base tramite ADO e’ quello di dichiarare ed
istanziare una variabile di tipo puntatore a Connection:
46
// stringa di connessione ad Access
// apriamo la connessione tramite l’utilizzo del metodo
// Open dell’oggetto Connection
pConn->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data
Source=d:\\test.mdb;", "", "", adConnectUnspecified);
Dato che ADO puo’ connettersi a qualsiasi fonte dati e’ necessario specificare all’interno
del metodo Open dell’oggetto Connection a quale database ci vogliamo connettere
(OLEDB Provider) e quale e’ il nome della fonte dati che identifica il vero e proprio db.
Nel caso di Microsoft Access come identificativo della fonte dati possiamo utilizzare
direttamente il nome del file mdb che contiene il database, ma se ad esempio volessimo
utilizzare ADO per accedere a SQL Server avremmo dovuto fornire dei parametri di
connessione diversi:
// apre il recordset
pRst->Open(
"countries", // nome tabella db
_variant_t((IDispatch *) pConn, true),
adOpenStatic, // recordset statico
adLockReadOnly, // la tabella e’ lockata
adCmdTable); // stiamo leggendo una tabella
pRst->Open(
"SELECT * FROM countries ", // SQL
_variant_t((IDispatch *) pConn, true),
adOpenStatic,
adLockReadOnly,
1); // stiamo passando un’istruzione SQL
una volta che il recordset e’ aperto abbiamo a disposizione tutto l’insieme dei record della
tabella (o dello stament SQL) e possiamo quindi scorrere e posizionarci all’interno del
RecordSet:
47
// si posiziona sul primo elemento del recordset
pRst->MoveFirst();
while (!pRst->EndOfFile)
{
// Legge il campo ‘Country’ dal recordset
vtCountry =
pRst->GetFields()->GetItem("country")->GetValue();
pRst->MoveNext();
}
e’ necessario sempre ricordarsi di chiudere sia l’oggetto recordset che la connessione una
volta terminate le operazioni sul database:
pRst->Close();
pConn->Close();
Per poter eseguire uno statement SQL che effettui una modifica al database
(INSERT,UPDATE o DELETE) e’ necessario utilizzare l’oggetto ADO Command:
_ConnectionPtr pConnection("ADODB.Connection");
_CommandPtr pCmdChange("ADODB.Command");
_RecordsetPtr pRstTitles("ADODB.RecordSet");
// apertura connessione
…
// apertura del recordset
pRstTitles->Open ("Titles",
_variant_t((IDispatch *) pConnection,true), adOpenStatic,
adLockOptimistic, adCmdTable);
48
In alternativa il metodo execute puo’ essere applicato direttamente all’oggetto
Connection passandogli una stringa contenente lo statement SQL da eseguire:
pConnection->Execute(strSQLRestore, NULL,
adExecuteNoRecords);
Gli oggetti COM sono identificati univocamente tramite valori a 128 bit; il Class Id
identifica l’oggetto COM e deve essere utilizzato per crearne un’istanza, e l’Interface Id
identifica una delle interfacce all’interno di un oggetto COM.
Esempio:
49
// dichiariamo un puntatore all’interfaccia
IMath *pMath;
// rilasciamo l’interfaccia
PMath->Release();
COM Servers
Un programma eseguibile che implementa al suo interno un oggetto COM e’ chiamato
“COM Server”; per poter utilizzare un oggetto implementato all’interno di un COM
Server e’ necessario che tale oggetto sia “registrato” (ovvero esista all’interno del registro
di sistema un’associazione tra il CLASS ID ed il nome dell’eseguibile al cui interno e’
implementato l’oggetto COM).
In una prima release della tecnologia COM era possibile, da un client, istanziare solo
oggetti COM che erano registrati sulla stessa macchina, ma successivamente fu introdotta
un’evoluzione (chiamata DCOM, Distributed COM) che permetteva ad un oggetto di
girare su di un qualsiasi COM server all’interno di una rete.
Tramite un meccanismo chiamato “marshaling” la locazione dell’oggetto DCOM e’
completamente trasparente al programma che lo utilizza, nel senso che e’ la tecnologia
COM che risolve la chiamata al metodo remoto sia che l’ogggetto si trovi sullo stesso
computer, all’interno di una LAN oppure su di un server remoto a cui si accede tramite
Internet.
50
incorporare al suo interno documenti creati da “Active Document Serves” come Word ed
Excel.
ActiveX
Un’altra delle tecnologie COM-based di Microsoft che vale la pena di menzionare e’
denominata “ActiveX”; questo tipo di tecnologia e’ nota soprattutto perche’ permette di
realizzare controlli particolari (denominati controlli ActiveX) i quali sono oggetti COM a
tutti gli effetti che hanno la particolarita’ di poter essere utilizzati indifferentemente
all’interno di un’applicazione VC++ o Visual Basic e allo stesso tempo all’interno di una
pagina Internet (e possono essere manipolati tramite linguaggi di scripting come
VBScript).
COM e MFC
Il ruolo di MFC all’interno della tecnologia COM, e’ quello di fornire al programmatore
VC++ un’infrastruttura applicativa che semplifichi il piu’ possibile la realizzazione di
oggetti COM (che siano ActiveX o OLE Server o Active Documents).
Senza il supporto di MFC la realizzazione di un oggetto COM si rivelerebbe un compito
di estrema difficolta’ dato che lo standard COM prevede che l’oggetto implementi tutta
una serie di interfacce (e di metodi) predefinite che regolamentano il protocollo di
chiamata e di accesso all’oggetto.
Tecniche di “Automazione”
Con “automazione” si definisce una tecnologia “COM based” che permette ad
un’applicazione Windows di esporre le proprie feature ad un’applicazione client scritta in
Visual Basic, VBA oppure VBScript; indirettamente e’ possibile utilizzare l’automazione
anche tramite il linguaggio C++.
Esporre le proprie feature significa che il comportamento e le funzionalita’ di un
applicazione possono essere “programmati” da un’applicazione esterna tramite l’utilizzo
di un linguaggio di scripting.
Le applicazioni che offrono questa possibilita’ sono definite “automation servers”,
mentre quelle che ne fanno uso vengono denominate “automation clients”.
In genere l’automazione viene usata per permettere ad una applicazione di interagire con
uno dei pacchetti di Office; ad esempio per esportare i dati in un foglio Excel oppure in
un documento Word, oppure per realizzare un grafico appoggiandosi alla potente
capacita’ di Excel di produrre grafici.
51
(esempio Visual Basic)
Dim math as Object
Set math = CreateObject(“Math.Object”)
// impostiamo una proprieta’ dell’oggetto
Math.base = 10
// accediamo al metodo add dell’oggetto
Sum = math.add(2,2)
Set math = nothing
Questo esempio scritto in Visual Basic mostra l’estrema semplicita’ con cui si puo’
accedere ad un automation server chiamato Math.Object.
Il linguaggio Visual C++ non dispone di tutti i meccanismi del Visual Basic che
facilitano la scrittura di automation client, rendendo quindi la scrittura di tali applicazioni
un compito molto piu’ complesso (e spesso anche inutile dato che si puo’ scrivere tali
client con linguaggi piu’ adatti e gia’ orientati allo “scripting”).
Dove l’accoppiata MFC / Visual C++ si rivela vincente e’ nella scrittura di server di
automazione, la quale diventa un’operazione alquanto semplice in VC++ grazie
soprattutto allo Wizard che si occupa di generare gran parte del codice necessario a
costruire l’infrastruttura di automazione.
- Utilizzare MFC App Wizard per creare un nuovo progetto chiamato AutoMath;
scegliere Single Document Interface (SDI) e cliccare sul supporto per
l’automazione (selezionando Automation e scegliendo Full Server).
- Nello step successivo (4) premere il bottone “advanced” ed indicare
“AutoMath.Object” nel campo “File Type ID”.
- Negli step successivi eliminare tutto quello che non serve (funzioni di stampa,
toolbar ecc.)
- Una volta generato lo scheletro dell’applicazione lanciare il Class Wizard da
View -> Class Wizard e spostarsi nella pagina Automation; selezionare la classe
CAutoMathDoc e cliccare su ‘Add Method’; denominare il metodo ‘Add’,
premere OK e poi su ‘Edit Code’ per aggiungere l’implementazione del metodo
52
Double CautoMathDoc::GetPi()
{
return 3.1415926;
}
Per eseguire il codice VBS e’ sufficiente eseguire un doppio click sul file sorgente, dato
che il motore di scripting e’ integrato nei sistemi operativi Windows 2000 e Windows
XP.
53
Introduzione ad ATL
La Active Template Library (ATL) e’ una collezione di template che fa parte di
Microsoft Visual C++ e che serve per facilitare il compito di creare oggetti COM.
Sebbene sia possibile creare oggetti COM (come abbiamo visto nei capitoli precedenti)
tramite la scrittura manuale di codice C++ oppure tramite l’utilizzo di MFC e’ consigliato
l’utilizzo di ATL per applicazioni professionali dato che il codice generato da ATL e’
particolarmente performante ed efficiente.
Inoltre, lo wizard contenuto nel VC++ permette di creare oggetti COM utilizzando ATL
in un tempo brevissimo rispetto ad altre metodologie di sviluppo, dato che si occupa di
generare tutto il codice di base e le interfacce richieste dallo standard COM.
Le interfacce personalizzate dall’utente possono essere facilmente realizzate con ATL,
tramite l’utilizzo di un linguaggio di definizione delle interfacce denominate IDL
(Interface Definition Language).
- Creare un nuovo progetto VC++ e selezionare “ATL COM App Wizard”; dare un
nome al progetto come “Simple_ATL”.
- Nella dialog successiva specificare che si vuole creare un oggetto COM di tipo
“Server DLL” e lasciare invariate tutte le altre opzioni.
- Una volta che il wizard ha creato i file di base per il nostro progetto selezionare
Insert->New ATL Object dal menu; tra i vari oggetti proposti scegliere “Simple
Object” e cliccare sul pulsante “Next”
- Nella dialog successiva indicare come short name “First_ATL” e poi cliccare sul
tab “Attributes”; impostare Interface->Custom e Aggregation->No e premere
OK per fare in modo che il wizard crei il nostro oggetto ATL.
- Se osserviamo il “Class View” per il nostro progetto possiamo vedere
un’interfaccia denominata “IFirst_ATL”; fare click del pulsante destro su questa
interfaccia e selezionare “Add Method”
- Il nome del metodo sara’ AddNumber e come parametri possiamo specificare:
- Una volta specificata l’interfaccia IDL per il nostro metodo e’ necessario fornirne
l’implementazione; per fare questo dal Class View espandere (+) l’albero
dell’interfaccia e fare doppio click su “AddNumbers”; il codice del metodo verra’
generato, e sara’ possibile quindi aggiungere l’istruzione di somma:
54
STDMETHODIMP CFirst_ATL::AddNumbers(long Num1, long Num2,
long *ReturnVal)
{
// TODO: Add your implementation code here
*ReturnVal = Num1 + Num2;
return S_OK;
}
- a questo punto il nostro oggetto COM minimale realizzato tramite ATL e’ pronto
per essere compilato (Build); una volta generata la DLL il VC++ provvedera’ a
registrare l’oggetto COM in modo da poterlo utilizzare da altre applicazioni.
- Per testare l’oggetto COM dal Visual Basic, aprire un nuovo progetto VB e dal
menu Project->References selezionare la libreria “Simple ATL 1.0 Type
Library” per aggiungere un riferimento all’oggetto COM all’interno del progetto
Visual Basic.
- Adesso e’ possibile scrivere ed eseguire il seguente codice Visual Basic che
utilizza il metodo AddNumbers del nostro oggetto COM:
objTestATL.AddNumbers 5, 7, lngReturnValue
55
Parte 4 : Appendice
Glossario
56
Documentazione per il corso:.....................................................................................................1
“Sviluppo Applicazioni in ambiente Windows con Microsoft Visual C++ 6.0”..............................1
Parte 1 : Dall’SDK ad MFC.......................................................................................................1
Introduzione alla programmazione in ambiente Windows..................................................1
Programmare con l’SDK.....................................................................................................2
Introduzione a MFC............................................................................................................8
La prima applicazione MFC in dettaglio..............................................................................8
Parte 2 : Le classi MFC e l’interfaccia di Windows.................................................................12
Gli elementi base di MFC: Disegnare tramite un “Device Context”, gestione di mouse e
tastiera, gestione dei menu..............................................................................................12
I controlli di base e le “dialog box”..................................................................................18
Le classi di utilita’ MFC e la “serializzazione”...................................................................25
Introduzione all’architettura “Document View”................................................................27
Le applicazioni SDI (Single Document Interface).............................................................28
Le applicazioni MDI (Multiple Document Interface)..........................................................34
Le funzionalita’ di stampa ...............................................................................................35
Parte 3 : Oltre l’interfaccia...................................................................................................37
La gestione dei timer e del ciclo “Idle”.............................................................................37
Gestione dei “Thread”......................................................................................................40
Introduzione alle tecniche di accesso ai dati....................................................................43
Accesso ai database tramite ADO.....................................................................................45
Gli oggetti COM................................................................................................................49
Tecniche di “Automazione”..............................................................................................51
Introduzione ad ATL.........................................................................................................54
Parte 4 : Appendice.............................................................................................................56
Indice dei listati a corredo del testo.................................................................................56
Glossario..........................................................................................................................56
57