I've often pondered how hard it might be to take a cCJGrid and add the ability to group and show preview text. Over the last two days I thought I'd try. And guess what? It turns out the answer is "quite" bordering on "very". But I've had a stab and I thought I'd submit my results.
A few general statements to begin with.
I did this 'for fun' and just to see what the results might be. Please feel free to play with the code or change it in any way you so wish. I don't intend to support this or submit new functionality but if anyone else wants to then of course feel free. I can't say I've particularly tested it much either so there could be some unexpected behaviour.
The Grid is a read-only grid and so is effectively like the cSigCJReportControl. Is it a replacement for the cSigCJReportControl? No. Not at all. The cSigCJReportControl offers greater functionality I'm sure. I'd still advice using the SIG class.
So if it isn't a replacement for the SIG control then what's the point? Well in a nutshell because it is a subclass of the cCJGrid it means it means that it will work and model just like the cCJGrid. The API is the same. You fill it in the same way. You set properties in the same way. It uses the same events etc. So basically it simply means that you don't have two completely different styles of coding should you already have various cCJGrid objects but would also like to add some read-only style report grids.
I will attach some images along with the code so you can see the different styles. One if just the plain grid. One is the Grid once grouped. Once is the Grid grouped and also showing preview text. The other is not grouped but still shows preview text.
It was quite a challenge. The cCJGrid really isn't written in a way that lends itself easily to grouping. Firstly it is in Virtual mode but that's easy enough to turn off. The main challenge is that the Grid and the datasource are kept in synch with each other via use of the ReportControls RowIndex. This is a pointer to the row in the datasource struct such that data can be moved from one to the other.
So here was the first challenge. Say you add 100 rows to your data source (just in the same way as you would fill a cCJGrid). Once this is grouped in the ReportControl you have more than 100 rows. You have 100 records PLUS the rows that show the Groups. So straight away the Grid and the data source are out of step and you get "Index Number out of Range" errors when it tries to move the cell data in.
To be honest ultimately to do all this properly you would need to completely re-write the code in the background that concerns the data source and how it is all kept in step but that was too big a job than I was prepared to take on. So I took the pragmatic approach and added dummy group rows in to the data source. This means that the data source should always have the same number of rows as the Grid.
This is great but then you collapse one of the groups (or expand one). In the ReportControl (cCJGrid) this reduces the number of rows/changes all the Row Indexes (as rows are shunted up) and once more the grid is out of synch with the data source. So the next challenge was to make it that when you collapse a group we also remove the rows from the data source. Conversely when you expand a group you need to put them back in again (so they need to be stored).
You do all this and you think you're nearly there. But then sorting becomes a problem. Firstly when sorting you need to make sure that rows are sorted within their groups. Secondly if you collapse a group and THEN sort the grid you have to remember to ALSO sort the rows that were temporarily removed from the data source - otherwise when you expand the group and add in the rows they won't honor the current sort order.
So anyway, most of the code is done inside the DataSource and some in the SortHandler (to make sure we sort within the groups).
To be honest, it is all a bit messy but at first glance at least seems to work.
The code is as follows. You create a normal grid and fill it in the normal way:
Code:
Object oGroupedGrid is a cCJGrid
Set Size to 200 300
Set Location to 0 0
Set peAnchors to anAll
Object oCustomer_Customer_Number is a cCJGridColumn
Set piWidth to 63
Set psCaption to "Number"
Send CreateNumericMask 6 0
End_Object
Object oCustomer_Name is a cCJGridColumn
Set piWidth to 295
Set psCaption to "Customer Name"
End_Object
Object oCustomer_Status is a cCJGridColumn
Set piWidth to 53
Set psCaption to "Status"
Set pbCheckbox to True
Set psCheckboxTrue to "Y"
Set psCheckboxFalse to "N"
End_Object
Object oCustomer_Balance is a cCJGridColumn
Set piWidth to 114
Set psCaption to "Balance"
Send CreateCurrencyMask 6 2
Procedure OnSetDisplayMetrics Handle hoGridItemMetrics Integer iRow String ByRef sValue
If (sValue>=1000) Begin
Set ComForeColor of hoGridItemMetrics to clRed
End
End_Procedure
End_Object
Object oCustomer_State is a cCJGridColumn
Set piWidth to 50
Set psCaption to "State"
End_Object
Procedure LoadData
Handle hoDataSource
tDataSourceRow[] TheData
Boolean bFound
Integer iRows iNum iName iStatus iState iBalance
Get phoDataSource to hoDataSource
// Get the datasource indexes of the various columns
Get piColumnId of oCustomer_Customer_Number to iNum
Get piColumnId of oCustomer_Name to iName
Get piColumnId of oCustomer_Status to iStatus
Get piColumnId of oCustomer_Balance to iBalance
Get piColumnId of oCustomer_State to iState
// Load all data into the datasource array
Clear Customer
Find ge Customer by 1
Move (Found) to bFound
While bFound
Move Customer.Customer_Number to TheData[iRows].sValue[iNum]
Move Customer.Name to TheData[iRows].sValue[iName]
Move Customer.Status to TheData[iRows].sValue[iStatus]
Move Customer.Balance to TheData[iRows].sValue[iBalance]
Move Customer.State to TheData[iRows].sValue[iState]
Find gt Customer by 1
Move (Found) to bFound
Increment iRows
Loop
// Initialize Grid with new data
Send InitializeData TheData
Send MovetoFirstRow
End_Procedure
Procedure Activating
Forward Send Activating
Send LoadData
End_Procedure
End_Object
This will create a standard cCJGrid.
So we now change the cCJGrid to be a cCJGroupedGrid. This will then operate in the same way (except will be read-only)
Then identify the column that you wish to group by and set a property "phoGroupColumn" to that column ID.
Code:
Object oCustomer_State is a cCJGridColumn
Set piWidth to 50
Set psCaption to "State"
Set phoGroupColumn to Self
End_Object
Here (above) we will group by the state. Compile and Run and will do just that. By default the Group caption will be the value from the column but if you don't want that there is an event where you can change it. Here I will change it to the name of the state instead of the code (although I could have added the name to the datasource in the first place of course!)
Code:
Procedure OnSetGroupCaption String ByRef sCaption
Get Find_Code_Description of Customer_State_VT sCaption to sCaption
End_Procedure
This is an event within the Grid, not the column.
If you want the Groups to be sorted in reverse order there is a property for this:
Code:
Set pbReverseGroupOrdering to True
There is also a property that controls whether you also want to see the row count as part of the group caption.
Code:
Set pbShowGroupCounts to True
By default this IS true but can be set to False.
As for preview text, you have to add it as the TAG value of the DataSource:
Code:
// Load all data into the datasource array
Clear Customer
Find ge Customer by 1
Move (Found) to bFound
While bFound
// You can add preview text by setting it to be the TAG value
Move Customer.Comments to TheData[iRows].vTag
Move Customer.Customer_Number to TheData[iRows].sValue[iNum]
Move Customer.Name to TheData[iRows].sValue[iName]
Move Customer.Status to TheData[iRows].sValue[iStatus]
Move Customer.Balance to TheData[iRows].sValue[iBalance]
Move Customer.State to TheData[iRows].sValue[iState]
Find gt Customer by 1
Move (Found) to bFound
Increment iRows
Loop
Once you've done this you can set the property pbShowPreviewText to True (by default it is FALSE)
Code:
Set pbShowPreviewText to True
And that should be that. You don't have to group to show preview text - if you don't set the phoGroupColumn value you can still do everything else.
Anyway - have fun. It may inspire someone else to take this on further but as I say it is NOT intended to be a replacement for the SIG control. It was done just to see how hard it might be. Others may have tackled this differently but in my desire to try to do something quickly the route I took seemed to be the line of least resistance.
Oh yes - I should add that because the grid is loading the Customer data you'll need to add the source to the Order Entry sample. Or a copy of.