FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin for Harbour/xHarbour Graph/Chart Scrollable
Posts: 866
Joined: Tue Oct 16, 2007 08:57 AM
Re: Graph/Chart Scrollable
Posted: Sat Jul 13, 2024 04:13 PM
Antonio Linares wrote:Dear Richard,

I have to check it with Mr. Rao

thanks for the reminder
Dear Antonio,

Any News?
Best Regards,



Richard



Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 32bit

MySQL v8.0

Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 64bit
Posts: 44162
Joined: Thu Oct 06, 2005 05:47 PM
Re: Graph/Chart Scrollable
Posted: Sat Jul 13, 2024 06:42 PM

Dear Richard,

Can you please provide me an example (PRG) of a non scrollable chart ?

I will implement it as scrollable.

We do apologize for the delay on this

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44162
Joined: Thu Oct 06, 2005 05:47 PM
Re: Graph/Chart Scrollable
Posted: Sat Jul 13, 2024 07:51 PM

got the example, working on it

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 866
Joined: Tue Oct 16, 2007 08:57 AM
Re: Graph/Chart Scrollable
Posted: Thu Aug 08, 2024 09:08 AM
Antonio Linares wrote:got the example, working on it
Any News?
Best Regards,



Richard



Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 32bit

MySQL v8.0

Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 64bit
Posts: 44162
Joined: Thu Oct 06, 2005 05:47 PM
Re: Graph/Chart Scrollable
Posted: Thu Aug 08, 2024 12:10 PM

Dear Richard,

We were considering to use a scrollpanel and use it to scroll the graph to each side

Would that be a good solution ?

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 866
Joined: Tue Oct 16, 2007 08:57 AM
Re: Graph/Chart Scrollable
Posted: Fri Aug 09, 2024 07:11 AM
Antonio Linares wrote:Dear Richard,

We were considering to use a scrollpanel and use it to scroll the graph to each side

Would that be a good solution ?
Sounds good.
Best Regards,



Richard



Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 32bit

MySQL v8.0

Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 64bit
Posts: 866
Joined: Tue Oct 16, 2007 08:57 AM
Re: Graph/Chart Scrollable
Posted: Sun Feb 09, 2025 07:32 PM
Antonio Linares wrote: Dear Richard,

We were considering to use a scrollpanel and use it to scroll the graph to each side

Would that be a good solution ?
any sample file for try?
Best Regards,



Richard



Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 32bit

MySQL v8.0

Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 64bit
Posts: 866
Joined: Tue Oct 16, 2007 08:57 AM
Re: Graph/Chart Scrollable
Posted: Wed Jul 02, 2025 05:25 AM
Antonio Linares wrote: Dear Richard,

We were considering to use a scrollpanel and use it to scroll the graph to each side

Would that be a good solution ?
Dear Antonio,

My clients can dynamically display chart changes based on date ranges
So ... Yes, it's good solution.
Best Regards,



Richard



Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 32bit

MySQL v8.0

Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 64bit
Posts: 44162
Joined: Thu Oct 06, 2005 05:47 PM
Re: Graph/Chart Scrollable
Posted: Wed Jul 02, 2025 06:33 AM

Dear Richard,

It seems to me as Silvio implemented it recently.

Going to check it with Silvio

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 866
Joined: Tue Oct 16, 2007 08:57 AM
Re: Graph/Chart Scrollable
Posted: Wed Jul 02, 2025 01:13 PM
Antonio Linares wrote: Dear Richard,

It seems to me as Silvio implemented it recently.

Going to check it with Silvio
Dear Antonio,

Ok. Waiting...
Best Regards,



Richard



Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 32bit

MySQL v8.0

Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 64bit
Posts: 44162
Joined: Thu Oct 06, 2005 05:47 PM
Re: Graph/Chart Scrollable
Posted: Wed Jul 02, 2025 04:22 PM
Dear Richard,

Using vscode + Copilot Agent and the techniques that we are going to review in the webinar, here you have a modified FWH Class TScrollPanel that supports horizontal scrolling:

scrlpanl.prg
/*
*
*  ScrlPanl.prg
*  Author: GNRao,India + enhancements by Copilot Agent
*  08-07-2009 08:48 PM
*
*/

#include "FiveWin.Ch"

#define GWL_STYLE             -16
#define GWL_EXSTYLE           -20
#define CS_DBLCLKS              8

#define MK_MBUTTON          0x0010
#define MK_SHIFT            0x0004

static lVScrolled := .f.
static lHScrolled := .f.

//------------------------------------------------------------------//

CLASS TScrollPanel FROM TPanel

   CLASSDATA lRegistered AS LOGICAL

   DATA aPages    INIT Array( 0 )

   DATA vStep     INIT 20
   DATA hStep     INIT 20

   DATA oCtrlTop
   DATA oCtrlBot
   DATA nCtrlTopTop
   DATA nCtrlBotTop

   // Horizontal scroll support
   DATA oCtrlLeft
   DATA oCtrlRight
   DATA nCtrlLeftLeft
   DATA nCtrlRightLeft

   // Margins to be left from right & bottom of Parent window when resized
   DATA nRightMargin    INIT 0
   DATA nBottomMargin   INIT 0

   DATA nScrollRange
   DATA nHScrollRange

   DATA aSays     INIT Array( 0 )

   // Scroll By Drag DATA
   DATA lScrollDrag     INIT .f.
   DATA nOldRow   INIT 0
   DATA nOldCol   INIT 0
   //

   METHOD New( nTop, nLeft, nBottom, nRight, oWnd, lNoBorder ) CONSTRUCTOR
   METHOD Resize( nSizeType, nWidth, nHeight )
   METHOD CheckResize()
   ACCESS nCtrlHt INLINE ( ::oCtrlBot:nTop + ::oCtrlBot:nHeight - ::oCtrlTop:nTop )
   ACCESS nCtrlWd INLINE ( ::oCtrlRight:nLeft + ::oCtrlRight:nWidth - ::oCtrlLeft:nLeft )
   ACCESS nPos INLINE Max(1, ( ::nCtrlTopTop - ::oCtrlTop:nTop ) )
   ACCESS nHPos INLINE Max(1, ( ::nCtrlLeftLeft - ::oCtrlLeft:nLeft ) )
   METHOD vScroll( nWParam, nLParam )
   METHOD hScroll( nWParam, nLParam )
   METHOD vSetPos()
   METHOD hSetPos()
   METHOD MouseWheel( nKeys, nDelta, nXPos, nYPos )

   METHOD GoTop()       INLINE ( ::ScrollBy( ::nCtrlTopTop - ::oCtrlTop:nTop, 0 ), ::oVScroll:SetPos( 1 ) )
   METHOD GoBottom()    INLINE ( ::ScrollBy( -::oCtrlBot:nTop, 0 ), ::oVScroll:SetPos( ::oVScroll:GetRange()[ 2 ] ) )
   METHOD GoLeft()      INLINE ( ::ScrollBy( 0, ::nCtrlLeftLeft - ::oCtrlLeft:nLeft ), ::oHScroll:SetPos( 1 ) )
   METHOD GoRight()     INLINE ( ::ScrollBy( 0, -::oCtrlRight:nLeft ), ::oHScroll:SetPos( ::oHScroll:GetRange()[ 2 ] ) )

   METHOD GoUp( nPix )
   METHOD GoDown( nPix )
   METHOD GoLeftPix( nPix )
   METHOD GoRightPix( nPix )
   METHOD GoToPos( nPos )
   METHOD GoToHPos( nPos )
   METHOD PageUp() INLINE ::GoUp( 2 * ::vStep )
   METHOD PageDown() INLINE ::GoDown( 2 * ::vStep )
   METHOD PageLeft() INLINE ::GoLeftPix( 2 * ::hStep )
   METHOD PageRight() INLINE ::GoRightPix( 2 * ::hStep )
   METHOD ControlIntoView( oControl )

   METHOD SetRange()
   METHOD ScrollBy( nPixV, nPixH )

   METHOD SetPage( cPage, nRow ) INLINE AAdd( ::aPages, { Upper( AllTrim( cPage ) ), nRow } )
   METHOD GoToPage( cPage )

   METHOD NcMouseMove( nHitTestCode, nRow, nCol )

   METHOD Display()  INLINE ( ::BeginPaint(), ::Paint(), ::EndPaint(), 0 )
   METHOD Paint()
   METHOD PaintText( nItem )
   METHOD AddText( nRow, nCol, nWidth, nHeight, cbText, cPicture, oFont, ;
            lTransparent, nClrText, nClrBack, nAlign )

//   METHOD GotFocus( hCtlLost )
//   METHOD GoNextCtrl( hCtrl )
//   METHOD GoPrevCtrl( hCtrl )

   // Scroll By Drag METHODS
   METHOD LButtonDown( nRow, nCol, nFlags, lTouch )  INLINE ( ::lScrollDrag := .t., ::nOldRow := nRow, ::nOldCol := nCol, ::Super:LButtonDown( nRow, nCol, nFlags, lTouch ) )
   METHOD LButtonUp( nRow, nCol, nFlags )    INLINE ( ::lScrollDrag := .f., ::nOldRow := 0, ::nOldCol := 0, ::Super:LButtonUp( nRow, nCol, nFlags ) )
   METHOD MouseMove( nRow, nCol, nFlags )    INLINE ( ;
         if( !::lDrag, ;
         ( If( lAnd( nFlags, 1 ), nil, ( ::lScrollDrag := .f., ::nOldRow := 0, ::nOldCol := 0 ) ), ;
           If( ::lScrollDrag, ( If( nRow > ::nOldRow, ::GoUp( nRow - ::nOldRow ), ::GoDown( ::nOldRow - nRow ) ), ;
                               If( nCol > ::nOldCol, ::GoLeftPix( nCol - ::nOldCol ), ::GoRightPix( ::nOldCol - nCol ) ), ;
                               ::nOldRow := nRow, ::nOldCol := nCol ), nil ) ), ), ;
         ::Super:MouseMove( nRow, nCol, nFlags ) )
   METHOD HandleGesture( nGesture, nLParam )
   //
   METHOD cGenPrg( lDlgUnits )

