تركز هذه المقالة على كيفية استخدام عنصر تحكم TreeView. يحتوي عنصر التحكم TreeView على وظائف غنية ويمكن استخدامه في العديد من المواقف. ملخص: يصف كيفية إضافة وظيفة ربط البيانات إلى عنصر تحكم TreeView، وهو أحد أمثلة تطوير عناصر التحكم في Microsoft Windows. يمكنك قراءة هذه المقالة بالتزامن مع المقالة العامة ذات الصلة.
مقدمة
حيثما أمكن، يجب أن تبدأ بعناصر التحكم الجاهزة لأن عناصر التحكم في Microsoft® Windows® Forms المتوفرة تتضمن الكثير من التعليمات البرمجية والاختبارات، مما يجعل التخلي عنها والبدء من نقطة الصفر مضيعة للوقت. وبناءً على ذلك، في هذا المثال، سوف أرث عنصر تحكم Windows Forms الموجود، TreeView، ثم أقوم بتخصيصه. عند تنزيل التعليمات البرمجية لعنصر تحكم TreeView ، يمكنك أيضًا الحصول على أمثلة إضافية لتطوير التحكم، بالإضافة إلى تطبيق نموذجي يوضح كيفية استخدام TreeView المحسّن مع عناصر التحكم الأخرى المرتبطة بالبيانات.
تصميم عرض شجرة ربط البيانات
بالنسبة لمطوري Windows، تعد إضافة ربط البيانات إلى عنصر تحكم TreeView مشكلة شائعة، ولكن نظرًا لوجود اختلاف رئيسي واحد بين TreeView وعناصر التحكم الأخرى (مثل ListBox أو DataGrid ) (أي يعرض TreeView بيانات هرمية)، فإن عنصر التحكم الأساسي هو هذه الميزة. غير مدعوم بعد (أي أنه لا يزال يتعين علينا استخدامه). بالنظر إلى جدول البيانات، من الواضح كيفية عرض تلك المعلومات في ListBox أو DataGrid ، ولكن استخدام طبيعة TreeView ذات الطبقات لعرض نفس البيانات يعد أقل وضوحًا. شخصيًا، قمت بتطبيق العديد من الطرق المختلفة عند عرض البيانات باستخدام TreeView ، ولكن هناك طريقة واحدة هي الأكثر استخدامًا: تجميع البيانات في الجدول حسب حقول معينة، كما هو موضح في الشكل 1.
الشكل 1: عرض البيانات في TreeView
في هذا المثال، سأقوم بإنشاء عنصر تحكم TreeView الذي يمكنني من خلاله تمرير مجموعة بيانات مسطحة (كما هو موضح في الشكل 2) وإنتاج النتائج الموضحة في الشكل 1 بسهولة.
الشكل 2: مجموعة النتائج المسطحة التي تحتوي على جميع المعلومات اللازمة لإنشاء الشجرة الموضحة في الشكل 1
قبل أن أبدأ في البرمجة، توصلت إلى تصميم لعنصر التحكم الجديد الذي سيتعامل مع مجموعة البيانات المحددة هذه، وآمل أن ينجح في العديد من المواقف المماثلة الأخرى. أضف مجموعة مجموعات كبيرة بما يكفي لإنشاء بنية هرمية باستخدام معظم البيانات الثابتة، حيث تحدد حقل تجميع وحقل عرض وحقل قيمة لكل مستوى من مستويات التسلسل الهرمي (يجب أن تكون أي حقول أو جميعها متماثلة). من أجل تحويل البيانات الموضحة في الشكل 2 إلى TreeView الموضحة في الشكل 1، يتطلب عنصر التحكم الجديد منك تحديد مستويين للتجميع، Publisher وTitle، وتحديد pub_id كحقل التجميع لمجموعة Publisher و title_id كحقل التجميع في حقل المجموعة. بالإضافة إلى حقول التجميع، تحتاج أيضًا إلى تحديد حقول العرض والقيمة لكل مجموعة لتحديد النص الذي يتم عرضه على عقدة المجموعة المقابلة والقيمة التي تحدد المجموعة المحددة بشكل فريد. عند مواجهة هذا النوع من البيانات، استخدم pub_name/pub_id و title/title_id كحقول العرض/القيمة لهاتين المجموعتين. تصبح معلومات المؤلف هي العقد الطرفية للشجرة (العقد الموجودة في نهاية التسلسل الهرمي للتجميع)، وتحتاج أيضًا إلى تحديد المعرف ( au_id ) وحقول العرض ( au_lname ) لهذه العقد.
عند إنشاء عنصر تحكم مخصص، فإن تحديد كيفية استخدام المبرمج لعنصر التحكم قبل بدء البرمجة سيساعد في جعل عنصر التحكم أكثر كفاءة. في هذه الحالة، أود أن يكون المبرمج (بالنظر إلى البيانات الموضحة سابقًا والنتيجة المرجوة) قادرًا على إنجاز التجميع باستخدام بضعة أسطر من التعليمات البرمجية مثل هذا:
| مع دي بي تري كونترول .ValueMember = au_id .DisplayMember = au_lname .DataSource = myDataTable.DefaultView .AddGroup(الناشر، pub_id، pub_name، pub_id) .AddGroup(العنوان، title_id، العنوان، title_id) نهاية مع |
ملحوظة: هذا ليس السطر الأخير من الكود الذي كتبته، ولكنه مشابه. أثناء تطوير عنصر التحكم، أدركت أنني بحاجة إلى ربط فهرس الصورة في ImageList المرتبط بـ TreeView بكل مستوى تجميع، لذلك اضطررت إلى إضافة معلمة إضافية إلى أسلوب AddGroup .
لإنشاء الشجرة فعليًا، سأراجع البيانات وأبحث عن التغييرات في الحقول (المحددة كقيم التجميع لكل مجموعة) أثناء إنشاء عقد تجميع جديدة إذا لزم الأمر وعقدة طرفية لكل عنصر بيانات. وبسبب عقد التجميع، سيكون إجمالي عدد العقد أكبر من عدد العناصر الموجودة في مصدر البيانات، ولكن ستكون هناك عقدة طرفية واحدة بالضبط لكل عنصر في البيانات الأساسية.
الشكل 3: عقد المجموعة والعقد الورقية
سيكون التمييز بين العقد الورقية وعقد المجموعة (كما هو موضح في الشكل 3) مهمًا لبقية هذه المقالة. قررت التعامل مع هذين النوعين من العقد بشكل مختلف، وإنشاء عقد مخصصة لكل نوع من العقد، ورفع أحداث مختلفة بناءً على نوع العقدة المحددة.
تنفيذ ربط البيانات
الخطوة الأولى في كتابة التعليمات البرمجية لعنصر التحكم هذا هي إنشاء المشروع وفئة البداية المقابلة له. في هذا المثال، أقوم أولاً بإنشاء مكتبة تحكم Windows جديدة، ثم أحذف فئة UserControl الافتراضية واستبدلها بفئة جديدة ترث من عنصر تحكم TreeView :
الفئة العامة dbTreeControl
يرث System.Windows.Forms.TreeView
من الآن فصاعدًا، سأقوم بتصميم عنصر تحكم يمكن وضعه على نموذج ويكون له شكل ووظيفة TreeView العادية. الخطوة التالية هي البدء في إضافة الكود المطلوب للتعامل مع الوظيفة الجديدة التي تتم إضافتها إلى TreeView ، وهي ربط البيانات وتجميع البيانات.
إضافة خاصية مصدر البيانات
تعد جميع وظائف عنصر التحكم الجديد مهمة، ولكن هناك مشكلتان رئيسيتان في إنشاء عناصر تحكم معقدة مرتبطة بالبيانات وهما معالجة خصائص DataSource واسترداد العناصر الفردية من كل كائن في مصدر البيانات.
إنشاء روتين السمة
أولاً، أي عنصر تحكم يستخدم لتنفيذ ربط البيانات المعقدة يحتاج إلى تنفيذ روتين خاصية DataSource والحفاظ على متغيرات الأعضاء المناسبة:
| m_DataSource خاص ككائن _ مصدر بيانات الملكية العامة () ككائن يحصل إرجاع m_DataSource نهاية الحصول على تعيين (قيمة ByVal ككائن) إذا كانت القيمة لا شيء ثم سم = لا شيء تغيير التجميع() آخر إذا لم يكن الأمر كذلك (نوع القيمة هو Ilist أو _ نوع القيمة هو IListSource) إذن ' ليس مصدر بيانات صالحًا لهذا الغرض رمي System.Exception الجديد (مصدر بيانات غير صالح) آخر إذا كانت قيمة TypeOf هي IListSource إذن تعتيم myListSource كـ IlistSource myListSource = CType (القيمة، IListSource) إذا myListSource.ContainsListCollection = صحيح إذن رمي System.Exception الجديد (مصدر بيانات غير صالح) آخر "نعم، نعم." فهو مصدر بيانات صالح m_DataSource = القيمة سم = CType(Me.BindingContext(Value)، _ مدير العملة) تغيير التجميع() نهاية إذا آخر m_DataSource = القيمة سم = CType(Me.BindingContext(Value)، _ مدير العملة) تغيير التجميع() نهاية إذا نهاية إذا نهاية إذا نهاية المجموعة نهاية الملكية |
يتم دعم الكائنات التي يمكن استخدامها كمصادر بيانات لربط البيانات المعقدة بشكل عام. تعرض هذه الواجهة البيانات كمجموعة من الكائنات وتوفر العديد من الخصائص المفيدة، مثل Count . يتطلب عنصر تحكم TreeView الجديد كائنًا مدعومًا بـ IList في ربطه، ولكن استخدام واجهة أخرى يعمل بشكل جيد لأنه يوفر طريقة ملائمة للحصول على كائن IList ( GetList ). عند تعيين خاصية DataSource ، أقوم أولاً بتحديد ما إذا تم توفير كائن صالح، أي كائن يدعم IList أو IListSource . ما أريده حقًا هو IList ، لذا إذا كان الكائن يدعم IListSource فقط (مثل DataTable )، فسأستخدم طريقة GetList() لتلك الواجهة للحصول على الكائن الصحيح.
تحتوي بعض الكائنات التي تطبق IListSource (مثل DataSet ) في الواقع على قوائم متعددة ممثلة بواسطة خاصية يحتوي علىListCollection . إذا كانت هذه الخاصية صحيحة ، فسيقوم GetList بإرجاع كائن IList يمثل قائمة (تحتوي على قوائم متعددة). في المثال الخاص بي، قررت دعم الاتصالات المباشرة بكائنات IList أو كائنات IListSource التي تحتوي على كائن IList واحد فقط، وتجاهل الكائنات التي تتطلب عملاً إضافيًا لتحديد مصدر بيانات، مثل DataSet .
ملاحظة: إذا كنت تريد دعم مثل هذه الكائنات ( DataSet أو ما شابه ذلك)، يمكنك إضافة خاصية إضافية (مثل DataMember ) لتحديد قائمة فرعية محددة للربط.
إذا كان مصدر البيانات المقدم صالحًا، فستكون النتيجة النهائية هي مثيل تم إنشاؤه ( cm = Me.BindingContext(Value) ). نظرًا لأنه سيتم استخدام هذا المثيل للوصول إلى مصدر البيانات الأساسي وخصائص الكائن ومعلومات الموقع، فسيتم تخزينه في متغيرات محلية.
إضافة عرض وقيمة خصائص الأعضاء
يعد الحصول على مصدر بيانات هو الخطوة الأولى في تنفيذ ربط البيانات المعقدة، ولكن يحتاج عنصر التحكم إلى معرفة الحقول أو خصائص البيانات المحددة التي سيتم استخدامها كأعضاء عرض وقيمة. سيتم استخدام عضو العرض كعنوان لعقدة الشجرة، بينما يمكن الوصول إلى عضو القيمة من خلال خاصية قيمة العقدة. هذه الخصائص كلها عبارة عن سلاسل تمثل أسماء الحقول أو الخصائص ويمكن إضافتها بسهولة إلى عنصر التحكم:
| m_ValueMember الخاص كسلسلة m_DisplayMember خاص كسلسلة _ قيمة الملكية العامة () كسلسلة يحصل إرجاع m_ValueMember نهاية الحصول على تعيين (قيمة ByVal كسلسلة) m_ValueMember = القيمة نهاية المجموعة نهاية الملكية _ الملكية العامة DisplayMember() كسلسلة يحصل إرجاع m_DisplayMember نهاية الحصول على تعيين (قيمة ByVal كسلسلة) m_DisplayMember = القيمة نهاية المجموعة نهاية الملكية |
في TreeView هذا، ستمثل هذه الخصائص فقط أعضاء العرض والقيمة للعقد الطرفية، وسيتم تحديد المعلومات المقابلة لكل مستوى تجميع في أسلوب AddGroup .
باستخدام كائن مدير العملة
في الخاصية DataSource التي تمت مناقشتها مسبقًا، يتم إنشاء مثيل لفئة CurryManager وتخزينه في متغير على مستوى الفئة. تعد فئة currencyManager التي يتم الوصول إليها من خلال هذا الكائن جزءًا أساسيًا من تنفيذ ربط البيانات لأنها تحتوي على خصائص وأساليب وأحداث تمكن الوظائف التالية:
استرداد السمة/قيمة الحقل
يسمح لك كائن currencyManager باسترداد قيم الخاصية أو الحقل، مثل قيمة حقل DisplayMember أو ValueMember ، من عنصر فردي في مصدر بيانات من خلال أسلوب GetItemProperties الخاص به. ثم استخدم كائن PropertyDescriptor للحصول على قيمة حقل أو خاصية معينة في عنصر قائمة محدد. يوضح مقتطف التعليمات البرمجية التالي كيفية إنشاء كائنات PropertyDescriptor هذه وكيفية استخدام الدالة GetValue للحصول على قيمة الخاصية لعنصر في مصدر البيانات الأساسي. لاحظ خاصية القائمة لكائن currencyManager : فهي توفر الوصول إلى مثيل IList الذي يرتبط به عنصر التحكم:
| قم بتعتيم myNewLeafNode كـ TreeLeafNode خافت currObject ككائن currObject = cm.List(currentListIndex) إذا كان Me.DisplayMember <> وأيضًا Me.ValueMember <> إذن 'إضافة عقدة ورقة؟ تعتيم pdValue كـ System.ComponentModel.PropertyDescriptor خافت pdDisplay كـ System.ComponentModel.PropertyDescriptor pdValue = cm.GetItemProperties()(Me.ValueMember) pdDisplay = cm.GetItemProperties()(Me.DisplayMember) ماي نيوليف نود = _ New TreeLeafNode(CStr(pdDisplay.GetValue(currObject)))، _ الكائن الحالي، _ pdValue.GetValue(currObject)، _ مؤشر القائمة الحالية) |
يتجاهل GetValue نوع البيانات الأساسي للخاصية عند إرجاع كائن، لذا يجب تحويل قيمة الإرجاع قبل استخدامه.
حافظ على مزامنة عناصر التحكم المرتبطة بالبيانات
يحتوي currencyManager على ميزة رئيسية أخرى: بالإضافة إلى توفير الوصول إلى مصادر البيانات المرتبطة وخصائص العناصر، فإنه يسمح باستخدام مصدر البيانات نفسه لتنسيق ربط البيانات بين عنصر التحكم هذا وأي عنصر تحكم آخر. يمكن استخدام هذا الدعم للتأكد من بقاء عناصر التحكم المتعددة المرتبطة بنفس مصدر البيانات في نفس الوقت على نفس العنصر من مصدر البيانات. بالنسبة لعنصر التحكم الخاص بي، أريد التأكد من أنه عند تحديد عنصر ما في الشجرة، فإن كافة عناصر التحكم الأخرى المرتبطة بنفس مصدر البيانات تشير إلى نفس العنصر (نفس السجل أو الصف أو حتى الصفيف، إذا كنت ترغب في ذلك) للتفكير فيما يتعلق بقاعدة البيانات). للقيام بذلك، قمت بتجاوز أسلوب OnAfterSelect في TreeView الأساسي. في هذه الطريقة (والتي يتم استدعاؤها بعد تحديد عقدة الشجرة)، أقوم بتعيين خاصية الموضع لكائن مدير العملة على فهرس العنصر المحدد حاليًا. يوضح نموذج التطبيق المتوفر مع عنصر تحكم TreeView كيف تعمل عناصر التحكم في المزامنة على تسهيل إنشاء واجهات مستخدم مرتبطة بالبيانات. لتسهيل تحديد موضع القائمة للعنصر المحدد حاليًا، استخدمت فئة TreeNode مخصصة ( TreeLeafNode أو TreeGroupNode ) وقمت بتخزين فهرس القائمة لكل عقدة في خاصية Position التي قمت بإنشائها:
| التجاوزات المحمية الفرعية OnAfterSelect _ (ByVal وSystem.Windows.Forms.TreeViewEventArgs) خافت tln باسم TreeLeafNode إذا كان TypeOf e.Node هو TreeGroupNode إذن tln = FindFirstLeafNode(e.Node) تعتيم المجموعة Args كمجموعة جديدةTreeViewEventArgs(e) RaiseEvent AfterGroupSelect(groupArgs) ElseIf TypeOf e.Node هو TreeLeafNode إذن أوراق خافتة Args كأوراق جديدة TreeViewEventArgs(e) RaiseEvent AfterLeafSelect(leafArgs) tln = CType(e.Node، TreeLeafNode) نهاية إذا إذا لم يكن tln لا شيء إذن إذا cm.Position <> tln.Position ثم cm.Position = tln.Position نهاية إذا نهاية إذا MyBase.OnAfterSelect(ه) نهاية الفرعية |
في مقتطف الكود السابق، ربما لاحظت وظيفة تسمى FindFirstLeafNode ، والتي أريد تقديمها بإيجاز هنا. في TreeView الخاص بي، تتوافق العقد الطرفية فقط (العقد النهائية في التسلسل الهرمي) مع العناصر الموجودة في DataSource ، ويتم استخدام جميع العقد الأخرى فقط لإنشاء بنية التجميع. إذا كنت أرغب في إنشاء عنصر تحكم مرتبط بالبيانات جيد الأداء، فأنا بحاجة دائمًا إلى تحديد عنصر يتوافق مع DataSource ، لذا كلما قمت بتحديد عقدة مجموعة، أجد العقدة الطرفية الأولى ضمن المجموعة، كما لو كانت هذه العقدة هي الاختيار الحالي. يمكنك الاطلاع على المثال عمليًا، ولكن في الوقت الحالي لا تتردد في استخدامه.
| الوظيفة الخاصة FindFirstLeafNode(ByVal currNode As TreeNode) _ مثل TreeLeafNode إذا كان TypeOf currNode هو TreeLeafNode إذن إرجاع CType (currNode، TreeLeafNode) آخر إذا currNode.Nodes.Count > 0 ثم إرجاع FindFirstLeafNode(currNode.Nodes(0)) آخر العودة لا شيء نهاية إذا نهاية إذا وظيفة النهاية |
يؤدي تعيين خاصية الموضع لكائن مدير العملة إلى مزامنة عناصر التحكم الأخرى مع التحديد الحالي، ولكن عندما يتغير موضع عناصر التحكم الأخرى، يقوم مدير العملة أيضًا بإنشاء أحداث بحيث يتغير التحديد وفقًا لذلك. لكي يكون مكونًا جيدًا مرتبطًا بالبيانات، يجب أن يتحرك التحديد مع تغير موقع مصدر البيانات، ويجب تحديث العرض عند تعديل بيانات أحد العناصر. هناك ثلاثة أحداث أثارها مدير العملة : CurrentChanged ، وItemChanged ، و PositionChanged . الحدث الأخير بسيط إلى حد ما؛ أحد أغراض مدير العملة هو الحفاظ على مؤشر الموضع الحالي لمصدر البيانات بحيث يمكن لعناصر التحكم المرتبطة المتعددة عرض نفس السجل أو عنصر القائمة، ويتم رفع هذا الحدث كلما تغير الموضع. يتداخل الحدثان الآخران أحيانًا ويكون التمييز بينهما أقل وضوحًا. يصف ما يلي كيفية استخدام هذه الأحداث في عناصر التحكم المخصصة: يعتبر PositionChanged حدثًا بسيطًا نسبيًا ولن يتم وصفه هنا عندما تريد ضبط العنصر المحدد حاليًا في عنصر تحكم معقد مرتبط بالبيانات (مثل الشجرة)، يرجى استخدام The حدث. يتم رفع الحدث ItemChanged عندما يتم تعديل عنصر في مصدر البيانات، بينما يتم رفع الحدث CurrentChanged فقط عند تعديل العنصر الحالي.
في TreeView الخاص بي، وجدت أنه كلما قمت بتحديد عنصر جديد، يتم رفع جميع الأحداث الثلاثة، لذلك قررت التعامل مع حدث PositionChanged عن طريق تغيير العنصر المحدد حاليًا وعدم القيام بأي شيء مع العنصرين الآخرين. من المستحسن إرسال مصدر البيانات إلى IBindingList (إذا كان مصدر البيانات يدعم IBindingList ) واستخدام الحدث ListChanged بدلاً من ذلك، لكنني لم أقم بتنفيذ هذه الوظيفة.
| خاص فرعي cm_PositionChanged(ByVal sender As Object, _ ByVal e As System.EventArgs) يتعامل مع cm.PositionChanged خافت tln باسم TreeLeafNode إذا كان TypeOf Me.SelectedNode هو TreeLeafNode إذن tln = CType(Me.SelectedNode، TreeLeafNode) آخر tln = FindFirstLeafNode(Me.SelectedNode) نهاية إذا إذا tln.Position <> cm.Position ثم Me.SelectedNode = FindNodeByPosition(cm.Position) نهاية إذا نهاية الفرعية وظيفة التحميل الزائد الخاصة FindNodeByPosition(مؤشر ByVal كعدد صحيح) _ مثل TreeNode إرجاع FindNodeByPosition(index, Me.Nodes) وظيفة النهاية وظيفة التحميل الزائد الخاصة FindNodeByPosition(مؤشر ByVal كعدد صحيح، _ ByVal NodesToSearch As TreeNodeCollection) كـ TreeNode خافت ط كعدد صحيح = 0 خافت currNode كـ TreeNode خافت tln باسم TreeLeafNode افعل بينما أنا <NodesToSearch.Count currNode = NodesToSearch(i) أنا += 1 إذا كان TypeOf currNode هو TreeLeafNode إذن tln = CType(currNode، TreeLeafNode) إذا tln.Position = فهرس ثم إرجاع currNode نهاية إذا آخر currNode = FindNodeByPosition(index, currNode.Nodes) إذا لم يكن currNode لا شيء إذن إرجاع currNode نهاية إذا نهاية إذا حلقة العودة لا شيء وظيفة النهاية |
تحويل مصدر البيانات إلى شجرة
بعد كتابة كود ربط البيانات، يمكنني المضي قدمًا وإضافة الكود لإدارة مستويات التجميع، وإنشاء الشجرة وفقًا لذلك، ثم إضافة بعض الأحداث والأساليب والخصائص المخصصة.
مجموعة الإدارة
لتكوين مجموعة من المجموعات، يجب على المبرمج إنشاء وظائف AddGroup و RemoveGroup و ClearGroups . كلما تم تعديل مجموعة المجموعة، يجب إعادة رسم الشجرة (لتعكس التكوين الجديد)، لذلك قمت بإنشاء إجراء عام، GroupingChanged ، يمكن استدعاؤه بواسطة تعليمات برمجية مختلفة في عنصر التحكم عندما تتغير الظروف وتحتاج الشجرة إلى إجبارها على ذلك إعادة البناء:
| مجموعات الأشجار الخاصة كقائمة صفيف جديدة () إزالة المجموعة الفرعية العامة (مجموعة ByVal كمجموعة) إذا لم يكن TreeGroups.Contains(group) ثم TreeGroups.إزالة (مجموعة) تغيير التجميع() نهاية إذا نهاية الفرعية التحميل الزائد العام Sub AddGroup (مجموعة ByVal كمجموعة) يحاول TreeGroups.Add(مجموعة) تغيير التجميع() يمسك إنهاء المحاولة نهاية الفرعية التحميل الزائد العام Sub AddGroup (اسم ByVal كسلسلة، _ مجموعة ByValBy As String، _ ByVal عرض العضو كسلسلة، _ قيمة ByValMember كسلسلة، _ ByVal imageIndex كعدد صحيح، _ ByVal المحدد ImageIndex كعدد صحيح) تعتيم myNewGroup كمجموعة جديدة (name، groupBy، _ عرض عضو، قيمة عضو، _ مؤشر الصورة، مؤشر الصورة المحدد) Me.AddGroup(myNewGroup) نهاية الفرعية الوظيفة العامة GetGroups() كمجموعة() إرجاع CType(treeGroups.ToArray(GetType(Group)), Group()) وظيفة النهاية |
شجرة ممتدة
تتم عملية إعادة البناء الفعلية للشجرة من خلال عمليتين: BuildTree و AddNodes . نظرًا لأن الكود الخاص بهاتين العمليتين طويل جدًا، فإن هذه المقالة لا تسردهما جميعًا، ولكنها تحاول تلخيص سلوكهما (بالطبع، يمكنك تنزيل الكود الكامل إذا أردت). كما ذكرنا سابقًا، يمكن للمبرمجين التفاعل مع عنصر التحكم هذا عن طريق إعداد سلسلة من المجموعات، ثم استخدام هذه المجموعات في BuildTree لتحديد كيفية إعداد عقد الشجرة. يقوم BuildTree بمسح مجموعة العقد الحالية ثم يتكرر عبر مصدر البيانات بأكمله لمعالجة تجميع المستوى الأول (ذكر الناشر في المثال والرسم التوضيحي سابقًا في هذه المقالة)، وإضافة عقدة لكل قيمة تجميع مختلفة (باستخدام البيانات الموجودة في المثال، لكل عقدة بقيمة pub_id )، ثم قم باستدعاء AddNodes لملء كافة العقد ضمن تجميع المستوى الأول. تستدعي AddNodes نفسها بشكل متكرر للتعامل مع أكبر عدد ممكن من المستويات، وإضافة العقد الجماعية والعقد الطرفية حسب الضرورة. استخدم فئتين مخصصتين استنادًا إلى TreeNode للتمييز بين العقد الجماعية والعقد الطرفية وتوفير السمات المقابلة لنوعي العقد.
أحداث TreeView المخصصة
عندما يتم تحديد عقدة، يقوم TreeView بتشغيل حدثين: BeforeSelect و AfterSelect . ولكن في عنصر التحكم الخاص بي، أردت أن أجعل أحداث العقد الجماعية والعقد الطرفية مختلفة، لذلك أضفت الأحداث الخاصة بي BeforeGroupSelect/AfterGroupSelect و BeforeLeafSelect/AfterLeafSelect بالإضافة إلى الأحداث الأساسية، قمت أيضًا بتشغيل فئة معلمة حدث مخصصة:
| حدث عام قبلGroupSelect _ (مرسل ByVal ككائن، ByVal e كgroupTreeViewCancelEventArgs) حدث عام AfterGroupSelect _ (مرسل ByVal ككائن، ByVal e كgroupTreeViewEventArgs) حدث عامBeforeLeafSelect _ (مرسل ByVal ككائن، ByVal e As leafTreeViewCancelEventArgs) حدث عام AfterLeafSelect _ (مرسل ByVal ككائن، ByVal e As leafTreeViewEventArgs) التجاوزات المحمية الفرعية OnBeforeSelect _ (ByVal وSystem.Windows.Forms.TreeViewCancelEventArgs) إذا كان TypeOf e.Node هو TreeGroupNode إذن تعتيم المجموعةArgs كمجموعة جديدةTreeViewCancelEventArgs(e) RaiseEvent BeforeGroupSelect(CObj(Me)، groupArgs) ElseIf TypeOf e.Node هو TreeLeafNode إذن أوراق خافتة Args كأوراق جديدة TreeViewCancelEventArgs(e) RaiseEvent BeforeLeafSelect(CObj(Me)، leafArgs) نهاية إذا MyBase.OnBeforeSelect(e) نهاية الفرعية التجاوزات المحمية الفرعية OnAfterSelect _ (ByVal وSystem.Windows.Forms.TreeViewEventArgs) خافت tln باسم TreeLeafNode إذا كان TypeOf e.Node هو TreeGroupNode إذن tln = FindFirstLeafNode(e.Node) تعتيم المجموعة Args كمجموعة جديدةTreeViewEventArgs(e) RaiseEvent AfterGroupSelect(CObj(Me)، groupArgs) ElseIf TypeOf e.Node هو TreeLeafNode إذن أوراق خافتة Args كأوراق جديدة TreeViewEventArgs(e) RaiseEvent AfterLeafSelect(CObj(Me)، leafArgs) tln = CType(e.Node، TreeLeafNode) نهاية إذا إذا لم يكن tln لا شيء إذن إذا cm.Position <> tln.Position ثم cm.Position = tln.Position نهاية إذا نهاية إذا MyBase.OnAfterSelect(ه) نهاية الفرعية |
يتم تضمين فئات العقدة المخصصة ( TreeLeafNode و TreeGroupNode ) وفئات معلمات الأحداث المخصصة في التعليمات البرمجية القابلة للتنزيل.
تطبيق عينة
لفهم كافة التعليمات البرمجية الموجودة في نموذج التحكم هذا بشكل كامل، يجب أن تفهم كيفية عمله في التطبيق الخاص بك. يستخدم نموذج التطبيق المضمن قاعدة بيانات Pubs.mdb Access ويوضح كيفية استخدام عنصر تحكم الشجرة مع عناصر التحكم الأخرى المرتبطة بالبيانات لإنشاء تطبيقات Windows. تتضمن الميزات الرئيسية التي يجب ملاحظتها بشكل خاص في هذه الحالة مزامنة الشجرة مع عناصر التحكم المرتبطة الأخرى والاختيار التلقائي لعقد الشجرة عند إجراء بحث على مصدر البيانات.
ملاحظة: تم تضمين نموذج التطبيق هذا (المسمى TheSample) في تنزيل هذه المقالة.
الشكل 4: التطبيق التجريبي لـ TreeView المرتبط بالبيانات
ملخص
عنصر تحكم الشجرة المرتبط بالبيانات الموضح في هذه المقالة غير مناسب لكل مشروع يتطلب عنصر تحكم شجرة لعرض معلومات قاعدة البيانات، ولكنه يقدم أسلوبًا لتخصيص عنصر التحكم لأغراض شخصية. تذكر أن أي عنصر تحكم معقد مرتبط بالبيانات ترغب في إنشائه سيحتوي على الكثير من نفس التعليمات البرمجية الموجودة في عنصر تحكم الشجرة، ويمكنك تبسيط تطوير عنصر التحكم المستقبلي عن طريق تعديل التعليمات البرمجية الموجودة.