views:

857

answers:

4

I have a subclass of System.Windows.Forms.TreeView that's manually "bound" to a set of hierarchical data. I want the user to be able to edit the labels of the tree, and have the changes reflected back to the data. So I set LabelEdit to true and overrode OnAfterLabelEdit to the tune of:

protected override void OnAfterLabelEdit(NodeLabelEditEventArgs e)
{
    base.OnAfterLabelEdit(e);

    TreeNode node = e.Node;

    if (PassesSomeValidation(e.Label))
    {
        MyDataNode dataNode = node.Tag as MyDataNode;
        dataNode.SomeBoundValue = e.Label;

        int oldIndex = node.Index;
        int newIndex = RepositionChangedDataNode(dataNode);

        TreeNode parent = node.Parent;
        parent.Nodes.RemoveAt(oldIndex);
        parent.Nodes.Insert(newIndex, node);
    }
    else
    {
        e.CancelEdit = true;
    }
}

RepositionChangedDataNode() re-sorts the data and returns the index into which the change node moved after sorting. I was hoping I could simply move the edited node to reflect this move.

The problem is that this causes the node to stay in edit mode! I've tried calling EndEdit(), cloning the node before inserting it, setting LabelEdit to false and back to true, wrapping the change in BeginUpdate()/EndUpdate(), and various combinations of these ideas, but none of them have any effect.

The culprit seems to be the insertion. Even if I attempt to insert a totally new node, it will go into edit mode immediately.

So, is there any way to make TreeView not behave this way? And if not, is there a good workaround?

Some ideas I've considered:

  1. Set a custom TreeViewNodeSorter. Would prefer not to have to sort my data twice, though.
  2. Set a flag and delay the remove-insert step until some point after AfterLabelEdit. It works to do it during WndProc, but this feels like a big kludge that is likely to fail somehow.
  3. Use BeginInvoke() to push the remove-insert step back onto the message queue like so:

    BeginInvoke(new MethodInvoker(delegate(
    {
        parent.Nodes.RemoveAt(oldIndex);
        parent.Nodes.Insert(newIndex, node);
    }));
    

    This works and seems cleaner to me than #2, but I know this is probably not how BeginInvoke() was intended to be used, and that it may have repercussions that my very limited knowledge of the message pump cannot predict.

A: 

If you're using databinding, shouldn't the update to the data source (SomeBoundValue) trigger a refresh of the nodes? Maybe you can force the currency manager to repopulate the tree view.... If you're worried about performance you could use one of the sorting algorithms that works well with data that's almost already sorted (e.g., NOT quicksort - merge or heapsort come to mind)

Or you could dispense with data binding entirely and manually handle the repositioning since you're already half way there with RepositionChangedDataNode()....

Arnshea
TreeView doesn't support databinding by itself, which is why I'm manually copying the changed value to the backing source. Manually handling the repositioning is exactly what I'm trying to do, but it's causing this wacky can't-stop-editing behavior.
Andrew Watt
+1  A: 

Hello,

try create a global variable, let's say:

private bool _allowEdit;

initialize it to true,

in your OnAfterLabelEdit method set it to false after your changes:

... int oldIndex = node.Index;
    int newIndex = RepositionChangedDataNode(dataNode);

    TreeNode parent = node.Parent;
    parent.Nodes.RemoveAt(oldIndex);
    parent.Nodes.Insert(newIndex, node);
    **_allowEdit = false;**
}
else ...

then capture the OnBeforeLabelEdit event like this:

    protected override void OnBeforeLabelEdit(NodeLabelEditEventArgs e)
    {
        base.OnBeforeLabelEdit(e);
        e.CancelEdit = !_allowEdit;
        _allowEdit = true;
    }

I noticed that just after 'AfterLabelEdit' is fired, 'BeforeLabelEdit' is refired. That's why you have to stop it right there.

najmeddine
Tried this; it doesn't work. The inserted node still goes into edit mode.
Andrew Watt
A: 

You could try to unhook your OnEdit handler before adding the new node and re-hooking it after. I've seen that behavior before and that's how I handled it.

dviljoen
But the problem isn't that any handler of mine is being called when it shouldn't be. This won't help.
Andrew Watt
A: 

If you set LabelEdit for the TreeView to false, newly added nodes will not be in edit mode.

You just have to handle the case where the user wants to edit a label: Create a handler for the MouseClick event of the TreeView, where you get the clicked node by location. Set LabelEdit to true and call BeginEdit(). At the end of your handler for the AfterLabelEdit event (and after calling EndEdit(...) at an appropriate point), set LabelEdit to false again.

This works for me, whereas the solution with BeginInvoke only changed which node was in edit mode at the end.

danile