Domanda

Ho un controllo TreeView di WinForm che visualizza la relazione padre figlio di CaseNotes (so che non significa nulla per la maggior parte di voi ma mi aiuta a visualizzare le risposte).

Ho una DataTable delle CaseNote che devo visualizzare. Parent / Child è definito come: Se la riga ha un ParentNoteID, allora è un childNode di quella nota, altrimenti è un rootNode. Potrebbe anche essere una nota principale (ma non un rootNode) se un'altra riga ha il suo ID in quanto ParentNoteID.

Per complicare (forse semplificare) le cose ho il seguente codice (principalmente) funzionante che colora i nodi alternativamente. Ho creato manualmente una raccolta statica per il treeview e li colora in modo abbastanza corretto. Ora ho bisogno di popolare dinamicamente i nodi dalla mia DataTable.

Dato che sto già attraversando il nodo treeview per nodo non dovrei essere in grado di aggiungere i dati in questo processo in qualche modo? Forse devo prima creare i nodi e poi colorare come una routine separata, ma il metodo di ricorsione si applicherebbe ancora, giusto?

Diciamo che voglio visualizzare CaseNoteID per ciascun nodo. Viene restituito nella DataTable ed è unico.

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);
            }
        }
    }

Modifica

I miei pensieri / tentativi finora:

        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();
    }

Ovviamente questo è difettoso. Uno mostra semplicemente ContactDate più e più volte. Concesso, mostra il numero corretto di volte, ma vorrei il valore di ContactDate (che è una colonna nel database e viene restituito nella tabella dei dati. In secondo luogo, devo aggiungere la logica ChildNode. Un if (nodo. parentNode = node.CaseNoteID) blah ...

MODIFICA 2

Quindi ho trovato questo link, qui e mi sembra di dover inserire la mia DataTable in una matrice. È corretto?

EDIT 3

Okay, grazie a Cerebus questo funziona principalmente. Ho solo un'altra domanda. Come prendo questo - >

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

e utilizzare la mia DataTable restituita in questo? Devo solo sostituire questo - >

    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" });

La mia confusione, penso, è ancora necessario fare Column.Add e Row.Adds? Inoltre, come si tradurrebbe DataColumn nella mia struttura di dati reale? Ci scusiamo per le domande molto ignoranti, la buona notizia è che non devo mai fare due volte.

MODIFICA 4

Di seguito viene riportato un errore di runtime.

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'errore è il seguente: > Impossibile trovare la colonna [ea8428e4] che sono le prime 8 cifre del NoteID corretto (devo usare una guida). Dovrebbe cercare una colonna con quel nome ?? Perché sto usando un Guid c'è qualcos'altro che devo fare? Ho cambiato tutti i riferimenti nel mio e nel tuo codice in Guid ...

È stato utile?

Soluzione

Per tentare di risolvere questo problema, ho creato un modulo Windows di esempio e ho scritto il seguente codice. Ho immaginato il design datatable come segue:

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

Questo dovrebbe creare un albero come ( scusa, non sono molto bravo con l'arte ASCII! ):

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

Lo pseudocodice va così:

  1. Scorrere tutte le righe nel datatable.
  2. Per ogni riga, creare un TreeNode e impostarne le proprietà. Ripeti in modo ricorsivo il processo per tutte le righe che hanno un ParentNodeID corrispondente all'ID di questa riga.
  3. Ogni iterazione completa restituisce un nodo che conterrà tutti i nodi secondari corrispondenti con annidamento infinito.
  4. Aggiungi la lista dei nomi completata a TreeView.

Il problema nel tuo scenario deriva dal fatto che la "chiave esterna" fa riferimento a una colonna nella stessa tabella. Ciò significa che quando eseguiamo l'iterazione tra le righe, dobbiamo tenere traccia di quali righe sono già state analizzate. Ad esempio, nella tabella sopra, il nodo corrispondente alla seconda e alla terza riga sono già aggiunti nella prima iterazione completa. Pertanto, non dobbiamo aggiungerli di nuovo. Esistono due modi per tenerne traccia:

  1. Mantiene un elenco di ID che sono stati eseguiti ( doneNotes ). Prima di aggiungere ogni nuovo nodo, verificare se noteID esiste in quell'elenco. Questo è il metodo più veloce e normalmente dovrebbe essere preferito. ( questo metodo è commentato nel codice seguente )
  2. Per ogni iterazione, utilizzare un delegato generico predicato ( TrovaNodo ) per cercare l'elenco dei nodi aggiunti (tenendo conto dei nodi nidificati) per vedere se il nodo da aggiungere esiste in quell'elenco. Questa è la soluzione più lenta, ma mi piace un po 'il codice complicato! : P

Ok, ecco il codice provato e testato (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);
      }
    }
  }
}

Altri suggerimenti

Ho creato un metodo di estensione molto più semplice per TreeView, che prevede l'uso di una nuova classe di estensione semplice che aggiunge due utili proprietà a 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;
    }

Grazie a modiX per i suoi metodi per ottenere tutti i nodi (nidificati).

controlla questo:

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

Per altro e codice sorgente completo: Come popolare treeview da datatable in vb.net

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top