[ Tricks e tips ] [ Links ] [ Documenti ]
Sommario
(dove non diversamente
indicato ci si riferisce sempre al Visual dBase 7.01, parte del
materiale e' tratto dai newsgroup italiani)
Premetto innanzitutto che una maniera di passare dati tra forms e' la seguente, indicata sui newsgroups italiani da Michele Narder :
parameter bModal, dato1
local f
f = new finesForm()
f.miavar = dato1 // e' piu' sicuro assegnare il parametro ad una proprieta' della form in quanto i parametri sono variabili di tipo privato (Gary White, dai newsgroup)
if (bModal)
f.mdi = false // impone non sia MDI
f.readModal()
else
f.open()
endif
return
** END HEADER -- non togliere questa linea
//
// Generato il 24/05/2000
//
parameter bModal
local f
f = new finesForm()
if (bModal)
f.mdi = false // impone non sia MDI
f.readModal()
else
f.open()
endif
........
Una maniera alternativa, che ritengo la migliore, e' la seguente suggerita da Geoff Wass il 29/04/1998 nei vecchi newsgroups della Borland e che ho cercato di tradurre per maggiore chiarezza:
-------------
Si puo' lasciare
anche il proprio .wfm " come e' " e nella form
chiamante inserire:
SET PROCEDURE TO mia.wfm ADDITIVE
x=new miaform( )
x.miavar1 = "dato1"
x.miavar2 = "dato2"
x.open( )
Non dimenticare di aggiungere del codice a mia.wfm per essere sicuro che essa gestisca la situazione di quando la form e' aperta con uno o piu' parametri omessi.
Lavorano bene anche le variabili di tipo _app (cioe' porre prima della x.open un'istruzione del tipo _app.var1="dato1", _app.var2="dato2" ecc., personalmente lo sconsiglio [LGS] ).
Tuttavia si deve fare cio' in maniera intelligente se si vuol permettere all'utente di aprire diverse volte la stessa form simultaneamente e si desideri passare parametri differenti per ciascuna form che si apre .
------------
Vorrei aggiungere, in conclusione, che e' indispensabile, per capire a fondo questa problematica, leggere attentamente il documento formvars.how di Alan Katz tradotto egregiamente da Antonio Campagna ed inserito nella sezione Documenti.
Con le istruzioni seguenti e' possibile referenziare qualsiasi oggetto che si trovi nella scheda madre, mentre e' attiva la scheda figlia. Allo stesso modo e' possibile referenziare gli oggetti della scheda figlia mentre e' attiva la scheda madre (Dan Howard).
set procedure to child.wfm additive
form.child = new form1()
form.child.parent=form
form.child.open()
Al fine di rilasciare le risorse impegnate, se la form e' stata aperta con readmodal() ed e' mdi=false, e' poi consigliabile aggiungere le seguenti istruzioni (Guido Ferruzzi, dai newsgroups)
close procedure child.wfm
release object Form.child
Come usare tabelle temporanee in un ambiente
di rete
Puo' capitare di dovere usare delle tabelle
temporanee in una form o in un report.
Fin quando si lavora su un singolo PC non
vi sono problemi, ma quando si lavora in
rete se si cerca
di cancellare con emptytable() una tabella
temporanea e contemporaneamente essa e' utilizzata
da un altro
operatore il secondo operatore dovra' attendere
la fine dell'utilizzo da parte del primo
operatore.
Per ovviare a tale problema nel metodo Open
della form vanno allora inserite le seguenti
istruzioni:
******************
function form_open
******************
** Esiste gia' una tabella PIANI, vuota,
ma con eventuali indici gia' definiti,
** con la struttura della tabella temporanea
che si intende usare.
** Cio' per evitare di ridefinire i campi.
tabA = funique("TMP?????.DBF" )
form.farmacia1.copytable("piani","&tabA")
_app.tabA=tabA
this.PIANI1 = new QUERY()
this.PIANI1.parent = this
with (this.PIANI1)
database = form.farmacia1
sql = 'select * from &tabA'
active = true
endwith
with (this.PIANI1.rowset)
indexName = "INDEX1"
endwith
** Bisogna definire i datalink alla tabella
temporanea
this.entryfield3.dataLink = form.piani1.rowset.fields["datain"]
this.entryfield4.dataLink = form.piani1.rowset.fields["farmacista"]
this.entryfield5.dataLink = form.piani1.rowset.fields["nota"]
....................
*** Altre tabelle temporanee.....
.................
return PIANOFORM::open()
All'interno della form i dati della tabella
temporanea possono essere gestiti normalmente.
Se alla form e' collegato un report (ad esempio
con due tabelle temporanee !!) la query
deve essere impostata cosi':
this.PIANI1 = new QUERY()
this.PIANI1.parent = this
with (this.PIANI1)
onOpen = class::PIANI1_ONOPEN
left = 1.2436
top = 2.4871
database = form.farmacia1
sql = "select * from piani.dbf a,ptemp.dbf
b where a.giorno=b.giorno order by a.giorno,b.sequenza
"
requestLive = false
active = true
endwith
**********************
function piani1_onOpen
**********************
this.active=false
tabA=_app.tabA
tabB=_app.tabB
this.sql="select * from '&tabA'
a,'&tabB' b where a.giorno=b.giorno order
by a.giorno,b.sequenza "
this.active=true
return
All'uscita della form le tabelle temporanee
devono essere cancellate.
Il metodo da me usato e' il seguente:
*********************
function form_onClose
*********************
this.piani1.active=false
this.ptemp1.active=false
tabA=_app.tabA
tabB=_app.tabB
form.farmacia1.droptable("&tabA")
form.farmacia1.droptable("&tabB")
return
ATTENZIONE:
Affinche' le tabelle temporanee vengano create
e cancellate nella directory definita dal
database (e non in locale !!)
bisogna, all'avvio dell'applicazione usare
la routine definita in BDEALIAS.CC (Libreria
DUFLP di Ken Mayer)
set procedure to bdealias.cc additive
b = new BDEAlias()
cfile=b.databasedir("farmacia")
set directory to &cfile.
DOMANDA (Roberto Consalvi, dai newsgroup):
>Qualcuno sa
dirmi se e' possibile usare l'operatore macro per individuare in
>run time un controllo di una data scheda ?
>
>Per esempio, voglio che se il valore di un dato campo č 1
venga scritta una
>data frase nell'oggetto form.entryfield1, mentre se il valore
del campo e' 2
>la stessa frase deve essere scritta nell'oggetto form.entryfield2.
>Ovviamente per 2 soli oggetti si potrebbe usare una semplice
istruzione
>condizionale ma, in realtą, il mio problema č dover gestire
circa 80 di tali
>oggetti entryfield e per questo vorrei poter usare un'unica
procedura che di
>volta in volta interessi l'oggetto entryfield che mi
interessa. Avevo
>pensato appunto all'operatore macro, ma come implementarlo in
questo caso ?
RISPOSTA :
Posto che i primi tre campi (nel tuo caso 80) siano campi caratteri e il quarto (entryfield4) sia numerico
(nel tuo caso l'81 esimo) il seguente esempio imposta in ogni entryfield una frase a seconda di quale
numero sia inserito nell'entryfield4.
** END HEADER -- non togliere questa linea
parameter bModal
local f
f = new prova1Form()
if (bModal)
f.mdi = false // impone non sia MDI
f.readModal()
else
f.open()
endif
class prova1Form of FORM
with (this)
onOpen = class::FORM_ONOPEN
scaleFontBold = false
height = 16
left = 37
top = 0
width = 40
text = ""
endwith
this.ENTRYFIELD1 = new ENTRYFIELD(this)
with (this.ENTRYFIELD1)
...........
...........
endwith
this.ENTRYFIELD2 = new ENTRYFIELD(this)
with (this.ENTRYFIELD2)
............
............
endwith
this.ENTRYFIELD3 = new ENTRYFIELD(this)
with (this.ENTRYFIELD3)
..............
endwith
this.ENTRYFIELD4 = new ENTRYFIELD(this)
with (this.ENTRYFIELD4)
onChange = class::ENTRYFIELD4_ONCHANGE
height = 1
left = 19
top = 8.5
width = 17
value = 0
validErrorMsg = "Dato immesso non valido "
endwith
function ENTRYFIELD4_onChange
a='form.entryfield'+ltrim(rtrim(str(this.value)))
b=&a.
b.value:=_app.deco[this.value]
return
function form_onOpen
_app.deco = new Array(3)
_app.deco[1] ="Valore1"
_app.deco[2] ="Valore2"
_app.deco[3] ="Valore3"
return
endclass
Se una funzione si trova all'esterno della classe bisogna porla in un CODEBLOCK:
Esempio: onClick={;LABEL::Anteprima}
dove Label e' la classe esterna ed Anteprima e' il nome della funzione che si desidera chiamare e che si trova nella classe esterna Label
DOMANDA (Roberto Peruzzi, dai newsgroup):
>In un
datamodulo ho inserito le query di due tabelle.
>Poi ho una scheda dove avviene tutta la gestione.
>Siccome devo attivare nel rowset di una tabella l'evento
onNavigate
>lo attivo nel datamodulo.Il problema mio e' che non riesco a
capire
>sintatticamente come fare riferimento agli oggetti che si
trovano nella
>scheda (nell'evento devo fare riferimento ad un RADIOBUTTON).
>Il programma mi da sempre errore di variabile non trovata!
RISPOSTA :
Una maniera e'
quella di mettere nel metodo OPEN della
FORM (o nell'evento OnOpen della stessa FORM) che contiene
il datamodule un riferimento di questo tipo:
this.datamodref1.ref.form=form
Quindi nel datamodule, nell'evento OnNavigate del rowset, il
riferimento dovrebbe essere:
this.parent.parent.form.contanagrafe.radiobutton1.value=...
dove
this = rowset
parent=query
parent=ref
.......
E' CERTO che dentro il datamodule, (senza il riferimento prima
indicato),
non si puo' arrivare alla form con qualsiasi numero di parent.
Il datamodule infatti puo' essere usato in un numero n di form e
non
puo' sapere in quel momento qual e' la form attiva.
Quando si usa LOOKUPSQL
se oltre alla descrizione del codice si desidera anche
visualizzare il codice stesso
bisogna ricorrere al seguente stratagemma.
Premesso che il campo usl ha una proprieta' lookupsql del tipo :
select usl,nomeusl from tabusl
Allora bisognera' scrivere:
datalink=form.tabusl1.rowset.fields['usl'].lookuprowset.fields['usl']
Posto che tabfunz.dbf sia il file che contiene il campo (codfunz) che non si desidera modificare nella grid:
form.TABFUNZ1 = new QUERY()
form.TABFUNZ1.parent = form
with (form.TABFUNZ1)
left = 11
top = 6.5
sql = 'select codfunz,descrizione from "tabfunz.dbf" '
active = true
endwith
with (form.TABFUNZ1.rowset)
fields[CODFUNZ].readOnly= true // disabilita il campo nella grid
endwith
DOMANDA (dai newsgroup americani)
>Sto
usando gli eventi onChange, onGotFocus, ecc. e molti di essi
fanno la stessa cosa
>come per esempio cambiare il testo in grassetto quando un
campo ha il focus.
RISPOSTA (Jim Sare)
Semplice soluzione:
CLASS MyEntryField(f) Of EntryField(f) Custom PROCEDURE OnGotFocus this.FontBold := True PROCEDURE OnLostFocus this.FontBold := False ENDCLASS
Fai la stessa cosa
per tutte le altre classi. Non si dovrebbero usare mai le classi
base direttamente in una form. Quando sara' necessario
implementare una variazione ad ogni entryfield o Text label o
qualunque altra cosa relativa alla propria intera applicazione si
avra' bisogno di andare in UN SOLO POSTO ed effettuare la
variazione.
Tutte le tue forms erediteranno la variazione.
Anche la maggior parte dei rimedi per un bug possono essere
implementati a questo livello. Cosi' quando si scopre un bug e si
trova una maniera di aggirarlo, e' solo necessario implementarlo
in un posto. Allora, quando il bug verra' corretto, si dovra'
rimuovere il workaround da un solo posto.
Un altro suggerimento
per chi produce diverse applicazioni. Si implementi un singolo
Base.CC che contenga le sottoclassi di ogni classe base. Questo e'
il posto dove si dovrebbero implementare i workarounds per i bugs
ed i comportamenti base che tutte le proprie applicazioni usano.
Successivamente per ciascuna applicazione si implementi un altro
<AppName>.CC file che contenga le sottoclassi di ciascuna
classe in Base.CC.
Le classi in <AppName>.CC sono quelle che hanno
comportamenti comuni a tutti i controlli di UNA specifica
applicazione. Anche se non si hanno specifici comportamenti che
sia necessario implementare OGGI per quell'applicazione. si
dovrebbe semplicemente implementare una classe vuota in <AppName>.CC:
CLASS DocEntryField(f) Of MyEntryField(f) Custom ENDCLASS
e DocEntryField e' cio' che si dovrebbe porre sulla propria form. Quando sara' necessario implementare un comportamento in tutti gli Entryfields di una intera applicazione DOMANI senza che cio' incida sulle altre applicazioni, si potra' andare in <AppName>.CC ed implementare la variazione in DocEntryField.
In pratica un relativamente piccolo ammontare di lavoro iniziale salvera' da tante ore di lavoro quando si renderanno necessarie, in seguito, delle variazioni.
E' possibile,
in SQL, per cercare nel campo Cust_Code tutti i records che
contengono la lettera b usare la seguente istruzione:
SELECT * FROM Customer WHERE Cust_Code LIKE "%b%"
:
La select
selezionera' tutti i records in cui e' contenuta la lettera b (come
l'operatore xBase $).
E' possibile anche
passare dei parametri :
b=form.entryfield1.value
form.anagrafi1.sql = [select * from anagrafi.dbf where denominazi
like ["%&b.%"]
La routine che
segue e' molto piu' veloce dell'operatore dBase $, consente di
poter
filtrare anche all'interno dei campi MEMO e su un numero
qualsiasi
di campi del proprio file.
Si possono mescolare, volendo, anche ricerche a partire dalla
sinistra
del campo con ricerche di parole all'interno del campo.
La function puo' essere agganciata all'evento onClick di un
pushbutton.
****************************
function PUSHBUTTON1_onClick
****************************
************************************************************************
** La procedura consente, dopo aver riempito
da 1 a 4 entryfield
** (2 di tipo carattere, 1 tipo data, 1 tipo
numerico), con dei criteri
** di ricerca, di effettuare,
** al click di un pushbutton, l'estrapolazione
VELOCE dei record
** desiderati.
** ATTENZIONE, la velocita' di estrapolazione
e' connessa con la
** lunghezza delle chiavi.
** Es. Chiave AL --> estrapolazione MENO
veloce
** Chiave ALBERTO --> estrapolazione veloce.
************************************************************************
try // Inizio procedura cattura errori
b=trim(form.entryfield1.value) // Campo carattere
c=trim(form.entryfield2.value) // Campo carattere
i=form.entryfield3.value // Campo data
** Da notare la seguente procedura per gestire
correttamente
** in SQL i campi data.
if not empty(i)
i=dtoc(i)
i=substr(i,4,2)+"/"+substr(i,1,2)+"/"+substr(i,7,4)
endif
l=trim(form.entryfield4.value) // Campo numerico
// Da notare l'uso dei doppi apici e delle
parentesi quadre
chiave1=""
chiave1+=IIF(not empty(b),[cognome like "%&b%"
and ],"")
chiave1+=IIF(not empty(c),[nome like "%&c%"
and ],"")
chiave1+=IIF(not empty(i),[datanasc = "&i"
and ],"")
chiave1+=IIF(not l=0,[usl = &l. and ],"")
chiave1=left(chiave1,len(chiave1)-5)
if not empty(trim(chiave1))
form.ricerca1.active=false
// Da notare anche qui l'uso delle parentesi
quadre
form.ricerca1.sql = [select * from ricerca
where &chiave1.]
form.ricerca1.active=true
else
msgbox("Inserire un criterio di ricerca","Attenzione",16)
endif
catch ( Exception e )
// In caso di un errore inaspettato ci si
aspetta che sia
// stato a causa di un carattere non valido
(come il doppio apice)
// Il programma pero' non va in crash !
msgbox("Carattere non valido nella ricerca","Attenzione",16)
endtry
return
Cio' e' possibile con la seguente semplice istruzione SQL:
with (this.alunni.query)
....
sql = 'select * from alunni.dbf where EXTRACT(YEAR from datanas)=1982'
......
endwith
DOMANDA (dai newsgroup americani)
Sto cercando di sommare numeri con lo stesso Id. La tabella e' simile alla seguente:
Cust_id ..........Quantity
1 ........................2
1 ........................3
1 ........................6
2 ....................... 1
3 ........................5
3........................ 1
Il mio file sql e':
SELECT distinct(cust_id), sum(quantity) FROM "C:\junk0\test.dbf"
Test group by cust_id, quantity
Tuttavia questa istruzione non mi da' la risposta che cercavo,
che e'
cust_id .........sum of Quantity
1 .........................11
2 ...........................1
3 ...........................6
RISPOSTA (Robert Dick)
La clausola Distinct non e' necessaria e si puo' rimuovere 'quantity' dalla clausola 'group by'
per es.
SELECT cust_id, sum(quantity)
FROM "C:\junk0\test.dbf" Test group by cust_id
Questo dovrebbe dare cio' che desideri
Ritengo la seguente la maniera
migliore. Essa usa variabili di tipo _app ed evita un problema
del modulo
di impostazione report che tende a decodificare la variabile dopo
una modifica visuale del report.
this.COSTI1 = new QUERY()
this.COSTI1.parent = this
with (this.COSTI1)
onOpen = class::TAB_ONOPEN
left = 0.2646
top = 2.196
sql = 'select * from "costi.DBF" where casa=:custid'
requestLive = false
params["custid"] = "106"
active = true
endwithed ecco l'evento onOpen
************************
function tab_onOpen
************************
this.params["custid"]=_app.casa1
this.requery()
return
DOMANDA (dai newsgroup americani)
> Ho
bisogno di creare un .wfm che permetta di impostare un filtro su
una tabella prima che il report
> venga creato.
RISPOSTA (Bowen Morsound)
Forse la piu' semplice tecnica e' di filtrare il rowset come si desidera proprio nella form. Per esempio:
set procedure to My.Rep additive
r = new MyReport()
r.Query1.rowset.filter = ["Order Date" = "12/10/1997"]
r.render()
DOMANDA (dai newsgroup americani)
> Sto
lottando per riuscire a stampare l'intestazione di un report su
pagine diverse dalla prima.
> Io ho un streamsource, un gruppo, un header.
RISPOSTA (Ken Mayer)
L'intestazione di un report viene stampata SOLO sulla prima pagina, cosi' come il footer del report viene stampato solo sull'ultima pagina. Quello che devi fare e' semplicemente porre gli oggetti direttamente sul pageTemplate (sopra lo streamFrame).