xbrowse MakeTotals()

xbrowse MakeTotals()

Postby reinaldocrespo » Fri Mar 27, 2015 8:49 pm

Hello everyone.

After updating to a more recent fwh, I find all my apps break when executing xbrowse's makeTotals() method after a cell is changed. This happens when MakeTotals is called on bchange or on bOnPostEdit.

I understand how new enhancements to the xbrowse class makes it unnecessary -at least on most cases- to execute MakeTotals after a cell is changed. That is good and I'm all for that. What is not good is when new fw behavior, features, or even fixes totally breaks existing code. I hope you can all understand it would be unreasonable to re-write your apps any time something changes in fw.

Trust me, there are legitimate reasons to call MakeTotoals on bChange or bOnPostEdit. I can demonstrate if necessary. I suppose there is a workaround to this problem. Can someone help? In other words, what can I do so that MakeTotals does not break my existing code? I think this was a change introduced into xbrowse in 2014.

Thank you very much,



Reinaldo.
User avatar
reinaldocrespo
 
Posts: 979
Joined: Thu Nov 17, 2005 5:49 pm
Location: Fort Lauderdale, FL

Re: xbrowse MakeTotals()

Postby reinaldocrespo » Fri Mar 27, 2015 11:59 pm

Below is a self-contained sample that demonstrates some of the problems with xbrowse. Once the app is built, click on the square "edit_get_button" button on any tree leaf for column "AmtPaid". Notice when clicking on the button of a branch cell of the tree it works as expected -that is, as coded on bEditBlock. When clicking on a leaf, the xbrowse breaks. If I compile with fwh 2012 it all works perfectly. If I compile with fwh 2014 it breaks. Notice the problem happens when you try to assign a value to a leaf cell on the tree, as in: oBrw:oTreeItem:Cargo[ 2 ] := 0.00.

Please test and please help!

Thank you.

Code: Select all  Expand view  RUN

#include "fivewin.ch"

#DEFINE _KEY 1
#DEFINE _PAID 1
#DEFINE _EXPECTED 2
#DEFINE _PENDING 3



STATIC aItems := { { "Id 1", { "Line 1", 100.00, 200.00, 0.00 },;
                          { "Line 2", 150.00, 100.00, 0.00 } },;
                   { "Id 2", { "Line 1", 300.00, 200.00, 0.00 },;
                          { "Line 2", 450.00, 100.00, 0.00 } },;
                   { "Id 3", { "Line 1", 100.00, 200.00, 0.00 },;
                          { "Line 2", 150.00, 100.00, 0.00 } },;
                   { "Id 4", { "Line 1", 300.00, 200.00, 0.00 },;
                          { "Line 2", 450.00, 100.00, 0.00 } } }


//------------------------------------------------------------------------------------------------
FUNCTION start()
   LOCAL oApp := TApp():New()
   
   oApp:CreateTree()
   oApp:Init()

   ACTIVATE WINDOW oApp:oWnd ON INIT oApp:ReCalculateCompleteTree()
   
RETURN NIL



//------------------------------------------------------------------------------------------------
CLASS TApp

   DATA oBrw, oWnd, oTree, oFont12_bold
   
   METHOD New() INLINE SELF
   METHOD init()

   METHOD CreateTree()
   METHOD SubTree()
   METHOD ReCalculate()
   METHOD ReCalculateCompleteTree()
   METHOD PayEachLeaf()

ENDCLASS

//------------------------------------------------------------------------------------------------
METHOD init()    

   DEFINE FONT ::oFont12_bold NAME "ARIAL" SIZE 6,-12 BOLD

   DEFINE WINDOW ::oWnd

   @ 0, 0 XBROWSE ::oBrw OF ::oWnd FOOTERS FASTEDIT
   ::oBrw:SetTree( ::oTree )

   ADD TO ::oBrw DATA ::oBrw:oTreeItem:Cargo[ _PAID ] HEADER "AmtPaid"  //2
   ADD TO ::oBrw DATA ::oBrw:oTreeItem:Cargo[ _EXPECTED ] HEADER "Expected"  PICTURE "9,999,999.99"
   ADD TO ::oBrw DATA ::oBrw:oTreeItem:Cargo[ _PENDING ] HEADER "Pending"   PICTURE "9,999,999.99"

   WITH OBJECT ::oBrw:AmtPaid

      :bOnPostEdit := { |o,x| ::oBrw:oTreeItem:Cargo[ _PAID ]:= x , ::ReCalculate() }

      :nEditType := EDIT_GET_BUTTON

      :bEditBlock := { |R, C, o, K| ;
               IF( ::oBrw:oTreeItem:nLevel == 1, ;
                  ::PayEachLeaf(), ;
                  iif( EMPTY( ::oBrw:oTreeItem:Cargo[ _PAID ] ),;
                     ::oBrw:oTreeItem:Cargo[ _PAID ] := ::oBrw:oTreeItem:Cargo[ _EXPECTED ],;
                     ::oBrw:oTreeItem:Cargo[ _PAID ] := 0.00 ) ),;
               ::ReCalculate() }

   
     
   END

   AEVAL( ::oBrw:aCols, { |o| o:oDataFont := {|| iif( ::oBrw:oTreeItem:nLevel == 1, ::oFont12_bold, ::oBrw:oFont ) } } )

   AEVAL( ::oBrw:aCols, { |e| e:nDataStrAlign := AL_RIGHT,;
                           e:nWidth := 70,;
                           e:cEditPicture := "999,999.99",;
                           e:nFooterType := AGGR_TOTAL,;
                           e:oFooterFont   := ::oFont12_bold,;
                           e:nFootStrAlign := AL_RIGHT }, 2, 4 )

   ::oBrw:CreateFromCode()
   ::oWnd:oClient := ::oBrw    

