Link to home
Start Free TrialLog in
Avatar of ThaCoyote
ThaCoyote

asked on

Changing Node.Text property in TreeView control

Hi, I have a treeview control (V6) with nodes that show chapters with paragraphs. A chapternumber is supplied with each node, i.e:

1. Chapter One
  1.1. Chapter One, Paragraph One
2. Chapter Two
  2.1. Chapter Two, Paragraph One
  2.2. Chapter Two, Paragraph Two

I have set the treeviews LabelEdit property to tvwAutomatic, so that the user can edit the titles.
The difficulty is that the user can now also edit the chapter number, which is not allowed.

I tried setting the SelectedItem.Text property to contain the title, stripped from its number in the BeforeLabelEdit-eventhandler, but found out that the treeview control fills an internal editbox with the entire text (ie 2.2. Chapter Two, Paragraph Two) prior to raising the BeforeLabelEdit-event. So, changing the node's text property won't help me...

Now, I'm this far that I got access to the internal edit control by using windows messages and changed the text in the editbox. Everything's working fine now: The user can edit only the title, save the pressing ENTER or ESC and the new title gets displayed again, supplied with its number...

The only thing is, and this finally brings me to the question:
The treeview places the internal edit control on top of the actual node. Since I stripped the number from the text in the edit control and not from the node's text property, the node's text property is longer than the edit control and therefore shows characters on the right side of the edit control.

Who knows of a way to either:
- Catch the escape-button pressed by the user in the edit control which ends the editing session and does not raise the AfterLabelEdit-event, so I could strip the number from the node's text property as well, and append the number again in case of an ESC.
- 'hide' this text on the right by enlarging the edit control, custom drawing, etc...

Thanks in advance,

Luc Derckx

ps: I don't think the code I used to send the messages is relevant here, but if you're interested I'll post it right here.
ASKER CERTIFIED SOLUTION
Avatar of fulscher
fulscher

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of arif_eqbal
arif_eqbal

There's another easier method of getting around the problem.

On BeforeLabelEdit event store the text in a variable
txt=TreeView1.SelectedItem.Text

Then on AfterLabelEdit check if the Text enetered contains the Chapter no and whether it is same as that stored in the variable earlier or not.

In case of wrong entry You can either ask the user to correct it or Automatically add the Chapter No. based on the stored value....

In case you want to automatically add the chapter no. on AfterLableEdit, you'll need to set Cancel=True and manually set the text of the node

TreeView1.SelectedItem.Text = txt & TreeView1.SelectedItem.Text

(assuming txt contains just the chapter no. so you need to extract chapter from the whole text we stored)
Avatar of ThaCoyote

ASKER

Hi, thanks for the replies...

======================================

arif_eqbal,
it's important to me that I don't provide the chapter no with the editable text, as that it might confuse users and is sensitive to errors. Your second recommendation works as long as the text actually gets changed (AfterLabelEdit gets raised), but fails if the user doesn't change the text or cancels by pressing escape. If there's a way I could just handle these cases, I'd indeed be able to reset the node's text; piece of cake...
I'd just do this:

Private ClickedNode As Node

Private Sub TreeView1_NodeClick(ByVal Node As MSComctlLib.Node)
   If Not ClickedNode Is Nothing Then
      If Node = ClickedNode Then
         TreeView1.SelectedItem.Text = Mid(TreeView1.SelectedItem.Text, Len(TreeView1.SelectedItem.Tag) + 3)
         TreeView1.StartLabelEdit
      Else
         Set ClickedNode = Node
      End If
   Else
      Set ClickedNode = Node
   End If
End Sub

Private Sub TreeView1_AfterLabelEdit(Cancel As Integer, NewString As String)
   Cancel = True
   TreeView1.SelectedItem.Text = TreeView1.SelectedItem.Tag & ": " & NewString
End Sub

Private Sub MyCancelEditHandler
   TreeView1.SelectedItem.Text = TreeView1.SelectedItem.Tag & ": " & _
      TreeView1.SelectedItem.Text
End Sub

======================================

fulscher,
I like your answer as it probably is solid and that it provides flexibility in design to add more features at wish. It's only that I'm affraid that this possible solution gives me too much work on modifying the current treeview's implementation. The flexibility it'd give me is great, but would also not be needed in this context; it will thrive beyond its goal...
The feeling I currently have that I'm very close to finding a solution makes me look for a light add in code to my current implementation, or an easy and small new implementation... If I'd just be able to easily get rid of the node's text on the right of the internal edit control...
This is the code I currently have...

Private Sub tvChapters_AfterLabelEdit(Cancel As Integer, NewString As String)
   'The node's tag always contains the ChapterNo
   NewString = tvChapters.SelectedItem.Tag & ": " & NewString
End Sub