ENDCLASS

//------------------------------------------------------------------//

METHOD New( nTop, nLeft, nBottom, nRight, oWnd, lNoBorder ) CLASS TScrollPanel

//   ::oWnd:bMouseWheel  := { | nKeys, nDelta, nXPos, nYPos | ::MouseWheel( nKeys, nDelta, nXPos, nYPos ) }

   DEFAULT nTop := 0, nLeft := 0, nBottom := 100, nRight := 100,;
           oWnd := GetWndDefault(), lNoBorder := .f.

   ::lUnicode = FW_SetUnicode()
   ::nTop    = nTop
   ::nLeft   = nLeft
   ::nBottom = nBottom
   ::nRight  = nRight
   ::oWnd    = oWnd
   ::nStyle  = nOr(  WS_CHILD, WS_VSCROLL, WS_HSCROLL,;
                     WS_VISIBLE, WS_TABSTOP, WS_CLIPCHILDREN )
   ::lDrag   = .F.
   ::nClrPane = CLR_WHITE     //GetSysColor( COLOR_BTNFACE )

   if ! lNoBorder
      ::nStyle    := nOr( ::nStyle, WS_BORDER )
   endif

   ::Register( nOr( CS_VREDRAW, CS_HREDRAW, CS_DBLCLKS ) )

   if ! Empty( ::oWnd:hWnd )
      ::Create()
      ::oWnd:AddControl( Self )
      if ::oWnd:oBrush != nil
         ::SetBrush( ::oWnd:oBrush )
      endif
   else
      ::oWnd:DefControl( Self )
   endif

   DEFINE SCROLLBAR ::oVScroll VERTICAL OF Self RANGE 1,2
   DEFINE SCROLLBAR ::oHScroll HORIZONTAL OF Self RANGE 1,2

   ::oWnd:OnResize   := { || ::CheckResize() }

RETURN Self

//------------------------------------------------------------------//

METHOD ReSize( nSizeType, nWidth, nHeight ) CLASS TScrollPanel

   if !::lDrag
   ::vSetPos() // nHeight )
   endif

return ::Super:ReSize( nSizeType, nWidth, nHeight )

//------------------------------------------------------------------//

METHOD CheckResize() CLASS TScrollPanel

   local aRect    := GetClientRect( ::oWnd:hWnd )
   local nHeight, nWidth

   if ::nBottomMargin > 0 .or. ::nRightMargin > 0
      if ::oWnd:oBottom != nil
         aRect[ 3 ]  -= ::oWnd:oBottom:nHeight
      elseif ::oWnd:oMsgBar != nil
         aRect[ 3 ]  -= ::oWnd:oMsgBar:nHeight
      endif

      nHeight        := aRect[ 3 ] - aRect[ 1 ] - ::nTop  - ::nBottomMargin //- 28
      nWidth         := aRect[ 4 ] - aRect[ 2 ] - ::nLeft - ::nRightMargin

      if nHeight != ::nHeight .or. nWidth != ::nWidth
         if ::nBottomMargin > 0
            ::nHeight   := nHeight
         endif
         if ::nRightMargin > 0
            ::nWidth    := nWidth
         endif
      endif
   endif

return nil

//----------------------------------------------------------------------------//

METHOD SetRange() CLASS TScrollPanel

   local oTop, oBot, oLeft, oRight, oCtrl, nTop, nBot, nLeft, nRight

   if Len( ::aControls ) > 0

      oTop := oBot := oLeft := oRight := ::aControls[ 1 ]
      nTop  := oTop:nTop
      nBot  := oBot:nTop + oBot:nHeight
      nLeft := oLeft:nLeft
      nRight := oRight:nLeft + oRight:nWidth

      for each oCtrl in ::aControls
         if oCtrl:nTop < nTop
            oTop     := oCtrl
            nTop     := oTop:nTop
         endif
         if oCtrl:nTop + oCtrl:nHeight > nBot
            oBot     := oCtrl
            nBot     := oBot:nTop + oBot:nHeight
         endif
         if oCtrl:nLeft < nLeft
            oLeft    := oCtrl
            nLeft    := oLeft:nLeft
         endif
         if oCtrl:nLeft + oCtrl:nWidth > nRight
            oRight   := oCtrl
            nRight   := oRight:nLeft + oRight:nWidth
         endif
         if lAnd( GetWindowLong( oCtrl:hWnd, GWL_STYLE ), WS_TABSTOP ) .and. ;
            ! oCtrl:IsKindOf( "TCOMBOBOX" )
            oCtrl:bGotFocus   := { |o| If( lVScrolled,,::ControlIntoView( o )), lVScrolled := .f. }
         endif
      next

      ::oCtrlTop        := oTop
      ::oCtrlBot        := oBot
      ::oCtrlLeft       := oLeft
      ::oCtrlRight      := oRight
      ::nCtrlTopTop     := ::oCtrlTop:nTop
      ::nCtrlBotTop     := ::oCtrlBot:nTop
      ::nCtrlLeftLeft   := ::oCtrlLeft:nLeft
      ::nCtrlRightLeft  := ::oCtrlRight:nLeft
      ::nScrollRange    := ::nCtrlBotTop
      ::nHScrollRange   := ::nCtrlRightLeft
      ::oVScroll:SetRange( 1, ::nScrollRange )
      ::oHScroll:SetRange( 1, ::nHScrollRange )
      ::vSetPos()
      ::hSetPos()

   endif

return Self

//----------------------------------------------------------------------------//

METHOD vSetPos() CLASS TScrollPanel

   ::oVScroll:SetPos( Max( 1, ::nCtrlTopTop - ::oCtrlTop:nTop ) )

return Self

//----------------------------------------------------------------------------//

METHOD hSetPos() CLASS TScrollPanel

   ::oHScroll:SetPos( Max( 1, ::nCtrlLeftLeft - ::oCtrlLeft:nLeft ) )

return Self

//------------------------------------------------------------------//

METHOD ScrollBy( nPixV, nPixH ) CLASS TScrollPanel

   DEFAULT nPixV := 0, nPixH := 0

   if ! Empty( nPixV ) .or. ! Empty( nPixH )
      if ! Empty( ::aSays )
         if ! Empty( nPixV )
            AEval( ::aSays, { |a| a[ 1 ][ 1 ] += nPixV, a[ 1 ][ 3 ] += nPixV } )
         endif
         if ! Empty( nPixH )
            AEval( ::aSays, { |a| a[ 1 ][ 2 ] += nPixH, a[ 1 ][ 4 ] += nPixH } )
         endif
         ::Refresh()
      endif
      ScrollWindow( ::hWnd, nPixH, nPixV, 0, GetClientRect( ::hWnd ) )
   endif

return nil

//----------------------------------------------------------------------------//

METHOD GoUp( nPix ) CLASS TScrollPanel

   DEFAULT nPix    := ::vStep

   nPix := Min( nPix, ::nCtrlTopTop - ::oCtrlTop:nTop )

   if nPix > 0
      ::ScrollBy( nPix, 0 )
      ::oVScroll:SetPos( ::nPos )
   endif

return Self

//------------------------------------------------------------------//

METHOD GoDown( nPix ) CLASS TScrollPanel

   DEFAULT nPix   := ::vStep

   nPix     := Min( nPix, ::oCtrlBot:nTop )

   if nPix > 0
      ::ScrollBy( -nPix, 0 )
      ::oVScroll:SetPos( ::nPos )
   endif

return Self

//------------------------------------------------------------------//

METHOD GoLeftPix( nPix ) CLASS TScrollPanel

   DEFAULT nPix    := ::hStep

   nPix := Min( nPix, ::nCtrlLeftLeft - ::oCtrlLeft:nLeft )

   if nPix > 0
      ::ScrollBy( 0, nPix )
      ::oHScroll:SetPos( ::nHPos )
   endif

return Self

//------------------------------------------------------------------//

METHOD GoRightPix( nPix ) CLASS TScrollPanel

   DEFAULT nPix   := ::hStep

   nPix     := Min( nPix, ::oCtrlRight:nLeft )

   if nPix > 0
      ::ScrollBy( 0, -nPix )
      ::oHScroll:SetPos( ::nHPos )
   endif

return Self

//------------------------------------------------------------------//

METHOD GoToPos( nPos ) CLASS TScrollPanel

   local nPix

   nPix     := ::nCtrlTopTop - nPos - ::oCtrlTop:nTop

   if nPix != 0
      ::ScrollBy( nPix, 0 )
      ::oVScroll:SetPos( ::nPos )
   endif

return Self

//------------------------------------------------------------------//

METHOD GoToHPos( nPos ) CLASS TScrollPanel

   local nPix

   nPix     := ::nCtrlLeftLeft - nPos - ::oCtrlLeft:nLeft

   if nPix != 0
      ::ScrollBy( 0, nPix )
      ::oHScroll:SetPos( ::nHPos )
   endif

return Self

//------------------------------------------------------------------//

METHOD VScroll( nWParam, nLParam ) CLASS TScrollPanel

   local nScrHandle  := nLParam
   local nScrollCode := nLoWord( nWParam )
   local nPos        := nHiWord( nWParam )

   lVScrolled := .t.
   if GetFocus() != ::hWnd
      SetFocus( ::hWnd )
   endif

   if nScrHandle == 0 .and. ::oVScroll != nil
      do case
      case nScrollCode == SB_LINEUP
         ::GoUp()

      case nScrollCode == SB_LINEDOWN
         ::GoDown()

      case nScrollCode == SB_PAGEUP
         ::PageUp()

      case nScrollCode == SB_PAGEDOWN
         ::PageDown()

      case nScrollCode == SB_TOP
         ::GoTop()

      case nScrollCode == SB_BOTTOM
         ::GoBottom()

      case nScrollCode == SB_THUMBPOSITION .or. ;
           nScrollCode == SB_THUMBTRACK

         do case
            case nPos == 1
               ::GoTop()
            case nPos == ::oVScroll:GetRange()[ 2 ]
               ::GoBottom()
            otherwise
               ::GoToPos( nPos )
         endcase

      otherwise
         return nil
      endcase
      lVScrolled := .t.
   endif

return 0

//----------------------------------------------------------------------------//

METHOD HScroll( nWParam, nLParam ) CLASS TScrollPanel

   local nScrHandle  := nLParam
   local nScrollCode := nLoWord( nWParam )
   local nPos        := nHiWord( nWParam )

   lHScrolled := .t.
   if GetFocus() != ::hWnd
      SetFocus( ::hWnd )
   endif

   if nScrHandle == 0 .and. ::oHScroll != nil
      do case
      case nScrollCode == SB_LINELEFT
         ::GoLeftPix()

      case nScrollCode == SB_LINERIGHT
         ::GoRightPix()

      case nScrollCode == SB_PAGELEFT
         ::PageLeft()

      case nScrollCode == SB_PAGERIGHT
         ::PageRight()

      case nScrollCode == SB_LEFT
         ::GoLeft()

      case nScrollCode == SB_RIGHT
         ::GoRight()

      case nScrollCode == SB_THUMBPOSITION .or. ;
           nScrollCode == SB_THUMBTRACK

         do case
            case nPos == 1
               ::GoLeft()
            case nPos == ::oHScroll:GetRange()[ 2 ]
               ::GoRight()
            otherwise
               ::GoToHPos( nPos )
         endcase

      otherwise
         return nil
      endcase
      lHScrolled := .t.
   endif

return 0

//----------------------------------------------------------------------------//

METHOD MouseWheel( nKeys, nDelta, nXPos, nYPos ) CLASS TScrollPanel

   local aPoint := { nYPos, nXPos }

   if !::lDrag
      ScreenToClient( ::hWnd, aPoint )

      if IsOverWnd( ::hWnd, aPoint[ 1 ], aPoint[ 2 ] )

         if lAnd( nKeys, MK_MBUTTON )
            if nDelta > 0
               ::PageUp()
            else
               ::PageDown()
            endif
         elseif lAnd( nKeys, MK_SHIFT )  // Shift + Mouse wheel para scroll horizontal
            if nDelta > 0
               ::GoLeftPix( WheelScroll() * ::hStep )
            else
               ::GoRightPix( WheelScroll() * ::hStep )
            endif
         else
            if nDelta > 0
               ::GoUp( WheelScroll() * ::vStep )
            else
               ::GoDown( WheelScroll() * ::vStep )
            endif
         endif

      endif
   endif

Return nil

//----------------------------------------------------------------------------//

METHOD NcMouseMove( nHitTestCode, nRow, nCol ) CLASS TScrollPanel

   TWindow():NcMouseMove( nHitTestCode, nRow, nCol )

return nil

//----------------------------------------------------------------------------//

METHOD GoToPage( cPage ) CLASS TScrollPanel

   local lSuccess := .f.
   local nAt

   cPage    := Upper( AllTrim( cPage ) )
   nAt      := AScan( ::aPages, { |a| a[ 1 ] == cPage } )
   if nAt > 0
      ::GoToPos( ::aPages[ nAt ][ 2 ] )
      lSuccess := .t.
   endif

return lSuccess

//----------------------------------------------------------------------------//

METHOD ControlIntoView( oControl ) CLASS TScrollPanel

   local nCtrlBottom, nPanelBottom, nCtrlRight, nPanelRight
   local lMoved      := .f.

   // Verificar scroll vertical
   if oControl:nTop < 0
      ::GoUp( -oControl:nTop )
      lMoved         := .t.
   else
      nPanelBottom   := GetClientRect( ::hWnd )[ 3 ]
      nCtrlBottom    := oControl:nTop + oControl:nHeight
      if nCtrlBottom > nPanelBottom
         ::GoDown( nCtrlBottom - nPanelBottom )
         lMoved      := .t.
      endif
   endif

   // Verificar scroll horizontal
   if oControl:nLeft < 0
      ::GoLeftPix( -oControl:nLeft )
      lMoved         := .t.
   else
      nPanelRight    := GetClientRect( ::hWnd )[ 4 ]
      nCtrlRight     := oControl:nLeft + oControl:nWidth
      if nCtrlRight > nPanelRight
         ::GoRightPix( nCtrlRight - nPanelRight )
         lMoved      := .t.
      endif
   endif

return lMoved

//----------------------------------------------------------------------------//

METHOD Paint() CLASS TScrollPanel

   local aInfo, aRect

   aInfo    := ::DispBegin()
//   ::CheckResize()
   if ::lTransparent .or. ::nOpacity < 255
      aRect    := GetClientRect( ::hWnd )
      SetBrushOrgEx( ::hDC, -::nLeft, -::nTop )
      FillRect( ::hDC, aRect, ::oWnd:oBrush:hBrush )
      if ! ::lTransparent
         FillRectEx( ::hDC, aRect, nARGB( ::nOpacity, ::nClrPane ) )
      endif
   else
      ::PaintBack( ::hDC )
   endif

   ::PaintText()

   if ValType( ::bPainted ) == "B"
      Eval( ::bPainted, ::hDC, ::cPS, Self )
   endif

   ::DispEnd( aInfo )

return nil

//----------------------------------------------------------------------------//

METHOD AddText( nRow, nCol, nWidth, nHeight, cbText, cPicture, oFont, ;
                lTransparent, nClrText, nClrBack, nAlign ) CLASS TScrollPanel

   DEFAULT lTransparent := .t., nAlign := 0

   AAdd( ::aSays, { { nRow, nCol, nRow + nHeight, nCol + nWidth }, ;
               cbText, cPicture, oFont, lTransparent, nClrText, nClrBack, nAlign } )

return Len( ::aSays )

//----------------------------------------------------------------------------//

METHOD PaintText( nItem ) CLASS TScrollPanel

   local n, nLen
   local oFont, nColor, cText
   local aSay

   if nItem == nil
      nLen     := Len( ::aSays )
      for n := 1 to nLen
         ::PaintText( n )
      next n
      return nil
   endif

   aSay     := ::aSays[ nItem ]
   if aSay[ 1 ][ 3 ] < 0 .or. aSay[ 1 ][ 1 ] > ::nHeight
      return nil
   endif

   cText    := If( ValType( aSay[ 2 ] ) == 'B', Eval( aSay[ 2 ] ), aSay[ 2 ] )
   cText    := If( Empty( aSay[ 3 ] ), cValToChar( cText ), Transform( cText, aSay[ 3 ] ) )
   oFont    := If( Empty( aSay[ 4 ] ), ::oFont, aSay[ 4 ] )
   nColor   := If( Empty( aSay[ 6 ] ), ::nClrText, aSay[ 6 ] )

   oFont:Activate( ::hDC )
   SetTextColor( ::hDC, nColor )
   n  := SetBkMode( ::hDC, 1 ) // TRANSPARENT
   DrawTextEx( ::hDC, cText, aSay[ 1 ], aSay[ 8 ] )
   SetBkMode( ::hDC, n ) // restore bkmode
   oFont:DeActivate( ::hDC )

return nil

//----------------------------------------------------------------------------//

