Selektieren des unmittelbar vorherigen bzw. unmittelbar nächsten Nodes eines TreeViews

    • C#

    Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von RushDen.

      Selektieren des unmittelbar vorherigen bzw. unmittelbar nächsten Nodes eines TreeViews

      in diesem Thread wurde das Problem besprochen, in einem TreeView den nächst folgenden Node zu selektieren (Post #13).
      So trivial ist das offensichtlich nicht, denn Frau Google bot bei Familie StackOverflow eine Reihe nicht so funktionierender Lösungen an.
      Also hab ich mir die Mühe gemacht, das Problem selbst zu lösen.
      Rausgekommen ist ein Code, der den nächsten bzw. vorherigen Node eines TreeViews selektiert.
      Der Text aller Nodes kann in einer RTB ausgegeben werden.
      Alle Nodes können aus- und eingeklappt werden und beim Durchklickern kann optional der restliche TreeView eingeklappt werden.
      Die Nodes habe ich im Designer angelegt. zur besseren Kenntlichmachung habe ich den Nodes ein graues und dem selektierten Node ein rotes Icon zugewiesen.

      Viel Spaß mit dem Code.
      NodeSelect.zip
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „RodFromGermany“ ()

      wärs dir möglich, evtl. einfach eine zip anzuhängen?

      guck - geht gleich los: Ich hab getreulichst dein Form nachgebaut, und unter Beachtung aller Regeln sowohl dein DesignerCode als auch dein Form-Code einkopiert. Dennoch Fehler:

      vermutlich fehlen Resourcen-Dateien.

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

      @ErfinderDesRades Selbstverständlich.
      Hab den Startpost editiert.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      Hi, hab mir das Problem angeschaut gehabt und auch was entworfen (wollt Ich nur mal erwähnen, damit meine Mühe nicht umsonst war :d)

      Spoiler anzeigen

      C#-Quellcode

      1. public Form1()
      2. {
      3. InitializeComponent();
      4. testiterator = customTreeView1.GetEnumerator();
      5. }
      6. ...
      7. private IEnumerator<TreeNode> testiterator;
      8. private void button2_Click(object sender, EventArgs e)
      9. {
      10. TreeNode lActualNode = testiterator.Current;
      11. // Mach was mit dem Knoten
      12. testiterator.MoveNext();
      13. }
      14. }

      Spoiler anzeigen

      C#-Quellcode

      1. public class CustomTreeView : TreeView, IEnumerable<TreeNode>
      2. {
      3. public IEnumerator<TreeNode> GetEnumerator()
      4. {
      5. return new TreeViewIterator(this);
      6. }
      7. IEnumerator IEnumerable.GetEnumerator()
      8. {
      9. return new TreeViewIterator(this);
      10. }
      11. private class TreeViewIterator : IEnumerator<TreeNode>
      12. {
      13. public TreeViewIterator(TreeView pTree)
      14. {
      15. pNodes = new TreeNode[pTree.GetNodeCount(true)];
      16. fillArray(pTree);
      17. _count = 0;
      18. }
      19. private TreeNode[] pNodes;
      20. private int _count;
      21. public TreeNode Current
      22. {
      23. get
      24. {
      25. return pNodes[_count];
      26. }
      27. }
      28. private void fillArray(TreeView pTree)
      29. {
      30. foreach (TreeNode elem in pTree.Nodes)
      31. {
      32. recursiveFilling(elem);
      33. }
      34. }
      35. private void recursiveFilling(TreeNode pNode)
      36. {
      37. pNodes[_count++] = pNode; // Preorder traversierung um bauminhalte abzuspeichern
      38. foreach (TreeNode elem in pNode.Nodes)
      39. {
      40. recursiveFilling(elem);
      41. }
      42. }
      43. object IEnumerator.Current
      44. {
      45. get
      46. {
      47. return pNodes[_count];
      48. }
      49. }
      50. public void Dispose()
      51. {
      52. throw new NotImplementedException(); // ist hier unwichtig
      53. }
      54. public bool MoveNext()
      55. {
      56. if (_count == pNodes.Length - 1)
      57. return false;
      58. _count++;
      59. return true;
      60. }
      61. public void Reset()
      62. {
      63. _count = 0;
      64. }
      65. }
      66. }


      Das ist (1) effizienter (stets Laufzeit von O(1) für nächste/vorherige, da bei next() nur der nächste index aufgerufen wird und ein arrayzugriff stattfindet), nur das Laden der Werte dauert verhältnismäßig "länger" (da die iterierung weiter delegiert wird (quasi an das array), anstatt explizit einen iterator für so eine baumstruktur zu implementieren) mit "länger" sinds denk ich paar ms wenn nicht ns gemeint (passiert aber nur 1x bei programmstart)
      (2) kann man das besser modizifizieren (z.B. wenn er beim letzten knoten ist und zum anfang zurück soll einfach die MoveNext Methode umschreiben sind 2-3 Zeilen) und (3) sieht's schicker aus (sehr wichtig!)
      Außerdem sind Konstrukte wie foreach (var item in customTreeView1) möglich, sodass er den Baum in gewünschter Reihenfolge ausgibt.
      Das vorherige markieren kann man auch noch einfügen (wobei man da die reset methode missbrauchen kann).
      na, das sehe ich aber noch nicht, dass man damit per Button-Click von einer gegebenen Position aus um genau einen Schritt vorwärts gehen kann.

      Ich hab Rods Ansatz mal auf den Kopf gestellt - für den Next-Button:

      C#-Quellcode

      1. private TreeNode GetNextNode(TreeNode nd) {
      2. #if false
      3. die 3 Richtungen, in Präferenz-Reihenfolge:
      4. 1) abwärts, zum ersten ChildNode
      5. 2) vorwärts, zum nächsten "Bruder-Node"
      6. 3) aufwärts in einer Schleife, bis ein Parent gefunden ist, von dem aus vorwärts gegangen werden kann
      7. #endif
      8. if (nd.Nodes.Count > 0) return nd.Nodes[0]; // 1 abwärts-Versuch
      9. var tryForward = nd.NextNode; // 1 vorwärts-Versuch
      10. while (tryForward == null) { // aufwärts-schleife, solange vorwärts-schreiten fehlschlägt
      11. nd = nd.Parent;
      12. if (nd == null) return null; // ende der Fahnenstange
      13. tryForward = nd.NextNode;
      14. }
      15. return tryForward;
      16. }
      Verantwortlichkeit stärker separiert, keine unnützen Tests (if (nd.Nodes == null)), knapper kommentiert, aber zusätzlich einen Gesamt-Kommentar, der ühaupt das Prinzip des Algos beleuchtet.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

      @RushDen Jou.
      Leider bietet das Standard-Interface IEnumerator kein MoveLast.
      Somit erfüllt Dein Code mit .MoveNext() nur den halben Anspruch. ;)
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      @ErfinderDesRades Jou, so isses.
      Da müsste beim Selektieren der Iterator geteached werden.
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!
      ok das hat ich vorher nicht gelesen.
      Habs jz umgeschrieben funzt jz alles (das expandieren etc. auch).
      Ist aber blöd wegen dem enumerable weil das so jz nich schön ist

      Spoiler anzeigen

      C#-Quellcode

      1. public partial class Form1 : Form
      2. {
      3. public Form1()
      4. {
      5. InitializeComponent();
      6. testiterator = customTreeView1.GetEnumerator();
      7. }
      8. private CustomEnumerator<TreeNode> testiterator;
      9. private void button2_Click(object sender, EventArgs e)
      10. {
      11. testiterator.MoveNext();
      12. RefreshNodes();
      13. }
      14. private void RefreshNodes()
      15. {
      16. TreeNode lActualNode = testiterator.Current;
      17. customTreeView1.SelectedNode = lActualNode;
      18. lActualNode.Expand();
      19. }
      20. private void button1_Click(object sender, EventArgs e)
      21. {
      22. testiterator.MoveLast();
      23. RefreshNodes();
      24. }
      25. private void customTreeView1_AfterSelect(object sender, TreeViewEventArgs e)
      26. {
      27. testiterator.MoveTo(e.Node);
      28. RefreshNodes();
      29. }
      30. }

      Spoiler anzeigen

      C#-Quellcode

      1. public abstract class CustomEnumerator<T> : IEnumerator<T>
      2. {
      3. public abstract T Current
      4. {
      5. get;
      6. }
      7. object IEnumerator.Current
      8. {
      9. get
      10. {
      11. return Current;
      12. }
      13. }
      14. public abstract bool MoveLast();
      15. public abstract bool MoveTo(T pElement);
      16. public abstract void Dispose();
      17. public abstract bool MoveNext();
      18. public abstract void Reset();
      19. }
      20. public class CustomTreeView : TreeView
      21. {
      22. public CustomEnumerator<TreeNode> GetEnumerator()
      23. {
      24. return new TreeViewIterator(this);
      25. }
      26. private class TreeViewIterator : CustomEnumerator<TreeNode>
      27. {
      28. public TreeViewIterator(TreeView pTree)
      29. {
      30. pNodes = new TreeNode[pTree.GetNodeCount(true)];
      31. fillArray(pTree);
      32. _count = -1;
      33. }
      34. private TreeNode[] pNodes;
      35. private int _count;
      36. private void fillArray(TreeView pTree)
      37. {
      38. foreach (TreeNode elem in pTree.Nodes)
      39. {
      40. recursiveFilling(elem);
      41. }
      42. }
      43. private void recursiveFilling(TreeNode pNode)
      44. {
      45. pNodes[_count++] = pNode; // Preorder traversierung um bauminhalte abzuspeichern
      46. foreach (TreeNode elem in pNode.Nodes)
      47. {
      48. recursiveFilling(elem);
      49. }
      50. }
      51. public override TreeNode Current
      52. {
      53. get
      54. {
      55. return pNodes[_count];
      56. }
      57. }
      58. public override bool MoveNext()
      59. {
      60. if (_count == pNodes.Length - 1)
      61. return false;
      62. _count++;
      63. return true;
      64. }
      65. public override void Reset()
      66. {
      67. _count = 0;
      68. }
      69. public override bool MoveLast()
      70. {
      71. if (_count == 0)
      72. return false;
      73. _count--;
      74. return true;
      75. }
      76. public override bool MoveTo(TreeNode pElem)
      77. {
      78. for (int i = 0; i < pNodes.Length; i++)
      79. {
      80. if (pNodes[i] == pElem)
      81. {
      82. _count = i;
      83. return true;
      84. }
      85. }
      86. return false;
      87. }
      88. public override void Dispose()
      89. {
      90. throw new NotImplementedException();
      91. }
      92. }
      93. }