Question

J'ai un contrôle WinForm TreeView qui affiche la relation parent-enfant de CaseNotes (je sais que cela ne signifie rien pour la plupart d'entre vous mais cela m'aide à visualiser les réponses).

J'ai un DataTable des CaseNotes que je dois afficher. Le parent / enfant est défini comme suit: Si la ligne a un ParentNoteID, alors c'est un childNode de cette note, sinon c'est un rootNode. Il peut également s'agir d'une note parent (mais pas d'un rootNode) si une autre ligne a son ID en tant que ParentNoteID.

Pour compliquer (peut-être simplifier) ??les choses, j'ai le code ci-dessous qui fonctionne (principalement) et qui colore les nœuds en alternance. J'ai créé manuellement une collection statique pour l'arborescence et elle les colore assez correctement. Maintenant, je dois remplir dynamiquement les nœuds de mon DataTable.

Étant donné que je suis déjà en train de parcourir l'arborescence, nœud par nœud, ne puis-je pas pouvoir ajouter les données à ce processus? Peut-être dois-je d'abord construire les nœuds, puis les colorer en tant que routine distincte, mais la méthode de récursivité s'appliquerait toujours, n'est-ce pas?

Disons que je veux afficher CaseNoteID pour chaque nœud. Cela est retourné dans le DataTable et est unique.

foreach (TreeNode rootNode in tvwCaseNotes.Nodes)
        {
            ColorNodes(rootNode, Color.MediumVioletRed, Color.DodgerBlue);

        }
protected void ColorNodes(TreeNode root, Color firstColor, Color secondColor)
    {
        root.ForeColor = root.Index % 2 == 0 ? firstColor : secondColor;

        foreach (TreeNode childNode in root.Nodes)
        {
            Color nextColor = childNode.ForeColor = childNode.Index % 2 == 0 ? firstColor : secondColor;

            if (childNode.Nodes.Count > 0)
            {
                // alternate colors for the next node
                if (nextColor == firstColor)
                    ColorNodes(childNode, secondColor, firstColor);
                else
                    ColorNodes(childNode, firstColor, secondColor);
            }
        }
    }

EDIT

Mes pensées / tentatives jusqu'à présent:

        public void BuildSummaryView()
    {
        tvwCaseNotes.Nodes.Clear();

        DataTable cNotesForTree = CurrentCaseNote.GetAllCNotes(Program._CurrentPerson.PersonID);
        foreach (var cNote in cNotesForTree.Rows)
        {

            tvwCaseNotes.Nodes.Add(new TreeNode("ContactDate"));
        }
        FormPaint();
    }

