FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin for Harbour/xHarbour xBrowse and Tree
Posts: 218
Joined: Mon Feb 07, 2022 09:54 PM
xBrowse and Tree
Posted: Thu Nov 27, 2025 05:25 PM

Hi all,

I want to create a dialog showing a one to many relationship of 2 dbf by an xBrowse Tree.

dbf 1 is "ID", "TASK"
           |
	   +------+
		  |
dbf 2 is "ID", "TASK_ID", "date", "FROM", "TO"

My problem is that at program start both dbf are empty.
Pressing a button 'ADD' allows the user to create entries.

How can i declare the xbrowse tree at beginning?

Regards, Detlef

Posts: 44162
Joined: Thu Oct 06, 2005 05:47 PM
Re: xBrowse and Tree
Posted: Thu Nov 27, 2025 09:32 PM

Dear Detlef,

work in progress:

#include "FiveWin.ch"

request DbfCdx

function Main()

   local oDlg, oBrw

   if ! File( "tasks.dbf")
      DbCreate( "tasks.dbf", { ;
         { "ID",   "+",  10, 0 }, ;
         { "TASK", "C",  50, 0 } } )
   endif   

   if ! File( "details.dbf")
      DbCreate( "details.dbf", { ;
         { "ID",      "N", 10, 0 }, ;
         { "TASK_ID", "N", 10, 0 },;
         { "DATE",    "D",  8, 0 },;
         { "FROM",    "C", 50, 0 },;
         { "TO",      "C", 50, 0 } } )
   endif   

   USE tasks VIA "DBFCDX"

   if Tasks->( RecCount() ) == 0
      Tasks->( DbAppend() )
      Tasks->Task := "Project Alpha"
   endif   

   INDEX ON field->ID TO "ID"
   SET ORDER TO TAG ID

   USE details VIA "DBFCDX" NEW

   if Details->( RecCount() ) == 0
      Details->( DbAppend() )
      Details->Task_id   := Tasks->Id
      Details->Date      := Date()
   endif   

   INDEX ON field->Task_id TO "TASK_ID"
   SET ORDER TO TAG TASK_ID

   SELECT "tasks"
   SET RELATION TO details->Task_id INTO details

   DEFINE DIALOG oDlg SIZE 1200, 400 PIXEL TRUEPIXEL

   @ 2.5, 2 XBROWSE oBrw SIZE 1170, 350 OF oDlg ;
      DATASOURCE "Tasks" ;
      CELL LINES NOBORDER
   

   oBrw:CreateFromCode()
   oBrw:SetTree( BuildTree() )
   oDlg:oClient = oBrw

   ACTIVATE DIALOG oDlg CENTERED ;
      ON INIT BuildBar( oDlg, oBrw ) 

return nil

function BuildBar( oDlg, oBrw )

   local oBar

   DEFINE BUTTONBAR oBar OF oDlg SIZE 50, 50 2007

   DEFINE BUTTON OF oBar PROMPT "Task" ;
      ACTION ( Tasks->( DbAppend() ), Details->( DbAppend() ),;
               Details->Task_id := Tasks->Id, Details->Date := Date(), ;
               oBrw:SetTree( BuildTree() ), oBrw:Refresh() )

   DEFINE BUTTON OF oBar PROMPT "Detail" ;
      ACTION ( details->( DbAppend() ), details->task_id := tasks->id, oBrw:SetTree( BuildTree() ), oBrw:Refresh() ) 
   

   oDlg:Resize()
 
return .T.

function BuildTree()

   local oTree, nRecNo := Tasks->( RecNo() )

   if Tasks->( RecCount() ) > 0
    TREE oTree  

    Tasks->( DbGoTop() )
    while ! Tasks->( Eof() )  

        // Create parent item from tasks  

        TREEITEM tasks->Task
        if Details->( RecCount( ) ) > 0
           TREE  

            while details->Task_id == tasks->id .and. ! details->( Eof() )  

                TREEITEM DToC( details->Date ) + " " + details->From + " to " + details->To
                details->(DbSkip())  

            end  

            ENDTREE  

        endif    

        tasks->(DbSkip())  

    end  

    ENDTREE  

    Tasks->( DbGoTo( nRecNo ) )
   else 
    TREE oTree
    ENDTREE
   endif 

