Page 1 of 2
xBrowser How to display customers/city a kind of treeview
Posted: Sun Dec 23, 2007 9:38 am
by Otto
I tried to display – this is easy and working- but if you skip than this seems to be much work.
Is there somewhere a sourcecode example showing how to do such a browse?
For example: I would like to show all customers if there are more than one customer in a city the city is only shown once
(a kind of treeview)
Posted: Sun Dec 23, 2007 1:42 pm
by nageswaragunupudi
For arrays I do like this :
Code: Select all
oCol:bStrData := {||iif( oBrw:nArrayAt > 1 .and. aArray[oBrw:nArrayAt][1] == aArray[oBrw:nArrayAt-1][1], space(10),aArray[oBrw:nArrayAt][1] )}
I commonly do this for arrays and ado recordsets. We can also do it for DBFs but the code depends on your specifc case.
Ideally we would like if no lines are painted within a group. This can be done but needs one more data item in the column object and some change in the browse's paint method
Posted: Sun Dec 23, 2007 3:51 pm
by Otto
Hello NageswaraRao,
Thank you for your code.
I tested and it works as suspected for arrays.
Am I right that if you use dbf-files aArray can’t be used?
Do I have to make an own array parallel to get this functionality?
To test I tried with dbf files - I think it is similar to your code but hard coded - I used:
STATIC cCity
func bStrDataFIELD_CITY()
local cTEmpCity := _FIELD->CITY
IF cCity = cTEmpCity
cTEmpCity := " "
else
cCity := _FIELD->CITY
ENDIF
return cCity
But if you skip this does not work and I thought to fill an array parallel with the current scope.
Could you imagine if this will work.
What is the reason why xBrowse distinguish between array and dbf-file. If you look at other languages they make a recordset or a dataadapter and bind the browser to this.
Regards,
Otto
Posted: Sun Dec 23, 2007 4:24 pm
by nageswaragunupudi
The main requirement is to test whether the current field value = the previous records fieldvalue.
You can achieve this in different ways. What you do may just depend on dbf size, index key and so on.
If the dbf is not too large, and confident that other users will not change during the session, one way to do this may be like this.
Traverse the DBF and store the record numbers of the first occurance of the keyfield in an array. Then
bStrData := {|| iif( ascan( recno(), aUniques ) == 0, fieldget(1), space( <len> ) }
There are many other ways.
Few examples:
you can have a udf to look up previous value and return if the current value is a repeat.
If you create a compound index in a suitable way you can self relate the dbf so that the child points to prev record in the index.
You choose the optimal method depending on each specific case.
Posted: Sun Dec 23, 2007 4:27 pm
by Otto
Sorry, I edited during you answered.
Regards,
Otto
Posted: Sun Dec 23, 2007 4:42 pm
by Otto
NageswaraRao,
Thank you for you answer. I thought to fill a temp-array in this part
of xBrowser and then to access this array.
METHOD Paint() CLASS TXBrowse
do while nRowPos <= nMaxRows
// We must also paint some times after the last visible column
---> fill the temp array
...
Do you think this could be possible?
Regards,
Otto
Posted: Sun Dec 23, 2007 4:51 pm
by nageswaragunupudi
My personal advise is not to change FWH xBrowse code. Even if you change please do not change Paint method for handling values.
Please try to manage it within our application code. I suggested some ways of doing it for dbf tables.
Posted: Sun Dec 23, 2007 5:46 pm
by Otto
Hello NageswaraRao,
I thought doing that you don't lose much speed because the array will only be a few records < 50 ?.
Best regards,
Otto
Posted: Sun Dec 23, 2007 8:54 pm
by James Bott
Otto,
Here is an idea. Instead of using the CITY field in the browse use a function. Then use bSkip to pass the city field to the function. Inside the function you need a static var, cLastCity. Using this var return the city name of the current record only when it is not the same as cLastCity, otherwise just return nil or a space.
The ideal way to do this is to create a customer class as a subclass from TData, then add a method to handle the above. Otherwise I would suggest using a static function.
James
Posted: Sun Dec 23, 2007 9:25 pm
by James Bott
Otto,
There is a problem with my idea above. I have used this for reports but with browses users can move backwards. This makes it more complicated. For each record movement, you would have to determine if you are at the first occurance of the city or not. So, you would have to skip in the appropriate direction (forward or back) one extra record to determine this, then skip back to the original record. It's more complicated, but I still think it could be done.
James
Posted: Sun Dec 23, 2007 10:23 pm
by James Bott
Otto,
Here is a working example using a customer class as a subclass of TData. I have also shown the city field so you can see it is working. This is not very efficient as it requires three disk reads to display each record. I think it could be done using static vars to eliminate the extra disk reads.
Here is the code:
Code: Select all
/*
Purpose: Display city name only on first occurance in a browse
Date : 12/23/2007
Author : James Bott, jbott@compuserve.com
Note : Requires TData class
*/
#include "fivewin.ch"
function main()
local oWnd, oLbx, oCustomer
use customer exclusive
index on upper(city) to cust2
use
oCustomer:= TCustomer():new()
//oCustomer:setOrder(2)
oCustomer:gotop()
define window oWnd title "Test City Group"
@0,0 listbox oLbx fields oCustomer:firstCity(), oCustomer:city, oCustomer:last, oCustomer:first;
headers "City","City","Last","First";
sizes 100,100,100,100;
alias oCustomer:cAlias;
of oWnd
oLbx:bSkip := {| nRecs | oCustomer:skipper( nRecs ) }
oWnd:oClient:= oLbx
activate window oWnd
oCustomer:end()
return nil
//---------------------------------------------------------------------------//
class TCustomer from TData
method new
method firstCity
endclass
//---------------------------------------------------------------------------//
method new()
super():new(,"customer")
::use()
//::addIndex("cust1") // primary key
::addIndex("cust2") // city
::gotop()
return self
//---------------------------------------------------------------------------//
method firstCity()
local cPrev,cCurrent
cCurrent:= ::city
::skip(-1)
cPrev:= ::city
::skip()
return if( cCurrent = cPrev, "", cCurrent)
// end
Posted: Sun Dec 23, 2007 10:52 pm
by James Bott
I have looked at this some more, and now I don't think you can do this without three record reads. You have to know what the city for the previous record is and you can't use static vars to keep track of this since you may be moving forward or backward in the browse. When I refer to the "previous" record I don't mean the last record read, but the record before the current record in the current order. So you have to do a skip(-1) to find this, then skip back to your original location.
In a browse the user can skip forward or backward one or more records at a time, so there is no way to keep track of the "previous" record--you just have to read it.
This means the browse will require three times the disk traffic as a browse without city groups. Only testing will tell if this is useable.
Regards,
James
Posted: Mon Dec 24, 2007 1:52 am
by nageswaragunupudi
Thats the reason I did not propose a similar approach. It it is a small table I would first scan and store recno's of the records to display. If it is large I would create an index like city + str(recno(),10,0) and set relation to the same table with prev rec. Then the condition is if main->city == child-city show blank else show city
Or we can use a UDF. Degrades performance. function is something like
func isRepeat
local thisval := field->city
local thisrec :=recno()
local lsame := .f.
dbskio(-1)
lsame := ( !bof() .and. thisval == field->city)
dbgoto(thisrec)
return lsame
Posted: Mon Dec 24, 2007 2:26 am
by James Bott
Nageswara,
> If it is large I would create an index like city + str(recno(),10,0) and set relation to the same table with prev rec. Then the condition is if main->city == child-city show blank else show city.
Wouldn't this also require multiple disk reads, one for the main table and one for child table? Perhaps this is only requires two reads instead of three?
James
Posted: Mon Dec 24, 2007 2:54 am
by nageswaragunupudi
Yes, true. But lesser client server traffic. Reading related tables is faster than the program sending requests to read.