return nil


//------------------------------------------------------------------------------------------------
METHOD CreateTree()
   LOCAL aItem, oItem

   TREE ::oTree                           //::oTree is type Tlinklist

   FOR EACH aItem IN aItems

      oItem := ::oTree:Add( aItem[ 1 ] )   //each oItem is type TTreeItem

      oItem:Cargo := { 0.00, 0.00, 0.00 }
      oItem:nLevel  := 1

      oItem:bAction := { |o| o:SetTree( ::SubTree( o ) ), o:bAction := Nil }

      EVAL( oItem:bAction, oItem )
     
   END

   ENDTREE

RETURN NIL


//------------------------------------------------------------------------------------------------
METHOD SubTree( oParent )
   LOCAL nLevel
   LOCAL oTree, oItem
   LOCAL aItem
   LOCAL cId := ALLTRIM( oParent:cPrompt )
   LOCAL nAt := aSCAN( aItems, { |e| e[ 1 ] == cId } )

   nLevel   := oParent:nLevel + 1
   TREE oTree

   FOR EACH aItem IN aItems[ nAt ]

      IF VALTYPE( aItem ) != "A"    ;LOOP   ;ENDIF

      TREEITEM oItem PROMPT aItem[ 1 ]    //creates a new leaf

      oItem:nlevel := nLevel

      oItem:Cargo  := { aItem[ 2 ], aItem[ 3 ] , aItem[ 4 ] }

   NEXT

   ENDTREE
   
RETURN oTree



//----------------------------------------------------------------------------//
METHOD PayEachLeaf()
   LOCAL nPaid    := ::oBrw:oTreeItem:Cargo[ _PAID ]
   LOCAL oItem    := ::oBrw:oTreeItem:GetNext()
   
   WHILE oItem != NIL .AND. oItem:nLevel == 2

      oItem:Cargo[ _PAID ] := IIF( nPaid == 0.00, oItem:Cargo[ _EXPECTED ], 0.00 )
      oItem := oItem:GetNext()
     
   END

RETURN NIL




//------------------------------------------------------------------------------------------------
METHOD ReCalculateCompleteTree()

   LOCAL oBranch := ::oBrw:oTree:oFirst
     
   WHILE oBranch != NIL
      ::Recalculate( oBranch )
      oBranch := oBranch:GetNext()

   END

RETURN NIL



//------------------------------------------------------------------------------------------------
METHOD Recalculate( oItem )
   LOCAL bAddTotals, oBranch, oParent
   LOCAL nTotPend       := 0.00
   LOCAL nTotExp            := 0.00
   LOCAL ntotPaid           := 0.00
   LOCAL nAt      := ::oBrw:oTreeItem:ItemNo()

   DEFAULT oItem := ::oBrw:oTreeItem
   
   bAddTotals := { | o | nTotExp    += o:Cargo[ _EXPECTED ],;
                         nTotPend   += o:Cargo[ _PENDING ],;
                         nTotPaid   += o:Cargo[ _PAID ] }

   

   oParent := IIF( oItem:nLevel == 2, oItem:Parent(), oItem )
   oParent:Open()
   oBranch := oParent:GetNext()

   //Visit each branch leaf
   WHILE oBranch != NIL .AND. oBranch:nLevel == 2

      EVAL( bAddTotals, oBranch )      
      oBranch := oBranch:GetNext()

   END

   oParent:Cargo[ _EXPECTED ] := nTotExp
   oParent:Cargo[ _PAID ]     := nTotPaid

   oParent:Cargo[ _PENDING ] := oParent:Cargo[ _EXPECTED ] - oParent:Cargo[ _PAID ]

   ::oBrw:MakeTotals()
   ::oBrw:Refresh()

   EVAL( ::oBrw:bBookmark, nAt )

RETURN NIL



 
User avatar
reinaldocrespo
 
Posts: 979
Joined: Thu Nov 17, 2005 5:49 pm
Location: Fort Lauderdale, FL

Re: xbrowse MakeTotals()

Postby Rick Lipkin » Sat Mar 28, 2015 1:52 pm

Reinaldo

Forgive me .. I am not a class and method kind of programmer .. I have found this code to be effective when you have to update Maketotals()

When ever I need to update my total ( once it is initialized ) just call oLbx:MakeTotals() from your function and refresh the browse and your totals are updated.

From looking at your code, it appears you are doing the same thing, just in a different way :)

Rick Lipkin

Code: Select all  Expand view  RUN

WITH OBJECT oLbxB
        :Balance:nFooterType  := AGGR_SUM  // balance is the column fieldname
        :MakeTotals()
END

...
...

Static Func YourFunction(oLbxB)

// add some records,delete some records or edit a record

oLbxB:MakeTotals()  // update the footer total
oLbxB:ReFresh()

Return(nil)
 
User avatar
Rick Lipkin
 
Posts: 2666
Joined: Fri Oct 07, 2005 1:50 pm
Location: Columbia, South Carolina USA

Re: xbrowse MakeTotals()

Postby reinaldocrespo » Sat Mar 28, 2015 2:15 pm

Thank you Rick. I appreciate your response.

Perhaps I should break this post into two separate subjects. The makeTotals() problem arrises when you try to run MakeTotals from the bChange or bOnPostEdit property of the column and problem #2 is the one I demonstrate with my code. I'm guessing problem #2 is related but I'm not certain. To reproduce problem #1 you can try code like this:

Code: Select all  Expand view  RUN

oBrw:ColHeaderName:bChange := { || oBrw:MakeTotals() }
 


Problem #2 can be reproduced when working with trees (linked lists) and my code above can be used to test. I will start a new thread about problem #2 in hopes that someone can confirm and perhaps fix this problem with xbrowse if i'm proven correct.

Reinaldo.
User avatar
reinaldocrespo
 
Posts: 979
Joined: Thu Nov 17, 2005 5:49 pm
Location: Fort Lauderdale, FL

Re: xbrowse MakeTotals()

Postby nageswaragunupudi » Sat Mar 28, 2015 3:40 pm

Code: Select all  Expand view  RUN

      :bOnPostEdit := { |o,x| ::oBrw:oTreeItem:Cargo[ _PAID ]:= x , ::ReCalculate() }
 

may please be replaced with the standard construct:
Code: Select all  Expand view  RUN

      :bOnPostEdit := { |o,x,k| If( k == VK_ESCAPE, nil, ( ::oBrw:oTreeItem:Cargo[ _PAID ]:= x , ::ReCalculate() ) ) }
 

which is safer.

With this change, the program works the same way as it was working in FWH 12.02 (the version I tested).

Standard safe practice is to assign the new value after considering the 3rd parameter nKey and the 2nd parameter xValue and then decide to assign the new value. Normal checks are that the nKey is not Escape key and 2nd parameter is not nil.

As we keep recommending and requesting from time to time, safer way would be to assign a SetGet block as bEditValue and let the XBrowse construct a safe bOnPostEdit codeblock. If there are some actions to be taken comsequent on a change in the value, as always we recommend using oCol:bOnChange codeblock. These recommendations apply to 2012 versions also as much as they apply to current versions. Our standard recommendation is not to code bOnPostEdit in the application program.

For example ( FWH12.08 till FWH15.03 )
Code: Select all  Expand view  RUN

   ADD TO ::oBrw DATA { |x| If( x == nil .or. ::Brw:oTreeItem:nLevel < 2, ::oBrw:oTreeItem:Cargo[ _PAID ], ;
                          ::oBrw:oTreeItem:Cargo[ _PAID ] := x ) } ;
                            HEADER "AmtPaid"
<other statements>

   ::oBrw:AmtPaid:bOnChange := { || ::Recalculate() }
 


I find all my apps break when executing xbrowse's makeTotals() method after a cell is changed. This happens when MakeTotals is called on bchange or on bOnPostEdit.

We are surprised and also pained to learn that all your appls are breaking with makeTotals(). Please let us know any other cases and we are too eager to support you.
For your information MakeTotals() today is the same as it was in the beginning of 2012. There are only two improvements (including 15.04 to be released) but those two are only improvements and method is fully backward compatible.

