Pregunta

Tengo un Control TreeView de WinForm que muestra la relación de Padres e Hijos de CaseNotes (sé que eso no significa nada para la mayoría de ustedes, pero me ayuda a visualizar las respuestas).

Tengo una tabla de datos de las notas de caso que necesito mostrar. El padre / hijo se define como: Si la fila tiene un ParentNoteID, entonces es un nodo secundario de esa nota, de lo contrario es un rootNode. También podría ser una nota principal (pero no un rootNode) si otra fila tiene su ID como ParentNoteID.

Para complicar (quizás simplificar) las cosas, tengo el siguiente código de trabajo (en su mayoría) que colorea los nodos alternativamente. Creé manualmente una colección estática para la vista de árbol y los colorea bastante correctamente. Ahora necesito rellenar dinámicamente los Nodos de mi DataTable.

Dado que ya estoy atravesando la vista de árbol por nodo, ¿no debería poder agregar los datos a este proceso de alguna manera? Tal vez necesito construir los nodos primero y luego colorearlos como una rutina separada, pero el Método de Recursión todavía se aplicaría, ¿correcto?

Digamos que quiero mostrar CaseNoteID para cada nodo. Eso se devuelve en el DataTable y es único.

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

EDITAR

Mis pensamientos / intentos hasta ahora:

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

Obviamente esto es defectuoso. Uno solo muestra la fecha de contacto una y otra vez. Concedido, muestra el número correcto de veces, pero me gustaría el valor de ContactDate (que es una columna en la base de datos y se está devolviendo en el DataTable. Segundo, necesito agregar la lógica del nodo de niños. Un si (nodo). parentNode = node.CaseNoteID) blah ...

EDIT 2

Encontré este enlace, here , y parece que necesito convertir mi DataTable en una ArrayList. ¿Es eso correcto?

EDITAR 3

Bien, gracias a Cerebus esto está funcionando principalmente. Solo tengo una pregunta más. ¿Cómo tomo esto - >

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

y usar mi DataTable devuelto en esto? Acabo de reemplazar esto - >

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

Creo que mi confusión es si todavía necesito hacer Column.Add y Row.Adds? Además, ¿cómo se traduciría DataColumn a mi estructura de datos real? Lo siento por las preguntas tan ignorantes, la buena noticia es que nunca tengo que preguntar dos veces.

EDITAR 4

Lo siguiente es proporcionar un error de tiempo de ejecución.

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

El error es el siguiente: > No se puede encontrar la columna [ea8428e4] , que son los primeros 8 dígitos de la identificación correcta (tengo que usar un Guid). ¿Debería estar buscando una columna de ese nombre? Porque estoy usando un Guid, ¿hay algo más que deba hacer? Cambié todas las referencias en la mía y su código a Guid ...

¿Fue útil?

Solución

Para intentar resolver este problema, creé un formulario de muestra de Windows y escribí el siguiente código. Imaginé el diseño de datos de la siguiente manera:

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

Esto debería crear un árbol como ( lo siento, no soy muy bueno con el arte ASCII! ):

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

El pseudocódigo es así:

  1. Iterar a través de todas las filas en el datatable.
  2. Para cada fila, crea un TreeNode y establece sus propiedades. Repita recursivamente el proceso para todas las filas que tengan un ParentNodeID que coincida con el ID de esta fila.
  3. Cada iteración completa devuelve un nodo que contendrá todos los nodos secundarios coincidentes con un anidado infinito.
  4. Agregue la lista de nodos completa a TreeView.

El problema en su escenario surge del hecho de que " clave externa " se refiere a una columna en la misma tabla. Esto significa que cuando iteramos a través de las filas, tenemos que hacer un seguimiento de qué filas ya se han analizado. Por ejemplo, en la tabla anterior, el nodo que coincide con la segunda y tercera fila ya se agregó en la primera iteración completa. Por lo tanto, no debemos añadirlos de nuevo. Hay dos formas de hacer un seguimiento de esto:

  1. Mantenga una lista de los ID que se han hecho ( doneNotes ). Antes de agregar cada nuevo nodo, verifique si existe el ID de nota en esa lista. Este es el método más rápido y normalmente debería ser preferido. ( este método se comenta en el código a continuación )
  2. Para cada iteración, use un delegado genérico de predicado ( FindNode ) para buscar en la lista de nodos agregados (que tienen en cuenta los nodos anidados) para ver si el nodo que se agregará existe en esa lista. Esta es la solución más lenta, ¡pero me gustan los códigos complicados! : P

Bien, aquí está el código probado (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);
      }
    }
  }
}

Otros consejos

He creado un método de extensión mucho más simple para TreeView, que incluye el uso de una nueva clase de extensión simple que agrega dos propiedades útiles 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;
    }

Gracias a modiX por su métodos para obtener todos los nodos (anidados).

comprueba esto:

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

Para obtener más y el código fuente completo: Cómo llenar una vista de árbol desde datatable en vb.net

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top