Entwickler-Ecke

WinForms - TreeView childnodes tauschen


lapadula - Do 08.06.17 21:17
Titel: TreeView childnodes tauschen
Hallo, weiss jemand wie man die childnodes innerhalb eines Knotens mit der Maus verschieben kann, sprich die Pätze sollen getauscht werden?

Hab mich ein wenig umgeguckt aber die meisten packen die childnodes in ein anders childnode.

z. B.

Automarken
- Audi
- BMW
- Mercedes

Mercedes soll nun an erster Stelle stehen, dies möchte ich aber mit der Maus verschieben.

Mfg


Ralf Jansen - Do 08.06.17 23:18

Wenn du dir das angesehen hast weißt du ja ungefähr wie man das Drag&Drop startet.
Um die Reihenfolge der ChildNodes zu ändern mußt du den gedragten Node beim droppen aus der Liste der Childnodes entfernen und wieder in der Liste einfügen und zwar mit dem Index an der der Stelle an der du einfügen willst


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
private void meinLieberTreeView_DragDrop(object sender, DragEventArgs e)
{
    // der gedraggte Node
    var sourceNode = e.Data.GetData(typeof(TreeNode)) as TreeNode; 
    if (sourceNode == null)
        return;

    // der Node über dem gedroppt wurde
    var pt = ((TreeView)sender).PointToClient(new Point(e.X, e.Y));
    var targetNode = ((TreeView)sender).GetNodeAt(pt);
    if (targetNode == null)
        return;

    // nur tauschen wenn beide Node am selben Parent hängen und Quell und Ziel Node nicht der gleiche ist
    if (!sourceNode.Equals(targetNode) && targetNode.Parent == sourceNode.Parent)
    {
        targetNode.Parent.Nodes.RemoveAt(sourceNode.Index);   // Node entfernen 
        targetNode.Parent.Nodes.Insert(targetNode.Index, sourceNode);  // Node wieder einfügen am Index des Ziel Nodes
    }
}


lapadula - Fr 09.06.17 10:28

Danke aber warum


C#-Quelltext
1:
targetNode.Parent.Nodes.RemoveAt(sourceNode.Index);                    


Müsste es nicht so richtig sein, weil ich möchte doch das targetNode entfernen und den SourceNode dahin platzieren?


C#-Quelltext
1:
targetNode.Parent.Nodes.RemoveAt(targetNode.Parent);                    


Ralf Jansen - Fr 09.06.17 10:38

Zitat:
Müsste es nicht so richtig sein, weil ich möchte doch das targetNode entfernen und den SourceNode dahin platzieren?

Eigentlich nicht. Du möchtest einen Node an der Stelle des TargetNodes einfügen. Stell dir vor deine 3 ChildNodes haben vollgende Indexe

Quelltext
1:
2:
3:
Audi
BMW
Mercedes

jetzt nimmst du den Node mit Index 3 (Mercedes) und droppst in auf den Node mit Index 1(Audi). Der Vorgang wäre

a.) Node 3(Mercedes) entfernen
b.) Und als Node 1 wieder einfügen.

Da es einen Node 1 (Audi) bereits gibt wird dessen Indes weitergeschoben und alle folgenden Nodes auch. So das du im Anschluss

Quelltext
1:
2:
3:
Mercedes
Audi
BMW

hast.


lapadula - Fr 09.06.17 10:58

Ahso

Und wenn ich Mercedes mit Audi tauschen möchte, also

Mercedes
BMW
Audi

zu

Audi
BMW
Mercedes

ohne das sich der Index von BMW sich ändert und weitergeschoben wird.


Ralf Jansen - Fr 09.06.17 13:09

Oh, ich hatte verschieben verstanden nicht tauschen.

Remove Insert muß man einfach 2 mal machen. Die Reihenfolge könnte etwas difizil sein damit genau das Richtige rauskommt ;)
Das will ich erstmal ausprobieren bevor ich das als Code zeige, ist mir aus dem lameng zu gefährlich ;)


lapadula - Fr 09.06.17 14:48

Hab mich da wohl wieder undeutlich ausgedrückt, sorry. Das englische wort wäre swap, glaube ich.

Habe das nun soweit, dass die Nodes richtig sortiert in ein Array kommen aber dieses Array mit Nodes kann ich nicht ausgeben und ich weiss echt nicht warum.

Hier mein Code:


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
if (!sourceNode.Equals(targetNode) && targetNode.Parent == sourceNode.Parent)
            {
                TreeNode[] newNodes = new TreeNode[sourceNode.Parent.Nodes.Count];
                

                int targetIndex = targetNode.Index;
                int sourceIndex = sourceNode.Index;

                for (int i = 0; i < sourceNode.Parent.Nodes.Count; i++)
                {
                    if (i == targetIndex)
                    {
                        newNodes[i] = sourceNode.Parent.Nodes[sourceIndex];
                    }
                    else if (i == sourceIndex)
                    {
                        newNodes[i] = sourceNode.Parent.Nodes[targetIndex];
                    }
                    else
                    {
                        newNodes[i] = sourceNode.Parent.Nodes[i];
                    }
                }

               
                sourceNode.Parent.Nodes.Clear();

                foreach (TreeNode node in newNodes)
                {
                    sourceNode.Parent.Nodes.Add(node); // Bleibt alles gecleart, es wird nicht hinzugefügt
                    //treeView2.Nodes.Add(node); //Klappt, nur verschwindet das Parent
                }
 
                //sourceNode.Parent.Nodes.AddRange(newNodes); // Bleibt gecleart
            }


Wenn ich mein treeView direkt anspreche, dann klappt das zwar, allerdings verschwindet das Parent. Habe das im Code nochmal zusätzlich kommentiert


lapadula - Fr 09.06.17 15:39


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
if (!sourceNode.Equals(targetNode) && targetNode.Parent == sourceNode.Parent)
            {
                TreeNode[] newNodes = new TreeNode[sourceNode.Parent.Nodes.Count];

                TreeNode parentNode = sourceNode.Parent;


                int targetIndex = targetNode.Index;
                int sourceIndex = sourceNode.Index;

                for (int i = 0; i < sourceNode.Parent.Nodes.Count; i++)
                {
                    if (i == targetIndex)
                    {
                        newNodes[i] = sourceNode.Parent.Nodes[sourceIndex];
                    }
                    else if (i == sourceIndex)
                    {
                        newNodes[i] = sourceNode.Parent.Nodes[targetIndex];
                    }
                    else
                    {
                        newNodes[i] = sourceNode.Parent.Nodes[i];
                    }
                }

                treeView2.Nodes.Clear();
                treeView2.Nodes.Add(parentNode);

                foreach (TreeNode node in newNodes)
                {
                    treeView2.Nodes[0].Nodes.Add(node);
                }
                treeView2.ExpandAll();
            }


Ich versuche nun alles zu clearen, vorher merke ich mir das Parent und möchte es dann wieder hinzufügen + die ganzen childnodes.

Leider bleibt die Reihenfolge gleich. Außer ich füge Parent.Text hinzu, oder "treeView".Nodes.Add("Parent"), dann macht er alles richtig.

Du bist meine letzte Hoffnung Ralf :D


Ralf Jansen - Fr 09.06.17 19:29

Das Ganze jetzt mit tauschen anstatt verschieben. Habe die Funktionalität zum tauschen, da wie angenommen etwas komplexer, in eine Extensionmethod ausgelagert.


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
private void meinLieberTreeView_DragDrop(object sender, DragEventArgs e)
{
    var sourceNode = e.Data.GetData(typeof(TreeNode)) as TreeNode;
    if (sourceNode == null)
        return;

    var pt = ((TreeView)sender).PointToClient(new Point(e.X, e.Y));
    var targetNode = ((TreeView)sender).GetNodeAt(pt);
    if (targetNode == null)
        return;

    if (!sourceNode.Equals(targetNode) && targetNode.Parent == sourceNode.Parent)
        targetNode.Parent.SwapNodes(sourceNode, targetNode);
}


und hier die Klasse mit der Extensionmethod


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
public static class TreeViewExtensions
{
    public static void SwapNodes(this TreeNode parent, TreeNode first, TreeNode second)
    {
        if (parent == null)
            throw new ArgumentNullException(nameof(parent));
        if (first == null)
            throw new ArgumentNullException(nameof(first));
        if (second == null)
            throw new ArgumentNullException(nameof(second));
        if (!parent.Nodes.Contains(first) || !parent.Nodes.Contains(second))
            throw new InvalidOperationException("To swap, both nodes have to be in the parent collection.");

        if (first.Index > second.Index)
            Swap(ref first, ref second);

        int firstIndex = first.Index;
        int secondIndex = second.Index;

        first.Remove();
        second.Remove();

        parent.Nodes.Insert(firstIndex, second);
        parent.Nodes.Insert(secondIndex, first);
    }

    private static void Swap<T>(ref T first, ref T second)
    {
        T temp = first;
        first = second;
        second = temp;
    }
}


Edit : Text der Fehlermeldung geändert.


Delete - Fr 09.06.17 20:25

- Nachträglich durch die Entwickler-Ecke gelöscht -


lapadula - Sa 10.06.17 10:38

Ohje, vielen Dank dafür!

Ich teste es im Laufe des Tages mal.

Bin bei meiner Lösung nicht weitergekommen.

Sowas müsste die Treeview eig standardmäßig können.


Ralf Jansen - Sa 10.06.17 12:11

Zitat:
Bin bei meiner Lösung nicht weitergekommen.

In deiner gezeigten Lösung schreib mal hinter

C#-Quelltext
1:
treeView1.Nodes.Clear();                    

auch

C#-Quelltext
1:
parentNode.Nodes.Clear();                    

Du dachtest wohl das ein Clear am Treeview alle Nodes löscht aber übersehen das an dem gemerkten ParentNode den du wieder anhängst auch weiterhin ChildNodes hängen.
Der

C#-Quelltext
1:
treeView1.Nodes.Add(parentNode);                    

fügst alle Nodes wieder hinzu weil die ja alle noch am Parentnode hängen. Clear+Add hat dann nichts am Inhalt des TreeView geändert falls der ParentNode der einzige Node in der ersten Ebene des TreeViews war. Danach weitere sortierte Nodes an den Parentnode hängen geht nicht weil die ja immer noch dran hängen und das ein Node zweimal im TreeView hängt ist logischerweise nicht erlaubt ;)

Wenn du zu Übungszwecken deine Lösung zum fliegen bringen willst fehlt nicht viel. Du solltest versuchen den TreeView selbst aus der Logik herauszuhalten und dich nur auf die Nodes (insbesondere ParentNode anstatt TreeView) zu beziehen. Ist eigentlich einfacher und die Lösung ist generischer da egal ist wo genau sich der relevante Node im Baum steckt. Deine Lösung funktioniert nur (mit kleiner Korrektur) wenn der ParentNode auch der erste (einzige) Node in der Root des TreeViews ist.


lapadula - So 11.06.17 14:55

Danke für die Erklärung und nun funktioniert auch meine Lösung (nur wie du sagst, wenn es nur ein Parent gibt)