Private Sub tvChapters_BeforeLabelEdit(Cancel As Integer)
   Dim ndeSel As MSComctlLib.Node 'The selected node to increase readability
   Dim EditBoxHWnd As Long
   
   Set ndeSel = tvChapters.SelectedItem
   If Not ndeSel Is Nothing Then
      'There's one problem: The treeview holds an internal edit control which is used...
      '...to edit the node's text.
      '...The contents of this edit control have ALREADY been set to the 'old' node's text, ...
      '...so any mutation we do to the node's text property will be of no use...
      '...So we should find a way to the internel edit control and modify it manually:

      'The first thing to do is to retrieve its handle (edit control)
      'SendMessage parameter reference for TVM_GETEDITCONTROL:
      'wParam: Must be zero
      'lParam: Must be zero
      EditBoxHWnd = SendMessage(tvHoofdstukken.hWnd, TVM_GETEDITCONTROL, CLng(0), CLng(0))
           
      'Now order the edit control to select the chapter no, using its handle
      'SendMessage parameter reference for EM_SETSEL:
      'wParam = start point of the selection (0 to n - 1)
      'lParam = end point of the selection (0 to n - 1)
      SendMessage EditBoxHWnd, EM_SETSEL, CLng(0), ByVal CLng(Len(ndeSel.Tag) + 2)
         
      'Now replace the chapter no with an empty string
      'SendMessage parameter reference for EM_REPLACESEL:
      'wParam = Boolean: Action Undoable?
      'lParam = String: Replacement string
      SendMessage EditBoxHWnd, EM_REPLACESEL, CLng(0), ByVal ""
      DoEvents
   Else
      Cancel = True
   End If
   Set ndeSel = Nothing
End Sub

======================================

Your replies gave me some new thinking:
- Is there a way to 'hook in' on the internal text control's notifications/events?
  This way I could possibly handle the cases where the editing gets cancelled
- If I could just have another SendMessage or something to enlarge the edit control horizontally...?
  This way the edit control will completely cover the annoying old node's text

Looking forward to hearing from you guys again,

Luc Derckx
Luc,

Try the SetWindowPos API function with the SWP_NOMOVE flag set. This allows you to resize the control.

HTH, Jan
Luc,

Have you thought about fetching the characters at the form level (KeyPreview = TRUE) and modifying the ESC code before it's sent to the TreeView? I'm not sure whether this works, but it might be worth a try: The VB form is the owner of the TreeView and therefore should see all characters before they are sent to the Treeview.

HTH, Jan
Fulscher,

I haven't yet tried the SetWindowPos API function (my day has come to an end :D), but tried the KeyPreview because it only took a minute to test it. Although it would only handle the escape-key and not other triggers that would end the editing session (clicking somewhere on the form for example), I still tried it because I was rather curious about how it would behave. I placed the code in Form_KeyDown and it worked:
If KeyCode = vbKeyEscape And Me.ActiveControl.Name = "tvChapters" Then KeyCode = 0

I'll fully explore this possible solution and try to catch the other triggers as well. I will return to this and share my findings with you in a few days, since I'm off to one of our customers for 2 days as of tomorrow.

Cheers!
Hi,

Sorry peeps for forgetting this question!
I eventually ended up using API calls to the internal text box of the treeview. Here's the code I used:

Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
   (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Private Sub tvHoofdstukken_BeforeLabelEdit(Cancel As Integer)
   Dim ndeSel As MSComctlLib.Node 'The selected node to increase readability
   Dim EditBoxHWnd As Long
   
   Set ndeSel = tvHoofdstukken.SelectedItem
   If Not ndeSel Is Nothing Then
      If Left$(ndeSel.Key, 1) = "L" Then
         Cancel = True
      ElseIf ndeSel.Key <> "\" Then
         If DisplayChapterNr Then
            'Set up the node's text: The .Tag property contains the ChapterNr.
            'ndeSel.Text = Mid(ndeSel.Text, Len(ndeSel.Tag) + 3)
           
            'Now there's only one problem remaining:
            '...The treeview's internal text box used for editing holds the old contents!
            '...appearantly these contents are saved BEFORE the BeforeLabelEdit-event fires.
            '...Now we've changed the actual node's text, we should also change the internal
            '...edit box text!
            'Collect the handle of the treeview controls edit box
            'wParam & lParam: Must be zero
            EditBoxHWnd = SendMessage(tvHoofdstukken.hWnd, TVM_GETEDITCONTROL, CLng(0), CLng(0))
           
            'Select the text that represents the ChapterNr in that text box
            'wParam = start point of the selection (0 to count - 1)
            'lParam = end point of the selection (0 to count - 1)
            'Special: start = 0, end = -1 --> Select everything
            'Special: start = -1 --> Select nothing
            SendMessage EditBoxHWnd, EM_SETSEL, CLng(0), ByVal CLng(Len(ndeSel.Tag) + 2)
           
            'Send the new text string
            'wParam = Boolean: Action Undoable?
            'lParam = String: Replacement string
            SendMessage EditBoxHWnd, EM_REPLACESEL, CLng(0), ByVal ""
            DoEvents
         End If
      End If
   Else
      Cancel = True
   End If
   Set ndeSel = Nothing
End Sub


I still like the possible solution fulscher provided and fulscher sticked with me the whole time. Therefore I give him full credit.

Thanks guys for helping.
Oops, didn't realize I was logged in on my other user account :(
Thank you!
Hi Modulo,

Please don't be so stern on me, IThema is my account used for corporate means, ThaCoyote is used for private means... I'd like to separate those. If you still don't allow this, then ThaCoyote should be the one to be removed. I'd also like to change the name of IThema to <removed by modulo for privacy reasons>, since IThema is my old employer. I don't like to be remembered to my old employer everytime I visit this place :D
If a corporate name as an alias is not allowed, then please remove ThaCoyote and rename IThema to ThaCoyote or TheCoyote or simply Coyote (preferred). (I don't think ThaCoyote will be allowed twice in database, although one will be blocked)

If you change my name, please send me an e-mail to my IThema-account, since I probably won't be able to log in anymore


Cheers,

Luc Derckx
No, remove the thacoyote please!