سؤال

لدي عنصر تحكم WinForm TreeView الذي يعرض العلاقة بين الوالدين والطفل في CaseNotes (أعلم أن هذا لا يعني شيئًا لمعظمكم ولكنه يساعدني في تصور الإجابات).

لدي DataTable لـ CaseNotes الذي أحتاج إلى عرضه.يتم تعريف الوالد/الطفل على النحو التالي:إذا كان الصف يحتوي على ParentNoteID، فهو عبارة عن عقدة فرعية لهذه الملاحظة وإلا فهو عقدة جذرية.يمكن أن تكون أيضًا ملاحظة أصل (ولكن ليست عقدة جذرية) إذا كان هناك صف آخر يحتوي على معرفه باعتباره ParentNoteID.

لتعقيد (ربما تبسيط) الأمور لدي رمز العمل (في الغالب) أدناه الذي يقوم بتلوين العقد بالتناوب.لقد قمت يدويًا بإنشاء مجموعة ثابتة لعرض الشجرة وهي تقوم بتلوينها بشكل صحيح إلى حد ما.أنا الآن بحاجة إلى ملء العقد ديناميكيًا من DataTable الخاص بي.

نظرًا لأنني أذهب بالفعل عبر عقدة Treeview تلو العقدة، ألا يجب أن أتمكن من إلحاق البيانات بهذه العملية بطريقة ما؟ربما أحتاج إلى إنشاء العقد أولاً ثم تلوينها كإجراء منفصل ولكن طريقة العودية ستظل سارية، أليس كذلك؟

لنفترض أنني أريد عرض CaseNoteID لكل عقدة.يتم إرجاع ذلك في DataTable وهو فريد من نوعه.

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

يحرر

أفكاري/محاولاتي حتى الآن:

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

ومن الواضح أن هذا معيب.أحدهما يعرض تاريخ الاتصال مرارًا وتكرارًا.من المؤكد أنه يظهر العدد الصحيح من المرات ولكني أرغب في الحصول على قيمة ContactDate(وهو عمود في قاعدة البيانات ويتم إرجاعه في DataTable.ثانيًا، أحتاج إلى إضافة منطق ChildNode.أ if (node.parentNode = node.CaseNoteID) blah...

تحرير 2

لذلك وجدت هذا الرابط , هنا, ، وهذا يجعل الأمر يبدو وكأنني بحاجة إلى إدخال DataTable الخاص بي في ArrayList.هل هذا صحيح؟

تحرير 3

حسنًا، بفضل Cerebus، يعمل هذا في الغالب.لدي سؤال واحد فقط.كيف أعتبر هذا-->

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

واستخدام DataTable الذي تم إرجاعه في هذا؟هل أقوم فقط باستبدال هذا -->

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

أعتقد أن حيرتي هي هل ما زلت بحاجة إلى إجراء Column.Add وRow.Adds؟أيضًا كيف يمكن ترجمة DataColumn إلى بنية البيانات الحقيقية الخاصة بي؟آسف على الأسئلة الجاهلة للغاية، والخبر السار هو أنني لن أضطر إلى السؤال مرتين أبدًا.

تحرير 4

ما يلي هو تقديم خطأ في وقت التشغيل.

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

الخطأ كالتالي --> لا يمكن العثور على العمود [ea8428e4] وهي أول 8 أرقام من NoteID الصحيح (لا بد لي من استخدام المرشد).هل ينبغي البحث عن عمود بهذا الاسم؟؟لأنني أستخدم المرشد، فهل هناك شيء آخر يجب أن أفعله؟لقد قمت بتغيير جميع المراجع الموجودة في الكود الخاص بي والكود الخاص بك إلى Guid ...

هل كانت مفيدة؟

المحلول

لمحاولة حل هذه المشكلة، قمت بإنشاء نموذج لنموذج Windows وكتبت الكود التالي.لقد تصورت تصميم جدول البيانات على النحو التالي:

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

يجب أن يؤدي هذا إلى إنشاء شجرة كـ (آسف، أنا لست جيدًا في فن ASCII!):

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

الكود الزائف يسير على النحو التالي:

  1. قم بالتكرار خلال جميع الصفوف في جدول البيانات.
  2. لكل صف، قم بإنشاء TreeNode وقم بتعيين خصائصه.كرر العملية بشكل متكرر لجميع الصفوف التي تحتوي على ParentNodeID المطابق لمعرف هذا الصف.
  3. يُرجع كل تكرار كامل عقدة تحتوي على جميع العقد الفرعية المطابقة مع تداخل لا نهائي.
  4. قم بإضافة قائمة العقدة المكتملة إلى TreeView.

تنشأ المشكلة في السيناريو الخاص بك من حقيقة أن "المفتاح الخارجي" يشير إلى عمود في نفس الجدول.هذا يعني أنه عندما نكرر الصفوف، علينا أن نتتبع الصفوف التي تم تحليلها بالفعل.على سبيل المثال، في الجدول أعلاه، تمت إضافة العقدة المطابقة للصفين الثاني والثالث بالفعل في التكرار الكامل الأول.لذلك يجب ألا نضيفهم مرة أخرى.هناك طريقتان لتتبع ذلك:

  1. احتفظ بقائمة المعرفات التي تم إجراؤها (doneNotes).قبل إضافة كل عقدة جديدة، تحقق من وجود معرف الملاحظة في تلك القائمة.هذه هي الطريقة الأسرع ويجب تفضيلها عادةً.(تم التعليق على هذه الطريقة في الكود أدناه)
  2. لكل تكرار، استخدم المفوض العام المسند (FindNode) للبحث في قائمة العقد المضافة (محاسبة العقد المتداخلة) لمعرفة ما إذا كانت العقدة المراد إضافتها موجودة في تلك القائمة.هذا هو الحل الأبطأ، لكني أحب التعليمات البرمجية المعقدة نوعًا ما!:ص

حسنًا، إليك الكود الذي تم تجربته واختباره (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);
      }
    }
  }
}

نصائح أخرى

لقد قمت بإنشاء طريقة تمديد أبسط بكثير لـ TreeView، والتي تتضمن استخدام فئة توسيع بسيطة جديدة تضيف خاصيتين مفيدتين إلى 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;
    }

شكرا ل modiX لأجله طُرق للحصول على كافة العقد (المتداخلة).

افحص هذا :

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

للمزيد وكود المصدر الكامل: كيفية ملء Treeview من datatable في vb.net

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top