Newsgroup: it.comp.lang.c++
Titolo: C o C++ ?

Vi espongo il mio problema. Io ho scritto un programma in C, che gira
sotto UNIX, che legge dei file ed in base alle informazioni in essi
contenute disegna una finestra, di tipo alfanumerico, con campi di testo
fisso e campi editabili. Per la gestione delle finestre ho utilizzato la
libreria new_curses. Ora devo scrivere un programma simile che giri
sotto Windows95. Ovviamente le finestre dovranno essere grafiche e non
più alfanumeriche. Attualmente dispongo del VC++ 4.0 e non so, dato che
non conosco molto questo prodotto, quale scelta mi conviene fare, se
continuare lo sviluppo in C o passare al C++. Premetto che conosco solo
superficialmente il C++ (e per nulla conosco le MFC), per cui  mi chiedo
se vale la pena investire del tempo nello studio del C++, oppure, visto
che in fondo si tratta di una gestione piuttosto banale di una finestra,
se e meglio provare con il C e le librerie del SDK.
Grazie.

se e meglio provare con il C e le librerie del SDK.
Su Windows, con C e SDK, *niente* e` "banale"... e` *TUTTO* difficile!-)
Secondo me (opinione del tutto personale) fai prima ad avere quel
minimo di infarinatura di C++ e MFC che ti serve per questo lavoro,
che non ad impararti il *paccone* di Win32/SDK che ti servirebbe
in alternativa.  In C++/MFC, chiedi al Wizard di farti un progetto
"form-based", cioe` basato su di un dialogo.  Poi, nella InitInstance,
leggi i tuoi file di testo (si`, si`, _puoi_ usare <stdio.h> e
fgets(), per evitare di impararti anche gli iostream:-) e prepari
una bella lista di informazioni su come devono essere fatti i
campi di testo da aggiungere al dialogo -- fissi o editabili,
coordinate, colori, ecc ecc.  Questa lista devi renderla accessibile
ai metodi della classe-dialogo, la soluzione piu` semplice e sporca
e` usare una variabile globale, la piu` pulita metterla nel
costruttore del dialogo, vedi un po' tu.
Infine, nella OnInitDialog della classe dialogo, che viene
automaticamente invocata quando la applicazione chiama il
DoModal del dialogo, consulti la lista e crei i campi che
servono.
Suppongo che ti servira` anche estrarre le *informazioni* immesse
dall'utente in questi campi... questo magari puoi farlo nella OnOK
della classe dialogo, rendendole opportunamente accessibili alla
classe applicazione (di nuovo, ci sono modi puliti e modi sporchi,
vedi un po' tu!-), che le accedera` e usera` rispondendo a un OK
(se l'utente da` un Cancel significa che ha deciso di non farne
nulla, quindi dovresti ignorare tutto quanto e uscire).
Il tutto magari viene un po' "accroccoso", pero` si mette insieme
in quattro e quattr'otto -- in SDK, secondo me, c'e` piu` da
imparare per cominciare, e parecchio piu` codice da scrivere
anche una volta che uno ha imparato.
Alex
--
 ____    Alex Martelli, Bologna, Italia -- email: 
al...@magenta.com
 \SM/___                                 
http://magenta.com/~alex
  \/\bi/     You never know what is enough unless you know what
     \/          is more than enough.

>se e meglio provare con il C e le librerie del SDK.
In risposta, io commentavo che C+SDK non sono mai di uso davvero
semplice, e probabilmente con C++/MFC si fara` prima.  Bisogna
aggiungere che ancora meglio si farebbe in Java, Visual Basic, ecc
ecc; e che il 4.0 e` un release piuttosto vecchio del VC++ (che da
allora ha fatto grandi passi verso lo Standard), quindi, per certe
cose di base (tipo stringhe, liste, ...), con VC 4.0 sara` meglio usare
le sue strutture proprietarie (CString, CList, ...) invece di quelle
standard C++ (string, list, ...).  Ma, tant'e`; per un problemino come
quello di Giulio, ci si puo` arrabattare anche cosi`.
Vediamo allora un po' meglio la soluzione che io propongo, anche
per essere direttamente di aiuto a Giulio.
Per cominciare, facciamo generare dal wizard un nuovo
progetto, un EXE con MFC, "form-based" cioe` basata su
di un dialogo; chiamiamolo ad esempio "dibs".  Modificando
questi sorgenti otterremo quel che ci interessa.
La piu` semplice delle modifiche, che sappiamo sara`
necessaria perche` ci serviranno le liste (CList), e` di
aggiungere a stdafx.h, il file degli "header precompilati",
la riga:    #include <afxtempl.h>
(Ora si puo` compilare stdafx.cpp, che ha come unico
scopo nella vita quello di incarnare gli header stessi).
Sappiamo poi che ci servira` una classe che rappresenti
un nostro "campo", ovvero casella di testo editabile o
meno, quindi facciamocela, chiedendo al Wizard di
aggiungere una classe, "generica", di nome field.  Questo
ci aggiunge al progetto due file, semivuoti, field.h (gli
header) e field.cpp (i contenuti).
Per "class field" c'e` da fare un poco di progetto.  Quali
attributi ha un campo?  Beh, il tipo (editabile o meno);
il contenuto, una stringa; le coordinate, un rettangolo che
ci dice dove si piazza entro il dialogo; questi ci servono
sicuramente.  Piu` discutibile, ma sembra ragionevole
dargli anche un ID (un identificatore numerico), per poter
agevolmente distinguere un campo dall'altro.
Potrebbe sembrare naturale che field avesse anche un
qualche riferimento ad una classe MFC che rappresenta
un campo (CEdit, ecc), ma in realta` cio` non ci interessa.
Viste le specifiche del problema, possiamo generare i
CEdit ecc "al volo" quando servono (lasciando poi che
vengano distrutti dal sistema all'uscita), e usare l'ID per
reperirli entro il dialogo quando ci servono.  In un caso
piu` generale la situazione sarebbe diversa, ma qui e`
meglio puntare alla semplicita`.
Identificare i dati che una classe incapsula e` meta`
del suo lavoro di progetto; l'altra meta` e` identificare
le sue funzionalita` (metodi).  Qui, abbiamo chiaramente
bisogno di potere:
1. costruire un field a partire dalle specifiche lette
    nel file di testo,
2. mostrare sullo schermo il campo corrispondente,
3. leggere quello che l'utente ha scritto nella casella
    corrispondente (se l'utente clicca OK)
Il punto 1 dipende dall'esatta sintassi del file di testo
in gioco; qui, come prototipo, prenderemo un caso
molto semplice in cui si specifica una casella per
riga nel formato:
T id left right top bottom Testo
dove T e` una lettera (E per editabile, altro=non editabile),
id l'id numerico, da left a bottom 4 coordinate, e il Testo
e` testo qualsiasi che fa da contenuto iniziale del campo
(editabile o meno).  Comunque, la funzione 1 corrisponde
chiaramente a un costruttore field(const char*) che parsa
questa sintassi (o altra piu` ricca) e setta di conseguenza
i dati dell'oggetto field.
Per il punto 2, bisognera` comunicare al metodo la finestra
in cui mostrare la casella; gli altri dati l'oggetto li ha gia`
tutti.  Pensiamo dunque ad un metodo del tipo
        void display(CWnd* parent)
