Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Searching Treeview

Status
Not open for further replies.

Scott24x7

Programmer
Jul 12, 2001
2,826
JP
Hi All,
I got the treeview working now that lets me navigate various tables and records, really like it.
But today I started the process to "search" the tree, similar to the way searching in a grid for names or values. But, I found rather quickly I am not sure how to go about that. Particularly when I have several layers, and a "word" could easily be duplicated.
Just need a point in the right direction. Any suggestions?
Thanks.

Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
Simple idea. Locate/Seek in the table, that is the source of the treeview nodes. Then lookup the key. Since that also is your node key you'll also find the node with it.

loNode = this.Nodes.Item(key) is the node you search, where key must be the same string you gave as parameter to Nodes.Add(), eg STR(id) or TRANSFORM(id) or whatever you used.

Bye, Olaf.


 
Olaf,
Perfect. I felt like I was missing something obvious, and that is certainly it! I didn't think to seek in the tables first, DUH. Thanks, just the direction I was looking for.
Cheers.

Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
Scott,

I see Olaf has given you a good answer.

However, for future information, if you ever want to search the actual tree rather than the underlying data, you can do it by iterating the Nodes collection:

Code:
llSuccess = .F.
FOR EACH loNode IN this.nodes
  IF ALLTRIM(UPPER(<what you are searching for>) = ALLTRIM(UPPER(loNode.Text))
    * You've found what you are looking for
    llSuccess = .T.
    EXIT
  ENDIF
ENDFOR
IF NOT llSuccess
  * Search failed
ENDIF

Of course, you can adjust it to cater for substring searches, etc.

Mike



__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Mike,
That's also brilliant. I'm wondering though as well (I can see a time I might need this) is it possible to only look at nodes at certain "tree level"? I haven't figured out how to identify the tree level... it's too bad there isn't something like an Array element where you reference (row,column) style. But I've not seen anything that matches to that idea. Is there a way to tell what tree level it's on? I notice Index just returns a value for the tree as though it were a 1 dimensional array.


Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
You could create cursor with meta data about nodes and put any info you need in there, while populating it.

Bye, Olaf.
 
Scott, the Node object has various properties that will tell where the node sits within the treee. I'm thinking of things like Child, Parent, Children, NextSibling, LastSibling, and several others. You can also use the Key to help with that. For example, if you tree contains cities within provinces within countries, you could prefix the keys of all your city nodes with CY, povince nodes with PR, and so on.

Also, there is a Tag property that you can use for anything you like. You could decide to use it to hold a simple number, which represents the level of the node within the tree.

Once you know which level you are on, it will be easy to adjust my code so that you search only those nodes.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Mike,
How funny, but that was "intuitively" exactly what I had done... I had added a "Alpha" value based on the table name to the front of the key. I didn't think about the ability to use just that as I look through them, but that's spot on.

I noticed one "weird" behavior though in searching, I tried to set the following:

This.Parent.Treeviewbase1.Nodes(loNode.Index).Selected = .T.

I was expecting that node to be highlighted (like a single-click on it) but it isn't... It does seem to "advance" the tree to that point if the node is not in the view but there was no selection that is visible, (And the node is visible). When I added:

This.Parent.Treeviewbase1.Nodes(loNode.Index).Expanded = .T.

Then it expands the view but the search is happening from an interactive change, so it opens nodes I don't want to have open. (I guess I could close them before the next interactive change move, but what I really was hoping to do was just highlight the node in the tree...)
Did I miss something?

Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
Scott, I don't understand why setting the node's Expanded to .T. would open nodes that you don't want to have open. Assuming you want to highlight the node that was found in the search, I would have thought that your code would do that. Am I missing something?

I can see how you might select a node that's outside the visible portion of the tree. In that case, the tree doesn't automatically scroll to bring the node into view. But that's a separate problem. I thought there was a property called something like MakeVisible that would solve that, but it seems there isn't (I might be getting confused with the Listview control). So, sorry, but I can't suggest anything for this.

One other point, instead of doing this:

[tt]This.Parent.Treeviewbase1.Nodes(loNode.Index).Expanded = .T.[/tt]

wouldn't the following achieve the same effect?
[tt]
loNode.Expanded = .T.[/tt]

Mike


__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Got it. The Node has got an method called EnsureVisible. Call this method for the node in question, passing .T. as the parameter. This will automatically scroll the tree to bring the node into view.

Mike



__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Another point to consider -- if you have a lot of nodes to add to a treeview it is very slow. I typically only load the root nodes first. Foe each node added I add a single child node with specific text in the name. Then on the Expand() event, I test the child to see if it this 'dummy' node. If it is the dummy node, then I remove it and add the children from the data source (and for each child added a new child is added to the child as a dummy). This gives a very responsive treeview control.

The problem now is when you search in the data source as shown above, you might find a node that has not yet been added. You can trap this in TRY-CATCH construct and in the CATCH add the appropriate children (you will have to climb back up the tree in the table and then work your way back down. One way to do this is to SELECT-SQL the data source into a cursor with an added boolean field "added". Set this value to True when you have added the node to the treeview. This will allow you to determine when you have a node that is already added when climbing back up.
 
Here is an example of searching a node by its text (caption) and its level

Code:
PUBLIC ofrm
RAND(-1)
ofrm=CREATEOBJECT("MyForm")
ofrm.show()

DEFINE CLASS MyForm as Form
	width=800
	height=420
	cCurList=SYS(2015)
	ADD OBJECT lbl as label WITH left = 450, caption = "Search node"
	ADD OBJECT txt as textbox WITH left = 450, top = 20
	ADD OBJECT lbl2 as label WITH left = 600, caption = "from level"
	ADD OBJECT txt2 as textbox WITH left = 600, top = 20, value = 1
	ADD OBJECT cmd as commandbutton WITH left = 450, top = 50, caption = "Find"
	ADD OBJECT teeview as olecontrol WITH oleclass="Mscomctllib.treectrl.2",width=400,height=400
	
	PROCEDURE trav
		LPARAMETERS loNode,lncurLevel,lnDesiredLevel,lcString,llAllChild
		LOCAL loChild
		DO CASE
		CASE m.lncurLevel < m.lnDesiredLevel
			DO CASE
			CASE m.loNode.children > 0 AND !m.llAllChild
				loChild = m.loNode.Child
				ThisForm.trav(m.loChild, m.lncurLevel + 1, m.lnDesiredLevel, m.lcString, .F.)
				DO WHILE TYPE("m.loChild.Next") == "O" AND !ISNULL(m.loChild.Next)
					ThisForm.trav(m.loChild.Next, m.lncurLevel + 1, m.lnDesiredLevel, m.lcString, .F.)
					loChild = m.loChild.Next
				ENDDO
			CASE m.lncurLevel > 0 
				ThisForm.trav(m.loNode.Parent, m.lncurLevel -1, m.lnDesiredLevel, m.lcString, .T.)
			ENDCASE
		CASE m.lncurLevel = m.lnDesiredLevel
			IF UPPER(ALLTRIM(m.loNode.text)) == UPPER(ALLTRIM(m.lcString))
				m.loNode.Selected = .T.
*				m.loNode.EnsureVisible()
				ThisForm.trav(m.loNode.Root, 0, m.lnDesiredLevel, m.lcString, .T.)
			ENDIF
		ENDCASE
	ENDPROC 
		
	PROCEDURE cmd.click
		ThisForm.trav(ThisForm.teeview.Nodes[1].Root,0,ThisForm.txt2.value,ThisForm.txt.Value,.F.)
	ENDPROC

	PROCEDURE init
		LOCAL lcKey,loNode,lcRoot,lcPar1,lcPar2,lcPar3,lcPar4,ln1,ln2,ln3,ln4,ln5
		STORE SYS(2015) TO lcRoot, lcKey
		loNode = thisform.TeeView.Nodes.Add(, 1, m.lcKey,'Root')
		loNode.expanded = .T.
		FOR ln1 = 1 TO INT(9 * RAND()) + 1
			lcPar1 = SYS(2015)
			loNode = thisform.TeeView.Nodes.Add(m.lcRoot, 4, m.lcPar1,"L1N" + TRANSFORM(m.ln1))
			loNode.expanded = .T.
			FOR ln2 = 1 TO INT(5 * RAND()) + 1
				lcPar2 = SYS(2015)
				loNode = thisform.TeeView.Nodes.Add(m.lcPar1, 4, m.lcPar2,"L2N" + TRANSFORM(m.ln1) + TRANSFORM(m.ln2))
				loNode.expanded = .T.
				FOR ln3 = 1 TO INT(5 * RAND()) + 1
					lcPar3 = SYS(2015)
					loNode = thisform.TeeView.Nodes.Add(m.lcPar2, 4, m.lcPar3,"L3N" + TRANSFORM(m.ln1) + TRANSFORM(m.ln2) + TRANSFORM(m.ln3))
					loNode.expanded = .T.
					FOR ln4 = 1 TO INT(5 * RAND()) + 1
						lcPar4 = SYS(2015)
						loNode = thisform.TeeView.Nodes.Add(m.lcPar3, 4, m.lcPar4,"L4N" + TRANSFORM(m.ln1) + TRANSFORM(m.ln2) + TRANSFORM(m.ln3) + TRANSFORM(m.ln4))
						loNode.expanded = .T.
						FOR ln5 = 1 TO INT(5 * RAND()) + 1
							lcKey = SYS(2015)
							loNode = thisform.TeeView.Nodes.Add(m.lcPar4, 4, m.lcKey,"L5N" + TRANSFORM(m.ln1) + TRANSFORM(m.ln2) + TRANSFORM(m.ln3) + TRANSFORM(m.ln4) + TRANSFORM(m.ln5))
							loNode.expanded = .T.
						NEXT
					NEXT
				NEXT
			NEXT
		NEXT
	ENDPROC
ENDDEFINE

Respectfully,
Vilhelm-Ion Praisach
Resita, Romania
 
I think I didn't explain that quite right...

I have a "search area" in the bottom of my form. The Tree view is actually made up using 5 separate table relationships. You may search Company, Contact, Documents, Locations (which is 2 tables). So the user starts to type in the text box, which uses interactive change method to narrow search after every keystroke. I have a different search box for each type of element to search. So I fixed the issue with nodes expanding that I don't want to be open, so that each time a key is pressed the current node is closed and it opens a new node (if the criteria changes, otherwise it keeps that node open). (Hence why the full qualification to the tree is required instead of loNode.<property> because the identification of the node is taking place in the textbox, and not in the treeview.

But the issue is with .Selected... It seems to do nothing. Other than progress the tree to the bottom of the tree view during the interactive change... is this a "focus" issue?
Because if I go to the tree and just expand a node, the node next to the + I selected is highlighted in blue. (I don't see any properties in the tree view itself like "DynamicBackColor" that can be changed. Maybe available at runtime...)

So the reason I used the .Expand was to at least give SOME visible cue that the interactive change is locating the right node in the tree...


Best Regards,
Scott
ATS, CDCE, CTIA, CTDC

"Everything should be made as simple as possible, and no simpler."[hammer]
 
Scott, regarding your point that .Selected appears to do nothing: is that because the node in question is outside the visible portion of the tree? If so, my suggestion of using EnsureVisible should fix that.

Also, you said: "Hence why the full qualification to the tree is required instead of loNode.<property> because the identification of the node is taking place in the textbox, and not in the treeview". It's difficult to be sure about this without seeing the whole code, but surely if you can access loNode.Index (which you are doing), you can access loNode.Expanded, or any other property of the node? However, this isn't central to your problem, so we needn't pursue it.

One other point. I wonder if it wouldn't look better if you do the search after the user has entered the entire search term, rather than character-by-character in the interactivechange. I realise that this would be a significant change to the UI, and one that might be less convenient for the user, but it would avoid the tree constantly opening and closing - and possibly scrolling up and down - as the user types the search term. Of course, you know your application - and your users - better than I do. This is only a suggestion.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
I just come back to a recommendation I made early on: Do yourself a favor and use any other treeview than the MS one. The one I mentioned from Exontrol is not cheap, but worth it and Exontrol has tons of VFP sample code, see You can test the Exontrol for free, it will display a license message in the control area.

Bye, Olaf.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top