/*
METHOD GotFocus( hCtlLost ) CLASS TScrollPanel

   ::Super:GotFocus( hCtlLost )

   if ! Empty( ::hCtlFocus )
      SetFocus( ::hCtlFocus )
   else
      ::hCtlFocus = NextDlgTab( ::hWnd ) // ,::aControls[ 1 ]:hWnd
      SetFocus( ::hCtlFocus )
   endif

return nil

//----------------------------------------------------------------------------//

METHOD GoNextCtrl( hCtrl ) CLASS TScrollPanel

   local nAt, hNext
   local nNext := 0

   nAt := AScan( ::aControls, { | o | o:hWnd == hCtrl } )
   if nAt > 0
      hNext    := NextDlgTab( ::hWnd, hCtrl )
      nNext    := AScan( ::aControls, { |o| o:hWnd == hNext } )
      ::hCtlFocus := hCtrl
      if nNext < nAt .and. Len( ::oWnd:aControls ) > 1
         ::oWnd:GoNextCtrl( ::hWnd )
      else
         SetFocus( hNext )
      endif
   endif

return nil

//----------------------------------------------------------------------------//

METHOD GoPrevCtrl( hCtrl ) CLASS TScrollPanel

   local nAt, hPrev
   local nPrev := 0

   nAt := AScan( ::aControls, { | o | o:hWnd == hCtrl } )
   if nAt > 0
      hPrev    := NextDlgTab( ::hWnd, hCtrl, .t. )
      nPrev    := AScan( ::aControls, { |o| o:hWnd == hPrev } )
      ::hCtlFocus := hCtrl
      if ( nPrev == 0 .or. nPrev > nAt ) .and. Len( ::oWnd:aControls ) > 1
         ::oWnd:GoPrevCtrl( ::hWnd )
      else
         SetFocus( hPrev )
      endif
   endif

return nil

//----------------------------------------------------------------------------//
*/

/*
METHOD GoPrevCtrl( hCtrl ) CLASS TScrollPanel

   local hCtlPrev := NextDlgTab( ::hWnd, hCtrl, .t. )
   local oNext, n, cFoldName, nAt

   nAt = AScan( ::aControls, { | o | o:hWnd == hCtrl } )
   if nAt != 0
      nAt   := If( nAt > 1, nAt - 1, Len( ::aControls ) )
      do while !( oNext := ::aControls[ nAt ] ):WinStyle( WS_TABSTOP ) .or. !oNext:lWhen()
         nAt   := If( nAt > 1, nAt - 1, Len( ::aControls ) )
      enddo
      SetFocus( ::aControls[ nAt ]:hWnd )
      return nil
   endif

return nil

//----------------------------------------------------------------------------//
*/

METHOD HandleGesture( nGesture, nLParam ) CLASS TScrollPanel

   static nPrevRow   := 0

   local aInfo, nRow


   if nGesture == GID_PAN
      aInfo     := GESTUREINFO( nLParam )
      if aInfo[ 1 ] == GID_PAN
         nRow  := aInfo[ 3 ]
         if aInfo[ 2 ] == GF_BEGIN
            nPrevRow    := nRow
         else
            if nRow > nPrevRow
               ::GoUp( nRow - nPrevRow )
            else
               ::GoDown( nPrevRow - nRow )
            endif
            nPrevRow    := nRow
         endif
         return 0
      endif
   endif

return ::Super:HandleGesture( nGesture, nLParam )

//----------------------------------------------------------------------------//

METHOD cGenPrg( lDlgUnits ) CLASS TScrollPanel

   local cSource     := ""
   local nFactorX, nFactorY

   DEFAULT lDlgUnits := .T.

   if ::oWnd:IsKindOf( "TDIALOG" )
      lDlgUnits := .T.
   endif

   nFactorX = If( lDlgUnits, 4 / nLoWord( GetDlgBaseUnits() ), 1 )
   nFactorY = If( lDlgUnits, 8 / nHiWord( GetDlgBaseUnits() ), 1 )

   ::CoorsUpdate()

   cSource += CRLF + "   " + ::cVarName + " := " + ;
              "TScrollPanel():New( " + ;
              AllTrim( Str( ::nTop * nFactorX ) ) + ", " + ;
              AllTrim( Str( ::nLeft * nFactorY ) ) + ", " + ;
              AllTrim( Str( ::nBottom * nFactorX ) ) + ", " + ;
              AllTrim( Str( ::nRight * nFactorY ) ) + ", " + ;
              ::oWnd:cVarName + ", .F. ) " + CRLF
   cSource += "   "  + ::cVarName + ":SetRange()" + CRLF + CRLF

   if ! Empty( ::aControls )
      AEval( ::aControls, { | oCtrl | cSource += oCtrl:cGenPRG( lDlgUnits ) } )
   endif

return cSource

//----------------------------------------------------------------------------//
## Summary of Changes Made to TScrollPanel Class

### **Overview**
The TScrollPanel class has been enhanced from a vertical-only scrolling panel to a full **2D scrolling panel** that supports both horizontal and vertical scrolling capabilities.

### **New Data Members Added**

**Horizontal Scroll Support:**
- `hStep` - Horizontal scroll step size (default: 20 pixels)
- `oCtrlLeft`, `oCtrlRight` - References to leftmost and rightmost child controls
- `nCtrlLeftLeft`, `nCtrlRightLeft` - Horizontal positioning reference points
- `nHScrollRange` - Horizontal scroll range
- `nOldCol` - Column position for drag scrolling

**Static Variables:**
- `lHScrolled` - Flag to track horizontal scroll events
- `MK_SHIFT` constant (0x0004) - For detecting Shift key in mouse events

### **New Methods and Properties**

**Access Properties:**
- `nCtrlWd` - Total content width (rightmost control edge)
- `nHPos` - Current horizontal scroll position

**Horizontal Navigation Methods:**
- `hScroll()` - Handles horizontal scrollbar messages
- `hSetPos()` - Sets horizontal scrollbar position
- `GoLeft()`, `GoRight()` - Jump to horizontal start/end
- `GoLeftPix()`, `GoRightPix()` - Move horizontally by pixels
- `GoToHPos()` - Jump to specific horizontal position
- `PageLeft()`, `PageRight()` - Page-wise horizontal movement

### **Enhanced Existing Methods**

**ScrollBy() Method:**
- **Before**: `ScrollBy(nPix)` - vertical only
- **After**: `ScrollBy(nPixV, nPixH)` - supports both vertical and horizontal scrolling

**SetRange() Method:**
- Now calculates both vertical and horizontal content boundaries
- Identifies leftmost and rightmost controls
- Sets up both vertical and horizontal scrollbar ranges

**MouseWheel() Method:**
- **Standard wheel**: Vertical scrolling (unchanged)
- **Shift + wheel**: NEW - Horizontal scrolling
- **Middle button + wheel**: Page scrolling (unchanged)

**Mouse Drag Scrolling:**
- Enhanced to support **2D dragging** (both X and Y axes)
- Tracks both `nOldRow` and `nOldCol` for smooth 2D navigation

**ControlIntoView() Method:**
- Now ensures controls are visible both vertically AND horizontally
- Automatically scrolls in both directions when controls receive focus

### **Window Style Updates**
- Added `WS_HSCROLL` flag to window style
- Created horizontal scrollbar (`oHScroll`) alongside existing vertical scrollbar

### **Key Features Summary**

✅ **Full 2D Scrolling**: Both horizontal and vertical scroll support
✅ **Drag Scrolling**: Click and drag in any direction
✅ **Keyboard Navigation**: Shift + mouse wheel for horizontal scrolling
✅ **Auto-Navigation**: Controls automatically scroll into view when focused
✅ **Dual Scrollbars**: Independent horizontal and vertical scrollbars
✅ **Backward Compatibility**: All existing vertical-only functionality preserved

### **Usage Impact**
The enhanced TScrollPanel is now perfect for containing wide content like charts, large forms, or any control that exceeds the visible area in both dimensions. The graph example with 36 monthly bars can now scroll horizontally to view all data while maintaining vertical scrolling capability.

**Constructor remains the same**: `TScrollPanel():New(nTop, nLeft, nBottom, nRight, oWnd, lNoBorder)`
**Setup remains simple**: Just call `oScrollPanel:SetRange()` after adding child controls.
Here you have a working example:
graphscr.prg
#include "Fivewin.ch"
#include "TGraph.ch"

FUNCTION MAIN()

    LOCAL oDlg, oGraph, oScrPanel

    DEFINE WINDOW oDlg;
           SIZE 800, 600;
           TITLE "Ejemplo Gráfico de Barras con Scroll Horizontal"

    // Crear panel scrolleable usando TScrollPanel
    oScrPanel := TScrollPanel():New( 10, 10, 550, 780, oDlg, .F. )

    // Gráfico más ancho para acomodar todas las barras
    @ 10, 10 GRAPH oGraph OF oScrPanel;
           SIZE 1400, 350;
           TYPE GRAPH_TYPE_BAR;
           YVALUES XGRID YGRID XVALUES LEGENDS

    // Configuración de series
    oGraph:aSeries = { { "Ventas 2022-2024", CLR_BLUE, GRAPH_TYPE_BAR, .T. } }

    // Etiquetas del eje Y (meses y trimestres de varios años)
    oGraph:aYVals = { "Ene 2022", "Feb 2022", "Mar 2022", "Abr 2022", "May 2022", "Jun 2022",;
                      "Jul 2022", "Ago 2022", "Sep 2022", "Oct 2022", "Nov 2022", "Dic 2022",;
                      "Ene 2023", "Feb 2023", "Mar 2023", "Abr 2023", "May 2023", "Jun 2023",;
                      "Jul 2023", "Ago 2023", "Sep 2023", "Oct 2023", "Nov 2023", "Dic 2023",;
                      "Ene 2024", "Feb 2024", "Mar 2024", "Abr 2024", "May 2024", "Jun 2024",;
                      "Jul 2024", "Ago 2024", "Sep 2024", "Oct 2024", "Nov 2024", "Dic 2024" }

    // Datos del gráfico (valores de ventas de 3 años - 36 barras)
    oGraph:aData = { { 15000, 18500, 22000, 19500, 25000, 28000,;
                       24000, 26500, 23000, 21000, 19500, 32000,;
                       16000, 19000, 24000, 21500, 27000, 31000,;
                       26000, 28500, 25000, 23000, 21500, 35000,;
                       18000, 21000, 26000, 23500, 29000, 33000,;
                       28000, 30500, 27000, 25000, 23500, 38000 } }

    // Configuración adicional
    oGraph:nClrX = CLR_BLACK        // Color del eje X
    oGraph:nClrY = CLR_BLACK        // Color del eje Y
    oGraph:l3D = .T.                // Activar vista 3D
    oGraph:lViewSRLegend = .T.      // Mostrar leyenda

    // Tooltip personalizado
    oGraph:cToolTip = {|oGph,nSerie,nPos,nVal| ;
        oGph:aSeries[nSerie][1] + CRLF + ;
        oGraph:aYVals[nPos] + ": $" + Transform(nVal, "999,999")}

    // IMPORTANTE: Configurar el rango del scroll panel después de crear todos los controles
    oScrPanel:SetRange()

    ACTIVATE WINDOW oDlg;
             CENTER

   RETURN NIL
I do appreciate your tests and feedback :idea: :!:
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 866
Joined: Tue Oct 16, 2007 08:57 AM
Re: Graph/Chart Scrollable
Posted: Wed Jul 02, 2025 06:12 PM
Antonio Linares wrote: Dear Richard,

Using vscode + Copilot Agent and the techniques that we are going to review in the webinar, here you have a modified FWH Class TScrollPanel that supports horizontal scrolling:

scrlpanl.prg
/*
*
*  ScrlPanl.prg
*  Author: GNRao,India + enhancements by Copilot Agent
*  08-07-2009 08:48 PM
*
*/

#include "FiveWin.Ch"

#define GWL_STYLE             -16
#define GWL_EXSTYLE           -20
#define CS_DBLCLKS              8

#define MK_MBUTTON          0x0010
#define MK_SHIFT            0x0004

static lVScrolled := .f.
static lHScrolled := .f.

//------------------------------------------------------------------//

CLASS TScrollPanel FROM TPanel

   CLASSDATA lRegistered AS LOGICAL

   DATA aPages    INIT Array( 0 )

   DATA vStep     INIT 20
   DATA hStep     INIT 20

   DATA oCtrlTop
   DATA oCtrlBot
   DATA nCtrlTopTop
   DATA nCtrlBotTop

   // Horizontal scroll support
   DATA oCtrlLeft
   DATA oCtrlRight
   DATA nCtrlLeftLeft
   DATA nCtrlRightLeft

   // Margins to be left from right & bottom of Parent window when resized
   DATA nRightMargin    INIT 0
   DATA nBottomMargin   INIT 0

   DATA nScrollRange
   DATA nHScrollRange

   DATA aSays     INIT Array( 0 )

   // Scroll By Drag DATA
   DATA lScrollDrag     INIT .f.
   DATA nOldRow   INIT 0
   DATA nOldCol   INIT 0
   //

   METHOD New( nTop, nLeft, nBottom, nRight, oWnd, lNoBorder ) CONSTRUCTOR
   METHOD Resize( nSizeType, nWidth, nHeight )
   METHOD CheckResize()
   ACCESS nCtrlHt INLINE ( ::oCtrlBot:nTop + ::oCtrlBot:nHeight - ::oCtrlTop:nTop )
   ACCESS nCtrlWd INLINE ( ::oCtrlRight:nLeft + ::oCtrlRight:nWidth - ::oCtrlLeft:nLeft )
   ACCESS nPos INLINE Max(1, ( ::nCtrlTopTop - ::oCtrlTop:nTop ) )
   ACCESS nHPos INLINE Max(1, ( ::nCtrlLeftLeft - ::oCtrlLeft:nLeft ) )
   METHOD vScroll( nWParam, nLParam )
   METHOD hScroll( nWParam, nLParam )
   METHOD vSetPos()
   METHOD hSetPos()
   METHOD MouseWheel( nKeys, nDelta, nXPos, nYPos )

   METHOD GoTop()       INLINE ( ::ScrollBy( ::nCtrlTopTop - ::oCtrlTop:nTop, 0 ), ::oVScroll:SetPos( 1 ) )
   METHOD GoBottom()    INLINE ( ::ScrollBy( -::oCtrlBot:nTop, 0 ), ::oVScroll:SetPos( ::oVScroll:GetRange()[ 2 ] ) )
   METHOD GoLeft()      INLINE ( ::ScrollBy( 0, ::nCtrlLeftLeft - ::oCtrlLeft:nLeft ), ::oHScroll:SetPos( 1 ) )
   METHOD GoRight()     INLINE ( ::ScrollBy( 0, -::oCtrlRight:nLeft ), ::oHScroll:SetPos( ::oHScroll:GetRange()[ 2 ] ) )

   METHOD GoUp( nPix )
   METHOD GoDown( nPix )
   METHOD GoLeftPix( nPix )
   METHOD GoRightPix( nPix )
   METHOD GoToPos( nPos )
   METHOD GoToHPos( nPos )
   METHOD PageUp() INLINE ::GoUp( 2 * ::vStep )
   METHOD PageDown() INLINE ::GoDown( 2 * ::vStep )
   METHOD PageLeft() INLINE ::GoLeftPix( 2 * ::hStep )
   METHOD PageRight() INLINE ::GoRightPix( 2 * ::hStep )
   METHOD ControlIntoView( oControl )

   METHOD SetRange()
   METHOD ScrollBy( nPixV, nPixH )

   METHOD SetPage( cPage, nRow ) INLINE AAdd( ::aPages, { Upper( AllTrim( cPage ) ), nRow } )
   METHOD GoToPage( cPage )

   METHOD NcMouseMove( nHitTestCode, nRow, nCol )

   METHOD Display()  INLINE ( ::BeginPaint(), ::Paint(), ::EndPaint(), 0 )
   METHOD Paint()
   METHOD PaintText( nItem )
   METHOD AddText( nRow, nCol, nWidth, nHeight, cbText, cPicture, oFont, ;
            lTransparent, nClrText, nClrBack, nAlign )

//   METHOD GotFocus( hCtlLost )
//   METHOD GoNextCtrl( hCtrl )
//   METHOD GoPrevCtrl( hCtrl )

   // Scroll By Drag METHODS
   METHOD LButtonDown( nRow, nCol, nFlags, lTouch )  INLINE ( ::lScrollDrag := .t., ::nOldRow := nRow, ::nOldCol := nCol, ::Super:LButtonDown( nRow, nCol, nFlags, lTouch ) )
   METHOD LButtonUp( nRow, nCol, nFlags )    INLINE ( ::lScrollDrag := .f., ::nOldRow := 0, ::nOldCol := 0, ::Super:LButtonUp( nRow, nCol, nFlags ) )
   METHOD MouseMove( nRow, nCol, nFlags )    INLINE ( ;
         if( !::lDrag, ;
         ( If( lAnd( nFlags, 1 ), nil, ( ::lScrollDrag := .f., ::nOldRow := 0, ::nOldCol := 0 ) ), ;
           If( ::lScrollDrag, ( If( nRow > ::nOldRow, ::GoUp( nRow - ::nOldRow ), ::GoDown( ::nOldRow - nRow ) ), ;
                               If( nCol > ::nOldCol, ::GoLeftPix( nCol - ::nOldCol ), ::GoRightPix( ::nOldCol - nCol ) ), ;
                               ::nOldRow := nRow, ::nOldCol := nCol ), nil ) ), ), ;
         ::Super:MouseMove( nRow, nCol, nFlags ) )
   METHOD HandleGesture( nGesture, nLParam )
   //
   METHOD cGenPrg( lDlgUnits )

ENDCLASS

//------------------------------------------------------------------//

METHOD New( nTop, nLeft, nBottom, nRight, oWnd, lNoBorder ) CLASS TScrollPanel

//   ::oWnd:bMouseWheel  := { | nKeys, nDelta, nXPos, nYPos | ::MouseWheel( nKeys, nDelta, nXPos, nYPos ) }

   DEFAULT nTop := 0, nLeft := 0, nBottom := 100, nRight := 100,;
           oWnd := GetWndDefault(), lNoBorder := .f.

   ::lUnicode = FW_SetUnicode()
   ::nTop    = nTop
   ::nLeft   = nLeft
   ::nBottom = nBottom
   ::nRight  = nRight
   ::oWnd    = oWnd
   ::nStyle  = nOr(  WS_CHILD, WS_VSCROLL, WS_HSCROLL,;
                     WS_VISIBLE, WS_TABSTOP, WS_CLIPCHILDREN )
   ::lDrag   = .F.
   ::nClrPane = CLR_WHITE     //GetSysColor( COLOR_BTNFACE )

   if ! lNoBorder
      ::nStyle    := nOr( ::nStyle, WS_BORDER )
   endif

   ::Register( nOr( CS_VREDRAW, CS_HREDRAW, CS_DBLCLKS ) )

   if ! Empty( ::oWnd:hWnd )
      ::Create()
      ::oWnd:AddControl( Self )
      if ::oWnd:oBrush != nil
         ::SetBrush( ::oWnd:oBrush )
      endif
   else
      ::oWnd:DefControl( Self )
   endif

   DEFINE SCROLLBAR ::oVScroll VERTICAL OF Self RANGE 1,2
   DEFINE SCROLLBAR ::oHScroll HORIZONTAL OF Self RANGE 1,2

   ::oWnd:OnResize   := { || ::CheckResize() }

RETURN Self

//------------------------------------------------------------------//

METHOD ReSize( nSizeType, nWidth, nHeight ) CLASS TScrollPanel

   if !::lDrag
   ::vSetPos() // nHeight )
   endif

return ::Super:ReSize( nSizeType, nWidth, nHeight )

//------------------------------------------------------------------//

METHOD CheckResize() CLASS TScrollPanel

   local aRect    := GetClientRect( ::oWnd:hWnd )
   local nHeight, nWidth

   if ::nBottomMargin > 0 .or. ::nRightMargin > 0
      if ::oWnd:oBottom != nil
         aRect[ 3 ]  -= ::oWnd:oBottom:nHeight
      elseif ::oWnd:oMsgBar != nil
         aRect[ 3 ]  -= ::oWnd:oMsgBar:nHeight
      endif

      nHeight        := aRect[ 3 ] - aRect[ 1 ] - ::nTop  - ::nBottomMargin //- 28
      nWidth         := aRect[ 4 ] - aRect[ 2 ] - ::nLeft - ::nRightMargin

      if nHeight != ::nHeight .or. nWidth != ::nWidth
         if ::nBottomMargin > 0
            ::nHeight   := nHeight
         endif
         if ::nRightMargin > 0
            ::nWidth    := nWidth
         endif
      endif
   endif

return nil

//----------------------------------------------------------------------------//

METHOD SetRange() CLASS TScrollPanel

   local oTop, oBot, oLeft, oRight, oCtrl, nTop, nBot, nLeft, nRight

   if Len( ::aControls ) > 0

      oTop := oBot := oLeft := oRight := ::aControls[ 1 ]
      nTop  := oTop:nTop
      nBot  := oBot:nTop + oBot:nHeight
      nLeft := oLeft:nLeft
      nRight := oRight:nLeft + oRight:nWidth

      for each oCtrl in ::aControls
         if oCtrl:nTop < nTop
            oTop     := oCtrl
            nTop     := oTop:nTop
         endif
         if oCtrl:nTop + oCtrl:nHeight > nBot
            oBot     := oCtrl
            nBot     := oBot:nTop + oBot:nHeight
         endif
         if oCtrl:nLeft < nLeft
            oLeft    := oCtrl
            nLeft    := oLeft:nLeft
         endif
         if oCtrl:nLeft + oCtrl:nWidth > nRight
            oRight   := oCtrl
            nRight   := oRight:nLeft + oRight:nWidth
         endif
         if lAnd( GetWindowLong( oCtrl:hWnd, GWL_STYLE ), WS_TABSTOP ) .and. ;
            ! oCtrl:IsKindOf( "TCOMBOBOX" )
            oCtrl:bGotFocus   := { |o| If( lVScrolled,,::ControlIntoView( o )), lVScrolled := .f. }
         endif
      next

      ::oCtrlTop        := oTop
      ::oCtrlBot        := oBot
      ::oCtrlLeft       := oLeft
      ::oCtrlRight      := oRight
      ::nCtrlTopTop     := ::oCtrlTop:nTop
      ::nCtrlBotTop     := ::oCtrlBot:nTop
      ::nCtrlLeftLeft   := ::oCtrlLeft:nLeft
      ::nCtrlRightLeft  := ::oCtrlRight:nLeft
      ::nScrollRange    := ::nCtrlBotTop
      ::nHScrollRange   := ::nCtrlRightLeft
      ::oVScroll:SetRange( 1, ::nScrollRange )
      ::oHScroll:SetRange( 1, ::nHScrollRange )
      ::vSetPos()
      ::hSetPos()

   endif

return Self

//----------------------------------------------------------------------------//

METHOD vSetPos() CLASS TScrollPanel

   ::oVScroll:SetPos( Max( 1, ::nCtrlTopTop - ::oCtrlTop:nTop ) )

return Self

//----------------------------------------------------------------------------//

METHOD hSetPos() CLASS TScrollPanel

   ::oHScroll:SetPos( Max( 1, ::nCtrlLeftLeft - ::oCtrlLeft:nLeft ) )

return Self

//------------------------------------------------------------------//

METHOD ScrollBy( nPixV, nPixH ) CLASS TScrollPanel

   DEFAULT nPixV := 0, nPixH := 0

   if ! Empty( nPixV ) .or. ! Empty( nPixH )
      if ! Empty( ::aSays )
         if ! Empty( nPixV )
            AEval( ::aSays, { |a| a[ 1 ][ 1 ] += nPixV, a[ 1 ][ 3 ] += nPixV } )
         endif
         if ! Empty( nPixH )
            AEval( ::aSays, { |a| a[ 1 ][ 2 ] += nPixH, a[ 1 ][ 4 ] += nPixH } )
         endif
         ::Refresh()
      endif
      ScrollWindow( ::hWnd, nPixH, nPixV, 0, GetClientRect( ::hWnd ) )
   endif

return nil

//----------------------------------------------------------------------------//

METHOD GoUp( nPix ) CLASS TScrollPanel

   DEFAULT nPix    := ::vStep

   nPix := Min( nPix, ::nCtrlTopTop - ::oCtrlTop:nTop )

   if nPix > 0
      ::ScrollBy( nPix, 0 )
      ::oVScroll:SetPos( ::nPos )
   endif

return Self

//------------------------------------------------------------------//

METHOD GoDown( nPix ) CLASS TScrollPanel

   DEFAULT nPix   := ::vStep

   nPix     := Min( nPix, ::oCtrlBot:nTop )

   if nPix > 0
      ::ScrollBy( -nPix, 0 )
      ::oVScroll:SetPos( ::nPos )
   endif

return Self

//------------------------------------------------------------------//

METHOD GoLeftPix( nPix ) CLASS TScrollPanel

   DEFAULT nPix    := ::hStep

   nPix := Min( nPix, ::nCtrlLeftLeft - ::oCtrlLeft:nLeft )

   if nPix > 0
      ::ScrollBy( 0, nPix )
      ::oHScroll:SetPos( ::nHPos )
   endif

return Self

//------------------------------------------------------------------//

METHOD GoRightPix( nPix ) CLASS TScrollPanel

   DEFAULT nPix   := ::hStep

   nPix     := Min( nPix, ::oCtrlRight:nLeft )

   if nPix > 0
      ::ScrollBy( 0, -nPix )
      ::oHScroll:SetPos( ::nHPos )
   endif

return Self

//------------------------------------------------------------------//

METHOD GoToPos( nPos ) CLASS TScrollPanel

   local nPix

   nPix     := ::nCtrlTopTop - nPos - ::oCtrlTop:nTop

   if nPix != 0
      ::ScrollBy( nPix, 0 )
      ::oVScroll:SetPos( ::nPos )
   endif

return Self

//------------------------------------------------------------------//

METHOD GoToHPos( nPos ) CLASS TScrollPanel

   local nPix

   nPix     := ::nCtrlLeftLeft - nPos - ::oCtrlLeft:nLeft

   if nPix != 0
      ::ScrollBy( 0, nPix )
      ::oHScroll:SetPos( ::nHPos )
   endif

return Self

//------------------------------------------------------------------//

METHOD VScroll( nWParam, nLParam ) CLASS TScrollPanel

   local nScrHandle  := nLParam
   local nScrollCode := nLoWord( nWParam )
   local nPos        := nHiWord( nWParam )

   lVScrolled := .t.
   if GetFocus() != ::hWnd
      SetFocus( ::hWnd )
   endif

   if nScrHandle == 0 .and. ::oVScroll != nil
      do case
      case nScrollCode == SB_LINEUP
         ::GoUp()

      case nScrollCode == SB_LINEDOWN
         ::GoDown()

      case nScrollCode == SB_PAGEUP
         ::PageUp()

      case nScrollCode == SB_PAGEDOWN
         ::PageDown()

      case nScrollCode == SB_TOP
         ::GoTop()

      case nScrollCode == SB_BOTTOM
         ::GoBottom()

      case nScrollCode == SB_THUMBPOSITION .or. ;
           nScrollCode == SB_THUMBTRACK

         do case
            case nPos == 1
               ::GoTop()
            case nPos == ::oVScroll:GetRange()[ 2 ]
               ::GoBottom()
            otherwise
               ::GoToPos( nPos )
         endcase

      otherwise
         return nil
      endcase
      lVScrolled := .t.
   endif

return 0

//----------------------------------------------------------------------------//

METHOD HScroll( nWParam, nLParam ) CLASS TScrollPanel

   local nScrHandle  := nLParam
   local nScrollCode := nLoWord( nWParam )
   local nPos        := nHiWord( nWParam )

   lHScrolled := .t.
   if GetFocus() != ::hWnd
      SetFocus( ::hWnd )
   endif

   if nScrHandle == 0 .and. ::oHScroll != nil
      do case
      case nScrollCode == SB_LINELEFT
         ::GoLeftPix()

      case nScrollCode == SB_LINERIGHT
         ::GoRightPix()

      case nScrollCode == SB_PAGELEFT
         ::PageLeft()

      case nScrollCode == SB_PAGERIGHT
         ::PageRight()

      case nScrollCode == SB_LEFT
         ::GoLeft()

      case nScrollCode == SB_RIGHT
         ::GoRight()

      case nScrollCode == SB_THUMBPOSITION .or. ;
           nScrollCode == SB_THUMBTRACK

         do case
            case nPos == 1
               ::GoLeft()
            case nPos == ::oHScroll:GetRange()[ 2 ]
               ::GoRight()
            otherwise
               ::GoToHPos( nPos )
         endcase

      otherwise
         return nil
      endcase
      lHScrolled := .t.
   endif

return 0

//----------------------------------------------------------------------------//

METHOD MouseWheel( nKeys, nDelta, nXPos, nYPos ) CLASS TScrollPanel

   local aPoint := { nYPos, nXPos }

   if !::lDrag
      ScreenToClient( ::hWnd, aPoint )

      if IsOverWnd( ::hWnd, aPoint[ 1 ], aPoint[ 2 ] )

         if lAnd( nKeys, MK_MBUTTON )
            if nDelta > 0
               ::PageUp()
            else
               ::PageDown()
            endif
         elseif lAnd( nKeys, MK_SHIFT )  // Shift + Mouse wheel para scroll horizontal
            if nDelta > 0
               ::GoLeftPix( WheelScroll() * ::hStep )
            else
               ::GoRightPix( WheelScroll() * ::hStep )
            endif
         else
            if nDelta > 0
               ::GoUp( WheelScroll() * ::vStep )
            else
               ::GoDown( WheelScroll() * ::vStep )
            endif
         endif

      endif
   endif

Return nil

//----------------------------------------------------------------------------//

METHOD NcMouseMove( nHitTestCode, nRow, nCol ) CLASS TScrollPanel

   TWindow():NcMouseMove( nHitTestCode, nRow, nCol )

return nil

//----------------------------------------------------------------------------//

METHOD GoToPage( cPage ) CLASS TScrollPanel

   local lSuccess := .f.
   local nAt

   cPage    := Upper( AllTrim( cPage ) )
   nAt      := AScan( ::aPages, { |a| a[ 1 ] == cPage } )
   if nAt > 0
      ::GoToPos( ::aPages[ nAt ][ 2 ] )
      lSuccess := .t.
   endif

return lSuccess

//----------------------------------------------------------------------------//

METHOD ControlIntoView( oControl ) CLASS TScrollPanel

   local nCtrlBottom, nPanelBottom, nCtrlRight, nPanelRight
   local lMoved      := .f.

   // Verificar scroll vertical
   if oControl:nTop < 0
      ::GoUp( -oControl:nTop )
      lMoved         := .t.
   else
      nPanelBottom   := GetClientRect( ::hWnd )[ 3 ]
      nCtrlBottom    := oControl:nTop + oControl:nHeight
      if nCtrlBottom > nPanelBottom
         ::GoDown( nCtrlBottom - nPanelBottom )
         lMoved      := .t.
      endif
   endif

   // Verificar scroll horizontal
   if oControl:nLeft < 0
      ::GoLeftPix( -oControl:nLeft )
      lMoved         := .t.
   else
      nPanelRight    := GetClientRect( ::hWnd )[ 4 ]
      nCtrlRight     := oControl:nLeft + oControl:nWidth
      if nCtrlRight > nPanelRight
         ::GoRightPix( nCtrlRight - nPanelRight )
         lMoved      := .t.
      endif
   endif

return lMoved

//----------------------------------------------------------------------------//

METHOD Paint() CLASS TScrollPanel

   local aInfo, aRect

   aInfo    := ::DispBegin()
//   ::CheckResize()
   if ::lTransparent .or. ::nOpacity < 255
      aRect    := GetClientRect( ::hWnd )
      SetBrushOrgEx( ::hDC, -::nLeft, -::nTop )
      FillRect( ::hDC, aRect, ::oWnd:oBrush:hBrush )
      if ! ::lTransparent
         FillRectEx( ::hDC, aRect, nARGB( ::nOpacity, ::nClrPane ) )
      endif
   else
      ::PaintBack( ::hDC )
   endif

   ::PaintText()

   if ValType( ::bPainted ) == "B"
      Eval( ::bPainted, ::hDC, ::cPS, Self )
   endif

   ::DispEnd( aInfo )

return nil

//----------------------------------------------------------------------------//

METHOD AddText( nRow, nCol, nWidth, nHeight, cbText, cPicture, oFont, ;
                lTransparent, nClrText, nClrBack, nAlign ) CLASS TScrollPanel

   DEFAULT lTransparent := .t., nAlign := 0

   AAdd( ::aSays, { { nRow, nCol, nRow + nHeight, nCol + nWidth }, ;
               cbText, cPicture, oFont, lTransparent, nClrText, nClrBack, nAlign } )

return Len( ::aSays )

//----------------------------------------------------------------------------//

METHOD PaintText( nItem ) CLASS TScrollPanel

   local n, nLen
   local oFont, nColor, cText
   local aSay

   if nItem == nil
      nLen     := Len( ::aSays )
      for n := 1 to nLen
         ::PaintText( n )
      next n
      return nil
   endif

   aSay     := ::aSays[ nItem ]
   if aSay[ 1 ][ 3 ] < 0 .or. aSay[ 1 ][ 1 ] > ::nHeight
      return nil
   endif

   cText    := If( ValType( aSay[ 2 ] ) == 'B', Eval( aSay[ 2 ] ), aSay[ 2 ] )
   cText    := If( Empty( aSay[ 3 ] ), cValToChar( cText ), Transform( cText, aSay[ 3 ] ) )
   oFont    := If( Empty( aSay[ 4 ] ), ::oFont, aSay[ 4 ] )
   nColor   := If( Empty( aSay[ 6 ] ), ::nClrText, aSay[ 6 ] )

   oFont:Activate( ::hDC )
   SetTextColor( ::hDC, nColor )
   n  := SetBkMode( ::hDC, 1 ) // TRANSPARENT
   DrawTextEx( ::hDC, cText, aSay[ 1 ], aSay[ 8 ] )
   SetBkMode( ::hDC, n ) // restore bkmode
   oFont:DeActivate( ::hDC )

return nil

//----------------------------------------------------------------------------//

/*
METHOD GotFocus( hCtlLost ) CLASS TScrollPanel

   ::Super:GotFocus( hCtlLost )

   if ! Empty( ::hCtlFocus )
      SetFocus( ::hCtlFocus )
   else
      ::hCtlFocus = NextDlgTab( ::hWnd ) // ,::aControls[ 1 ]:hWnd
      SetFocus( ::hCtlFocus )
   endif

return nil

//----------------------------------------------------------------------------//

METHOD GoNextCtrl( hCtrl ) CLASS TScrollPanel

   local nAt, hNext
   local nNext := 0

   nAt := AScan( ::aControls, { | o | o:hWnd == hCtrl } )
   if nAt > 0
      hNext    := NextDlgTab( ::hWnd, hCtrl )
      nNext    := AScan( ::aControls, { |o| o:hWnd == hNext } )
      ::hCtlFocus := hCtrl
      if nNext < nAt .and. Len( ::oWnd:aControls ) > 1
         ::oWnd:GoNextCtrl( ::hWnd )
      else
         SetFocus( hNext )
      endif
   endif

return nil

//----------------------------------------------------------------------------//

METHOD GoPrevCtrl( hCtrl ) CLASS TScrollPanel

   local nAt, hPrev
   local nPrev := 0

   nAt := AScan( ::aControls, { | o | o:hWnd == hCtrl } )
   if nAt > 0
      hPrev    := NextDlgTab( ::hWnd, hCtrl, .t. )
      nPrev    := AScan( ::aControls, { |o| o:hWnd == hPrev } )
      ::hCtlFocus := hCtrl
      if ( nPrev == 0 .or. nPrev > nAt ) .and. Len( ::oWnd:aControls ) > 1
         ::oWnd:GoPrevCtrl( ::hWnd )
      else
         SetFocus( hPrev )
      endif
   endif

return nil

//----------------------------------------------------------------------------//
*/

/*
METHOD GoPrevCtrl( hCtrl ) CLASS TScrollPanel

   local hCtlPrev := NextDlgTab( ::hWnd, hCtrl, .t. )
   local oNext, n, cFoldName, nAt

   nAt = AScan( ::aControls, { | o | o:hWnd == hCtrl } )
   if nAt != 0
      nAt   := If( nAt > 1, nAt - 1, Len( ::aControls ) )
      do while !( oNext := ::aControls[ nAt ] ):WinStyle( WS_TABSTOP ) .or. !oNext:lWhen()
         nAt   := If( nAt > 1, nAt - 1, Len( ::aControls ) )
      enddo
      SetFocus( ::aControls[ nAt ]:hWnd )
      return nil
   endif

return nil

//----------------------------------------------------------------------------//
*/

METHOD HandleGesture( nGesture, nLParam ) CLASS TScrollPanel

   static nPrevRow   := 0

   local aInfo, nRow


   if nGesture == GID_PAN
      aInfo     := GESTUREINFO( nLParam )
      if aInfo[ 1 ] == GID_PAN
         nRow  := aInfo[ 3 ]
         if aInfo[ 2 ] == GF_BEGIN
            nPrevRow    := nRow
         else
            if nRow > nPrevRow
               ::GoUp( nRow - nPrevRow )
            else
               ::GoDown( nPrevRow - nRow )
            endif
            nPrevRow    := nRow
         endif
         return 0
      endif
   endif

return ::Super:HandleGesture( nGesture, nLParam )