visto che CWnd e` la classe MFC che rappresenta una
qualsiasi finestra.
Per il punto 3, sembra migliore un approccio a "due tempi":
una funzione che aggiorna i contenuti (la stringa che l'oggetto
field contiene) sulla base di quello che l'utente ha digitato,
e un'altra per accedere in qualsiasi momento al contenuto
della casella. Per evitare di rileggere inutilmente anche i
contenuti immutabili di caselle non editabili, pare il caso di
avere anche un accessore booleano al "tipo di casella" che
ci ricordi se e` o meno editabile.  Infine, ci servira` di
certo anche accedere all'ID della casella stessa.
Ecco quindi la dichiarazione della classe come risulta da
questo schizzo di progetto, dichiarazione che possiamo
porre in field.h:
class field  {
     int id;
     char type;
     CRect where;
     CString contents;
public:
     field();
     field(const char*);
     void display(CWnd* parent);
     void update(CWnd* parent);
     CString getContents() { return contents; }
     int getId() { return id; }
     BOOL isEditable() { return type=='E'; }
};
Abbiamo aggiunto un costruttore di default (che
sara` dummy) perche` molti contenitori, tipo le
liste, lo possono richiedere.  Vari dettagli del
design sono naturalmente alterabili (per esempio,
e` meglio passare il parent separatamente alla
display e alla update, o registrarselo nella classe
una volta per tutte?  Ecc ecc), comunque come
primo abbozzo puo` andare.
Da segnalare che CString e` la classe MFC che
rappresenta una stringa, CRect quella che
rappresenta un rettangolo.  Non ci serve un
distruttore perche` queste si "distruggono da
sole".  Infine, una scelta di progetto che questo
header contiene e` di tenere il tipo come un
carattere, con 'E' a rappresentare i campi
editabili.
L'implementazione della classe, che va nel file
field.cpp, e` pure abbastanza semplice.  Il
costruttore di default non fa proprio nulla:
field::field() {}
quello verace invece deve lavorare un po':
field::field(const char* s)
{
    char buffer[64];
    int left, right, top, bottom;
    sscanf(s,"%c %d %d %d %d %d %63[^\n]",
        &type, &id, &left, &right, &top, &bottom, buffer);
    contents = buffer;
    where = CRect(left, top, right, bottom);
}
e passando dal prototipo al sistema verace dovra`
essere sostituito dal vero parsing e validazione
del file di input.  Per usare sscanf, naturalmente,
ci serve un #include <stdio.h> all'inizio del file.
void field::display(CWnd* parent)
{
    DWORD style = WS_CHILD|WS_VISIBLE;
    if(isEditable())
        style |= WS_TABSTOP|WS_BORDER;
    else
        style |= WS_DISABLED;
    CEdit* ced = new CEdit();
    ced->Create(style, where, parent, id);
    ced->SetWindowText(contents);
}
Qui entra in gioco un minimo di specificita` Windows,
la definizione degli stili per caselle editabili e non, e
cosi` via, ma spero che si segua lo stesso.  Se si
lavorasse in C+WIN/SDK, di cose del genere ce
ne sarebbero molte di piu`...:-).  Comunque, e` da
notare la scelta di generare un CEdit al volo e poi
"abbandonarlo", come prima spiegato -- e` la cosa
piu` semplice in un programmino di questa portata
(in uno piu` serio/grosso/ecc, il CEdit* dovrebbe certo
essere registrato nell'oggetto field per potere poi
fare la distruzione quando opportuno).
E` piu` semplice la update:
void field::update(CWnd* parent) {
    parent->GetDlgItemText(id, contents);
}
e questo termina la classe field.
Naturalmente, questi field dobbiamo poi usarli
nella classe dialogo (CDibsDlg).  Piu` precisamente,
ne avremo una lista (o altro contenitore, ma una lista
va benissimo).  A questa lista dovremo potere
aggiungere campi, e dovremo poterla percorrere
dall'inizio alla fine -- queste funzionalita` andranno
aggiunte come metodi alla classe dialogo, e, come
dati, le serviranno una CList, e una POSITION che
tenga appunto la posizione corrente in essa.
Quindi, editiamo dibsDlg.h, aggiungendo, anzituttp
a inizio file una #include "field.h" per potere usare
la classe field; e, nella classe CDibsDlg, subito
dopo l'aperta-graffa (quindi come dati privati):
    CList<field, const field&> fields;
    POSITION current;
e, fra i metodi pubblici, dopo il costruttore:
    void addField(const field& f);
    field* first();
    field* next();
La first() e la next() dovranno tornare 0 (il
puntatore nullo) se la lista e` vuota/finita,
se no rispettivamente il "primo" e "prossimo"
campo.  L'implementazione da aggiungere
in dibsDlg.cpp e` abbastanza semplice:
void CDibsDlg::addField(const field& f)
{
    current = fields.AddTail(f);
}
field* CDibsDlg::first()
{
    current = fields.GetHeadPosition();
    return next();
}
field* CDibsDlg::next()
{
    if(!current) return 0;
    field& ref = fields.GetNext(current);
    return &ref;
}
Naturalmente questo dipende dalla logica
della CList dell'MFC e relative POSITION,
comunque nulla impedisce di farsi invece
la propria listaccia (o array) se si preferisce
evitarle (personalmente preferisco investire
il minimo di sforzo di studio per riusare del
codice gia` esistente in libreria).
La class dialogo naturalmente dovra` anche
usare la sua lista di campi per preparare il
dialogo quando viene inizializzato, e aggiornare
i risultati all'uscita.  La prima di queste cose
si fa` semplicemente nell'OnInitDialog che
il wizard ci ha gia` generato, aggiungendo le
due righe:
    for(field *pf=first(); pf; pf=next())
        pf->display(this);
(idioma C++ e` dichiarare la variabile di controllo
del ciclo, qui il puntatore pf, nella stessa for()).
Per leggere i campi all'uscita, potremmo fare la
override della OnOK, ma visto che il wizard ci
ha gia` fatto la DoDataExchange e` piu` semplice,
anche se meno pulito, usare invece quella, con
l'aggiunta delle righe:
    if(pDX->m_bSaveAndValidate) {
        for(field *pf=first(); pf; pf=next()) {
            if(pf->isEditable())
                pf->update(this);
        }
    }
dove l'if verifica che siamo in fase di "uscita"
dal dialogo piuttosto che non in "ingresso".
E con questo la nostra class di dialogo e` pure
pronta.  Basta adesso popolarla prima di
mostrarla, e consultare i campi dopo.  Questo
lo faremo nella classe-applicazione, file dibs.cpp,
che quindi dovra` avere aggiunte le due righe:
    #include <stdio.h>
    #include "field.h"
proprio all'inizio.
Tutto il lavoro si fa nella InitInstance (l'equivalente
MFC di un tradizionale "main" C, per cosi` dire).
Prima della chiamata a DoModal aggiungiamo la
lettura del file-testo di input e il popolamento del
dialogo -- nel nostro prototipo faremo solo:
    FILE* f = fopen("c:\\dibsin.txt","r");
    char buffer[80];
    if(f) {
        while(fgets(buffer,80,f)) {
            dlg.addField(field(buffer));
        }
        fclose(f);
    }
(nella realta`, bisognera` sicuramente recuperare
il filename in qualche modo [registry, linea di
comando, o altro], parsarlo piu` adeguatamente,
eccetera).
Infine, nel successivo ramo di if in cui sappiamo
che l'utente ha cliccato OK (non Cancel), possiamo
consultare e usare i dati che ha immesso, ad es:
    f = fopen("c:\\dibsout.txt", "w");
    for(field *pf=dlg.first(); pf; pf=dlg.next()) {
        if(pf->isEditable()) {
            int id = pf->getId();
            CString s = pf->getContents();
            fprintf(f,"%d: %s\n", id, s);
        }
    }
    fclose(f);
Ecco tutto!  Non troppo, spero... al netto delle mie
varie spiegazioni e ragionamenti, abbiamo dovuto
aggiungere a quello che ci fa il wizard:
-- una 40ina di righe fra header e body per la
    classe field,
-- una 30ina di righe alla classe dialogo,
-- una 20ina alla classe applicazione,
meno di 100 righe in tutto, e gia` abbiamo un
programmino che, benche` sia un prototipo, pure
ha una sua certa utilita`, permettendo di definire
al volo "maschere" in un file di testo, e ottenere
e salvare l'input dell'utente sui campi editabili di
questa maschera.
Ah, un ultimo dettaglio -- il dialogo vuoto che il
Wizard originalmente ti predispone non e` gran
che, un po' piccolo e coi bottoni OK e Cancel
in posizione un poco discutibile.  Comunque
quello e` facile editarlo graficamente; inoltre,
se lo desideri, non e` difficile prevedere, nel
file di testo, "comandi" per cambiare le dimensioni
del dialogo, spostarne i bottoni standard, ecc
ecc.  Partendo dal prototipo che ho qui iniziato
a tratteggiare, e usando l'help in linea del VC++,
si puo` "giocare" fruttuosamente imparando a
fare tante cose di questo tipo.
Facci sapere se questa impostazione ti e` stata
di una qualche utilita`...!
Alex

 .....
Anzitutto rigrazio molto Alex per il suo interessamento al mio problema. La
soluzione che ha proposto mi è sembrata, per quello che sono riuscito a
capire, adattarsi perfettamente al mio problema. L'implementazione suggerita
mi ha dato però dei probelemi in compilazione che non sono riuscito a
risolvere. In particolare il compilatore mi riporta due errori (uguali) e cioè
(+ o -):
...afxtempl.h     C2582    'field' : 'operator = ' function is not avaiable
Riporto la definizione della funzione interessata:
template<class TYPE, class ARG_TYPE>
POSITION CList<TYPE, ARG_TYPE>::AddTail(ARG_TYPE newElement)
{
 ASSERT_VALID(this);
 CNode* pNewNode = NewNode(m_pNodeTail, NULL);
 pNewNode->data = newElement;  <-----    ERRORE
 if (m_pNodeTail != NULL)
  m_pNodeTail->pNext = pNewNode;
 else
  m_pNodeHead = pNewNode;
 m_pNodeTail = pNewNode;
 return (POSITION) pNewNode;
}
in CDibsDlg.h abbiamo:
CList<field, const field&) fields;
POSITION current;
e in dibsDlg.cpp abbiamo:
void CDibsDlg::addField(const field& f)
{
    current = fields.AddTail(f);
}
Da quel poco che sono riuscito a capire il problema sembra essere legato in
qualche modo a un conflitto di tipo di dato nella funzione AddTail. Le mie
scarse conoscenze di C++ (spero ancora per poco..) non mi hanno consentito
tuttavia di risolvere il problema.
Grazie

qualche modo a un conflitto di tipo di dato nella funzione AddTail. Le mie
Semplicemente, come dice il messaggio, la classe field non ha
una funzione di assegnazione (chiamata "operator="), e quindi il
compilatore non puo` assegnare all'ultimo elemento della lista
un oggetto di tipo field, come gli stiamo chiedendo di fare.  Mi
pare che la versione che avevo postato, con la versione di Visual
C++ che e` in giro da almeno un annetto circa (la 5.0), funzionasse,
usufruendo della definizione "automatica" dell'assegnazione da
parte del compilatore; comunque, se questo non e` il caso per
la tua versione del compilatore e/o della classe field, bisognera`
che tu definisca appunto nella classe field un operator=.  Di
solito quel che impedisce al compilatore di farne uno in modo
automatico e` la presenza nella classe di campi a loro volta
assegnabili, come ad esempio delle reference.  Comunque,
lo schema generale e`, nel file field.h che dichiara la classe:
class field {
    // snippiamo il grosso
public:
    field& operator=(const field &);
};
e nel file di implementazione field.cpp:
    field& field::operator=(const field& f) {
        if(this!=&f) {    // precauzione per "x=x;"
            // copia opportunamente i campi di
            // f in quelli di questo oggetto, non
            // dimenticando di liberare eventuale
            // memoria allocata dal nostro, ecc
        }
        return *this;
    }
Facci sapere se con questo hai problemi...
Alex


....
Ha funzionato. Adattando le definizioni dei dati alle strutture specifiche dei
miei file ho ottenuto proprio quello che mi serviva.  Visto che sono qua ho
un'altra curiosità: se dovessi cambiare font e dimensione dei caratteri dei
campi di edit come dovrei fare ?
Molte, molte grazie!

Molte, molte grazie!
Prego!  E` un piacere vedere che qualcuno ancora usa ringraziare
per l'aiuto ricevuto...:-).


    per quei campi che lo richiedono.
Qua sorge il problema, nel senso che non mi č chiaro come inserire questa
chiamata. La
porzione di codice che genera i CEdit č la seguente:
void field::display(CWnd* parent)
{
     ....
 CEdit* ced = new CEdit();
 ced->Create(style,where,parent,id);
 ced->SetWindowText(contents);
  ....
}
 Se devo inserire qui la chiamata c'e' il problema che il puntatore al dialog
 (pTheDialog) non e' visibile dalla funzione. E' sufficiente che glielo faccia
vedere?
Ho provato invece a inserire la chiamata nel OnInitDialog (ho messo
SetFont(pSpecialFont()))
nella speranza che mi cambiasse il font del dialog, ma non sembra funzionare.
Visto che sono qui riprendo un'attimo il discorso sulle DLL-EXE (message DLL ?).
In effetti
il mio programmino non ha grossi problemi di passaggio dati con il chiamante
(che per inciso
č scritto in COBOL), ne le sue dimensioni creano alcun problema. Per cui forse
lasciarlo in
formato EXE (o COM come suggerisce Alex) e' la soluzione piu' semplice.
L'utilizzo delle DLL
era comunque una curiosita' prima che una necessita'.Grazie.

era comunque una curiosita' prima che una necessita'.Grazie.
Spero comunque che le mie poche note abbiano potuto soddisfare
questa curiosita`...:-).
Alex

         piu` pulita e "riusabile").
Ho provato a fare questa modifica. la funzione risultante č:
void field::display(CWnd *parent, CFont *SF)
{ ....
	CEdit* ced=new CEdit();
	SetFont(SF);
	ced->Create(...);
	ced->SetWindowText(...);
}
Perň durante l'esecuzione mi ritorna un'assertion failed sulla chiamata
SetFont.
Quale potrebbe essere il problema?
Second question:
Se volessi trasformare questo programma in una DLL in modo che venga
richiamato con una chiamata del tipo:
MyNewProgram (..parametri vari..);
cosa dovrei fare ?
Grazie!!

Se volessi trasformare questo programma in una DLL in modo che venga
Trasformare un eseguibile MFC in una DLL e` un'operazione di
una certa complessita`, sulla quale, a grandi linee, ho tra l'altro
postato assai di recente -- il 4 giugno, in risposta sempre a te,
e con il subject "Fw: DLL".  In quel messaggio, mi pare, spiego
anche perche` e percome il gioco (normalmente) non vale la
candela, e se ben ricordo in un tuo ulteriore follow-up ti dicevi
d'accordo, quindi e` curioso vedere di nuovo questa domanda.
Comunque, il mio post in questione da`, spero, le indicazioni
necessarie per cominciare a lavorare, se uno proprio vuole a
tutti i costi cimentarsi con questo compito.
Alex