return oTree
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 218
Joined: Mon Feb 07, 2022 09:54 PM
Re: xBrowse and Tree
Posted: Thu Nov 27, 2025 10:00 PM

Many thanks Antonio.
This is a very helpful code for me to start with. :D

Kind regards

Posts: 44162
Joined: Thu Oct 06, 2005 05:47 PM
Re: xBrowse and Tree
Posted: Fri Nov 28, 2025 11:13 AM

Enhanced version:

#include "FiveWin.ch"

request DbfCdx

function Main()

   local oDlg, oBrw

   if ! File( "tasks.dbf")
      DbCreate( "tasks.dbf", { ;
         { "ID",   "+",  10, 0 }, ;
         { "TASK", "C",  50, 0 } } )
   endif   

   if ! File( "details.dbf")
      DbCreate( "details.dbf", { ;
         { "ID",      "N", 10, 0 }, ;
         { "TASK_ID", "N", 10, 0 },;
         { "DATE",    "D",  8, 0 },;
         { "FROM",    "C", 50, 0 },;
         { "TO",      "C", 50, 0 } } )
   endif   

   USE tasks VIA "DBFCDX"

   if Tasks->( RecCount() ) == 0
      Tasks->( DbAppend() )
      Tasks->Task := "Project Alpha"
   endif   

   INDEX ON field->ID TO "ID"
   SET ORDER TO TAG ID

   USE details VIA "DBFCDX" NEW

   if Details->( RecCount() ) == 0
      Details->( DbAppend() )
      Details->Task_id   := Tasks->Id
      Details->Date      := Date()
      Details->From      := "09:00"
      Details->To        := "11:00"
   endif   

   INDEX ON field->Task_id TO "TASK_ID"
   SET ORDER TO TAG TASK_ID

   SELECT "tasks"
   SET RELATION TO details->Task_id INTO details

   DEFINE DIALOG oDlg SIZE 1200, 400 PIXEL TRUEPIXEL

   @ 2.5, 2 XBROWSE oBrw SIZE 1170, 350 OF oDlg ;
      DATASOURCE "Tasks" ;
      CELL LINES NOBORDER
   

   oBrw:CreateFromCode()
   oBrw:SetTree( BuildTree() )
   oBrw:aCols[ 1 ]:nWidth := 500
   oBrw:bEdit = { | o | EditItem( o:oBrw:oTreeItem, o:oBrw ) }
   oDlg:oClient = oBrw

   ACTIVATE DIALOG oDlg CENTERED ;
      ON INIT BuildBar( oDlg, oBrw ) 

return nil

function BuildBar( oDlg, oBrw )

   local oBar

   DEFINE BUTTONBAR oBar OF oDlg SIZE 50, 50 2007

   DEFINE BUTTON OF oBar PROMPT "Task" ;
      ACTION ( Tasks->( DbAppend() ), Details->( DbAppend() ),;
               Details->Task_id := Tasks->Id, Details->Date := Date(),;
               Details->From := "09:00", Details->To := "11:00",; 
               oBrw:oTree := BuildTree(), oBrw:Refresh() )

   DEFINE BUTTON OF oBar PROMPT "Detail" ;
      ACTION ( Details->( DbAppend() ), Details->task_id := Tasks->id,;
               Details->Date := Date(), Details->From := "09:00",;
               Details->To := "11:00",;
               oBrw:oTree := BuildTree(), oBrw:Refresh() ) 

   DEFINE BUTTON OF oBar PROMPT "Edit" ACTION oBrw:Edit()

   DEFINE BUTTON OF oBar PROMPT "Del"
   

   oDlg:Resize()
 
return .T.

function BuildTree()

   local oTree, nRecNo := Tasks->( RecNo() )

   if Tasks->( RecCount() ) > 0
    TREE oTree  

    Details->( DbGoTop() )
    Tasks->( DbGoTop() )
    while ! Tasks->( Eof() )  

       TREEITEM tasks->Task CARGO { "task", Tasks->( RecNo() ) }
       if Details->( RecCount() ) > 0
          TREE  

             while Details->Task_id == tasks->id .and. ! Details->( Eof() )  

                TREEITEM "Date: " + DToC( Details->Date ) + ;
                         " - From: " + RTRim( Details->From ) + ;
                         " - To: " + RTrim( Details->To ) ;
                         CARGO { "detail", Details->( RecNo() ) }
                Details->( DbSkip() )  

             end  

          ENDTREE  

       endif    

       Tasks->( DbSkip() )  

    end  

    ENDTREE  

    Tasks->( DbGoTo( nRecNo ) )
   else 
    TREE oTree
    ENDTREE
   endif 

return oTree

function EditItem( oTreeItem, oBrw )

   local nRec := oTreeItem:Cargo[ 2 ], oDlg
   local nBrwPos := oBrw:nRowSel

   if oTreeItem:Cargo[ 1 ] == "detail"
      DEFINE DIALOG oDlg SIZE 400, 220 PIXEL TRUEPIXEL TITLE "Edit detail"

  Details->( DbGoTo( nRec ) )

  @ 1, 2 SAY "Date:" OF oDlg 

  @ 1, 6 GET Details->Date PICTURE "9999-99-99" OF oDlg

  @ 3.2, 2 SAY "From:" OF oDlg 

  @ 3.5, 6 GET Details->From PICTURE "99:99" OF oDlg

  @ 5.3, 2 SAY "To:" OF oDlg

  @ 5.9, 6 GET Details->To PICTURE "99:99" OF oDlg

  @ 10, 14 BUTTON "OK" OF oDlg SIZE 80, 25 ;
     ACTION ( Details->( DbCommit() ), oDlg:End(),;
              oBrw:oTree := BuildTree(), oBrw:nRowSel := nBrwPos, oBrw:Refresh() )

  @ 10, 34 BUTTON "Cancel" OF oDlg SIZE 80, 25 ACTION oDlg:End()

  ACTIVATE DIALOG oDlg CENTERED
   else   

      DEFINE DIALOG oDlg SIZE 400, 220 PIXEL TRUEPIXEL TITLE "Edit task"

  Tasks->( DbGoTo( nRec ) )

  @ 1, 2 SAY "Task:" OF oDlg 

  @ 1, 6 GET Tasks->Task SIZE 340, 20 OF oDlg

  @ 10, 14 BUTTON "OK" OF oDlg SIZE 80, 25 ;
     ACTION ( Tasks->( DbCommit() ), oDlg:End(),;
              oBrw:oTree := BuildTree(), oBrw:Refresh() )

  @ 10, 34 BUTTON "Cancel" OF oDlg SIZE 80, 25 ACTION oDlg:End()

  ACTIVATE DIALOG oDlg CENTERED
   endif

return nil
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 218
Joined: Mon Feb 07, 2022 09:54 PM
Re: xBrowse and Tree
Posted: Fri Nov 28, 2025 03:56 PM

Thanks again, Antonio. That gets me much further.

Posts: 654
Joined: Fri Oct 21, 2005 05:54 AM
Re: xBrowse and Tree
Posted: Sat Nov 29, 2025 06:17 AM

Mr.Detlef,

Please remove the following line

:DATASOURCE "Tasks" ;

under @ 2.5, 2 XBROWSE oBrw SIZE 1170, 350 OF oDlg ; Clause
to avoid runtime error, when testing the sample code.

-Ramesh Babu P

Posts: 44162
Joined: Thu Oct 06, 2005 05:47 PM
Re: xBrowse and Tree
Posted: Sat Nov 29, 2025 10:13 AM

Enhanced version:

detlef.prg

#include "FiveWin.ch"

request DbfCdx

function Main()

   local oDlg, oBrw

   if ! File( "tasks.dbf")
      DbCreate( "tasks.dbf", { ;
         { "ID",   "+",  10, 0 }, ;
         { "TASK", "C",  50, 0 } } )
   endif   

   if ! File( "details.dbf")
      DbCreate( "details.dbf", { ;
         { "ID",      "N", 10, 0 }, ;
         { "TASK_ID", "N", 10, 0 },;
         { "DATE",    "D",  8, 0 },;
         { "FROM",    "C", 50, 0 },;
         { "TO",      "C", 50, 0 } } )
   endif   

   USE tasks VIA "DBFCDX"

   if Tasks->( RecCount() ) == 0
      Tasks->( DbAppend() )
      Tasks->Task := "Project Alpha"
   endif   

   INDEX ON field->ID TO "ID"
   SET ORDER TO TAG ID

   USE details VIA "DBFCDX" NEW

   if Details->( RecCount() ) == 0
      Details->( DbAppend() )
      Details->Task_id   := Tasks->Id
      Details->Date      := Date()
      Details->From      := "09:00"
      Details->To        := "11:00"
   endif   

   INDEX ON field->Task_id TO "TASK_ID"
   SET ORDER TO TAG TASK_ID

   SELECT "tasks"
   SET RELATION TO details->Task_id INTO details

   DEFINE DIALOG oDlg SIZE 1200, 400 PIXEL TRUEPIXEL

   @ 2.5, 2 XBROWSE oBrw SIZE 1170, 350 OF oDlg ;
      DATASOURCE "Tasks" ;
      CELL LINES NOBORDER
   

   oBrw:CreateFromCode()
   oBrw:SetTree( BuildTree() )
   oBrw:aCols[ 1 ]:nWidth := 500
   oBrw:bEdit = { | o | EditItem( o:oBrw:oTreeItem, o:oBrw ) }
   oBrw:bChange = { | o | If( Len( o:oTreeItem:Cargo ) == 2,;
                          Tasks->( DbGoTo( o:oTreeItem:Cargo[ 2 ] ) ),;
                          Details->( DbGoTo( o:oTreeItem:Cargo[ 2 ] ) ) ) }
   oDlg:oClient = oBrw

   ACTIVATE DIALOG oDlg CENTERED ;
      ON INIT BuildBar( oDlg, oBrw ) 

return nil

function BuildBar( oDlg, oBrw )

   local oBar

   DEFINE BUTTONBAR oBar OF oDlg SIZE 50, 50 2007

   DEFINE BUTTON OF oBar PROMPT "Task" RESOURCE "add" ;
      ACTION ( Tasks->( DbAppend() ), Details->( DbAppend() ),;
               Details->Task_id := Tasks->Id, Details->Date := Date(),;
               Details->From := "09:00", Details->To := "11:00",; 
               oBrw:oTree := BuildTree(), oBrw:Refresh() )

   DEFINE BUTTON OF oBar PROMPT "Detail" RESOURCE "add" ;
      ACTION ( Tasks->( DbGoTo( If( Len( oBrw:oTreeItem:Cargo ) == 2,;
               oBrw:oTreeItem:Cargo[ 2 ], oBrw:oTreeItem:Cargo[ 3 ] ) ) ),;
               Details->( DbAppend() ), Details->task_id := Tasks->id,;
               Details->Date := Date(), Details->From := "09:00",;
               Details->To := "11:00",;
               oBrw:oTree := BuildTree(), oBrw:Refresh() ) 

   DEFINE BUTTON OF oBar PROMPT "Edit" RESOURCE "edit" ACTION oBrw:Edit()

   DEFINE BUTTON OF oBar PROMPT "Del" RESOURCE "del"
   

   oDlg:Resize()
 
return .T.

function BuildTree()

   local oTree, nRecNo := Tasks->( RecNo() )

   if Tasks->( RecCount() ) > 0
    TREE oTree  

    Details->( DbGoTop() )
    Tasks->( DbGoTop() )
    while ! Tasks->( Eof() )  

       TREEITEM tasks->Task CARGO { "task", Tasks->( RecNo() ) }
       if Details->( RecCount() ) > 0
          TREE  

             while Details->Task_id == tasks->id .and. ! Details->( Eof() )  

                TREEITEM "Date: " + DToC( Details->Date ) + ;
                         " - From: " + RTRim( Details->From ) + ;
                         " - To: " + RTrim( Details->To ) ;
                     CARGO { "detail", Details->( RecNo() ), Tasks->( RecNo() ) }
                Details->( DbSkip() )  

             end  

          ENDTREE  

       endif    

       Tasks->( DbSkip() )  

    end  

    ENDTREE  

    Tasks->( DbGoTo( nRecNo ) )
   else 
    TREE oTree
    ENDTREE
   endif 

   // oTree:OpenAll()

return oTree

function EditItem( oTreeItem, oBrw )

   local nRec := oTreeItem:Cargo[ 2 ], oDlg
   local nBrwPos := oBrw:nRowSel

   if oTreeItem:Cargo[ 1 ] == "detail"
      DEFINE DIALOG oDlg SIZE 400, 220 PIXEL TRUEPIXEL TITLE "Edit detail"

  Details->( DbGoTo( nRec ) )

  @ 1, 2 SAY "Date:" OF oDlg 

  @ 1, 6 GET Details->Date PICTURE "9999-99-99" OF oDlg

  @ 3.2, 2 SAY "From:" OF oDlg 

  @ 3.5, 6 GET Details->From PICTURE "99:99" OF oDlg

  @ 5.3, 2 SAY "To:" OF oDlg

  @ 5.9, 6 GET Details->To PICTURE "99:99" OF oDlg

  @ 10, 14 BUTTON "OK" OF oDlg SIZE 80, 25 ;
     ACTION ( Details->( DbCommit() ), oDlg:End(),;
              oBrw:oTree := BuildTree(), oBrw:nRowSel := nBrwPos, oBrw:Refresh() )

  @ 10, 34 BUTTON "Cancel" OF oDlg SIZE 80, 25 ACTION oDlg:End()

  ACTIVATE DIALOG oDlg CENTERED
   else   

      DEFINE DIALOG oDlg SIZE 400, 220 PIXEL TRUEPIXEL TITLE "Edit task"

  Tasks->( DbGoTo( nRec ) )

  @ 1, 2 SAY "Task:" OF oDlg 

  @ 1, 6 GET Tasks->Task SIZE 340, 20 OF oDlg

  @ 10, 14 BUTTON "OK" OF oDlg SIZE 80, 25 ;
     ACTION ( Tasks->( DbCommit() ), oDlg:End(),;
              oBrw:oTree := BuildTree(), oBrw:Refresh() )

  @ 10, 34 BUTTON "Cancel" OF oDlg SIZE 80, 25 ACTION oDlg:End()

  ACTIVATE DIALOG oDlg CENTERED
   endif

return nil

detlef.rc

add BITMAP  "../../../bitmaps/plus.bmp"
edit BITMAP "../../../bitmaps/32x32/edit.bmp"
del BITMAP  "../../../bitmaps/delete2.bmp"
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 218
Joined: Mon Feb 07, 2022 09:54 PM
Re: xBrowse and Tree
Posted: Sat Nov 29, 2025 02:52 PM

Hello Antonio.
"I’ve been retired for some years now and only do a little programming as a hobby.
But every time, I’m amazed by the great helpfulness in this forum. :D

Please, one question:

I tried

   oBmp1 := LoadBitmap( GetResources(), "folder" )
   oBmp2 := LoadBitmap( GetResources(), "folder2" )
   oBmp3 := LoadBitmap( GetResources(), "go" )

   oImageList := TImageList():New()
   oImageList:Add( oBmp1 )
   oImageList:Add( oBmp2 )
   oImageList:Add( oBmp3 )

This gives me the error
Error BASE/1004 Class: 'NUMERIC' has no exported method: HBITMAP
Args:
[ 1] = N 0

How can i create the imagelist for the tree with bitmaps from a resource file?

Thanks, Detlef

Posts: 9022
Joined: Thu Oct 06, 2005 08:17 PM
Re: xBrowse and Tree
Posted: Sat Nov 29, 2025 05:48 PM
Detlef wrote:
   oBmp1 := LoadBitmap( GetResources(), "folder" )
   oBmp2 := LoadBitmap( GetResources(), "folder2" )
   oBmp3 := LoadBitmap( GetResources(), "go" )

   oImageList := TImageList():New()
   oImageList:Add( oBmp1 )
   oImageList:Add( oBmp2 )
   oImageList:Add( oBmp3 )

Try:

oBmp1 := TImage():Define( "folder" )
...

Posts: 218
Joined: Mon Feb 07, 2022 09:54 PM
Re: xBrowse and Tree
Posted: Sat Nov 29, 2025 08:14 PM

Hello Enrico,

thanks for your suggest.
I tried it but unfortunately I get an error :
Called from: source\rtl\tobject.prg => TLINKLIST:ERROR( 0 )
Called from: source\rtl\tobject.prg => TLINKLIST:MSGNOTFOUND( 0 )
Called from: source\rtl\tobject.prg => TLINKLIST:SETIMAGELIST( 0 )

Posts: 9022
Joined: Thu Oct 06, 2005 08:17 PM
Re: xBrowse and Tree
Posted: Sat Nov 29, 2025 08:32 PM

Can I see the new code you are trying?

Posts: 218
Joined: Mon Feb 07, 2022 09:54 PM
Re: xBrowse and Tree
Posted: Sat Nov 29, 2025 08:51 PM

Hi Enrico,
thanks for jumping in.

FUNCTION Main()
LOCAL oDlg, oBmp1, oBmp2, oBmp3, oImageList, oBrw, oTree


   RddSetDefault( "DBFCDX" )
   SET DATE GERMAN

   xbrNumFormat( "E", .t. )  // "E" for European, "A" for American and others
                             // .t. for showing thousand separators

   OpenDbfs()

   oImageList := TImageList():New()

   oBmp1 := TImage():Define( "folder" )
   oBmp2 := TImage():Define( "folder2" )
   oBmp3 := TImage():Define( "go" )

   oImageList:Add( oBmp1 )
   oImageList:Add( oBmp2 )
   oImageList:Add( oBmp3 )

   oTree := BuildTree()
   oTree:SetImageList( oImageList )

   DEFINE DIALOG oDlg NAME "DLG_MAIN"

   REDEFINE XBROWSE oBrw ID 10 OF oDlg LINES CELL

   oBrw:SetTree( oTree )

   oBrw:nMarqueeStyle = MARQSTYLE_HIGHLROW

   // --- Buttons -----------------------------------------------------
   REDEFINE BUTTON oBtnTaskNew    ID 20 OF oDlg ACTION TaskEdit( .t. )
   REDEFINE BUTTON oBtnTaskEdit   ID 30 OF oDlg ACTION TaskEdit( .f. )
   REDEFINE BUTTON oBtnTaskDelete ID 40 OF oDlg ACTION TaskDelete()

   REDEFINE BUTTON oBtnExport     ID 50 OF oDlg ACTION ExportCsv()
   REDEFINE BUTTON                ID 60 OF oDlg ACTION oDlg:End()
   ACTIVATE DIALOG oDlg CENTER

   tsk->( DbCloseArea() )
   trm->( DbCloseArea() )

RETURN NIL

and

STATIC FUNCTION BuildTree()
//-------------------------
LOCAL oTree, nRecNo := tsk->( RecNo() )

   if tsk->( RecCount() ) > 0
      trm->( DbGoTop() )
      tsk->( DbGoTop() )

  TREE oTree BITMAPS
     while ! tsk->( Eof() )
        TREEITEM tsk->Task CARGO { "task", tsk->( RecNo() ) }

        if trm->( RecCount() ) > 0
           TREE
              while trm->Task_id == tsk->id .and. ! trm->( Eof() )
                 TREEITEM "Date: " + DToC( trm->Date ) + ;
                          " - From: " + RTRim( trm->From ) + ;
                          " - To: " + RTrim( trm->To ) ;
                          CARGO { "detail", trm->( RecNo() ) }

                 trm->( DbSkip() )
              end
           ENDTREE
        endif

        tsk->( DbSkip() )
     end

  ENDTREE

  tsk->( DbGoTo( nRecNo ) )
   else
      TREE oTree
      ENDTREE
   endif

return oTree
Posts: 44162
Joined: Thu Oct 06, 2005 05:47 PM
Re: xBrowse and Tree
Posted: Sat Nov 29, 2025 11:11 PM

You're asking about the difference between two tree control implementations in the FiveWin framework: one using TLinkList (a custom implementation) and TTreeView (which wraps the Windows API TreeView control), and whether image lists can be used with both.<cite />

Key Differences

TTreeView (Windows API Control)

TTreeView is a wrapper around the native Windows TreeView control (SysTreeView32) 0-cite-0 . It inherits from TControl and provides direct access to Windows API functionality 0-cite-1 . This implementation:

  • Uses the Windows Common Controls TreeView (SysTreeView32)
  • Stores items in aItems array of TTreeItem objects 0-cite-2
  • Supports native Windows features like checkboxes (TVS_CHECKBOXES), editable labels (TVS_EDITLABELS), and standard tree styles 0-cite-3
  • Has built-in Windows API support for image lists via SetImageList() method 0-cite-4

TTreeView with TLinkList (Custom Implementation)

The custom tree implementation in samples/trees/treeview.prg uses TLinkList as its underlying data structure 0-cite-5 . This implementation:

  • Uses a custom listbox-based control with owner-draw functionality (LBS_OWNERDRAWVARIABLE) 0-cite-6
  • Stores tree structure in RootLink (a TTreeLink object) 0-cite-7
  • Manually handles drawing and tree navigation
  • Uses bitmap arrays (aBitmaps, aMasks) for icons 0-cite-7
  • Creates control as "LISTBOX" class 0-cite-8

Image List Support

Yes, image lists work with both implementations, but differently:

TTreeView (Windows API) - Native ImageList Support

The Windows API TTreeView has native support for TImageList through the SetImageList() method 0-cite-4 , which calls the Windows API function TreeView_SetImageList 0-cite-9 .

Example usage: 0-cite-10

TLinkList-based TTreeView - Bitmap Arrays

The custom implementation uses bitmap arrays instead of image lists. You pass bitmaps via the acBitmaps and acMasks parameters 0-cite-11 , which are stored in aBitmaps and aMasks data members 0-cite-7 . The SetBitmaps() method handles this 0-cite-12 .

Example usage: 0-cite-13

Notes

The Windows API TTreeView (in source/classes/ttreevie.prg) is the modern, recommended approach as it leverages native Windows controls with full OS support for accessibility, theming, and performance 0-cite-14 . The custom TLinkList-based implementation (in samples/trees/treeview.prg) appears to be an older approach from before Windows Common Controls were widely available, maintained for backward compatibility<cite />.

Wiki pages you might want to explore:

Citations

File: source/classes/ttreevie.prg (L42-48)

Code (text): Select all Collapse
#define TVS_HASBUTTONS          1
#define TVS_HASLINES            2
#define TVS_LINESATROOT         4
#define TVS_SHOWSELALWAYS       32 //  0x0020
#define TVS_DISABLEDRAGDROP     16 //  0x0010
#define TVS_CHECKBOXES          256 //  0x0100
#define TVS_EDITLABELS          8

File: source/classes/ttreevie.prg (L50-61)

Code (text): Select all Collapse
#define CTRL_NAME "SysTreeView32"

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

CLASS TTreeView FROM TControl

   DATA   aItems
   DATA   oImageList
   DATA   bChanged
   DATA   bExpanded
   DATA   hEdit

File: source/classes/ttreevie.prg (L125-125)

Code (text): Select all Collapse
   METHOD SetImageList( oImageList )

File: samples/trees/treeview.prg (L43-53)

Code (text): Select all Collapse
CLASS TTreeView FROM TControl

   CLASSDATA lRegistered AS LOGICAL

   DATA RootLink, nIndent, aBitmaps, aMasks, oItem
   DATA oEditTimer, lEdit, oTipTimer
   DATA nTreeStyle

   METHOD New( nRow, nCol, nWidth, nHeight, oWnd, acBitmaps, acMasks, ;
	       bChange, bDblClick, bValid, nHelpID, ;
	       nClrFore, nClrBack, oFont, cMsg, bWhen, lPixel, nTreeStyle ) CONSTRUCTOR

File: samples/trees/treeview.prg (L62-62)

Code (text): Select all Collapse
   METHOD cToChar() INLINE Super:cToChar( "LISTBOX" )

File: samples/trees/treeview.prg (L126-126)

Code (text): Select all Collapse
   METHOD SetBitmaps( acBitmaps )

File: samples/trees/treeview.prg (L183-186)

Code (text): Select all Collapse
   ::nStyle	= nOr( WS_CHILD, WS_VISIBLE, WS_BORDER, WS_TABSTOP, ;
		       WS_VSCROLL, WS_HSCROLL, ;
		       LBS_NOTIFY, LBS_OWNERDRAWVARIABLE, ;
		       LBS_NOINTEGRALHEIGHT, LBS_NOREDRAW )

File: source/winapi/treeview.c (L100-109)

HB_FUNC( TVSETIMAGELIST ) // ( hWnd, hImageList, nType )
{
    #ifndef _WIN64
      hb_retnl( ( LONG ) TreeView_SetImageList( ( HWND ) hb_parnl( 1 ),
            ( HIMAGELIST ) hb_parnl( 2 ), hb_parnl( 3 ) ) );
   #else
      hb_retnll( ( LONGLONG ) TreeView_SetImageList( ( HWND ) hb_parnll( 1 ),
            ( HIMAGELIST ) hb_parnll( 2 ), hb_parnl( 3 ) ) );
   #endif
}

File: samples/test/testtree.prg (L32-44)

Code (text): Select all Collapse
   oImageList = TImageList():New()

   oBmp1 = TBitmap():Define( "folder",, oWnd )
   oBmp2 = TBitmap():Define( "fldMask",, oWnd )

   oImageList:Add( oBmp1, oBmp2 )

   oTree = TTreeView():New( 2, 0, oWnd )

   oTree:bChanged = { || oWnd:SetText( If( oTree:GetSelected():GetParent() != nil,;
                                           oTree:GetSelected():GetParent():cPrompt + " + ", "" ) + ;
                                       oTree:GetSelText() ) }
   oTree:SetImageList( oImageList )

File: samples/test/testtre3.prg (L52-63)

Code (text): Select all Collapse
   oImageList := TImageList():New()              // imagelist items are zero-based!

   oImageList:Add( TBitmap():Define( "folder",, oChild ), ;        // nImage == 0 (default if not specified)
                   TBitmap():Define( "fldMask",, oChild ) )
   oImageList:Add( TBitmap():Define( "form",, oChild ), ;          // nImage == 1
                   TBitmap():Define( "frmMask",, oChild ) )
   oImageList:Add( TBitmap():Define( "icon",, oChild ),;           // nImage == 2
                   TBitmap():Define( "icoMask",, oChild ) )
   oImageList:Add( TBitmap():Define( "bitmap",, oChild ),;         // nImage == 4
                   TBitmap():Define( "bmpMask",, oChild ) )

   oTree := TTreeView():New( 2, 0, oChild )

File: docs/reference/classes/TTreeView.md (L1-11)

Code (markdown): Select all Collapse
# TTreeView Class

The `TTreeView` class provides a comprehensive implementation of a tree view control that displays hierarchical data in a collapsible tree structure. It supports advanced features including checkboxes, editable labels, custom images, and extensive event handling.

**Source File:** [source/classes/ttreevie.prg](../../../source/classes/ttreevie.prg)

## Overview

The `TTreeView` class encapsulates the Windows TreeView control, providing a high-level interface for managing hierarchical data structures. As a subclass of `TControl`, it inherits all standard control functionality while adding specialized behavior for tree navigation, item management, and user interaction.

Tree views are essential UI components for displaying hierarchical data such as file systems, organizational charts, navigation menus, and structured data with parent-child relationships.
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 9022
Joined: Thu Oct 06, 2005 08:17 PM
Re: xBrowse and Tree
Posted: Sun Nov 30, 2025 11:14 AM

Your oTree is not a TTreeView object. So you can't use the methods of TTreeView class. With TLinkList (that's what you are using) you have to use the RESOURCE clause of TREEITEM command:

TREEITEM...
    RESOURCE "folder"
    ...

Continue the discussion