Tree Gadget Mini-Modul und Testprogramm

Für allgemeine Fragen zur Programmierung mit PureBasic.
puretom
Beiträge: 109
Registriert: 06.09.2013 22:02

Tree Gadget Mini-Modul und Testprogramm

Beitrag von puretom »

Hi Leute!

Mein Eintrag ist ein Code-Schnippsel und einige Fragen in einem!

Nachdem ich ja mein Skriptsprachentutorial (siehe Signatur) begonnen habe, bekam ich wieder Lust, eine Adventure-Engine anzufangen (die 543. nicht vollendete, denke ich :lol: ).

Dazu brauche ich aber Tree Gadgets für einen Projekt Tree a la Quest:
Bild

Da ich mit dem Tree Gadget schon einmal nur Ärger hatte (habe das damals mit einer Linked List gelöst, mit der ich dann den Tree jedesmal neu befüllt habe bei einer Änderung), wollte ich es jetzt besser machen.

Also habe ich mich an eine Designstudie gesetzt (die mich furchtbar geärgert hat und 3 Tage gekostet hat >_< ), die ich euch nicht vorenthalten will.

Besonderes Augenmerk möchte ich auf 5 Dinge lenken:
  • CreateTreeItem() wollte ewig nicht ordentlich funktionieren, wenn das Element 0 schon ein Child-Node hatte (Level 1), dann war es nicht mehr möglich, ein neues Item auf dem 0-Level einzufügen. Konsequent erschien es im Child Node des Element 0. Nur durch Herumprobieren habe ich es hingekriegt. Warum es so funktioniert, verstehe ich nicht! Bitte um Hilfe und Erklärungen!
  • Modul-Methoden für das Löschen und Umbenennen sind nicht notwendig, weil das das Gadget nativ toll hinkriegt.
  • Ich habe eine kleine Möglichkeit eingebaut, die Trees in Textdateien zu laden und aus ihnen wieder zu speichern (Beispieldatei liegt bei.
  • Meine Methode GetParentOfTreeItem(), mit der ich die Nummer des Parent Nodes eines Nodes ermittle, muss doch auch ohne Schleife mittels API gehen? Oder? Bitte um Tipps durch die API-Könner!
  • In der Debug-Ausgabe feuert das Event 2-mal, was man auch am doppelten Text für das angeklickte Node erkennt. Kann man das verhindern, bzw. welche 2 Events sind das?
Viel Spaß damit! :)

Das Modul Tree:

Code: Alles auswählen

  EnableExplicit

; *******************************************************************
; * Konzept für ein Module TREE GADGET            
; * (c)2014 Puretom, viel Spaß damit
; *******************************************************************
  
  DeclareModule Tree
  
    Declare SaveTreeToDisk(file_ID,path.s, tree_gadget_ID)
    Declare LoadTreeFromDisk(file_ID, path.s, tree_gadget_ID)
    Declare GetParentOfTreeItem(tree_gadget_ID, item_nr)
    Declare CreateTreeItem(tree_gadget_ID, item_text.s, click_selected_item_nr, sub_item )
    
  EndDeclareModule 
  Module Tree
  
  EnableExplicit
  
; File System
;  
  Procedure SaveTreeToDisk(file_ID,path.s, tree_gadget_ID)
    
    Protected item_nr, max_items
    
  ; File öffnen,
    CreateFile(file_ID, path)

  ; Speichert folgende Daten in ein File als Strings:
  ;   1. Text des Gadget Nodes
  ;   2. Sublevel-Nummer des Gadget Nodes
  ; Anmerkung: Man könnte 2 auch mit z.B WriteAsciiCharacter o.Ä.
  ;            speichern, aber zum besseren Herzeigen, hier als Text
  
    max_items = CountGadgetItems(tree_gadget_ID)-1     
    For item_nr = 0 To max_items
      WriteStringN(file_ID, GetGadgetItemText(tree_gadget_ID, item_nr))
      WriteStringN(file_ID, Str(GetGadgetItemAttribute(tree_gadget_ID, item_nr, #PB_Tree_SubLevel )))
    Next

    CloseFile(file_ID)
  
  EndProcedure
  Procedure LoadTreeFromDisk(file_ID, path.s, tree_gadget_ID)
  
    Protected item_nr
    Protected node_name.s, node_type, node_sublevel
  
    ClearGadgetItems(tree_gadget_ID)
    
    item_nr=0
    ReadFile(file_ID, path)
    
    While Eof(0) = 0         
     
      ; Daten aus Datei laden
        node_name.s  = ReadString(file_ID)
        node_sublevel= Val(ReadString(file_ID))

      ; Tree Node erzeugen
      ; hier könnte man gleich das Image dazugeben -------.
      ; wenn man nicht modular bleiben möchte             |
      ;                                                   v
        AddGadgetItem(tree_gadget_ID, item_nr, node_name, 0, node_sublevel)
        
      ; Next Item Nr  
        item_nr+1
        
    Wend
    
    CloseFile(file_ID)
  
  EndProcedure

; Parent eines Items ermitteln
; ( das muss doch auch mit API und ohne Schleife gehen? Oder ? )
  Procedure GetParentOfTreeItem(tree_gadget_ID, item_nr)
   
    Protected sub_level

    sub_level=GetGadgetItemAttribute(tree_gadget_ID, item_nr, #PB_Tree_SubLevel)  
    If sub_level<>0 
      Repeat
        item_nr-1
      Until sub_level>GetGadgetItemAttribute(tree_gadget_ID, item_nr, #PB_Tree_SubLevel)
    Else
      item_nr=-1
    EndIf
    
    ProcedureReturn item_nr
    
  EndProcedure

; Create Tree Item
;
  Procedure CreateTreeItem(tree_gadget_ID, item_text.s, click_selected_item_nr, sub_item )
  
    Protected new_item_nr, sub_level
    
  ; Falls es sich um ein Unter-Item handelt: sub_item=1, sonst sub_item=0
  ; das Sub-Level wird GRÖSSER, wenn Unter-Level
    sub_level = GetGadgetItemAttribute(tree_gadget_ID, click_selected_item_nr, #PB_Tree_SubLevel) + sub_item
    
  ; ??? Eine sehr seltsame Verhaltensweise, die ich durch reines Probieren herausgefunden habe ???   
  ; ??? Keine Ahnung, warum das so sein muss ???
    new_item_nr = click_selected_item_nr
    If sub_item=1: new_item_nr+1: EndIf
    
  ; anlegen  
    AddGadgetItem(tree_gadget_ID, new_item_nr, item_text, 0, sub_level )     
    SetGadgetItemState(tree_gadget_ID, click_selected_item_nr,  #PB_Tree_Expanded )
    
  EndProcedure

  EndModule  
Das dazugehörende Testprogramm Pure Board Tree Gadget (Form).pbf :

Code: Alles auswählen

Enumeration FormWindow
  #Window_TestTreeGadgetModule
EndEnumeration

Enumeration FormGadget
  #Tree_Project
  #Editor_Explains
  #Container_0
  #Button_Loeschen
  #Button_Umbenennen
  #Button_Debug_Anzeige_Loeschen
  #Button_NeuesUnterItem
  #Button_LoadTree
  #Button_SaveTree
  #Button_NeuesItem
EndEnumeration

Enumeration FormFont
  #Font_Window_TestTreeGadgetModule_0
EndEnumeration

LoadFont(#Font_Window_TestTreeGadgetModule_0,"Courier", 8)

Declare ResizeGadgetsWindow_TestTreeGadgetModule()

Declare Button_LoadTree(EventType)
Declare Tree_Project(EventType)
Declare Button_Debug_Anzeige_Loeschen(EventType)
Declare Button_NeuesUnterItem(EventType)
Declare Button_Umbenennen(EventType)
Declare Button_NeuesItem(EventType)
Declare Button_Loeschen(EventType)
Declare Button_SaveTree(EventType)

Procedure OpenWindow_TestTreeGadgetModule(x = 0, y = 0, width = 655, height = 635)
  OpenWindow(#Window_TestTreeGadgetModule, x, y, width, height, "Pure Board Tree Gadget Test Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_SizeGadget | #PB_Window_ScreenCentered)
  TreeGadget(#Tree_Project, 0, 0, 370, 460)
  EditorGadget(#Editor_Explains, 370, 0, 285, 635, #PB_Editor_WordWrap)
  SetGadgetFont(#Editor_Explains, FontID(#Font_Window_TestTreeGadgetModule_0))
  ContainerGadget(#Container_0, 0, 460, 370, 175)
  ButtonGadget(#Button_Loeschen, 235, 145, 125, 22, "Item löschen")
  ButtonGadget(#Button_Umbenennen, 235, 105, 125, 22, "Item umbenennen")
  ButtonGadget(#Button_Debug_Anzeige_Loeschen, 30, 10, 300, 22, "Debuganzeige löschen")
  GadgetToolTip(#Button_Debug_Anzeige_Loeschen, "Löscht den Editor, der als Debugausgabe dient.")
  ButtonGadget(#Button_NeuesUnterItem, 235, 75, 125, 22, "Neues Unter-Item")
  GadgetToolTip(#Button_NeuesUnterItem, "Legt auf einem Level über dem selektierten Element ein Item an.")
  ButtonGadget(#Button_LoadTree, 10, 110, 95, 22, "Tree laden")
  GadgetToolTip(#Button_LoadTree, "Lädt tree von einem Texfile")
  ButtonGadget(#Button_SaveTree, 10, 80, 95, 22, "Tree speichern")
  GadgetToolTip(#Button_SaveTree, "Speichert den Tree in ein Textfile")
  ButtonGadget(#Button_NeuesItem, 235, 50, 125, 22, "Neues Item")
  GadgetToolTip(#Button_NeuesItem, "Legt auf demselben Level wie das selektierte Element ein Item an.")
  CloseGadgetList()
EndProcedure

Procedure ResizeGadgetsWindow_TestTreeGadgetModule()
  Protected FormWindowWidth, FormWindowHeight
  FormWindowWidth = WindowWidth(#Window_TestTreeGadgetModule)
  FormWindowHeight = WindowHeight(#Window_TestTreeGadgetModule)
  ResizeGadget(#Tree_Project, 0, 0, 370, FormWindowHeight - 175)
  ResizeGadget(#Editor_Explains, 370, 0, FormWindowWidth - 370, FormWindowHeight - 0)
  ResizeGadget(#Container_0, 0, FormWindowHeight - 175, 370, 175)
  ResizeGadget(#Button_Loeschen, 235, 145, 125, GadgetHeight(#Container_0) - 153)
  ResizeGadget(#Button_Umbenennen, 235, 105, 125, GadgetHeight(#Container_0) - 153)
  ResizeGadget(#Button_Debug_Anzeige_Loeschen, 30, 10, 300, GadgetHeight(#Container_0) - 153)
  ResizeGadget(#Button_NeuesUnterItem, 235, 75, 125, GadgetHeight(#Container_0) - 153)
  ResizeGadget(#Button_LoadTree, 10, 110, 95, GadgetHeight(#Container_0) - 153)
  ResizeGadget(#Button_SaveTree, 10, 80, 95, GadgetHeight(#Container_0) - 153)
  ResizeGadget(#Button_NeuesItem, 235, 50, 125, GadgetHeight(#Container_0) - 153)
EndProcedure

Procedure Window_TestTreeGadgetModule_Events(event)
  Select event
    Case #PB_Event_SizeWindow
      ResizeGadgetsWindow_TestTreeGadgetModule()
    Case #PB_Event_CloseWindow
      ProcedureReturn #False

    Case #PB_Event_Menu
      Select EventMenu()
      EndSelect

    Case #PB_Event_Gadget
      Select EventGadget()
        Case #Tree_Project
          Tree_Project(EventType())          
        Case #Button_Loeschen
          Button_Loeschen(EventType())          
        Case #Button_Umbenennen
          Button_Umbenennen(EventType())          
        Case #Button_Debug_Anzeige_Loeschen
          Button_Debug_Anzeige_Loeschen(EventType())          
        Case #Button_NeuesUnterItem
          Button_NeuesUnterItem(EventType())          
        Case #Button_LoadTree
          Button_LoadTree(EventType())          
        Case #Button_SaveTree
          Button_SaveTree(EventType())          
        Case #Button_NeuesItem
          Button_NeuesItem(EventType())          
      EndSelect
  EndSelect
  ProcedureReturn #True
EndProcedure
Das dazugehörende Testprogramm Pure Board Tree Gadget.pb :

Code: Alles auswählen


; *******************************************************************
; * Beispielprogramm für die Nutzung des Moduls TREE GADGET         
; * (c)2014 Puretom                                                 
; *******************************************************************

  XIncludeFile "Pure Board Tree Gadget (Form).pbf"

; Handler-Routinen für die Buttons
; 
  Procedure Button_NeuesItem(EventType)
      
  Protected item_text.s, click_selected_item_nr 
  
  ; Daten aus dem Tree holen
  ; holt item_nr des aktuell geklickten Items
    click_selected_item_nr = GetGadgetState(#Tree_Project) 
  
  ; Item anlegen
    item_text= InputRequester("Namen eingeben",
               "Geben Sie den Namen des neuen Items ein:","")
    
    If item_text<>""   
      Tree::CreateTreeItem(#Tree_Project, item_text, 
                           click_selected_item_nr, 0)
    EndIf
    
  EndProcedure
  Procedure Button_NeuesUnterItem(EventType)
      
  Protected item_text.s, click_selected_item_nr 
  
  ; Daten aus dem Tree holen
  ; holt item_nr des aktuell geklickten Items  
    click_selected_item_nr = GetGadgetState(#Tree_Project)
  
  ; Item anlegen
    item_text= InputRequester("Namen eingeben",
               "Geben Sie den Namen des neuen Items ein:","")
    
    If item_text<>""   
      Tree::CreateTreeItem(#Tree_Project, item_text, 
                           click_selected_item_nr, 1)
    EndIf
    
  EndProcedure
  Procedure Button_SaveTree(EventType)
    Protected path.s
    
    path=SaveFileRequester("Tree laden",GetCurrentDirectory(),"Text-Dateien (*.txt) | *.txt",0)
    If path<>""
      Tree::SaveTreeToDisk(0,path, #Tree_Project)  
    EndIf
    
  EndProcedure
  Procedure Button_LoadTree(EventType)
    
    Protected path.s
    
    path=OpenFileRequester("Tree laden",GetCurrentDirectory(),"Text-Dateien (*.txt) | *.txt",0)
    If path<>""
      Tree::LoadTreeFromDisk(0,path, #Tree_Project)  
    EndIf
    
  EndProcedure
  Procedure Button_Debug_Anzeige_Loeschen(EventType)
    SetGadgetText(#Editor_Explains,"")  
  EndProcedure
  Procedure Button_Umbenennen(EventType)
    
    Protected item_text.s
  
    ; Item anlegen
      item_text= InputRequester("Namen eingeben",
                 "Geben Sie den Namen des neuen Items ein:","")
      
      If item_text<>""   
        SetGadgetText(#Tree_Project,item_text)
      EndIf
      
  EndProcedure
  Procedure Button_Loeschen(EventType)
    
    Protected item_nr
    
    item_nr=GetGadgetState(#Tree_Project)    
    RemoveGadgetItem(#Tree_Project, item_nr)
    
  EndProcedure
  
; Event des Tree Gadgets (hier, irgendein Event, z.B. Click)
; trägt Infos in den Debug-Editor ein
;  
  Procedure Tree_Project(EventType)
    AddGadgetItem(#Editor_Explains, -1, 
         LSet("select: "+GetGadgetText(#Tree_Project),35," ")+
         LSet("| Node: "  +Str(GetGadgetState(#Tree_Project)),15," ")+
         LSet("| Level: "+Str(GetGadgetItemAttribute(#Tree_Project, 
                     GetGadgetState(#Tree_Project), 
                     #PB_Tree_SubLevel)),15," ")+
         "| Parent: "+Str(Tree::GetParentOfTreeItem(#Tree_Project, 
                          GetGadgetState(#Tree_Project))))
  EndProcedure
  
; Start und Event-Schleife des Test-Programms
;    
  Procedure RunTestProgram()
    
    Protected  event, text$
    
  ; Öffne Window und setze Erklärungen ein
    OpenWindow_TestTreeGadgetModule()
  
  ; Event-Schleife
    Repeat
      event = WaitWindowEvent()
      Window_TestTreeGadgetModule_Events(event)    
    Until event = #PB_Event_CloseWindow
    
  EndProcedure
  
  RunTestProgram()
  

Ein Beispiel Tree, einfach als eine Textdatei speichern und mit dem Laden-Knopf laden:

Code: Alles auswählen

Objects
0
game
1
Verbs
2
Verb: answer
3
Commands
2
Dark corridor
1
player
2
Shirt
3
Cord trousers
3
Black Shoes
3
cell phone
1
Functions
0
Timers
0
cell phone call 1
1
ringing phone stopper
1
Walkthrough
0
Advanced
0
Settings
1

(Enter-Taste hinter dem letzten 1er ist wichtig, sonst erkennt ReadString() den String nicht)


Also, viel Spaß damit!

Und ich bitte um Verbesserungsvorschläge und Tipps zum Umgang mit Trees (siehe markierte Fragen oben), denn so ganz im Griff hatte ich die Sache nicht. :lol:

LG Puretom!


Edit: Kleiner Bug in GetParentOfTreeItem() ausgebessert

Code: Alles auswählen

Else
      item_nr=-1
statt

Code: Alles auswählen

Else
      item_nr=0
sonst kann man nicht unterscheiden, ob das Parent Node das Element 0 ist oder ob das Parent Node nicht vorhanden ist, weil wir schon auf der obersten Ebene sind. Erkennungsmarke ist für das oberste Level jetzt mal willkürlich -1.
Windows 7 und Windows 10 (Laptop), PB 5.62 | Projekt: Tutorial - Compiler und Virtual Machine | vielleicht einmal ein Old School Text-Adventure Tutorial | Neu: Spielereien, Üben rund um OOP in PB