Home برمجة جافا التعامل مع JList في Swing
التعامل مع JList في Swing

التعامل مع JList في Swing

105
2

سنتناول في هذا الموضوع لمحة بسيطة عن استخدام الJList وعمل Extending لها لكي تتناسب مع حاجة المستخدم ،، والJList وكغيرها من الSwing Component مبنية على مفهوم الModel-View-Controller وبالتالي اذا كنت تريد تغيير الObject الموجود داخل الComponent سوف تتعامل مع الModel ، واذا أردت تغيير طريقة العرض بالشكل الذي تريدها سوف تتعامل مع الView ، وهذه مرونة ممتازة في
تصميم الComponent .

* Inserting and Removing Values
* Rendering Values
* Custom List

نبدأ بالحالة الأولى والأسهل وهي الحالة الإفتراضية للJList وسنقوم بعمل JList يتكون من العديد من النصوص String ، كما يبين ذلك في الصورة من المثال التالي:
jlist_1

سنقوم بعمل الJList مكونة من العديد من النصوص :

وبالشكل الإفتراضي الJList ليست Scrollable لذلك يجب أن تضيف الlist الى JScrollPane وتضيف الأخير للPanel :

هذه الlist أيضا بالوضع الإفتراضي تتكون من 8 خانات يمكنك تغييرها باستخدام الدالة setVisibleRowCount :

الإتجاه Orientation في هذه الlist في الوضع الإفتراضي هو الvertical ويتم تحديد ذلك من خلال الدالة setLayoutOrientation ، ومن القيم لهذه الدالة:
JList.VERTICAL (الإفتراضي وكل الitems ستكون عموديا أسفل بعضها )
JList.VERTICAL_WRAP وهنا تكون الItems أيضا عموديا ولكن يتم الإنتقال لعمود أخر اذا كان عدد الitems أكبر من المسموح به في العمود الواحد)
JList.HORIZONTAL_WRAP نفس السابق ولكنه أفقيا ..

طريقة التحديد Selection mode في الوضع الإفتراضي هي Multiple وتسمح لك باختيار أكثر من item سواء بالضغط على الitems مع الزر CTRL لتحديد عدة items متفرقة أو SHIFT لتحديد عدة items متجاورة . وتستطيع تغيير الselection mode باستخدام الدالة setSelectionMode كما يلي :

بالنسبة للتعامل مع الأحداث في الJList فسوف يحدث الevent بمجرد تحديد أي من الitem في الlist ، وهنا يجب أن يكون لديك listener يقوم بتطبيق implements الListSelectionListener ويعرف الدالة valueChanged

في الحقيقة عند الضغط على الitem سوف يحدث حدثين واحد عن الضغط بالفأرة mouse down والأخر عند رفع الفأرة mouse up ، لذلك في الغالب سوف نتعامل مع حدث واحد ، ويمكن معرفة نوع الحدث الحالى عن طريق الدالة :

في حال أرجعت الدالة true هذا يعني أنه الأن تم الضغط بالفأرة mouse down ، والا فإنه mouse up .. عادة سنتعامل مع الMouse up ولك الخيار فيما تريد.

بعد أن تم تنبيهك بالحدث ، يجب أن تعرف ما هي الitems التي تم اختيارها ، ويمكنك ذلك من خلال الدالة getSelectedValues وهي ترجع مصفوفه من الكائنات Array of Objects ، يمكنك عمل cast to string لأي خانه منها ، كما يلي :

في حال كانت الlist لا تسمح بالإختيار المتعدد ، فيمكنك الحصول على الitem الذي تم الضغط عليه من خلال:

هذا هو المثال :

 

List Internals :
كما ذكرنا في بداية الموضوع أنه الJList مصممه بالMVC ، وهو يفصل الواجهه (التي تعرض Rendered بشكل ما ) عن المحتوى (Underlying Objects) . وهنا سنجد أن الكلاس JList يمثل الواجهه View (أي هو المسؤول عن العرض) ، أما المحتوي فيحصل عليها من خلال كائن يطبق implements الواجهه interface التاليه :

من خلال هذه الواجهه يستطيع الJList الحصول على عدد الitems والحصول على الitems ، أيضا يضع listener على أي تغيير (اضافة وحذف) في المحتوي وبالتالي يستطيع اعادة الرسم مرة أخرى بعد عمل التحديث المطلوب .

وبما أن JList فقط يحصل على الitems ولا يهمه كيف يخزن ، فيمكن أن تقوم بعمل دالة getElementAt لكنها تقوم بتوليد نصوص في وقت التشغيل.

هناك interface أخر مفيد وهو AbstractListModel وهو مشابه لListModel ولكنه يعرف الaddListDataListener و removeListDataListener وبالتالي سوف يسهل علينا كثيرا ( فقط سنقوم بتعريف getSize و getElementAt ) .. :

Inserting and Removing Values
لعمل list قابلة للتعديلات (الإضافة والحذف ) فهذا يتطلب اضافة بسيطة وذلك لأن الJList والListModel لا يحتويان على أي دوال للإضافة والحذف ، وهنا سوف نستخدم الكلاس DefaultListModel ونقوم بادخال العناصر فيها باستخدام addElement .. وأخيرا ندخل الكائن من الDefaultListModel في الJList :

الأن يمكنك اجراء التعديلات من خلال الmodel وهو سيتولي اخبار الJList بعملية التغيير وحينها سيقوم الJList باعادة الرسم :

 

Rendering Values
الى هنا فإن أي list قمنا بعمله يتكون من Strings فقط ، وفي الحقيقة يمكن انشاء list من أي Objects كان ، ولكن لكي يتم عرضه بشكل صحيح ، يجب أن نضع طريقة لعرض المحتوي List Cell Rendering في الlist وذلك عن طريق أي كائن يطبق الواجهه التالية :

هذه الدالة getListCellRendererComponent يتم استدعائها لعرض كل items وهي ترجع الitem لكي يتم وضعه في الlist . وأحد الطرق لذلك هو وراثه أي من الComponent كما يلي :

المثال التالي سوف نقوم بعمل list مكونه من ImageIcon كما في الشكل التالي :
jlist_2

والRendering يتم عن طريق الوراثه من الJLabel وبالتالي لن نحتاج الى الpaintComponent والgetPreferredSize فهو يقوم بها بالطريقة التي نريد .

 

Custom JLists
لو فرضنا أن لديك list من 1000 خانة ، فعملية البحث عن العنصر المطلوب أمر مرهق وفي هذه الحالة يعتبر التصميم bad design ، لكن ماذا لو قمنا بوضع text field يكتب فيه المستخدم الحرف الأول من الخانه وسيتم تعديل الlist وفقط تظهر الخانات التي تبدأ بهذا الحرف الأول .. كما توضح ذلك الصورة التالية عند الضغط على الحرف S سيظهر كل الأسماء التي تبدأ بS .

jlist_3

في هذه الحالة سوف يكون لدينا Model ولكن طريقتين للعرض ، الأولى عرض جميع الخانات (تكون النصوص موجودة في مصفوفه )والثانية عرض جميع الخانات التي تبدأ بالحرف المعين (وأيضا سيتم تخزينها في مصفوفه أخرى ) ،،

سنقوم الأن بتصميم الكلاس FilteredList وسيكون بداخلها كلاسين الأول FilterModel للعرض والثاني FilterField للكتابة .
نبدأ الأن بالوراثة من الJList ونضيف الأشياء المطلوبة :

هذه الlist الجديدة ، وفي دالة البناء وضعنا الModel بأنه هو FilteredModel وهو كلاس داخلي يحتفظ بالنصوص جميعها والنصوص التي تبدأ بالحرف المعين وبالطبع هو يرث AbstractListModel كما سنشاهد بعد قليل ، ويحتوي الlist الجديد على دالة setMode تتأكد من النوع أولا .. وهناك دالة لإدخال العناصر في الmodel وهناك دالة لأرجاع الtextfield حيث أننا نريد أن نضعه على الframe .

أما الكلاس FilteredModel فهو يرث AbstractListModel ويعيد تعريف الدالة getElementAt و getSize ، ويحتوي كما ذكرنا على مصفوفتين الأولى لجميع النصوص وهي التي سوف تضاف للmodel ، والأخرى للنصوص التي تحتوي على الحرف المعين ..

أما الكلاس FilterField فهو يرث الJTextField ووظيفته باستدعاء الدالة refilter متى تغير أي شيء فيه .

الكود هنا :

أمل أن يكون الموضوع المفيد ،، ويمكن تخصيص الlist بالشكل الذي تريد بعد الأن ..

وسبب كتابتي للموضوع هو أني أستخدمت list بها أيقونات في مشروع بسيط ، وأردت مشاركة الفكرة ، واجهه المشروع Frame تقسم الى قسمين Panel على اليمين وPanel على اليسار (باستخدام JSplitterPane ) و تكون هناك JList بها أيقونات على الجهه اليسري من الJSplitterPane، وكل أيقونة تمثل حدث معين مثلا Connect أو Send أو Encryption .. وعند الضغط على احد الأيقونات فسوف يظهر البانل الخاص به على الجهه اليمني من الJSplitterPane. سوف يظهر أي بانل في الجهه اليمني وبنفس الحجم ، والسبب هو استخدام الCardLayout في تقسيم الجهه اليمني من الJSplitterPane .

References for more :
Swing Hacks , By Chris Adamson, Joshua Marinacci
Core Java™ Volume II–Advanced Features

Happy Swing Hacking :) .

(105)

وجدي عصام مهندس برمجيات مهتم بعلوم الحاسب وبالأخص مجال الخوارزميات وهندسة البرمجيات وحماية التطبيقات،

Comment(2)

  1. مقال جميل يا وجدي، وكم يسرني رؤية محتوى عربي عن الجافا بهذا المستوى. لدي ملاحظتين حول المقال:

    1- ابتداءً من جافا 7، كلاسات الـ JList أصبحت generic، وذلك يشمل AbstractListModel و ListCellRenderer.
    2- عن نفسي لا أحبذ استخدام setSize بشكل صريح، ولكن أفضّل استخدام pack اللتي تعمتد على الـ preferred size الخاص بكل component.

    1. 1- ممتاز أول مرة أعلم عن ذلك في جافا 7 سوف اقرأ عن الجديد بخصوص GUI API فيها.

      2- نعم pack افضل بالتأكيد وتعطيك حجم مناسب فوق الComponents خاصتك، لكنها تجدي في الأمثله السريعه 🙂

      سعدت بمرورك وملاحظاتك

LEAVE YOUR COMMENT

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

مشاركة