Évidemment, cela est imparfait. Il suffit simplement d’afficher ContactDate à plusieurs reprises. Certes, il affiche le nombre correct de fois mais je voudrais la valeur de ContactDate (qui est une colonne dans la base de données et est renvoyée dans le DataTable. Deuxièmement, je dois ajouter la logique ChildNode. Un if (noeud. parentNode = node.CaseNoteID) blah ...

EDIT 2

J'ai donc trouvé ce lien, here , et cela donne l’impression que j’ai besoin d’obtenir mon DataTable dans un ArrayList. Est-ce correct?

EDIT 3

D'accord, grâce à Cerebus, cela fonctionne principalement. J'ai juste une question de plus. Comment puis-je prendre cela - >

DataTable cNotesForTree = CurrentCaseNote.GetAllCNotes(Program._CurrentPerson.PersonID);

et utiliser mon DataTable retourné dans ceci? Est-ce que je viens de remplacer ceci - >

    dt = new DataTable("CaseNotes");
dt.Columns.Add("NoteID", typeof(string));
dt.Columns.Add("NoteName", typeof(string));
DataColumn dc = new DataColumn("ParentNoteID", typeof(string));
dc.AllowDBNull = true;
dt.Columns.Add(dc);

// Add sample data.
dt.Rows.Add(new string[] { "1", "One", null });
dt.Rows.Add(new string[] { "2", "Two", "1" });
dt.Rows.Add(new string[] { "3", "Three", "2" });
dt.Rows.Add(new string[] { "4", "Four", null });
dt.Rows.Add(new string[] { "5", "Five", "4" });
dt.Rows.Add(new string[] { "6", "Six", null });
dt.Rows.Add(new string[] { "7", "Seven", null });
dt.Rows.Add(new string[] { "8", "Eight", "7" });
dt.Rows.Add(new string[] { "9", "Nine", "8" });

Je pense que la confusion est la suivante: dois-je toujours faire les commandes Column.Add et Row.Adds? Aussi, comment DataColumn se traduirait-il dans ma structure de données réelle? Désolé pour les questions très ignorantes, la bonne nouvelle est que je n’ai jamais à demander deux fois.

EDIT 4

Ce qui suit fournit une erreur d'exécution.

if (nodeList.Find(FindNode) == null)
  {
    DataRow[] childRows = dt.Select("ParentNoteID = " + dr["NoteID"]);
    if (childRows.Length > 0)
    {
      // Recursively call this function for all childRowsl
      TreeNode[] childNodes = RecurseRows(childRows);

      // Add all childnodes to this node.
      node.Nodes.AddRange(childNodes);
    }

    // Mark this noteID as dirty (already added).
    //doneNotes.Add(noteID);
    nodeList.Add(node);
  }

L'erreur est la suivante - > La colonne [ea8428e4] est introuvable. Il s'agit des 8 premiers chiffres du bon identificateur de note (je dois utiliser un GUID). Devrait-il chercher une colonne de ce nom? Parce que j'utilise un Guid, y a-t-il autre chose que je doive faire? J'ai changé toutes les références dans le mien et votre code en Guid ...

Était-ce utile?

La solution

Pour tenter de résoudre ce problème, j'ai créé un exemple de formulaire Windows et écrit le code suivant. J'ai envisagé la conception datatable comme suit:

 NoteID  NoteName  ParentNoteID
   "1"    "One"        null
   "2"    "Two"        "1"
   "3"    "Three"      "2"
   "4"    "Four"       null
...

Cela devrait créer un arbre en tant que ( désolé, je ne suis pas très bon avec l'art ASCII! ):

One
 |
 ——Two
 |
 ————Three
 |
Four

Le pseudocode ressemble à ceci:

  1. Parcourez toutes les lignes du datatable.
  2. Pour chaque ligne, créez un TreeNode et définissez ses propriétés. Répétez le processus de manière récursive pour toutes les lignes dont l'identifiant ParentNodeID correspond à celui de cette ligne.
  3. Chaque itération terminée retourne un noeud qui contiendra tous les noeuds correspondants avec imbrication infinie.
  4. Ajoutez la liste de noeuds terminée à la vue TreeView.

Le problème de votre scénario provient du fait que la "clé étrangère" fait référence à une colonne du même tableau. Cela signifie que lorsque nous parcourons les lignes, nous devons savoir quelles lignes ont déjà été analysées. Par exemple, dans le tableau ci-dessus, le nœud correspondant aux deuxième et troisième lignes est déjà ajouté lors de la première itération complète. Par conséquent, nous ne devons pas les ajouter à nouveau. Il y a deux façons de suivre cela:

  1. Maintenir une liste des identifiants définis ( doneNotes ). Avant d'ajouter chaque nouveau nœud, vérifiez si l'ID de la note existe dans cette liste. C'est la méthode la plus rapide et devrait normalement être préférée. ( cette méthode est commentée dans le code ci-dessous )
  2. Pour chaque itération, utilisez un délégué générique de prédicat ( FindNode ) pour rechercher dans la liste des noeuds ajoutés (en tenant compte des noeuds imbriqués) pour voir si le noeud à ajouter existe dans cette liste. C'est la solution la plus lente, mais j'aime un peu le code compliqué! : P

Ok, voici le code qui a fait ses preuves (C # 2.0):

public partial class TreeViewColor : Form
{
  private DataTable dt;
  // Alternate way of maintaining a list of nodes that have already been added.
  //private List<int> doneNotes;
  private static int noteID;

  public TreeViewColor()
  {
    InitializeComponent();
  }

  private void TreeViewColor_Load(object sender, EventArgs e)
  {
    CreateData();
    CreateNodes();

    foreach (TreeNode rootNode in treeView1.Nodes)
    {
      ColorNodes(rootNode, Color.MediumVioletRed, Color.DodgerBlue);
    }
  }

  private void CreateData()
  {
    dt = new DataTable("CaseNotes");
    dt.Columns.Add("NoteID", typeof(string));
    dt.Columns.Add("NoteName", typeof(string));
    DataColumn dc = new DataColumn("ParentNoteID", typeof(string));
    dc.AllowDBNull = true;
    dt.Columns.Add(dc);

    // Add sample data.
    dt.Rows.Add(new string[] { "1", "One", null });
    dt.Rows.Add(new string[] { "2", "Two", "1" });
    dt.Rows.Add(new string[] { "3", "Three", "2" });
    dt.Rows.Add(new string[] { "4", "Four", null });
    dt.Rows.Add(new string[] { "5", "Five", "4" });
    dt.Rows.Add(new string[] { "6", "Six", null });
    dt.Rows.Add(new string[] { "7", "Seven", null });
    dt.Rows.Add(new string[] { "8", "Eight", "7" });
    dt.Rows.Add(new string[] { "9", "Nine", "8" });
  }

  private void CreateNodes()
  {
    DataRow[] rows = new DataRow[dt.Rows.Count];
    dt.Rows.CopyTo(rows, 0);
    //doneNotes = new List<int>(9);

    // Get the TreeView ready for node creation.
    // This isn't really needed since we're using AddRange (but it's good practice).
    treeView1.BeginUpdate();
    treeView1.Nodes.Clear();

    TreeNode[] nodes = RecurseRows(rows);
    treeView1.Nodes.AddRange(nodes);

    // Notify the TreeView to resume painting.
    treeView1.EndUpdate();
  }

  private TreeNode[] RecurseRows(DataRow[] rows)
  {
    List<TreeNode> nodeList = new List<TreeNode>();
    TreeNode node = null;

    foreach (DataRow dr in rows)
    {
      node = new TreeNode(dr["NoteName"].ToString());
      noteID = Convert.ToInt32(dr["NoteID"]);

      node.Name = noteID.ToString();
      node.ToolTipText = noteID.ToString();

      // This method searches the "dirty node list" for already completed nodes.
      //if (!doneNotes.Contains(doneNoteID))

      // This alternate method using the Find method uses a Predicate generic delegate.
      if (nodeList.Find(FindNode) == null)
      {
        DataRow[] childRows = dt.Select("ParentNoteID = " + dr["NoteID"]);
        if (childRows.Length > 0)
        {
          // Recursively call this function for all childRowsl
          TreeNode[] childNodes = RecurseRows(childRows);

          // Add all childnodes to this node.
          node.Nodes.AddRange(childNodes);
        }

        // Mark this noteID as dirty (already added).
        //doneNotes.Add(noteID);
        nodeList.Add(node);
      }
    }

    // Convert this List<TreeNode> to an array so it can be added to the parent node/TreeView.
    TreeNode[] nodeArr = nodeList.ToArray();
    return nodeArr;
  }

  private static bool FindNode(TreeNode n)
  {
    if (n.Nodes.Count == 0)
      return n.Name == noteID.ToString();
    else
    {
      while (n.Nodes.Count > 0)
      {
        foreach (TreeNode tn in n.Nodes)
        {
          if (tn.Name == noteID.ToString())
            return true;
          else
            n = tn;
        }
      }
      return false;
    }
  }

  protected void ColorNodes(TreeNode root, Color firstColor, Color secondColor)
  {
    root.ForeColor = root.Index % 2 == 0 ? firstColor : secondColor;

    foreach (TreeNode childNode in root.Nodes)
    {
      Color nextColor = childNode.ForeColor = childNode.Index % 2 == 0 ? firstColor : secondColor;

      if (childNode.Nodes.Count > 0)
      {
        // alternate colors for the next node
        if (nextColor == firstColor)
          ColorNodes(childNode, secondColor, firstColor);
        else
          ColorNodes(childNode, firstColor, secondColor);
      }
    }
  }
}

Autres conseils

J'ai créé une méthode d'extension beaucoup plus simple pour TreeView, impliquant l'utilisation d'une nouvelle classe d'extension simple qui ajoute deux propriétés utiles à TreeNode.

    internal class IdNode : TreeNode
    {
        public object Id { get; set; }
        public object ParentId { get; set; }
    }

    public static void PopulateNodes(this TreeView treeView1, DataTable dataTable, string name, string id, string parentId)
    {
        treeView1.BeginUpdate();
        foreach (DataRow row in dataTable.Rows)
        {
            treeView1.Nodes.Add(new IdNode() { Name = row[name].ToString(), Text = row[name].ToString(), Id = row[id], ParentId = row[parentId], Tag = row });
        }
        foreach (IdNode idnode in GetAllNodes(treeView1).OfType<IdNode>())
        {
            foreach (IdNode newparent in GetAllNodes(treeView1).OfType<IdNode>())
            {
                if (newparent.Id.Equals(idnode.ParentId))
                {
                    treeView1.Nodes.Remove(idnode);
                    newparent.Nodes.Add(idnode);
                    break;
                }
            }
        }
        treeView1.EndUpdate();
    }

    public static List<TreeNode> GetAllNodes(this TreeView tv)
    {
        List<TreeNode> result = new List<TreeNode>();
        foreach (TreeNode child in tv.Nodes)
        {
            result.AddRange(GetAllNodes(child));
        }
        return result;
    }
    public static List<TreeNode> GetAllNodes(this TreeNode tn)
    {
        List<TreeNode> result = new List<TreeNode>();
        result.Add(tn);
        foreach (TreeNode child in tn.Nodes)
        {
            result.AddRange(GetAllNodes(child));
        }
        return result;
    }

Merci à la modiX pour son méthodes pour obtenir tous les nœuds (imbriqués).

vérifiez ceci:

Public Sub BuildTree(ByVal dt As DataTable, ByVal trv As TreeView, ByVal expandAll As [Boolean])
    ' Clear the TreeView if there are another datas in this TreeView
    trv.Nodes.Clear()
    Dim node As TreeNode
    Dim subNode As TreeNode
    For Each row As DataRow In dt.Rows
        'search in the treeview if any country is already present
        node = Searchnode(row.Item(0).ToString(), trv)
        If node IsNot Nothing Then
           'Country is already present
            subNode = New TreeNode(row.Item(1).ToString())
            'Add cities to country
            node.Nodes.Add(subNode)
        Else
            node = New TreeNode(row.Item(0).ToString())
            subNode = New TreeNode(row.Item(1).ToString())
            'Add cities to country
            node.Nodes.Add(subNode)
            trv.Nodes.Add(node)
        End If
    Next
    If expandAll Then
        ' Expand the TreeView
        trv.ExpandAll()
    End If
End Sub

Pour plus de code source complet: Comment renseigner l'arborescence depuis datatable in vb.net

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top