I understand how new enhancements to the xbrowse class makes it unnecessary -at least on most cases- to execute MakeTotals after a cell is changed.

There is nothing new. This was there prior to 2012 too. And it remains the same even today.

Trust me, there are legitimate reasons to call MakeTotoals

We do agree. The automatic recalculation of totals is not adequate in all cases and there are always good reasons to call :MakeTotals() again and again consequent on some changes. And please trust us too when we say there is no change in MakeTotals() method that breaks earlier code.

We may like to make one or two suggestions to the sample code provided.
MakeTotals() is not very appropriate to this case. Method Recalculate() which calculates the group totals can also calculate grand totals and displayed with oCol:bFooter codeblock.

The main problem is MakeTotals() by default total all rows. In this case it totals both group totals and also the leaf values resulting in the footer totals being twice the actual totals. You may see that the footer total of the "Paid" column in the above sample is displayed as 4000 whereas the correct total is 2000. (Whether you use 2012 or 2015 versions)

At the same time XBrowse provides for conditional totals similar to SUMIF(....). In this case
oCol:bSumCondition := { |n,o| o:oBrw:oTreeItem:nLevel == 1 }
totals only values for the Level 1 items. If you use this for columns 2,3,4, MakeTotals() gives the correct totals.
This feature was available in FWH12.02 too and continues to function the same way even today.
But the limitation is that the automaic retotal feature dose not work correctly We need to call MakeTotals() whenever there is a change.
Regards

G. N. Rao.
Hyderabad, India
User avatar
nageswaragunupudi
 
Posts: 10656
Joined: Sun Nov 19, 2006 5:22 am
Location: India

Re: xbrowse MakeTotals()

Postby reinaldocrespo » Sat Mar 28, 2015 6:31 pm

Mr. Rao.

Thank you very much for your response and detailed explanation.

:bOnPostEdit := { |o,x,k| If( k == VK_ESCAPE, nil, ( ::oBrw:oTreeItem:Cargo[ _PAID ]:= x , ::ReCalculate() ) ) }


Notice the error occurs when clicking on the square box of the leaf cell -**Esc key is not being explicitly pressed**- and how with 2012 it works even w/out doing this check. I can, however, understand how some code will eventually fail if it is different or "faulty" from the beginning. I can perfectly understand how this is not xbrowse's fault. I apologize. Having said that, I think the action taken by xbrowse when clicking on the square box, should not cause a run time error even if bOnPostEdit is not checking for the esc key. Does this make sense?

My sample code above does not demonstrate problems with MakeTotals that the use of bSumCondition can't fix. I haven't been using makeTotals with Trees since I did not know about bSumCondition. I thought I did not have to demonstrate the problem with MakeTotals as I thought it was well known and was hoping for a quick fix. However, after reading your response I now realize the problem was on my use of bOnPostEdit which in turn made any sum() function fail when encountering Nil values.

Even though you argue that there is no change on MakeTotals, I still think there are since I had to add code to return cursor position to the current cell after calling MakeTotals. I did not have to do this with 2012.

This new/odd behavior is what mislead me into thinking all the problems were MakeTotals() related. I was wrong. I hope I don't have to demonstrate that the cursor does move from the current cell to the very last row after calling Maketotals() on fw 2014. Granted, this is not a huge problem and I found a quick fix for it so it is not my point on this post. Like I said -I was, obviously, mislead into thinking MakeTotals() was the culprit of -what we now know- was my bad use of bOnPostEdit.

Someone should write a guide on how to best use xbrowse as this class keeps getting more and more complex with the passing of time -which is a good thing and I wouldn't have it any other way.

Again, thank you very much for your help and I hope you continue to improve xbrowse.


Reinaldo.
User avatar
reinaldocrespo
 
Posts: 979
Joined: Thu Nov 17, 2005 5:49 pm
Location: Fort Lauderdale, FL

Re: xbrowse MakeTotals()

Postby nageswaragunupudi » Sat Mar 28, 2015 6:43 pm

Even though you argue that there is no change on MakeTotals, I still think there are since I had to add code to return cursor position to the current cell after calling MakeTotals.

I do not know which current version are you using.
You do not need to re-position after MakeTotals() in the current versions. Please re-check again and if you still have the problem please let us know your version. I do not think that it is current. If the version is older, let us check and provide fix if needed.

Sorry, I do not argue. I try my best to help and support.
Regards

G. N. Rao.
Hyderabad, India
User avatar
nageswaragunupudi
 
Posts: 10656
Joined: Sun Nov 19, 2006 5:22 am
Location: India


Return to FiveWin for Harbour/xHarbour

Who is online

Users browsing this forum: No registered users and 99 guests