Page 1 of 1

Treeview with reactive checkboxes

Posted: Thu Aug 20, 2015 6:51 pm
by Keya
This is a multi-OS demo for a treeview with reactive checkboxes (basically a manual version of Windows "auto-3state" flag, which Mac and Linux dont have). So by reactive I mean "typical expected treeview-with-checkboxes behavior", where when you click a Rootnode all of its Subnodes will be Checked/Unchecked (and just its Subnodes - not Subnodes of other Rootnodes), or if you click a Subnode then its parent Rootnode will be set to Checked/Unchecked/Inbetween depending on the overall status of all its Subnodes.

This is working well in Windows and Linux, but isn't quite right in Mac :( ... the main reason for that is that although the PB docs state that the user can only set to Checked/Unchecked (and only programatically set Inbetween), THIS IS NOT THE CASE ON MAC, WHERE THE USER CAN SET ALL THREE STATES! Im not quite sure how to address this issue - if a solution can't be found perhaps we might have to not use the #PB_TREE_THREESTATE flag and just have it as two-state for Mac, although you can't use the root node to indicate partially-checked then with the Inbetween style.

Code: Select all

#Dlg1  = 0
#Tree1 = 1


Procedure FillTreeWithDemoItems()
  lTreeItem.l = 0
  For lRootNode = 1 To 3
    AddGadgetItem (#Tree1, -1, "Root " + Str(lRootNode) + " <TreeItem index "+Str(lTreeItem)+">", 0, 0)
    lTreeItem + 1
    For lSubNode = 1 To 4
      AddGadgetItem (#Tree1, -1, "Subnode " + Str(lSubNode) + " of Root " + Str(lRootNode) + " <TreeItem index "+Str(lTreeItem)+">", 0, 1)
      SetGadgetItemData(#Tree1, lTreeItem, lTreeItem - lSubNode)  ;ItemData = the RootNode id of this SubNode  <- IMPORTANT! (youll probably get rid of this Procedure, but ensure you still use SetGadgetItemData to set the RootNode id of each Subnode you add)
      lTreeItem + 1
    Next 
    SetGadgetItemState(#Tree1,lTreeItem - lSubNode,#PB_Tree_Expanded)  ;expand the node now we've finished adding to it
  Next lRootNode
EndProcedure



Procedure SetTreeSubnodes(Tree, TreeRootNode, NewState, lastitem.l=0)    ;Set all subnodes of root node TreeRootNode to NewState
  If lastitem.l=0: lastitem = CountGadgetItems(Tree) - 1:  EndIf
  If lastitem <= TreeRootNode+1: ProcedureReturn: EndIf
  For nexttreeitem = TreeRootNode+1 To lastitem
    level = GetGadgetItemAttribute(Tree, nexttreeitem, #PB_Tree_SubLevel)
    If level = 0: Break: EndIf
    SetGadgetItemState(Tree, nexttreeitem, NewState)
  Next nexttreeitem  
EndProcedure


Procedure SetRootNode(Tree, TreeSubnode, lastitem.l=0)     ;Scans all subnodes of a root node and then sets the root node state to Checked/Inbetween/Unchecked depending on overall status of subnode states
  If lastitem.l=0: lastitem = CountGadgetItems(Tree) - 1:  EndIf
  If lastitem <= TreeSubnode+1: ProcedureReturn: EndIf
  nuncheck = 0:  nchecked = 0
  RootNode = GetGadgetItemData(Tree, TreeSubnode)
  For nexttreeitem = RootNode+1 To lastitem
    level = GetGadgetItemAttribute(Tree, nexttreeitem, #PB_Tree_SubLevel)
    If level = 0: Break: EndIf
    If GetGadgetItemState(Tree, nexttreeitem) & #PB_Tree_Checked  ;& not =, as its part of a mask
      nchecked + 1
    Else
      nuncheck + 1
    EndIf
  Next nexttreeitem
  If nchecked > 0 And nuncheck = 0
    SetGadgetItemState(Tree, RootNode, #PB_Tree_Checked)
  ElseIf nchecked > 0 And nuncheck > 0
    SetGadgetItemState(Tree, RootNode, #PB_Tree_Inbetween)
  Else
    SetGadgetItemState(Tree, RootNode, 0) ;Unchecked
  EndIf
EndProcedure


Procedure TreeItemClicked(TreeItem)
  level = GetGadgetItemAttribute(#Tree1, TreeItem, #PB_Tree_SubLevel)
  lastitem = CountGadgetItems(#Tree1) - 1
  If level = 0  ;RootNode clicked
    SetTreeSubnodes(#Tree1, TreeItem, GetGadgetItemState(#Tree1, TreeItem))
  Else          ;SubNode clicked
    SetRootNode(#Tree1, TreeItem)  
  EndIf
EndProcedure


Procedure Tree1Proc(EventType)
  If eventType =  #PB_EventType_Change  ;_Change and _LeftClick both seem to work, but _LeftClick sometimes 'skips' when we click too fast, whereas Change doesnt have that problem
    TreeItemClicked  ( GetGadgetState(#Tree1) ) 
  EndIf
EndProcedure



Procedure Dlg1_Events(event)
  Select event
    Case #PB_Event_CloseWindow
      ProcedureReturn #False
      
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #Tree1
          Tree1Proc(EventType())          
      EndSelect
  EndSelect
  ProcedureReturn #True
EndProcedure



OpenWindow(#Dlg1, x, y, 570, 320, "Treeview Checked Nodes Demo", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_WindowCentered)
TreeGadget(#Tree1, 0, 0, 570, 320, #PB_Tree_CheckBoxes | #PB_Tree_ThreeState)
FillTreeWithDemoItems()

Repeat
  Event = WaitWindowEvent()
  EventWindow = EventWindow()
  Select Event
    Case #PB_Event_CloseWindow
      CloseWindow(EventWindow)
      If EventWindow= #Dlg1:  End:   EndIf 
  EndSelect
  
  Select EventWindow
    Case #Dlg1 
      Dlg1_Events(Event)
      
  EndSelect
ForEver

Re: Treeview with reactive checkboxes

Posted: Thu Aug 20, 2015 7:21 pm
by RSBasic
Very good! Thanks for sharing.

\\Edit:
Please use EnableExplicit:

Code: Select all

EnableExplicit

#Dlg1  = 0
#Tree1 = 1

Define Event
Define EventWindow

Procedure FillTreeWithDemoItems()
  Protected lTreeItem = 0
  Protected lRootNode
  Protected lSubNode
  
  For lRootNode = 1 To 3
    AddGadgetItem (#Tree1, -1, "Root " + Str(lRootNode) + " <TreeItem index "+Str(lTreeItem)+">", 0, 0)
    lTreeItem + 1
    For lSubNode = 1 To 4
      AddGadgetItem (#Tree1, -1, "Subnode " + Str(lSubNode) + " of Root " + Str(lRootNode) + " <TreeItem index "+Str(lTreeItem)+">", 0, 1)
      SetGadgetItemData(#Tree1, lTreeItem, lTreeItem - lSubNode)  ;ItemData = the RootNode id of this SubNode  <- IMPORTANT! (youll probably get rid of this Procedure, but ensure you still use SetGadgetItemData to set the RootNode id of each Subnode you add)
      lTreeItem + 1
    Next
    SetGadgetItemState(#Tree1,lTreeItem - lSubNode,#PB_Tree_Expanded)  ;expand the node now we've finished adding to it
  Next lRootNode
EndProcedure

Procedure SetTreeSubnodes(Tree, TreeRootNode, NewState, lastitem=0)    ;Set all subnodes of root node TreeRootNode to NewState
  Protected nexttreeitem
  Protected level
  
  If lastitem=0: lastitem = CountGadgetItems(Tree) - 1:  EndIf
  If lastitem <= TreeRootNode+1: ProcedureReturn: EndIf
  For nexttreeitem = TreeRootNode+1 To lastitem
    level = GetGadgetItemAttribute(Tree, nexttreeitem, #PB_Tree_SubLevel)
    If level = 0: Break: EndIf
    SetGadgetItemState(Tree, nexttreeitem, NewState)
  Next nexttreeitem 
EndProcedure

Procedure SetRootNode(Tree, TreeSubnode, lastitem=0)     ;Scans all subnodes of a root node and then sets the root node state to Checked/Inbetween/Unchecked depending on overall status of subnode states
  Protected nuncheck
  Protected nchecked
  Protected RootNode
  Protected nexttreeitem
  Protected level
  
  If lastitem=0: lastitem = CountGadgetItems(Tree) - 1:  EndIf
  If lastitem <= TreeSubnode+1: ProcedureReturn: EndIf
  nuncheck = 0:  nchecked = 0
  RootNode = GetGadgetItemData(Tree, TreeSubnode)
  For nexttreeitem = RootNode+1 To lastitem
    level = GetGadgetItemAttribute(Tree, nexttreeitem, #PB_Tree_SubLevel)
    If level = 0: Break: EndIf
    If GetGadgetItemState(Tree, nexttreeitem) & #PB_Tree_Checked  ;& not =, as its part of a mask
      nchecked + 1
    Else
      nuncheck + 1
    EndIf
  Next nexttreeitem
  If nchecked > 0 And nuncheck = 0
    SetGadgetItemState(Tree, RootNode, #PB_Tree_Checked)
  ElseIf nchecked > 0 And nuncheck > 0
    SetGadgetItemState(Tree, RootNode, #PB_Tree_Inbetween)
  Else
    SetGadgetItemState(Tree, RootNode, 0) ;Unchecked
  EndIf
EndProcedure

Procedure TreeItemClicked(TreeItem)
  Protected level
  Protected lastitem
  Protected nchecked
  Protected nunchecked
  
  level = GetGadgetItemAttribute(#Tree1, TreeItem, #PB_Tree_SubLevel)
  lastitem = CountGadgetItems(#Tree1) - 1
  nchecked = -1
  nunchecked = -1
  If level = 0  ;RootNode clicked
    SetTreeSubnodes(#Tree1, TreeItem, GetGadgetItemState(#Tree1, TreeItem))
  Else          ;SubNode clicked
    SetRootNode(#Tree1, TreeItem) 
  EndIf
EndProcedure

Procedure Tree1Proc(EventType)
  If eventType =  #PB_EventType_Change  ;_Change and _LeftClick both seem to work, but _LeftClick sometimes 'skips' when we click too fast, whereas Change doesnt have that problem
    TreeItemClicked  ( GetGadgetState(#Tree1) )
  EndIf
EndProcedure

Procedure Dlg1_Events(event)
  Select event
    Case #PB_Event_CloseWindow
      ProcedureReturn #False
      
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #Tree1
          Tree1Proc(EventType())         
      EndSelect
  EndSelect
  ProcedureReturn #True
EndProcedure

OpenWindow(#Dlg1, 0, 0, 570, 320, "Treeview Checked Nodes Demo", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_WindowCentered)
TreeGadget(#Tree1, 0, 0, 570, 320, #PB_Tree_CheckBoxes | #PB_Tree_ThreeState)
FillTreeWithDemoItems()

Repeat
  Event = WaitWindowEvent()
  EventWindow = EventWindow()
  Select Event
    Case #PB_Event_CloseWindow
      CloseWindow(EventWindow)
      If EventWindow= #Dlg1:  End:   EndIf
  EndSelect
  Select EventWindow
    Case #Dlg1
      Dlg1_Events(Event)
  EndSelect
ForEver

Re: Treeview with reactive checkboxes

Posted: Thu Aug 20, 2015 8:43 pm
by kenmo
Nice useful trick!

I'm not on a Mac to test right now, but...

couldn't you check the ItemState right before calling TreeItemClicked() -- if you intercept a clicked #PB_Tree_InBetween state, immediately change it to #PB_Tree_Checked (or Unchecked). Perhaps?

Re: Treeview with reactive checkboxes

Posted: Fri Aug 21, 2015 7:35 am
by Keya
kenmo thanks, ive tried to implement that :)

and RSBasic yes i will use EnableExplicit for everything from now on, the helpfile suggests its good to prevent typos so it sold me :)

This is a MAC-ONLY version ... (i think in the end we'll have to use CompilerIf's to have a combined Win&Linux version and a Mac version, but first lets just get the Mac code working! ... no biggie though, it may only work out to a few lines difference) :)

One thing with Mac is that SetGadgetItemState works differently in that it seems to not only Check the item, but also SELECT the item, which then causes further issues

Ive found that by specifically setting to 4 (Checked + Unselected) and 2 (Unchecked + Unselected) it seems to work ok. (But these settings aren't compatible at all on Win/Linux)

Anyway I hope other Mac users will try it out and see how it goes and maybe provide some suggestions, as im not very good with Mac.

There is still a problem however... when you click on an item too fast you can still find it being set to Inbetween, even though im trying to override those by setting them to Checked

Another thing is that it seems with Mac we need to use #PB_EventType_LeftClick, but #PB_EventType_Change works better in Windows & Linux

Code: Select all

EnableExplicit

#Dlg1  = 0
#Tree1 = 1

Define Event
Define EventWindow

Procedure FillTreeWithDemoItems()
  Protected lTreeItem, lRootNode, lSubNode 
  For lRootNode = 1 To 3
    AddGadgetItem (#Tree1, -1, "Root " + Str(lRootNode) + " <TreeItem index "+Str(lTreeItem)+">", 0, 0)
    lTreeItem + 1
    For lSubNode = 1 To 4
      AddGadgetItem (#Tree1, -1, "Subnode " + Str(lSubNode) + " of Root " + Str(lRootNode) + " <TreeItem index "+Str(lTreeItem)+">", 0, 1)
      SetGadgetItemData(#Tree1, lTreeItem, lTreeItem - lSubNode)  ;ItemData = the RootNode id of this SubNode  <- IMPORTANT! (youll probably get rid of this Procedure, but ensure you still use SetGadgetItemData to set the RootNode id of each Subnode you add)
      lTreeItem + 1
    Next
    SetGadgetItemState(#Tree1,lTreeItem - lSubNode,#PB_Tree_Expanded)  ;expand the node now we've finished adding to it
  Next lRootNode
EndProcedure



Procedure TreeRootNodeClicked(Tree, TreeRootNode, lastitem=0)    ;Set all subnodes of root node TreeRootNode to NewState
  Protected nexttreeitem, level
  Protected NewState = GetGadgetItemState(#Tree1, TreeRootNode)
  If NewState & #PB_Tree_Inbetween
    NewState = 4
  Else
    NewState = 2
  EndIf
  SetGadgetItemState(Tree, TreeRootNode, NewState)
  If lastitem=0: lastitem = CountGadgetItems(Tree) - 1:  EndIf
  For nexttreeitem = TreeRootNode+1 To lastitem
    level = GetGadgetItemAttribute(Tree, nexttreeitem, #PB_Tree_SubLevel)
    If level = 0: Break: EndIf
    SetGadgetItemState(Tree, nexttreeitem, NewState)
  Next nexttreeitem
EndProcedure




Procedure TreeSubNodeClicked(Tree, TreeSubnode, lastitem=0)    ;Scans all subnodes of a root node and then sets the root node state to Checked/Inbetween/Unchecked depending on overall status of subnode states
  Protected nuncheck, nchecked, RootNode, nexttreeitem, level
  Protected NewState = GetGadgetItemState(#Tree1, TreeSubnode)
  If NewState & #PB_Tree_Inbetween
    NewState = 4  ;Checked and unselected
  Else
    NewState = 2  ;Unchecked and unselected
  EndIf
  SetGadgetItemState(Tree, TreeSubnode, NewState)
  If lastitem=0: lastitem = CountGadgetItems(Tree) - 1:  EndIf
  nuncheck = 0:  nchecked = 0
  RootNode = GetGadgetItemData(Tree, TreeSubnode)
  For nexttreeitem = RootNode+1 To lastitem
    level = GetGadgetItemAttribute(Tree, nexttreeitem, #PB_Tree_SubLevel)
    If level = 0: Break: EndIf
    If GetGadgetItemState(Tree, nexttreeitem) & #PB_Tree_Checked  ;& not =, as its part of a mask
      nchecked + 1
    Else
      nuncheck + 1
    EndIf
  Next nexttreeitem
  If nchecked > 0 And nuncheck = 0
    SetGadgetItemState(Tree, RootNode, 4)  ;Checked and unselected
  ElseIf nchecked > 0 And nuncheck > 0
    SetGadgetItemState(Tree, RootNode, #PB_Tree_Inbetween)
  Else
    SetGadgetItemState(Tree, RootNode, 2)  ;Unchecked and unselected
  EndIf
EndProcedure



Procedure TreeItemClicked(TreeItem)
  Protected level, lastitem, nchecked, nunchecked, ItemState, sTmp.s
  ;If TreeItem = -1: ProcedureReturn: EndIf
  ItemState = GetGadgetItemState(#Tree1, TreeItem)
  level = GetGadgetItemAttribute(#Tree1, TreeItem, #PB_Tree_SubLevel)
  ;If ItemState & #PB_Tree_Checked: sTmp.s = " +CHECKED ": Else: sTmp.s + " -UNCHECKED ": EndIf
  ;If ItemState & #PB_Tree_Inbetween: sTmp.s = " +INBETWEEN ": EndIf
  ;Debug("Item=" + Str(TreeItem) + " (level " + Str(level) + ")  State=" + Str(ItemState) + " " + sTmp )
  lastitem = CountGadgetItems(#Tree1) - 1
  nchecked = -1
  nunchecked = -1
  If level = 0  ;RootNode clicked
    TreeRootNodeClicked(#Tree1, TreeItem)
  Else          ;SubNode clicked
    TreeSubNodeClicked(#Tree1, TreeItem)
  EndIf
EndProcedure



Procedure Tree1Proc(EventType)
  If eventType =  #PB_EventType_LeftClick  ;_Change and _LeftClick both seem to work, but _LeftClick sometimes 'skips' when we click too fast, whereas Change doesnt have that problem
    TreeItemClicked  ( GetGadgetState(#Tree1) )
  EndIf
EndProcedure



Procedure Dlg1_Events(event)
  Select event
    Case #PB_Event_CloseWindow
      ProcedureReturn #False
     
    Case #PB_Event_Gadget
      Select EventGadget()
        Case #Tree1
          Tree1Proc(EventType())         
      EndSelect
  EndSelect
  ProcedureReturn #True
EndProcedure



OpenWindow(#Dlg1, 0, 0, 570, 320, "MAC-ONLY Treeview Checked Nodes Demo", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_WindowCentered)
TreeGadget(#Tree1, 0, 0, 570, 320, #PB_Tree_CheckBoxes | #PB_Tree_ThreeState)
FillTreeWithDemoItems()

Repeat
  Event = WaitWindowEvent()
  EventWindow = EventWindow()
  Select Event
    Case #PB_Event_CloseWindow
      CloseWindow(EventWindow)
      If EventWindow= #Dlg1:  End:   EndIf
  EndSelect
  Select EventWindow
    Case #Dlg1
      Dlg1_Events(Event)
  EndSelect
ForEver

Re: Treeview with reactive checkboxes

Posted: Sat Aug 22, 2015 11:20 am
by mk-soft
Works very fine on Mac :D

Check now the Mouse position because of unchecked error

Code: Select all

Procedure TreeItemClicked(TreeItem)
  Protected level, lastitem, nchecked, nunchecked, ItemState, sTmp.s, pos_x
  
  pos_x = WindowMouseX(EventWindow()) - GadgetX(EventGadget())
  If pos_x > 16
    ProcedureReturn
  EndIf
  ;If TreeItem = -1: ProcedureReturn: EndIf
  ItemState = GetGadgetItemState(#Tree1, TreeItem)
  level = GetGadgetItemAttribute(#Tree1, TreeItem, #PB_Tree_SubLevel)
  ;If ItemState & #PB_Tree_Checked: sTmp.s = " +CHECKED ": Else: sTmp.s + " -UNCHECKED ": EndIf
  ;If ItemState & #PB_Tree_Inbetween: sTmp.s = " +INBETWEEN ": EndIf
  ;Debug("Item=" + Str(TreeItem) + " (level " + Str(level) + ")  State=" + Str(ItemState) + " " + sTmp )
  lastitem = CountGadgetItems(#Tree1) - 1
  nchecked = -1
  nunchecked = -1
  If level = 0  ;RootNode clicked
    TreeRootNodeClicked(#Tree1, TreeItem)
  Else          ;SubNode clicked
    TreeSubNodeClicked(#Tree1, TreeItem)
  EndIf
EndProcedure
Thanks to share :wink: