Pergunta

Eu tenho um TreeView controle WinForm que mostra a relação pai-filho de CaseNotes (eu sei que não significa nada para a maioria de vocês, mas isso me ajuda a visualizar as respostas).

Eu tenho um DataTable dos CaseNotes que eu preciso para mostrar. O pai / filho é definido como: Se a linha tem uma ParentNoteID então é uma childNode dessa nota caso contrário, é um rootNode. Também poderia ser uma nota pai (mas não um rootNode) se outra linha tem o seu ID como é ParentNoteID.

Para complicar (talvez simplificar) coisas que tenho a seguir código de trabalho (principalmente) que cores os nós alternadamente. Eu criei manualmente uma coleção estática para o treeview e colore-los bastante corretamente. Agora eu preciso preencher dinamicamente os nós da minha DataTable.

Desde já estou atravessando o nó TreeView nó que eu não deveria ser capaz de anexar os dados para este processo de alguma forma? Talvez eu preciso para construir os nós em primeiro lugar e, em seguida, de cor como uma rotina separada, mas o método de recursão ainda seria aplicável, correto?

Vamos dizer que eu quero mostrar CaseNoteID para cada nó. Que é devolvido na DataTable e é ú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

Os meus pensamentos / tentativas até agora:

        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, isso é falho. ContactDate um apenas de exibição mais e mais. Concedido ele mostra que o número correto de vezes, mas eu gostaria que o Valor do ContactDate (que é uma coluna no banco de dados e está sendo retornado no DataTable. Em segundo lugar eu preciso adicionar o Logic childNode. A if (node.parentNode = node.CaseNoteID) blah...

EDIT 2

Então, eu encontrei este link, aqui , e faz parecer que eu preciso para obter minha DataTable em um ArrayList. Isso está correto?

EDIT 3

Está bem, graças a Cerebus Isto é principalmente trabalhar. Eu só tenho mais uma pergunta. Como faço para fazer isso ->

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

e usar o meu retornado DataTable nisto? Eu só substituir este ->

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

A minha confusão, penso eu, é ainda preciso fazer o Column.Add e Row.Adds? Também como seria o DataColumn traduzir a minha estrutura de dados real? Desculpem a perguntas muito ignorantes, a boa notícia é que eu nunca ter que pedir duas vezes.

EDIT 4

A seguir está fornecendo um erro de execução.

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

O erro é a seguinte -> Não é possível localizar coluna [ea8428e4] que é o primeiro 8 dígitos do NoteID correta (eu tenho que usar um Guid). Caso seja procurando uma coluna com esse nome ?? Porque eu estou usando um Guid há algo mais que eu preciso fazer? Mudei todas as referências na minha e seu código para Guid ...

Foi útil?

Solução

Para tentar resolver este problema, eu criei uma amostra janelas formar e escreveu o seguinte código. Eu imaginava o design tabela de dados da seguinte forma:

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

Isso deve criar uma árvore como ( desculpe, eu não sou muito bom com arte ASCII !):

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

Pseudocódigo é assim:

  1. percorrer todos as linhas na tabela de dados.
  2. Para cada linha, crie um TreeNode e definir suas propriedades. Recursively repetir o processo para todas as linhas que têm um ParentNodeID correspondência ID desta linha.
  3. Cada iteração completa retorna um nó que irá conter todos os childNodes correspondência com nidificação infinito.
  4. Adicione o nodelist preenchido para o TreeView.

O problema em seu cenário decorre do fato de a "chave estrangeira" refere-se a uma coluna na mesma tabela. Isto significa que quando nós iterar através das linhas, nós temos que manter o controle de quais linhas já foram analisados. Por exemplo, na tabela acima, o nó correspondente as segunda e terceira linhas são já adicionado na primeira iteração completa. Portanto, não deve adicioná-los novamente. Há duas maneiras de manter o controle de este:

  1. Mantenha uma lista de de identificação que foram feitas (doneNotes). Antes de adicionar cada novo nó, verifique se o NoteID existe nessa lista. Este é o método mais rápido e deve normalmente ser preferida. ( este método é comentada no código abaixo )
  2. Para cada iteração, use um delegado genérico predicado (FindNode) para procurar a lista de nós adicionado (que representam nós aninhados) para ver se o nó a-ser adicionado existe na lista. Esta é a solução mais lento, mas eu meio que gosto de código complicado! : P

Ok, aqui está o código testado e aprovado (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);
      }
    }
  }
}

Outras dicas

Eu criei muita método de extensão simplier para TreeView, envolvendo o uso da nova classe estendendo simples que adiciona duas propriedades úteis para 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;
    }

Graças à modiX para sua métodos para obter todos nós (aninhados).

verificar isso:

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 código-fonte mais e completa: Como preencher treeview da tabela de dados em vb.net

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top