dBase - Tricks and tips

[ 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)

  FORMS
Il passaggio di parametri tra due forms
Come si crea, in una scheda, un riferimento ad una scheda figlia e viceversa
   
  PROGRAMMAZIONE
Come usare tabelle temporanee in un ambiente di rete
Come usare l'operatore macro per individuare un controllo di una scheda
Riferimento ad una funzione che si trova in una classe esterna
Come riferirsi ad un oggetto in una form dall'interno di un datamodule
Proprieta' lookupsql (come far comparire il codice invece della descrizione)
Come si imposta la modalita' non edit di una sola colonna in una grid

Perche' creare una sottoclasse
   
  S Q L
Ricerca all'interno di un SOLO CAMPO mediante l'istruzione SQL LIKE
Ricerca all'interno di PIU' CAMPI mediante l'istruzione SQL LIKE
Filtraggio mediante un'istruzione SQL di tutti i record relativi ad un dato anno
Uso della funzione SUM in una istruzione SQL
   
  REPORTS
Come passare un parametro in un report
Come impostare il filtro in un report

Come mostrare le intestazioni di un report su ogni pagina

FORMS

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

 

PROGRAMMAZIONE



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.

 


S Q L

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



 

 

 

 

 

REPORTS


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
endwith

ed 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).