//----------------------------------------------------------------------------//

METHOD cGenPrg( lDlgUnits ) CLASS TScrollPanel

   local cSource     := ""
   local nFactorX, nFactorY

   DEFAULT lDlgUnits := .T.

   if ::oWnd:IsKindOf( "TDIALOG" )
      lDlgUnits := .T.
   endif

   nFactorX = If( lDlgUnits, 4 / nLoWord( GetDlgBaseUnits() ), 1 )
   nFactorY = If( lDlgUnits, 8 / nHiWord( GetDlgBaseUnits() ), 1 )

   ::CoorsUpdate()

   cSource += CRLF + "   " + ::cVarName + " := " + ;
              "TScrollPanel():New( " + ;
              AllTrim( Str( ::nTop * nFactorX ) ) + ", " + ;
              AllTrim( Str( ::nLeft * nFactorY ) ) + ", " + ;
              AllTrim( Str( ::nBottom * nFactorX ) ) + ", " + ;
              AllTrim( Str( ::nRight * nFactorY ) ) + ", " + ;
              ::oWnd:cVarName + ", .F. ) " + CRLF
   cSource += "   "  + ::cVarName + ":SetRange()" + CRLF + CRLF

   if ! Empty( ::aControls )
      AEval( ::aControls, { | oCtrl | cSource += oCtrl:cGenPRG( lDlgUnits ) } )
   endif

return cSource

//----------------------------------------------------------------------------//
## Summary of Changes Made to TScrollPanel Class

### **Overview**
The TScrollPanel class has been enhanced from a vertical-only scrolling panel to a full **2D scrolling panel** that supports both horizontal and vertical scrolling capabilities.

### **New Data Members Added**

**Horizontal Scroll Support:**
- `hStep` - Horizontal scroll step size (default: 20 pixels)
- `oCtrlLeft`, `oCtrlRight` - References to leftmost and rightmost child controls
- `nCtrlLeftLeft`, `nCtrlRightLeft` - Horizontal positioning reference points
- `nHScrollRange` - Horizontal scroll range
- `nOldCol` - Column position for drag scrolling

**Static Variables:**
- `lHScrolled` - Flag to track horizontal scroll events
- `MK_SHIFT` constant (0x0004) - For detecting Shift key in mouse events

### **New Methods and Properties**

**Access Properties:**
- `nCtrlWd` - Total content width (rightmost control edge)
- `nHPos` - Current horizontal scroll position

**Horizontal Navigation Methods:**
- `hScroll()` - Handles horizontal scrollbar messages
- `hSetPos()` - Sets horizontal scrollbar position
- `GoLeft()`, `GoRight()` - Jump to horizontal start/end
- `GoLeftPix()`, `GoRightPix()` - Move horizontally by pixels
- `GoToHPos()` - Jump to specific horizontal position
- `PageLeft()`, `PageRight()` - Page-wise horizontal movement

### **Enhanced Existing Methods**

**ScrollBy() Method:**
- **Before**: `ScrollBy(nPix)` - vertical only
- **After**: `ScrollBy(nPixV, nPixH)` - supports both vertical and horizontal scrolling

**SetRange() Method:**
- Now calculates both vertical and horizontal content boundaries
- Identifies leftmost and rightmost controls
- Sets up both vertical and horizontal scrollbar ranges

**MouseWheel() Method:**
- **Standard wheel**: Vertical scrolling (unchanged)
- **Shift + wheel**: NEW - Horizontal scrolling
- **Middle button + wheel**: Page scrolling (unchanged)

**Mouse Drag Scrolling:**
- Enhanced to support **2D dragging** (both X and Y axes)
- Tracks both `nOldRow` and `nOldCol` for smooth 2D navigation

**ControlIntoView() Method:**
- Now ensures controls are visible both vertically AND horizontally
- Automatically scrolls in both directions when controls receive focus

### **Window Style Updates**
- Added `WS_HSCROLL` flag to window style
- Created horizontal scrollbar (`oHScroll`) alongside existing vertical scrollbar

### **Key Features Summary**

✅ **Full 2D Scrolling**: Both horizontal and vertical scroll support
✅ **Drag Scrolling**: Click and drag in any direction
✅ **Keyboard Navigation**: Shift + mouse wheel for horizontal scrolling
✅ **Auto-Navigation**: Controls automatically scroll into view when focused
✅ **Dual Scrollbars**: Independent horizontal and vertical scrollbars
✅ **Backward Compatibility**: All existing vertical-only functionality preserved

### **Usage Impact**
The enhanced TScrollPanel is now perfect for containing wide content like charts, large forms, or any control that exceeds the visible area in both dimensions. The graph example with 36 monthly bars can now scroll horizontally to view all data while maintaining vertical scrolling capability.

**Constructor remains the same**: `TScrollPanel():New(nTop, nLeft, nBottom, nRight, oWnd, lNoBorder)`
**Setup remains simple**: Just call `oScrollPanel:SetRange()` after adding child controls.
Here you have a working example:
graphscr.prg
#include "Fivewin.ch"
#include "TGraph.ch"

FUNCTION MAIN()

    LOCAL oDlg, oGraph, oScrPanel

    DEFINE WINDOW oDlg;
           SIZE 800, 600;
           TITLE "Ejemplo Gráfico de Barras con Scroll Horizontal"

    // Crear panel scrolleable usando TScrollPanel
    oScrPanel := TScrollPanel():New( 10, 10, 550, 780, oDlg, .F. )

    // Gráfico más ancho para acomodar todas las barras
    @ 10, 10 GRAPH oGraph OF oScrPanel;
           SIZE 1400, 350;
           TYPE GRAPH_TYPE_BAR;
           YVALUES XGRID YGRID XVALUES LEGENDS

    // Configuración de series
    oGraph:aSeries = { { "Ventas 2022-2024", CLR_BLUE, GRAPH_TYPE_BAR, .T. } }

    // Etiquetas del eje Y (meses y trimestres de varios años)
    oGraph:aYVals = { "Ene 2022", "Feb 2022", "Mar 2022", "Abr 2022", "May 2022", "Jun 2022",;
                      "Jul 2022", "Ago 2022", "Sep 2022", "Oct 2022", "Nov 2022", "Dic 2022",;
                      "Ene 2023", "Feb 2023", "Mar 2023", "Abr 2023", "May 2023", "Jun 2023",;
                      "Jul 2023", "Ago 2023", "Sep 2023", "Oct 2023", "Nov 2023", "Dic 2023",;
                      "Ene 2024", "Feb 2024", "Mar 2024", "Abr 2024", "May 2024", "Jun 2024",;
                      "Jul 2024", "Ago 2024", "Sep 2024", "Oct 2024", "Nov 2024", "Dic 2024" }

    // Datos del gráfico (valores de ventas de 3 años - 36 barras)
    oGraph:aData = { { 15000, 18500, 22000, 19500, 25000, 28000,;
                       24000, 26500, 23000, 21000, 19500, 32000,;
                       16000, 19000, 24000, 21500, 27000, 31000,;
                       26000, 28500, 25000, 23000, 21500, 35000,;
                       18000, 21000, 26000, 23500, 29000, 33000,;
                       28000, 30500, 27000, 25000, 23500, 38000 } }

    // Configuración adicional
    oGraph:nClrX = CLR_BLACK        // Color del eje X
    oGraph:nClrY = CLR_BLACK        // Color del eje Y
    oGraph:l3D = .T.                // Activar vista 3D
    oGraph:lViewSRLegend = .T.      // Mostrar leyenda

    // Tooltip personalizado
    oGraph:cToolTip = {|oGph,nSerie,nPos,nVal| ;
        oGph:aSeries[nSerie][1] + CRLF + ;
        oGraph:aYVals[nPos] + ": $" + Transform(nVal, "999,999")}

    // IMPORTANTE: Configurar el rango del scroll panel después de crear todos los controles
    oScrPanel:SetRange()

    ACTIVATE WINDOW oDlg;
             CENTER

   RETURN NIL
I do appreciate your tests and feedback :idea: :!:
Dear Antonio,

I test it and show it below:
Best Regards,



Richard



Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 32bit

MySQL v8.0

Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 64bit
Posts: 44162
Joined: Thu Oct 06, 2005 05:47 PM
Re: Graph/Chart Scrollable
Posted: Wed Jul 02, 2025 06:37 PM

Yes, right.

There you have the horizontal scrolling effect.

Is that what you were looking for ?

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 866
Joined: Tue Oct 16, 2007 08:57 AM
Re: Graph/Chart Scrollable
Posted: Thu Jul 03, 2025 01:50 AM
Antonio Linares wrote: Yes, right.

There you have the horizontal scrolling effect.

Is that what you were looking for ?
Dear Antonio,

No. This is not what I want.
I want to Dynamic Chart with Form Control Scrollbar.
Look it below:
Best Regards,



Richard



Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 32bit

MySQL v8.0

Harbour 3.2.0dev (r2503251254) => Borland C++ v7.7 64bit
Posts: 44162
Joined: Thu Oct 06, 2005 05:47 PM
Re: Graph/Chart Scrollable
Posted: Thu Jul 03, 2025 04:21 AM

Dear Richard,

It seems as what you are looking for can't be achieved with a TScollPanel object and we need to implement horizontal scrolling capabilites into Class TGraph itself.

Working into this

regards, saludos

Antonio Linares
www.fivetechsoft.com