xBrowser How to display customers/city a kind of treeview
xBrowser How to display customers/city a kind of treeview
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)
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)
- nageswaragunupudi
- Posts: 8017
- Joined: Sun Nov 19, 2006 5:22 am
- Location: India
- Contact:
For arrays I do like this :
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
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] )}
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
Regards
G. N. Rao.
Hyderabad, India
G. N. Rao.
Hyderabad, India
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
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
Last edited by Otto on Sun Dec 23, 2007 4:26 pm, edited 2 times in total.
- nageswaragunupudi
- Posts: 8017
- Joined: Sun Nov 19, 2006 5:22 am
- Location: India
- Contact:
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.
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.
Regards
G. N. Rao.
Hyderabad, India
G. N. Rao.
Hyderabad, India
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
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
- nageswaragunupudi
- Posts: 8017
- Joined: Sun Nov 19, 2006 5:22 am
- Location: India
- Contact:
- James Bott
- Posts: 4654
- Joined: Fri Nov 18, 2005 4:52 pm
- Location: San Diego, California, USA
- Contact:
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
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
- James Bott
- Posts: 4654
- Joined: Fri Nov 18, 2005 4:52 pm
- Location: San Diego, California, USA
- Contact:
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
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
- James Bott
- Posts: 4654
- Joined: Fri Nov 18, 2005 4:52 pm
- Location: San Diego, California, USA
- Contact:
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:
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
- James Bott
- Posts: 4654
- Joined: Fri Nov 18, 2005 4:52 pm
- Location: San Diego, California, USA
- Contact:
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
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
- nageswaragunupudi
- Posts: 8017
- Joined: Sun Nov 19, 2006 5:22 am
- Location: India
- Contact:
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
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
Regards
G. N. Rao.
Hyderabad, India
G. N. Rao.
Hyderabad, India
- James Bott
- Posts: 4654
- Joined: Fri Nov 18, 2005 4:52 pm
- Location: San Diego, California, USA
- Contact:
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
> 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
- nageswaragunupudi
- Posts: 8017
- Joined: Sun Nov 19, 2006 5:22 am
- Location: India
- Contact: