Custom TreeGadget with column support, cross-platform

Share your advanced PureBasic knowledge/code with the community.
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

Re: Custom TreeGadget with column support, cross-platform

Post by Kukulkan »

Keya wrote:... but im sad to read you've discontinued it!
I only discontinued the initial code, which is not a module. I still use the module version by myself and, if I need more functionality or find bugs, I will post the updates here :wink:

The most recent module version is up in the thread. It is also directly linked in the top post. I recommend to use the module version only as it also contains some fixes and the changes are quickly done if you use search and replace.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Custom TreeGadget with column support, cross-platform

Post by Keya »

Code: Select all

   Procedure.i _cust_tree_sort(List tree.ct_entry(), Mode.i)
      SortStructuredList(tree(), Mode.i, OffsetOf(ct_entry\text), #PB_String)
      ForEach tree()
         If ListSize(tree()\childs()) > 0
            _cust_tree_sort(tree()\childs(), Mode.i)
         EndIf
      Next
   EndProcedure
thats surprisingly easy to sort by the main parent text! :) but is it possible to sort by Column text? i am thinking for when user clicks on a Column header. (and how to detect when they click Column header? _cust_tree_checkColumnSizer?)

--
ps. customize() is missing an entry for linecolor, had me scratching my head for a bit heehee :)

Code: Select all

         If *config\lineColor <> #PB_Ignore  
            \lineColor = *config\lineColor
         EndIf
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

Re: Custom TreeGadget with column support, cross-platform

Post by Kukulkan »

Hi keya,

I just updated the module code in the thread. New functionality:

sortColumn(*treeId.ct_main, columnId.i, Mode.i = #PB_Sort_Ascending | #PB_Sort_NoCase)

Sort the TreeView by a given column. If you submit 0 as columnId, it is the same like calling normal sort(). If columnId > 0, it sorts by column content.

ColumnID.i = mouseOnHeader(*treeId.ct_main, x.i = #PB_Ignore, y.i = #PB_Ignore)

Returns the column ID if the current mouse position if above a header. Returns -1 if the mouse or coordinates are not above a header. It is possible to directly use this as input for sortColumn() (see hacky example code).

Your fix about missing \lineColor in customize() is also included. Thanks for this!

Best,

Kukulkan
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Custom TreeGadget with column support, cross-platform

Post by Keya »

Brilliant, thankyou!! trying your new version now :)

btw there is a GetSelected, but not a SetSelected? that would be really handy so we can implement #PB_EventType_KeyDown for Up/Down keys, or as the result of a Search for example :)

also no DeleteItem or InsertItem - possible? (surely not as simple as DeleteElement/InsertElement!?!? do we have to unlink child/parent ptr's, or...?) :) It doesn't have a checkboxes option either, but I can't think of anything else it needs though, pretty much the full package now!!!

although one thing im doing at the moment is manually setting the color of items depending on their string (which is amazingly easy thanks to your control!!), but perhaps adding a forecolor element to ct_entry would be a better allround approach?

it really is a fantastic control, i'm loving it and also learning about Purebasic custom controls for the first time so lots of fun for this weekend heehee

I just ran a test with 1,000,000 elements ... it still loaded in only 2 seconds (initialization that is -- open/close is still instant!), and performance was still excellent! :)

ps. i added a Static to remember the Mode to alternate between ascend/descend, its kinda ugly though - im sure there's a better way to switch between the two, the ~Not operator perhaps!? but this works for me in the meantime lol

Code: Select all

  ; sort all entries in the threeview by column incl. redraw
  Procedure sortColumn(*treeId.ct_main, columnId.i, Mode.i = #PB_Sort_Ascending | #PB_Sort_NoCase)
;---------------
     Static LastMode   ;this ignores the Mode parameter
     If LastMode = #PB_Sort_Ascending | #PB_Sort_NoCase
        LastMode = #PB_Sort_Descending | #PB_Sort_NoCase
     Else
        LastMode = #PB_Sort_Ascending | #PB_Sort_NoCase
     EndIf
;---------------
    If columnId.i < 0
      ; no sort
      ProcedureReturn
    EndIf
    If columnId.i = 0
      ; standard sort
      _cust_tree_sort(*treeId\childs(), LastMode.i)
    Else
      ; column sort
      _cust_tree_column_sort_prepare(*treeId\childs(), columnId.i - 1)
      _cust_tree_column_sort(*treeId\childs(), LastMode.i)
    EndIf
    redraw(*treeId)
  EndProcedure
And here is Expand/Collapse All Child Nodes Of Selected Item :) its great how you've linked them all together for easy traversal both up the parents and down the childs (it is childs, yes? children doesnt sound quite right heehee)

Code: Select all

Procedure ExpandCollapseChilds(*tree.ct_main, *Selected.ct_entry, newstate.i)  ;newstate=#collapsed(0)/#open(1)
  *Selected\status = newstate
  ForEach *Selected\childs()         
    *Selected\childs()\status  = newstate
  Next
  redraw(*tree)        
EndProcedure
and just a general question about custom controls, is the secret to implementing scrollbars simply printing partially offscreen? ie. scrollbar 5 units to the right = start painting 5 pixels to the left of the actual start ie. X = -5 ?
also have you ever used PB's ScrollArea gadget? i tried the demo for it in the helpfile and it looks like it could be applicable to your tree gadget so im wondering if it would make this even easier?
Last edited by Keya on Mon Dec 21, 2015 10:33 am, edited 14 times in total.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Custom TreeGadget with column support, cross-platform

Post by Keya »

just one minor glitch, if you resize a column the click triggers a column sort also heehee, i guess it needs to detect if the mouse is over the column line bit

and here's just a minor cosmetic enhancement! you already have a dark line at the bottom of the header, so simply by complementing it with a single light line at the top its an inexpensive way to add a nice faux 3d touch to the otherwise flat Header :)

Code: Select all

Procedure _cust_tree_redraw_header() ...
... at the bottom ...
      ; horizontal line
      Line(0, \lineHeight-2, \width, 1, \customization\lineColor)
      Line(0, 0, \width, 1, RGB(255,255,255))        ;<-- my amazing contribution. I am not liable for any damages
Image
it seems to overdraw the column line though - you can see its missing a pixel, perhaps just needs to be painted in different order! such a minor thing though so im not fussed if that pixel needs to stay lost, the added 3d look is worth it

{edit} i just inadvertently discovered another one heehee, also in the same procedure

Code: Select all

        Line(\header(c.i)\position + \header(c.i)\width + left.i, 0, 1, \height, \customization\lineColor)
Simply by changing "left.i" to "left.i-1" it achieves the vertical 3d in header part of the separating lines:
Image

and my other cosmetic... currently i get the feeling of an "empty icon", so i increased the width of the connecting line:
Image
that is, in _cust_tree_redraw_item, a simple change of "+ 14" (just felt right to me, might not to others!) to the second Line() statement:

Code: Select all

          Line(lineX.i,
               yEntry.i + \lineHeight + lineH.i,
               (\lineHeight / 2) + 14,             ;<----- added "+ 14", make into a #configvar perhaps?
               1,
               #tree_fx_linecolor)
just icing on the cake stuff heehee

Another thing i found amazing about your control ...
BEFORE:
textWidth = DrawText(xEntry.i, yEntry.i, tree()\text, \customization\foregroundColor, \customization\backgroundColor)
AFTER:
textWidth = DrawText(xEntry.i, yEntry.i, "X", RGB(255,0,0), \customization\backgroundColor)
textWidth + DrawText(xEntry.i+14, yEntry.i, tree()\text, \customization\foregroundColor, \customization\backgroundColor)
=
Image
multiple colors in the same line, its like richtext! and it was so simple! oh my gosh THE POWER!!! lol (room for a Checkbox there!) :)
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

Re: Custom TreeGadget with column support, cross-platform

Post by Kukulkan »

Hello Keya,

yes, this is the good things about having the source code. You can adapt to your needs :) Thank you for the kind words.

But I do not agree to your automatic ascending/descending order switch because of two reasons:

1) It ignores the procedure parameters a user has set and therefore breaks the API
2) If forces the option to work exactly this way with no respect of the used column or developers with. I believe the developer who calls the sortColumn() function should implement this kind of switching by himself.

Best,

Kukulkan
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

Re: Custom TreeGadget with column support, cross-platform

Post by Kukulkan »

I forgot to answer a question:
btw there is a GetSelected, but not a SetSelected?
Because getting the status of all selected trees is somehow looping recursively and not that easy. Thus I thought a helping function would be handy. But to set an entry as selected is very easy as you can access the elements structure directly:

Code: Select all

*anyEntry\selected = #True
You simply access the element you like to be selected and set it's "selected" property to #True. You might need to call redraw() after this to update the tree.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Custom TreeGadget with column support, cross-platform

Post by Keya »

thankyou but you made me realize i only asked half the question! after setting it as Selected, how do we set focus on it to ensure it's shown?
imagine for example the user types in a search query to find an item in the tree list, so it needs to go to that item :)
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

Re: Custom TreeGadget with column support, cross-platform

Post by Kukulkan »

Keya,

you are free to add some ensureVisibility(*anyEntry) function :wink:
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Custom TreeGadget with column support, cross-platform

Post by Keya »

if i could i wouldn't have asked :) but i get the hint!
User avatar
mk-soft
Always Here
Always Here
Posts: 5335
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Custom TreeGadget with column support, cross-platform

Post by mk-soft »

Nice Work :wink:
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

Re: Custom TreeGadget with column support, cross-platform

Post by Kukulkan »

UPDATE, 02 Feb 2018
On MacOS High Sierra, Apple changed the behavior of NSDeviceResolution. Due to the fact that PB is still not DPI aware, on Retina Displays it now does no longer work. I removed DPI awareness for MacOS because of this and replaced it by a static 1:1 conversion (check _cust_tree_DetermineSystemDPIFactor() function).

I only updated the Module version!
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

Re: Custom TreeGadget with column support, cross-platform

Post by Kukulkan »

UPDATE 13 July 2018
I just updated the TreeView module. It now offers an optional search feature and also did some small refactoring.

The code is to big for the forum (>60K) so you have to download here:
http://www.inspirant.de/download/treeViewEx.pbi

Best,

Kukulkan
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

Re: Custom TreeGadget with column support, cross-platform

Post by Kukulkan »

UPDATE 19 JULY 2018
Another update with enhanced functionality:

* Offers Checkbox-Mode (allow to check/uncheck entries by mouse or Space key)
* Now with cursor navigation (up/down for selection and left/right to open/close nodes)
* A bit enhanced optics
* New functions:
** TreeView::remove() for cleanup (needed if created/removed multiple times)
** TreeView::getByItemDataStr() for getting entries based on ItemData string
** TreeView::getByItemDataPtr() for getting entries based on ItemData pointer

See example calls at the end of the include.
nicoh
User
User
Posts: 26
Joined: Thu Sep 19, 2019 10:44 am

Re: Custom TreeGadget with column support, cross-platform

Post by nicoh »

Hi,

if you use the mouse wheel to scroll down, the bottom of the scroll bar will never be reached, because the scrolling stops a bit earlier.
When you click or drag the scroll bar to the bottom instead, it works as expected.
So, it's an issue inside your event handler.

In _cust_tree_eventHandler() change these lines:

Code: Select all

    If gadEvt.i = #PB_EventType_MouseWheel
      ; Mouse wheel scrolling
      *tree\scrollY = *tree\scrollY - (GetGadgetAttribute(gadId.i, #PB_Canvas_WheelDelta) * *tree\lineHeight)
      If *tree\scrollY > *tree\treeHeight - *tree\height + #tree_fx_scrollBarSize
        *tree\scrollY = *tree\treeHeight - *tree\height + #tree_fx_scrollBarSize
      EndIf
      If *tree\scrollY < 0: *tree\scrollY = 0: EndIf
      SetGadgetState(*tree\vScrollId, *tree\scrollY / *tree\lineHeight)
      ; redraw tree
      redraw(*tree) 
    EndIf
To:

Code: Select all

    If gadEvt.i = #PB_EventType_MouseWheel
      ; Mouse wheel scrolling
      
      Protected.i State, Max, Min
      
      State = GetGadgetState(*tree\vScrollId) - GetGadgetAttribute(gadId.i, #PB_Canvas_WheelDelta)
      Min = GetGadgetAttribute(*tree\vScrollId, #PB_ScrollBar_Minimum)
      Max = GetGadgetAttribute(*tree\vScrollId, #PB_ScrollBar_Maximum) - GetGadgetAttribute(*tree\vScrollId, #PB_ScrollBar_PageLength) + 1
      
      If State < Min : State = Min : EndIf 
      If State > Max : State = Max : EndIf
      
      SetGadgetState(*tree\vScrollId, State)
      *tree\scrollY = State * *tree\lineHeight
      
      ; redraw tree
      redraw(*tree) 
    EndIf
And this bug should be fixed.
Post Reply