Virtual Grids can view large text files?

General Help regarding HMG, Compilation, Linking, Samples

Moderator: Rathinagiri

edk
Posts: 999
Joined: Thu Oct 16, 2014 11:35 am
Location: Poland

Re: Virtual Grids can view large text files?

Post by edk »

HGAutomator wrote: Fri Dec 10, 2021 7:11 pm The scope problem, as I understand it, is that oFile is in the scope of QueryFile() and OpenFile(). But the buttons don't share that scope.
Notice what the OpenFile function returns.
The oFile object is returned, where in the main form it is assigned to oGridFile, i.e. when you refer to the oGridFile object in the main form, you are actually referring to the original oFile object declared in the OpenFile function. Note that oFile is of type Local in this function, therefore this object is passed to other functions as a parameter.
In the main form, try to reference the oGridFile object.

You could change the type of oFile from Local to Public and then you wouldn't have to pass it as a parameter.

Personally, I try to avoid public variables and that's why I have this style of code writing. Perhaps it is not very understandable for some, for which I sincerely apologize. Therefore, I tried to describe the individual stages in the code, but my English is not very good, so not everything is understood correctly.
HGAutomator
Posts: 202
Joined: Thu Jul 16, 2020 5:42 pm
DBs Used: DBF

Re: Virtual Grids can view large text files?

Post by HGAutomator »

edk,

You clarified the situation in the previous post. I had replied to franco, before seeing that.

Declaring oFile as Public or Static isn't necessary, because as you explained the proper object to mess with is the grid itself.

I'll test your example over the weekend, but I'm sure that's the right approach.

Thanks for your generous assistance, have a good weekend.


edk wrote: Fri Dec 10, 2021 8:12 pm
HGAutomator wrote: Fri Dec 10, 2021 7:11 pm The scope problem, as I understand it, is that oFile is in the scope of QueryFile() and OpenFile(). But the buttons don't share that scope.
Notice what the OpenFile function returns.
The oFile object is returned, where in the main form it is assigned to oGridFile, i.e. when you refer to the oGridFile object in the main form, you are actually referring to the original oFile object declared in the OpenFile function. Note that oFile is of type Local in this function, therefore this object is passed to other functions as a parameter.
In the main form, try to reference the oGridFile object.

You could change the type of oFile from Local to Public and then you wouldn't have to pass it as a parameter.

Personally, I try to avoid public variables and that's why I have this style of code writing. Perhaps it is not very understandable for some, for which I sincerely apologize. Therefore, I tried to describe the individual stages in the code, but my English is not very good, so not everything is understood correctly.
HGAutomator
Posts: 202
Joined: Thu Jul 16, 2020 5:42 pm
DBs Used: DBF

Re: Virtual Grids can view large text files?

Post by HGAutomator »

edk,

Can't thank you enough for assisting with this. I'll probably make a few more changes with e.g. the line number column and few other tweaks. But for now, here's my version.

Compile line is

Code: Select all

call ..\..\batch\compile.bat TbDelimit /LE c:\minigui\Harbour\lib\hbnf  %1 %2 %3 %4 %5 %6 %7 %8 %9

Code: Select all

#include "hmg.ch"
#Include "Fileio.CH"
#include "hbthread.ch"


#DEFINE FS_BEGINNING 0							// From Beginning of file
#DEFINE FS_RELATIVE 1								// From Current Pointer
#DEFINE FS_END 2										// From End of File


Function Main
Local cFileToOpen := ""


Local cDelimiter  := CHR ( 9 )
Local oGridFile
Local pSeekThread, pSeekMethodThread

DEFINE WINDOW Form_1 ;
	AT 0,0 ;
	WIDTH 1700 ;
	HEIGHT 1000 ;
	TITLE 'Tab-delimited text file viewer' ;
	MAIN ;
	ON INIT DoNothing()


	DEFINE MAIN MENU
		DEFINE POPUP 'File'
			MENUITEM 'Select Tab Delimited file)' ACTION ( cFileToOpen := Getfile ( { {'TSV Files','*.tsv'} } , 'Open TSV File' , GetCurrentFolder() , .f. , .f. ), oGridFile := OpenFile( cFileToOpen, cDelimiter, 1 ) )
				SEPARATOR
				ITEM 'Exit' ACTION ThisWindow.Release
		END POPUP
	END MENU


	@10, 10 TEXTBOX tb_goto WIDTH 100 VALUE 0 NUMERIC
	@10, 110 BUTTON bGoto CAPTION "Go to line" ACTION Form_1.Grid_1.Value := Form_1.tb_goto.Value

	@10, 315 TEXTBOX tb_Seek WIDTH 200 VALUE ""
	@10, 520 BUTTON bSeekMethod CAPTION "Search" ACTION MT_SeekLine( oGridFile )

	@10, 725 TEXTBOX tb_record WIDTH 100 VALUE 1 NUMERIC
	@10, 830 BUTTON bRecord CAPTION "Read Line" ACTION ( oGridFile:GoTo( Form_1.tb_record.Value + 1 /* "+ 1" first line is the header */ ), MsgBox( oGridFile:ReadLine() ) )


	Form_1.tb_goto.Visible := .F.
	Form_1.bGoto.Visible := .F.

	Form_1.tb_record.Visible := .F.
	Form_1.bRecord.Visible := .F.
	
	Form_1.tb_Seek.Visible := .F.
	Form_1.bSeekMethod.Visible := .F.


    
	DEFINE STATUSBAR
		STATUSITEM "" WIDTH Form_1.Width / 2
		STATUSITEM "" WIDTH Form_1.Width / 2 
	END STATUSBAR

	ShowProgressBar( 1, , , (Form_1.Width / 2) + 5 , Form_1.Width / 2 - 30 )		//init progress bar
	ShowProgressBar( 0 )												//hide progress bar
		
END WINDOW

ACTIVATE WINDOW Form_1

Return Nil
***************************************************


***************************************************
Function QueryFile( oFile, cDelimiter )
Local nRecord          := This.QueryRowIndex
Local nCol             := This.QueryColIndex
Local nListRows        := LISTVIEWGETCOUNTPERPAGE ( Form_1.Grid_1.Handle )	//Number of visible lines in the grid
Local nItemCount       := Form_1.Grid_1.ItemCount
Local nIncrementalRead := 1000		//nListRows * 2 
Local aMemAlloc        := {}

Static aFields         := {}

// xVar := This

IF oFile == Nil
	Return Nil
ENDIF



IF oFile:CurrentLine() <> nRecord + 1 /* "+ 1" first line is the header */ .OR. Len ( aFields ) == 0		//Prevents the current row from being reread when query to display the next column (speeding up)
	oFile:GoTo (  nRecord + 1 /* "+ 1" first line is the header */ )
	aFields   := hb_ATokens ( oFile:ReadLine(), cDelimiter )
	AAdd( aFields, "" )
	AAdd( aFields, ""   )
	AIns( aFields, 1 )
	aFields[ 1 ] := This.QueryRowIndex
ENDIF

This.QueryData := IF ( Len ( aFields ) < nCol , "", aFields [ nCol ] )

Return Nil

**************************************************************
Function OpenFile( cFileToOpen, cDelimiter, nMode )

Local aColumns := {}
Local aHeaders := {}
Local aWidths := {}
Local i
Local oFile
Local pMutexCount, nListRows
Default nMode := 0


IF File ( cFileToOpen )

		oFile := vfFileRead():New( cFileToOpen, , , )
		oFile:Open()

		IF oFile:Error()
			MsgStop ("Error " + Str( oFile:ErrorNo() ), "Error" )
			RETURN Nil
		ENDIF
		
	IF IsControlDefined ( Grid_1, Form_1 )
  		Form_1.Grid_1.Release
  	ENDIF


	//Preparing columns (based on the first row from the file)
	aColumns := hb_ATokens ( oFile:ReadLine(), cDelimiter )
	AAdd( aColumns, "Null"   )
	AIns( aColumns, 1 )
	aColumns[ 1 ] := "LineNumber"

	For i := 1 TO Len( aColumns )
		AAdd( aHeaders, aColumns [i] )
		AAdd( aWidths, Max(20, Len( aColumns [ i ] ) * 19 ) )
	Next i
	
	//Calculate the number of rows before displaying the Grid
		WAIT WINDOW "Counting the number of records ..."  NOWAIT
		ShowProgressBar( 2 )		//show progress bar
		Form_1.StatusBar.Item(1) := "Counting the number of records ... This may take some time .... "
	
		//code block executed while the CountLines() method is running
		oFile:exGauge := { | nPos, nLastPos, nLines, cState | ( Form_1.StatusBar.Item(1) := "Counting the number of records: " + AllTrim ( Str ( nLines ) ) + "... This may take some time ... " + cState, Form_1.PBar.Value := nPos / nLastPos * 100, doEvents() ) }
		oFile:CountLines()
		Form_1.StatusBar.Item(1) := ""
		ShowProgressBar( 0 )		//hide progress bar

		WAIT CLEAR
	
  	@ 40,10 GRID Grid_1 ;
  		OF Form_1 ;
		WIDTH 1600 ;
		HEIGHT 800 ;
		VALUE 1 ;
		HEADERS aHeaders ;
		WIDTHS aWidths;
		VIRTUAL ;
		ITEMCOUNT 0 ;
		ON QUERYDATA QueryFile( oFile, cDelimiter )

		Form_1.Grid_1.PaintDoubleBuffer := .T.
		
		Form_1.Grid_1.ItemCount := oFile:nLastLine - 1 /* "- 1" first line is the header */
		
		Form_1.tb_goto.Visible := .T.
		Form_1.bGoto.Visible := .T.

		Form_1.tb_record.Visible := .T.
		Form_1.bRecord.Visible := .T.

		Form_1.tb_Seek.Visible := .T.
		Form_1.bSeekMethod.Visible := .T.

		Form_1.Grid_1.Value := 1
		Form_1.Grid_1.Setfocus
		
		ON KEY CONTROL+F OF Form_1 ACTION Eval ( _GetControlAction ( 'bSeekMethod' , 'Form_1' ) )
		ON KEY CONTROL+G OF Form_1 ACTION Eval ( _GetControlAction ( 'bGoto' , 'Form_1' ) )
		ON KEY CONTROL+R OF Form_1 ACTION Eval ( _GetControlAction ( 'bRecord' , 'Form_1' ) )

	
ENDIF

RETURN oFile


Function Refr_Mem_Stat()
DO WHILE .T.
	FT_Sleep( 500 )
								
ENDDO
RETURN Nil
*********************************************

*********************************************
FUNCTION MessageRecNo( nValidRec, cRec )
IF Val( cRec ) == nValidRec
	MsgInfo ( cRec ,"The record number is correct." )
ELSE
	MsgStop ( cRec ,"The record number is invalid!, schould be " +AllTrim( Str( nValidRec ) ) )
ENDIF                 
RETURN  Nil

***************************************************************************
Function ShowProgressBar( nMode, nMin, nMax, nCol, nLenght )
Default nMode := 1		//1 = init, 2 = set/show, 3 = close, 0/other = hide
Default nMin:=1, nMax:=100
Default nCol:=20, nLenght:=740
DO CASE 
	Case nMode = 1 
		DEFINE PROGRESSBAR PBar
			PARENT Form_1
			ROW    10
			COL    nCol
			WIDTH  nLenght
			HEIGHT 12
			RANGEMIN nMin
			RANGEMAX nMax
			VALUE nMin
			TOOLTIP ""
			HELPID Nil
			VISIBLE .F.
			SMOOTH .T.
			VERTICAL .F. 
			BACKCOLOR Nil
			FORECOLOR Nil
		END PROGRESSBAR
		
		/* put the progress bar in the status bar */
		SETPARENT(Form_1.PBar.Handle, Form_1.STATUSBAR.Handle)
		
	Case nMode = 3
		Form_1.PBar.Release
		
	Case nMode = 2
		Form_1.PBar.RangeMin :=  nMin
		Form_1.PBar.RangeMax :=  nMax
		Form_1.PBar.Value :=  nMin
		Form_1.PBar.Visible := .T.
		DO EVENTS
	
	Other
		Form_1.PBar.Visible := .F.
		DO EVENTS
ENDCASE
RETURN Nil

*******************************************************************

*******************************************************************
Function MT_SeekLine( oFile )

Local lFound
Local ValueToSeek_s
Local TopOfGrid_n


IF !IsControlDefined ( Grid_1, Form_1 )
	RETURN Nil
ENDIF
		
Form_1.StatusBar.Item(1) := "Seeking ... This may take some time .... "
ShowProgressBar( 2 )		//show progress bar

ValueToSeek_s := AllTrim( Form_1.tb_Seek.Value )
TopOfGrid_n := Form_1.Grid_1.Value

	
lFound := oFile:SeekLine( ValueToSeek_s, TopOfGrid_n + 1 /* "+ 1" first line is the header */ , .F. , { | nPos, nLastPos | Form_1.PBar.Value := nPos / nLastPos * 100 } ) 	
  	
Form_1.StatusBar.Item(1) := ""
ShowProgressBar( 0 )		//hide progress bar
	
IF !lFound
	MsgStop ("Not found " + AllTRim( Form_1.tb_Seek.Value ) )
ELSE
	Form_1.Grid_1.Value := oFile:CurrentLine() - 1 /* "- 1" first line is the header */
ENDIF

Form_1.Grid_1.Setfocus
	
RETURN Nil
*******************************************************************


*******************************************************************
*   vfFileRead Class                                              *
*******************************************************************

#include "hbclass.ch"
#include "fileio.ch"

#define vf_DEF_READ_SIZE  	4096
#define vf_DELIMITER		hb_eol()
#define vf_REC_MAP_STEP		100000

CREATE CLASS vfFileRead

   VAR cFile                   
   VAR pHandle                 
   VAR nError                  
   VAR cDelim                  
   VAR nReadSize               
   VAR lEOF
   VAR lBOF
   VAR nCurrLine
   VAR nLastLine
   VAR exGauge
   VAR aRecordsMap INIT {} 		//PROTECTED
   VAR nRecordsMapStep PROTECTED
   
   METHOD New( cFile, nSize, cDelimiter, nRecMapStep )		// Create a new object (file name, buffer size , delimiter, map record indexed step)
   METHOD Open( nMode )								// Open the file ( nMode )
   METHOD Close()									// Close the file
   METHOD ReadLine()								// Read line of current record (when opened, current record is first)
   METHOD GoTop()									// Go to first record
   METHOD GoBottom()								// Go to last record
   METHOD GoTo( nLine )								// Go to <nLine> record
   METHOD Skip( nSkip )								// Skip by <nSkip> records
   METHOD IsEOF()									// Returns .T. if the current record is the last
   METHOD IsBOF()									// Returns .T. if the current record is the first
   METHOD Error()									// Returns .T. an error occurred
   METHOD ErrorNo()									// Returns the error number
   METHOD GetPointer()								// Reads the current position of the file pointer
   METHOD SetPointer( nPointer )						// Sets the file pointer to the indicated position
   METHOD GetLastLinePointer()						// Reads the file pointer position for the last record
   METHOD CurrentLine()								// Reads the current record
   METHOD CountLines( xGauge )						// Calculates the number of lines (records), as a parameter you can pass a block of code executed during the counting operation
   METHOD SeekLine( cSeekString, nFromRecord, lCaseSensitive, xGauge )	//Seek <cSeekString> in line, begin from <nFromRecord> and if <lCaseSensitive> (default is true)		//Rerturn true or false, set ::CurrentLine() to matched line
   METHOD GetRecordsMap()							// Gets a map of records
   METHOD PutRecordsMap( aMap )						// Puts a map of records
     
END CLASS

*******************************************************************

METHOD New( cFile, nSize, cDelimiter, nRecMapStep ) CLASS vfFileRead

Default nRecMapStep := vf_REC_MAP_STEP

nRecMapStep := Max ( nRecMapStep, 10000 )

IF nSize == NIL .OR. nSize < 1
	nSize := vf_DEF_READ_SIZE
ENDIF

::cFile     := cFile
::pHandle   := NIL
::nError    := 0
::nReadSize := nSize
::cDelim    := cDelimiter
::lEOF	  := .F.
::lBOF	  := .F.
::nCurrLine := 0
::nLastLine := NIL
::aRecordsMap:={}
::nRecordsMapStep := nRecMapStep
   
RETURN Self

*******************************************************************

METHOD Open( nMode ) CLASS vfFileRead

Local cLine
IF ::pHandle == NIL

	IF nMode == NIL
		nMode := FO_READ     // Default to read-only mode
	ENDIF
	::pHandle := hb_vfOpen( ::cFile, nMode )
	::aRecordsMap:={}
	IF ::pHandle == NIL
		::nError    := FError()       
		::lBOF      := .F.
		::nCurrLine := 0
	ELSE
		::nError    := 0
		::lBOF      := .T.
		::nCurrLine := 1
		
		//delimiter detection
		IF ::cDelim == NIL
			::cDelim := vf_DELIMITER
			cLine := hb_vfReadLen( ::pHandle, 64*1024 /* ::nReadSize */ )
			::GoTop()
			DO CASE
				CASE hb_AT( CHR (13) + CRLF, cLine ) > 0		//CRCRLF
					::cDelim := CHR (13) + CRLF
				CASE hb_AT( CRLF, cLine ) > 0					//CRLF
					::cDelim := CRLF
				CASE hb_AT( CHR (13), cLine ) > 0				//CR
					::cDelim := CHR (13)
				CASE hb_AT( CHR (10), cLine ) > 0				//LF
					::cDelim := CHR (10)
			ENDCASE
		ENDIF                      

	ENDIF
ELSE
      // The file is already open, so rewind to the beginning.
	IF !::GoTop()
      	::lBOF      := .F.
      	::nCurrLine := 0
      	::nError    := FError()
	ELSE
		::lBOF      := .T.
		::nCurrLine := 1
	ENDIF
ENDIF
   
RETURN Self

*******************************************************************

METHOD Close() CLASS vfFileRead

IF .NOT. ::pHandle == NIL
	hb_vfClose( ::pHandle )
	::pHandle := NIL           // The file is no longer open
ENDIF
::nError    := FError()
::lEOF      := .F.
::lBOF      := .F.
::nCurrLine := 0
::nLastLine := NIL

RETURN Self

*******************************************************************

METHOD ReadLine() CLASS vfFileRead

Local cLine       := ""
Local nCurrentPos := ::GetPointer()

DO WHILE !hb_vfEof( ::pHandle )
	cLine += hb_vfReadLen( ::pHandle, ::nReadSize )
	IF hb_AT( ::cDelim, cLine ) > 0
		cLine := Left ( cLine, hb_AT( ::cDelim, cLine ) - 1 )
		EXIT
	ENDIF
ENDDO

::SetPointer ( nCurrentPos )
::nError := FError()

RETURN cLine

*******************************************************************

METHOD GoTop() CLASS vfFileRead

::lEOF      := .F.
::lBOT      := .T.
::nCurrLine := 1

RETURN hb_vfSeek( ::pHandle, 0, FS_BEGINNING ) == 0



*******************************************************************

METHOD GoBottom() CLASS vfFileRead



hb_vfSeek( ::pHandle, 0, FS_END )
::Skip( -1 )
::lEOF      := .T.
::lBOT      := .F.
::nCurrLine := ::nLastLine
::nError    := FError()
      
RETURN FError() == 0   

*******************************************************************

METHOD GoTo( nLine ) CLASS vfFileRead

Local nToSkip ,nIntStep, nPos

DEFAULT nLine := ::nCurrLine

IF !hb_isNumeric (nLine)
	RETURN Self
ENDIF

IF ABS ( nLine - ::nCurrLine ) >= ::nRecordsMapStep		//only if we skip by indexed steps
	nIntStep := ( INT( nLine / ::nRecordsMapStep ) + IF ( nLine % ::nRecordsMapStep < (::nRecordsMapStep / 2), 0, 1 ) ) * ::nRecordsMapStep
	DO WHILE nIntStep >= 0
		nPos := hb_AScan( ::aRecordsMap, { |x|  Bin2ULL ( ULLUnShrink ( x [ 1 ] ) ) == nIntStep } )
		IF nPos > 0
			::nCurrLine := Bin2ULL ( ULLUnShrink ( ::aRecordsMap [ nPos ] [ 1 ] ) )
			::SetPointer( Bin2ULL ( ULLUnShrink ( ::aRecordsMap [ nPos ] [ 2 ] ) ) )
			EXIT
		ENDIF
		//Try with previous indexed step
		nIntStep -= ::nRecordsMapStep
	ENDDO
ENDIF

nToSkip := nLine - ::nCurrLine

DO CASE
	CASE hb_isNil( ::nCurrLine )			//We do not know the current line number, so start at the beginning of the file
		::GoTop()
		::Skip ( nLine - 1 )
	CASE nLine <= 1					//Line number less than 1, so start at the beginning of the file
		::GoTop()
	CASE hb_isNumeric( ::nLastLine ) .AND. nLine >= ::nLastLine		//Go beyond the known last line number, so go to the end.
		::GoBottom()
	CASE hb_isNumeric( ::nLastLine ) .AND. ::nLastLine - nLine < ABS ( nToSkip ) 	//We know the number of the last record, the nToSkip is greater than the difference between the last and the target record - so skip backward from the end (less skips)
		::GoBottom()
		::Skip( 0 - (::nLastLine - nLine ) )
	CASE nLine < 0 - nToSkip				//The target line is closer to the start than the nToSkip, so start at the beginning of the file.
		::GoTop()
		::Skip ( nLine - 1 )
	OTHER							
		::Skip ( nToSkip )             
ENDCASE
::nError := FError()

RETURN Self

*******************************************************************

METHOD Skip ( nSkip ) CLASS vfFileRead

Local cLine     := ""
Local lRun      := .T.
Local nSkips    := 0
Local nBuffSize := 64 * 1024	//::nReadSize  //A larger buffer speeds up the process
Local nLenDelim := Len (::cDelim)
Local nStart, nEnd, nBackOf, nPos, nPointer
Default nSkip   := 1

IF nSkip == 0
	RETURN Self
ENDIF

::lEOF := .F.
::lBOF := .F.

DO WHILE lRun
                                                            
	IF nSkip > 0
		IF hb_vfEof( ::pHandle )
			::Skip ( -1 )
			::nLastLine := ::nCurrLine
			::lEOF      := .T.
			EXIT
		ENDIF
		nStart   := 1
		nPointer := ::GetPointer()
		cLine    := hb_vfReadLen( ::pHandle, nBuffSize )
		DO WHILE Right (cLine, 1) $ ::cDelim .AND. !hb_vfEof( ::pHandle )		//Avoid splitting of end-of-line marks in the buffer.
			cLine += hb_vfReadLen( ::pHandle, nLenDelim )
		ENDDO
		DO WHILE ( nPos := hb_AT( ::cDelim, cLine, nStart ) ) > 0
			nSkips ++
			IF hb_isNumeric( ::nCurrLine )
				::nCurrLine ++
				IF ::nCurrLine % ::nRecordsMapStep == 0 .AND. hb_AScan( ::aRecordsMap, { |x|  Bin2ULL ( ULLUnShrink ( x [ 1 ] ) ) == ::nCurrLine } ) == 0
					AAdd( ::aRecordsMap, { ULLShrink( ULL2Bin( ::nCurrLine ) ), ULLShrink( ULL2Bin( nPointer + nPos  + nLenDelim - 1 ) ) } )
				ENDIF
				
			ENDIF
			nStart := nPos + nLenDelim
			IF nSkips == nSkip
				hb_vfSeek( ::pHandle, 0 - ( Len ( cLine ) - nStart + 1), FS_RELATIVE )
				lRun := .F.
				IF hb_vfEof( ::pHandle )
					::Skip ( -1 )
					::nLastLine := ::nCurrLine
					::lEOF      := .T.
				ENDIF
				EXIT
			ENDIF
		ENDDO
	ELSE			// -skip
	
		IF ::GetPointer() == 0		//top of file
			::lBOF      := .T.
			::nCurrLine := 1
			EXIT
		ENDIF
		cLine := ""
		
		DO WHILE ( nPos := hb_AT( ::cDelim, cLine ) ) == 0 .AND. hb_vfSeek( ::pHandle, 0, 1 ) <> 0		//Avoid splitting of end-of-line marks in the buffer.
			nBackOf  := Min( nBuffSize, ::GetPointer() )



			hb_vfSeek( ::pHandle, 0 - nBackOf, FS_RELATIVE )
			cLine    := hb_vfReadLen( ::pHandle, nBackOf ) + cLine
			hb_vfSeek( ::pHandle, 0 - nBackOf, FS_RELATIVE )
		ENDDO
		IF nPos == 0
			nPos := 1
		ENDIF
		
		nStart := nPos
		
		cLine  := SubStr ( cLine, nStart )
		hb_vfSeek( ::pHandle, nStart - 1, FS_RELATIVE )

		
		nPointer := ::GetPointer()
		nEnd     := Len( cLine )
		
		IF Right( cLine, nLenDelim) == ::cDelim
			nEnd -= nLenDelim
		ENDIF
	
		DO WHILE (nPos := hb_RAT( ::cDelim, cLine, 1, nEnd ) ) > 0
			nSkips --
			IF hb_isNumeric( ::nCurrLine )
				::nCurrLine --
				IF ::nCurrLine % ::nRecordsMapStep == 0 .AND. hb_AScan( ::aRecordsMap, { |x|  Bin2ULL ( ULLUnShrink ( x [ 1 ] ) ) == ::nCurrLine } ) == 0
					AAdd( ::aRecordsMap, { ULLShrink( ULL2Bin( ::nCurrLine ) ), ULLShrink( ULL2Bin( nPointer + nPos + nLenDelim -1 ) ) } )
				ENDIF
			ENDIF
			
			nEnd := nPos - 1		//nLenDelim

			IF nSkips == nSkip
				hb_vfSeek( ::pHandle, 0 + nPos + nLenDelim - 1 , FS_RELATIVE )
				lRun := .F.
				EXIT
			ENDIF
		ENDDO
	ENDIF
ENDDO

cLine := ''
::nError := FError()

RETURN Self

*******************************************************************

METHOD IsEOF() CLASS vfFileRead

IF !::lEOF .AND. hb_IsNumeric ( ::nCurrLine ) .AND. hb_IsNumeric ( ::nLastLine ) .AND. ::nCurrLine == ::nLastLine
	::lEOF := .T.
ENDIF

IF !::lEOF .AND. ::GetPointer() == ::GetLastLinePointer()
	::lEOF := .T.
ENDIF

RETURN hb_vfEof( ::pHandle ) .OR. ::lEOF

*******************************************************************

METHOD IsBOF() CLASS vfFileRead

RETURN ::GetPointer() == 0 .OR. ::lBOF

*******************************************************************

METHOD Error() CLASS vfFileRead

RETURN FError() != 0

*******************************************************************

METHOD ErrorNo() CLASS vfFileRead

RETURN ::nError

*******************************************************************

METHOD GetPointer() CLASS vfFileRead
	
RETURN hb_vfSeek( ::pHandle, 0, FS_RELATIVE )

*******************************************************************

METHOD SetPointer( nPointer ) CLASS vfFileRead

hb_vfSeek( ::pHandle, nPointer, FS_BEGINNING )
::nError := FError()

RETURN ::nError == 0

*******************************************************************

METHOD GetLastLinePointer() CLASS vfFileRead

Local nCurrentPointer := ::GetPointer()
Local nCurrentLine    := ::nCurrLine
Local lCurrentEOF     := ::lEOF
Local nLastLinePointer
::GoBottom()
nLastLinePointer := ::GetPointer()
::SetPointer ( nCurrentPointer )	//roll back pointer
::nCurrLine := nCurrentLine			//roll back ::nCurrLine
::lEOF := lCurrentEOF				//roll back ::lEOF
::nError := FError()

RETURN nLastLinePointer

*******************************************************************

METHOD CurrentLine() CLASS vfFileRead

RETURN ::nCurrLine

*******************************************************************

METHOD CountLines( xGauge ) CLASS vfFileRead

Local cLine       := ""
Local nCurrentPos := ::GetPointer()
Local nLastPos    := hb_vfSeek( ::pHandle, 0, FS_END )
Local nSkips      := 0
Local nlDelim     := Len ( ::cDelim )
Local nPrevPos    := 0
Local nBuffSize   := 64 * 1024
Local exCurrentGauge := ::exGauge
Local nPointer    := 0
Local nStart, nPos

IF HB_ISEVALITEM( xGauge )
	::exGauge := xGauge
ENDIF
::aRecordsMap := {}
::GoTop()
DO WHILE !hb_vfEof( ::pHandle )
	nStart   := 1
	nPointer := ::GetPointer()
	cLine    := hb_vfReadLen( ::pHandle, nBuffSize )
	DO WHILE Right (cLine, 1) $ ::cDelim .AND. !hb_vfEof( ::pHandle )	//Avoid splitting of end-of-line marks in the buffer.
		cLine += hb_vfReadLen( ::pHandle, nlDelim )
	ENDDO
	DO WHILE (nPos := hb_AT( ::cDelim, cLine, nStart )) > 0
		nSkips ++
		IF nSkips % ::nRecordsMapStep == 0
			AAdd( ::aRecordsMap, { ULLShrink( ULL2Bin( nSkips ) ), ULLShrink( ULL2Bin( nPointer + nPrevPos - 1 /* pointer starts from 0 not from 1 like AT( ) */ ) ) } )
			
			IF HB_ISEVALITEM( ::exGauge )
				Eval( ::exGauge, nPointer + nPrevPos - 1, nLastPos, nSkips , 'Processing', Self )
			ENDIF
		
		ENDIF
			
		nStart   := nPos + nlDelim
		nPrevPos := nStart

	ENDDO
ENDDO

IF .NOT. Right( cLine, nlDelim ) == ::cDelim .AND. nLastPos > 0		//The last line does not end with a delimiter. Make it the last record.
	nSkips ++
ENDIF

IF HB_ISEVALITEM( ::exGauge )
	Eval( ::exGauge, nPointer + nPrevPos - 1, nLastPos, nSkips , 'Done', Self )
ENDIF

::nLastLine := nSkips
::SetPointer ( nCurrentPos )
::nError    := FError()
::exGauge   := exCurrentGauge


RETURN nSkips
*******************************************************************

METHOD SeekLine( cSeekString, nFromRecord, lCaseSensitive, xGauge ) CLASS vfFileRead

Local cLine       := ""
Local nCurrentPos := ::GetPointer()
Local nLastPos    := hb_vfSeek( ::pHandle, 0, FS_END )
Local nSkips      := 0
Local nlDelim     := Len ( ::cDelim )
Local nPrevPos    := 0
Local nBuffSize   := 64 * 1024
Local exCurrentGauge := ::exGauge
//Local nPointer    := 0
Local nFoundLine  := 0
Local nStart, nPos, nPointer

Default cSeekString := "", nFromRecord := 1, lCaseSensitive := .T.

::SetPointer ( nCurrentPos )

IF Empty ( cSeekString ) .OR. cSeekString == ::cDelim
	RETURN nFoundLine <> 0
ENDIF

IF !lCaseSensitive
	cSeekString := HMG_Upper ( cSeekString )
ENDIF

IF HB_ISEVALITEM( xGauge )
	::exGauge := xGauge
ENDIF

::GoTo( nFromRecord )
DO WHILE !hb_vfEof( ::pHandle ) .AND. nFoundLine == 0
	nStart   := 1
	nPointer := ::GetPointer()
	cLine    := hb_vfReadLen( ::pHandle, nBuffSize )
	
	//Avoid splitting of whole line in the buffer.
	nPos := hb_RAt ( ::cDelim, cLine )
	IF nPos > 0
		cLine := Left ( cLine, nPos + nlDelim - 1)
		::SetPointer ( nPointer + Len ( cLine ) )
	ENDIF
	
	IF !lCaseSensitive
		cLine := HMG_Upper ( cLine )
	ENDIF
	
	DO WHILE (nPos := hb_AT( ::cDelim, cLine, nStart )) > 0
		
		IF hb_AT( cSeekString, cLine, nStart, nPos ) > 0
			nFoundLine := ::nCurrLine + nSkips
			EXIT
		ENDIF
		
		IF (::nCurrLine + nSkips) % ::nRecordsMapStep == 0
		
			IF hb_AScan( ::aRecordsMap, { |x|  Bin2ULL ( ULLUnShrink ( x [ 1 ] ) ) == (::nCurrLine + nSkips) } ) == 0
				AAdd( ::aRecordsMap, { ULLShrink( ULL2Bin( (::nCurrLine + nSkips) ) ), ULLShrink( ULL2Bin( nPointer + nPrevPos - 1 /* pointer starts from 0 not from 1 like AT( ) */ ) ) } )
			ENDIF
			
			IF HB_ISEVALITEM( ::exGauge )
				Eval( ::exGauge, nPointer + nPrevPos - 1, nLastPos, ::nCurrLine + nSkips , 'Processing', Self )
			ENDIF
		
		ENDIF
			
		nStart   := nPos + nlDelim
		nPrevPos := nStart
		
		nSkips ++

	ENDDO
	
ENDDO

IF HB_ISEVALITEM( ::exGauge )
	Eval( ::exGauge, nPointer + nPrevPos - 1, nLastPos, ::nCurrLine + nSkips , 'Done', Self )
ENDIF

::SetPointer ( nCurrentPos )
IF nFoundLine <> 0
	::GoTo ( nFoundLine )
ENDIF

::nError    := FError()
::exGauge   := exCurrentGauge

cLine := ""
 
RETURN nFoundLine <> 0

*******************************************************************

METHOD GetRecordsMap() CLASS vfFileRead

RETURN ::aRecordsMap

*******************************************************************

METHOD PutRecordsMap( aMap ) CLASS vfFileRead

IF aMap == NIL .OR. !hb_isArray( aMap )
	RETURN Self
ENDIF

::aRecordsMap := aMap

RETURN Self 

*******************************************************************

Function ULLShrink( cBin )
RETURN REMRIGHT( cBin, CHR(0) )

*******************************************************************

Function ULLUnShrink( cBin )
RETURN PADRIGHT( cBin, 8, CHR(0) )

*******************************************************************  


Function DoNothing
Local Nothing_s
Nothing_s := "Nothing"
Return NIL


Function GoToRecord( oFile, RecordNumber_n )
Local Record_n := 0
Record_n := oFile:GoTo( RecordNumber_n )
Return Record_n


//Support Unsigned Long Long
#pragma BEGINDUMP   
 

#include "hbapi.h"
#include "hbapiitm.h"
#include <mgdefs.h>
#include <psapi.h>



//        SetParent (hWndChild, hWndNewParent)
HB_FUNC ( SETPARENT )
{
   HWND hWndChild     = (HWND) HB_PARNL (1);
   HWND hWndNewParent = (HWND) HB_PARNL (2);
   hb_retnl ((LONG_PTR)  SetParent (hWndChild, hWndNewParent) );
}

HB_FUNC( BIN2ULL )
{
   PHB_ITEM pItem    = hb_param( 1, HB_IT_STRING );
   HB_U64   uiResult = 0;

   if( pItem )
   {
      HB_SIZE nLen = hb_itemGetCLen( pItem );
      if( nLen )
      {
         const char * pszString = hb_itemGetCPtr( pItem );
         uiResult = HB_GET_LE_UINT64( pszString );
          }
   }
   hb_retnint( uiResult );
}

HB_FUNC( ULL2BIN )
{
   char   szResult[ 8 ];
   HB_U64 uiValue = ( HB_U64 ) hb_parnint( 1 );

   HB_PUT_LE_UINT64( szResult, uiValue );
   hb_retclen( szResult, 8 );
}

#pragma ENDDUMP

************************ THE END *********************












edk wrote: Fri Dec 10, 2021 8:12 pm
HGAutomator wrote: Fri Dec 10, 2021 7:11 pm The scope problem, as I understand it, is that oFile is in the scope of QueryFile() and OpenFile(). But the buttons don't share that scope.
Notice what the OpenFile function returns.
The oFile object is returned, where in the main form it is assigned to oGridFile, i.e. when you refer to the oGridFile object in the main form, you are actually referring to the original oFile object declared in the OpenFile function. Note that oFile is of type Local in this function, therefore this object is passed to other functions as a parameter.
In the main form, try to reference the oGridFile object.

You could change the type of oFile from Local to Public and then you wouldn't have to pass it as a parameter.

Personally, I try to avoid public variables and that's why I have this style of code writing. Perhaps it is not very understandable for some, for which I sincerely apologize. Therefore, I tried to describe the individual stages in the code, but my English is not very good, so not everything is understood correctly.
Post Reply