اذهب إلى المحتوى

البحث في الموقع

المحتوى عن 'xaml'.

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المحتوى


التصنيفات

  • الإدارة والقيادة
  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • السلوك التنظيمي في المؤسسات
  • عالم الأعمال
  • التجارة والتجارة الإلكترونية
  • نصائح وإرشادات
  • مقالات ريادة أعمال عامة

التصنيفات

  • مقالات برمجة عامة
  • مقالات برمجة متقدمة
  • PHP
    • Laravel
    • ووردبريس
  • جافاسكربت
    • لغة TypeScript
    • Node.js
    • React
    • Vue.js
    • Angular
    • jQuery
    • Cordova
  • HTML
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • SQL
  • لغة C#‎
    • ‎.NET
    • منصة Xamarin
  • لغة C++‎
  • لغة C
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • لغة Rust
  • برمجة أندرويد
  • لغة R
  • الذكاء الاصطناعي
  • صناعة الألعاب
  • سير العمل
    • Git
  • الأنظمة والأنظمة المدمجة

التصنيفات

  • تصميم تجربة المستخدم UX
  • تصميم واجهة المستخدم UI
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب GIMP
    • كريتا Krita
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
    • Blender
  • نصائح وإرشادات
  • مقالات تصميم عامة

التصنيفات

  • مقالات DevOps عامة
  • خوادم
    • الويب HTTP
    • البريد الإلكتروني
    • قواعد البيانات
    • DNS
    • Samba
  • الحوسبة السحابية
    • Docker
  • إدارة الإعدادات والنشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
    • ريدهات (Red Hat)
  • خواديم ويندوز
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • شبكات
    • سيسكو (Cisco)

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات
  • تجارب ونصائح
  • مبادئ علم التسويق

التصنيفات

  • مقالات عمل حر عامة
  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • العمل الحر المهني
    • العمل بالترجمة
    • العمل كمساعد افتراضي
    • العمل بكتابة المحتوى

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
    • بريستاشوب
    • أوبن كارت
    • دروبال
  • الترجمة بمساعدة الحاسوب
    • omegaT
    • memoQ
    • Trados
    • Memsource
  • برامج تخطيط موارد المؤسسات ERP
    • تطبيقات أودو odoo
  • أنظمة تشغيل الحواسيب والهواتف
    • ويندوز
    • لينكس
  • مقالات عامة

التصنيفات

  • آخر التحديثات

أسئلة وأجوبة

  • الأقسام
    • أسئلة البرمجة
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات

التصنيفات

  • كتب ريادة الأعمال
  • كتب العمل الحر
  • كتب تسويق ومبيعات
  • كتب برمجة
  • كتب تصميم
  • كتب DevOps

ابحث في

ابحث عن


تاريخ الإنشاء

  • بداية

    نهاية


آخر تحديث

  • بداية

    نهاية


رشح النتائج حسب

تاريخ الانضمام

  • بداية

    نهاية


المجموعة


النبذة الشخصية

تم العثور على 8 نتائج

  1. تحدثنا في الدرس السابق عن المخطّط المطلق Absolute Layout وعن فائدته في التحكّم بمواضع وأحجام العناصر المرئيّة باستخدام الواحدات المستقلة عن الجهاز independent-device units. سنتابع عملنا في هذا الدرس ضمن سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms ببناء تطبيق عمليّ جميل يُعتبر تحسينًا لتطبيق تحريك المربّعات الثلاثة المتداخلة الذي أوردناه في نهاية الدرس السابق. يوضّح لنا هذا التطبيق المُحسّن كيفيّة التحكّم بهذه المربّعات باستخدام أزرار تمثّل الاتجاهات الأربعة الأساسيّة (أعلى – أسفل - يمين – يسار). وصف التطبيق واجهة هذا التطبيق بسيطة. فبالإضافة إلى المربّعات الثلاثة. سنضع أربعة أزرار عاديّة تمثّل الاتجاهات الأربعة الأساسيّة. بحيث تتوضّع هذه المربّعات أسفل ووسط الشاشة. عند بدء تشغيل التطبيق سنحصل على شكل شبيه بما يلي: لاحظ كيف تتوسّط الأزرار الأربعة الشاشة أفقيًّا، سنوضّح سبب ذلك بعد قليل. إذا حاول المستخدم في الوضع الحالي نقر أي زر من الأزرار الأربعة السابقة سيعرض له البرنامج رسالة يطلب منه فيها اختيار أحد المربّعات أولًا لتحريكه. عندما ينقر المستخدم على أحد المربّعات السابقة، ستتلوّن الأزرار الأربعة بنفس لون المربّع الذي نقره المستخدم. وعندها فقط يمكن استخدام الأزرار لتحريك المربّع المُختار في أيّ اتجاه يرغبه. في الشكل التالي قمت باختيار المربّع الأحمر، ومن ثمّ تحريكه إلى الأعلى ثم إلى اليمين: يمكنك تكرار نفس الخطوات السابقة لتحريك أيّ مربّع آخر. هذا هو مبدأ هذا البرنامج، وهو بسيط كما ترى، ولكن سنتعلّم من خلاله بعض التقنيّات المفيدة في ضبط التموضع ضمن المخطّط المطلق، بالإضافة إلى تعلّم كيفيّة إكساب عناصر BoxView قابلية الاستجابة إلى أحداث اللمس كما سنرى بعد قليل. تطبيق تحريك المربّعات باستخدام الأزرار أنشئ مشروعًا جديدًا من النوع Blank App (Xamarin.Forms Portable) وسمّه MoveSquaresByButtons، ثم أبق فقط على المشروعين MoveSquaresByButtons (Portable) و MoveSquaresByButtons.Droid كما وسبق أن فعلنا في هذا الدرس. بعد ذلك سنضيف صفحة محتوى تعتمد على رُماز XAML كما وسبق أن فعلنا في هذا الدرس سنسمّيها MoveSquaresByButtonsPage. احرص على أن تكون محتويات هذه الصفحة على الشكل التالي: 1 <?xml version="1.0" encoding="utf-8" ?> 2 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 3 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 4 x:Class="MoveSquaresByButtons.MoveSquaresByButtonsPage" 5 SizeChanged="PageResize"> 6 7 <AbsoluteLayout> 8 <BoxView x:Name="boxAccent" 9 Color="Accent" 10 AbsoluteLayout.LayoutBounds="25, 100, 100, 100" 11 Opacity="0.5" /> 12 13 <BoxView x:Name="boxRed" 14 Color="Red" 15 AbsoluteLayout.LayoutBounds="75, 150, 100, 100" 16 Opacity="0.5"/> 17 18 <BoxView x:Name="boxGreen" 19 Color="Green" 20 AbsoluteLayout.LayoutBounds="125, 200, 100, 100" 21 Opacity="0.5"/> 22 23 <Button x:Name="btnMoveUp" 24 StyleId="MoveBoxUp" 25 Clicked="MoveBox_Clicked" 26 FontSize="Large" 27 BorderWidth="0" 28 Text="↑"/> 29 30 <Button x:Name="btnMoveDown" 31 StyleId="MoveBoxDown" 32 Clicked="MoveBox_Clicked" 33 FontSize="Large" 34 BorderWidth="0" 35 Text="↓"/> 36 37 <Button x:Name="btnMoveRight" 38 StyleId="MoveBoxRight" 39 Clicked="MoveBox_Clicked" 40 FontSize="Large" 41 Text="→"/> 42 43 <Button x:Name="btnMoveLeft" 44 StyleId="MoveBoxLeft" 45 Clicked="MoveBox_Clicked" 46 FontSize="Large" 47 Text="←"/> 48 </AbsoluteLayout> 49 50 </ContentPage> لاحظ معي أنّ عناصر BoxView الموجودة في الأسطر من 8 حتى 21 هي نفسها الموجودة في تطبيق المربّعات المتداخلة من الدرس السابق. الجديد هنا هو تعيين معالج للحدث SizeChanged للصفحة (السطر 5) الذي يُستدعى كلّما اقتضت الحاجة إلى تغيير حجم الصفحة، وهو يحدث عادةً عند البدء بتشغيل التطبيق، وأيضًا عندما يُغيّر المستخدم اتجاه الجهاز من عمودي إلى أفقي أو بالعكس. الجديد هنا أيضًا هو الأزرار الأربعة الموجودة في الأسطر من 23 حتى 47. من الواضح أنّ جميع العناصر المرئيّة السابقة موجودة ضمن مخطّط مطلق AbsoluteLayout يضمن لنا التحكّم بمواقعها وبأحجامها كما أشرنا. تأمّل معي هذا الرماز الذي يمثّل زر الحركة نحو الأعلى: <Button x:Name="btnMoveUp" StyleId="MoveBoxUp" Clicked="MoveBox_Clicked" FontSize="Large" BorderWidth="0" Text="&#8593;"/> لقد وضعت القيمة "MoveBoxUp" للخاصيّة StyleId حيث سنستفيد منها ضمن معالج الحدث MoveBox_Clicked الذي أسندته للحدث Clicked كما هو واضح. سنرى بعد قليل الشيفرة البرمجيّة الخاصّة بهذا المعالج. الأمر الملفت هنا هو القيمة "&H8593;" التي ضبطُّها لخاصيّة النص. سيؤدي ذلك إلى وضع رمز السهم المتجه إلى أعلى ليظهر على هذا الزر. بنفس هذا الأسلوب تمّ تجهيز الأزرار الثلاثة الباقية التي سأسند إلى كلّ منها قيمة مختلفة للخاصيّة StyleId لتعبّر عن وظيفتها، ولكن مع إسناد نفس معالج الحدث MoveBox_Clicked لجميعها. لننتقل الآن إلى ملف الشيفرة البرمجية الموافق لصفحة المحتوى السابقة. احرص على أن تكون محتوياته على الشكل التالي: 1 using System; 2 using Xamarin.Forms; 3 4 namespace MoveSquaresByButtons 5 { 6 public partial class MoveSquaresByButtonsPage : ContentPage 7 { 8 private BoxView selectedBoxView; 9 private const double BUTTON_MARGIN = 5; 10 private const double PAGE_BUTTOM_MARGIN = 10; 11 private const double MOVE_AMOUNT = 15; 12 13 public MoveSquaresByButtonsPage() 14 { 15 InitializeComponent(); 16 17 TapGestureRecognizer tapGesture = new TapGestureRecognizer(); 18 19 tapGesture.Tapped += TapGesture_Tapped; 20 21 boxAccent.GestureRecognizers.Add(tapGesture); 22 boxRed.GestureRecognizers.Add(tapGesture); 23 boxGreen.GestureRecognizers.Add(tapGesture); 24 } 25 26 private void TapGesture_Tapped(object sender, EventArgs e) 27 { 28 selectedBoxView = (BoxView)sender; 29 30 btnMoveUp.BackgroundColor = selectedBoxView.Color; 31 btnMoveDown.BackgroundColor = selectedBoxView.Color; 32 btnMoveLeft.BackgroundColor = selectedBoxView.Color; 33 btnMoveRight.BackgroundColor = selectedBoxView.Color; 34 } 35 36 37 private void MoveBox_Clicked(object sender, EventArgs e) 38 { 39 Button moveButton = (Button)sender; 40 Rectangle rect; 41 42 if (selectedBoxView == null) 43 { 44 DisplayAlert("اختيار مربع", "اختر مربّعًا من فضلك", "موافق"); 45 return; 46 } 47 48 rect = AbsoluteLayout.GetLayoutBounds(selectedBoxView); 49 50 if (moveButton.StyleId == "MoveBoxUp") 51 { 52 rect = new Rectangle(rect.X, rect.Y - MOVE_AMOUNT, rect.Width, rect.Height); 53 } 54 else if (moveButton.StyleId == "MoveBoxDown") 55 { 56 rect = new Rectangle(rect.X, rect.Y + MOVE_AMOUNT, rect.Width, rect.Height); 57 } 58 else if (moveButton.StyleId == "MoveBoxRight") 59 { 60 rect = new Rectangle(rect.X + MOVE_AMOUNT, rect.Y, rect.Width, rect.Height); 61 } 62 else if (moveButton.StyleId == "MoveBoxLeft") 63 { 64 rect = new Rectangle(rect.X - MOVE_AMOUNT, rect.Y, rect.Width, rect.Height); 65 } 66 67 AbsoluteLayout.SetLayoutBounds(selectedBoxView, rect); 68 } 69 70 private void PageResize(object sender, EventArgs e) 71 { 72 ContentPage page = (ContentPage)sender; 73 74 double buttonWidth = btnMoveUp.Width; 75 double buttonHeight = btnMoveUp.Height; 76 double buttonsAreaWidth = buttonWidth * 3 + BUTTON_MARGIN * 2; 77 double buttonsAreaLeftDisplacement = (page.Width - buttonsAreaWidth) / 2; 78 79 AbsoluteLayout.SetLayoutBounds(btnMoveDown, 80 new Rectangle(buttonsAreaLeftDisplacement + buttonWidth + BUTTON_MARGIN, 81 page.Height - buttonHeight - PAGE_BUTTOM_MARGIN, 82 buttonWidth, 83 buttonHeight)); 84 85 AbsoluteLayout.SetLayoutBounds(btnMoveUp, 86 new Rectangle(buttonsAreaLeftDisplacement + buttonWidth + BUTTON_MARGIN, 87 page.Height - buttonHeight * 2 - BUTTON_MARGIN - PAGE_BUTTOM_MARGIN, 88 buttonWidth, 89 buttonHeight)); 90 91 92 AbsoluteLayout.SetLayoutBounds(btnMoveLeft, 93 new Rectangle(buttonsAreaLeftDisplacement, 94 page.Height - 3 * buttonHeight / 2 - PAGE_BUTTOM_MARGIN, 95 buttonWidth, 96 buttonHeight)); 97 98 AbsoluteLayout.SetLayoutBounds(btnMoveRight, 99 new Rectangle(buttonsAreaLeftDisplacement + buttonWidth * 2 + BUTTON_MARGIN * 2, 100 page.Height - 3 * buttonHeight / 2 - PAGE_BUTTOM_MARGIN, 101 buttonWidth, 102 buttonHeight)); 103 } 104 } 105 } يحتوي الصنف MoveSquaresByButtonsPage بشكل أساسيّ على بانية وعدّة توابع هي في الواقع معالجات للأحداث التي يحتاج إليها التطبيق. سنتحدّث عنها بشيء من التفصيل. البانية إذا نظرت إلى البانية (الأسطر من 13 حتى 24) فستجد أنّنا نعرّف ضمنها كائنًا من النوع TapGestureRecognizer (السطر 17) سنُسند هذا الكائن إلى المتغيّر tapGesture كما هو واضح. يفيد هذا الكائن في إكساب عنصر BoxView قابلية النقر لأنّه –وبشكل مغاير لعنصر الزر Button- لا يمتلكها أصلًا. نعمل في السطر 19 على تعيين معالج لحدث النقر (اللمس) Tapped للكائن السابق واسمه TapGesture_Tapped (سنتحدّث عنه بعد لحظة). العملية الأخيرة التي نجريها في البانية هي إضافة الكائن tapGesture إلى كل من المربّعات الثلاثة عن طريق التابع Add للمجموعة GestureRecognizers لكل مربع من هذه المربعات (الأسطر من 21 حتى 23). معالج حدث النقر على المربّعات TapGesture_Tapped يمكنك رؤية هذا المعالج في الأسطر من 26 حتى 34. الذي يحدث ضمن هذا المعالج هو أنّنا نحصل على مرجع المربّع الذي نقره المستخدم (السطر 28) ونُسنده إلى الحقل selectedBoxView وهو حقل خاص من النوع BoxView مصرّح عنه في السطر 8، ثم نلوّن أزرار التحريك الأربعة بنفس لون المربّع الذي نقره (لمسه) المستخدم. معالج حدث النقر على الأزرار MoveBox_Clicked بالنسبة لمعالج الحدث MoveBox_Clicked وهو موجود في الأسطر من 37 حتى 68 فهو يُستدعى عندما ينقر المستخدم أيًا من أزرار التحريك الأربعة. ووظيفته هو تحريك المربّع الذي اختاره المستخدم مسبقًا في الاتجاه المطلوب. نحصل في السطر 39 على مرجع الزر الذي تمّ نقره، ثمّ نختبر فيما إذا كان المستخدم قد قام فعلًا باختيار مربّع من قبل (الأسطر من 42 حتى 46) حيث يُعطي التطبيق تنبيهًا للمستخدم في حال لم يكن قد اختار مربّعًا من قبل. انظر الآن إلى السطر 48: rect = AbsoluteLayout.GetLayoutBounds(selectedBoxView); يُرجع التابع GetLayoutBounds من الصنف AbsoluteLayout بنية من النوع Rectangle لأيّ عنصر مرئي نمرّره له موجود ضمن المخطّط المطلق. نحتاج هنا إلى معرفة المستطيل المُحدّد للمربّع الذي اختاره المستخدم من قبل. يُعطينا المستطيل المحدّد معلومات حول إحداثيّي الزاوية اليسرى العليا للمربّع (الإحداثي X والإحداثي Y) بالإضافة إلى عرض Width وارتفاع Height المربّع. تقارن بعد ذلك عبارات if الموجودة في الأسطر من 50 حتى 65 الخاصيّة StyleId للزر الذي نقره المستخدم مع عدّة نصوص معدّة سلفًا، لمعرفة الاتجاه المطلوب التحريك وفقه. لكي يتمكّن البرنامج من تعديل الإحداثي المطلوب على المستطيل المحدّد للمربّع وذلك بالانتقال المسافة المعيّنة بالثابت MOVE_AMOUNT الذي عرّفناه في السطر 11. بعد ذلك يتم تعيين المستطيل المحدّد الجديد (بعد التعديل) إلى المربّع نفسه (السطر 67) مما يؤدي إلى إعطاء انطباع بالحركة. معالج حدث تغيير حجم الصفحة PageResize بالنسبة للمعالج PageResize (الأسطر من 70 حتى 103) فهو يُستدعى عندما يطرأ تعديل على حجم الصفحة. وتنحصر وظيفة هذا المعالج في الحقيقة بوضع أزرار الحركة الأربعة بالشكل الذي أوضحناه أوّل الدرس. حيث يعتمد على ثابتين: الثابت BUTTON_MARGIN (مصرّح عنه في السطر 9) ويمثّل التباعد بين أيّ زرّين. الثابت PAGE_BUTTOM_MARGIN (مصرّح عنه في السطر 10) ويمثّل الهامش السفلي لزر الحركة نحو الأسفل وذلك عن أسفل الشاشة. الأمر الملفت ضمن هذا المعالج هو كيفية حساب مسافة الإزاحة الأفقيّة لتوسيط مجموعة الأزرار أفقيًّا، حيث يتم ذلك بأسلوب حسابي بسيط. إذ أنّ الشيفرة تحسب العرض الإجمالي لثلاثة أزرار مضافًا إليها قيمتي هامشين (بين كل زرين هامش) وتضعه ضمن المتغيّر buttonsAreaWidth: double buttonsAreaWidth = buttonWidth * 3 + BUTTON_MARGIN * 2; ثمّ تقوم بطرح القيمة السابقة من عرض الصفحة page.Width وتقسّمه على 2. فنحصل على مقدار الإزاحة الأفقيّة الواجب تطبيقها من الجهة اليسرى وتضعها ضمن المتغيّر buttonsAreaLeftDisplacement: double buttonsAreaLeftDisplacement = (page.Width - buttonsAreaWidth) / 2; ما تبقى من عبارات برمجيّة هي بسيطة في الواقع ولا تعدو عن كونها عبارات تنظيميّة تهدف إلى وضع الأزرار على نحو متناسق. انتقل أخيرًا إلى الملف App.cs واحرص على أن تكون بانيته على الشكل التالي: public App() { // The root page of your application MainPage = new MoveSquaresByButtonsPage(); } أصبح التطبيق جاهزًا للتنفيذ. يمكنك تنفيذه بضغط المفتاح F5 كما تعلّمنا. الخلاصة تحدثنا في هذا الدرس عن تطبيق عمليّ يعتمد على المخطّط المطلق Absolute Layout. حيث تعلّمنا كيفيّة التحكم الدقيق بمواضع العناصر لإكساب واجهة المستخدم شكلًا دقيقًا مُعد مسبقًا. كما تعلّمنا كيفيّة إكساب أيّ عنصر من النوع BoxView إمكانية الاستجابة لحدث النقر عن طريق كائن من الصنف TapGestureRecognizer. وتعلّمنا أيضًا تقنيّات بسيطة لتحريك العناصر المرئية على الشاشة. سننهي بهذا المقال حديثنا عن المخطّطات التي تدعمها Xamarin. في الحقيقة يُعتبر الفهم الجيّد للمخطّطات مفتاحًا أساسيًّا لبناء واجهة المستخدم.
  2. يُعتبر هذا الدرس من الدروس المهمّة في سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms. في الواقع لقد أشرنا إلى هذا الدرس عدّة مرّات خلال الدروس السابقة. تكمن المشكلة في التطبيقات التي تناولناها في الدروس السابقة إلى أنّنا كنّا ننشئ واجهة التطبيق باستخدام الشيفرة البرمجيّة بشكل كامل مما يؤدّي إلى مشكلة كبيرة في تنظيم البرامج لأنّنا في هذه الحالة سنضطر غالبًا إلى وضع الشيفرة المسؤولة عن بناء الواجهات مع الشيفرة البرمجيّة المسؤولة عن تمثيل منطق العمل business logic في البرنامج، وهذا أمر سيء بالطبع. وفّرت Xamarin حلًا ممتازًا لهذه المشكلة يجعل من البرمجة باستخدامها تجربة غنيّة ومعاصرة، وذلك من خلال إنشاء الواجهات باستخدام رماز XAML. يشبه هذا الرماز إلى حدٍّ كبير رماز XML. حيث يتمّ إنشاء العناصر المرئيّة وضبط خصائصها باستخدام ما يشبه عناصر وسمات XML. تكمن الفائدة الأساسيّة من استخدام هذا الأسلوب هو عزل بناء واجهات البرنامج عن الشيفرة البرمجيّة المسؤولة عن معالجة منطق البرنامج، وهذا أمر مهم وخصوصًا عند بناء تطبيقات كبيرة. لنقارن مثلًا بين إنشاء لصيقة باستخدام الشيفرة البرمجيّة وباستخدام XAML: لا أعتقد أنّنا سنبذل كثيرًا من الجهد لملاحظة أنّ أسلوب XAML مباشر وواضح أكثر من أسلوب الشيفرة البرمجيّة. تُكتَب العناصر المرئيّة في Xamarin.Forms باستخدام XAML على شكل عناصر XML عاديّة، أمّا خصائصها فتكون على شكل سمات attributes لهذه العناصر. يوجد أسلوب آخر لكتابة خصائص العناصر ضمن XAML سنتحدّث عنه بعد قليل. يُكتب رماز XAML ضمن ملفات مخصّصة لهذه الغرض تحمل الامتداد xaml. وطريقة إضافتها للمشروع بسيطة سنتحدّث عنها أيضًا بعد قليل ضمن برنامج تطبيقي. في الحقيقة يعمل المُعرب parser الخاص بـ XAML على ترجمة كل عنصر يصادفه إلى الصنف المقابل له في الشيفرة. فمثلًا ومن المثال البسيط السابق عند يصادف هذا المُعرب العنصر Label سيعمل على إنشاء كائن برمجي من الصنف Label ثم يعمل على مطابقة السمات الموجودة ضمن العنصر في XAML مع الخصائص الفعليّة للصنف Label ويُسند قيم الخصائص وفقًا لها. فمثلًا، تحمل السمة HorizontalTextAlignment القيمة Center من رماز XAML. وهي تُطابق الخاصيّة ذات الاسم نفسه من الصنف Label. لذلك فعند إنشاء كائن اللصيقة Label سيتم إسناد القيمة TextAlignment.Center إلى الخاصيّة HorizontalTextAlignment. مثال أخر حول السمة FontAttributes التي تحمل القيمة "Bold, Italic" وهي تُطابق الخاصيّة ذات الاسم نفسه من الصنف Label. لذلك فعند إنشاء كائن اللصيقة Label سيتم إسناد القيمة التالية لهذه الخاصيّة: FontAttributes.Bold | FontAttributes.Italic لاحظ العامل "|" وهو عامل OR على مستوى البتّات bits وهو يفيد في تعيين أكثر من قيمة بنفس الوقت للخاصيّة FontAttributes. ويسري هذا الأمر على باقي السمات للعنصر Label. مثال تطبيقي حول استخدام XAML في بناء الواجهات أنشئ مشروعًا جديدًا من النوع Blank App (Xamarin.Forms Portable) وسمّه XamlDemoApp، ثم أبق فقط على المشروعين XamlDemoApp (Portable) و XamlDemoApp.Droid كما وسبق أن فعلنا في هذا الدرس. بعد ذلك سنضيف صفحة محتوى تعتمد على رماز XAML. لفعل ذلك انقر بزر الفأرة الأيمن على المشروع XamlDemoApp (Portable) واختر Add ثم New Item. من مربّع الحوار الذي سيظهر لك، اختر من الجانب الأيسر له Cross-Platform، ومن القسم الأيمن احرص على اختيار Forms Xaml Page، وسمّها XamlDemoPage كما في الشكل التالي: انقر بعد ذلك الزر Add لإضافة هذه الصفحة إلى المشروع. ستحصل على الرماز التالي بشكل افتراضي ضمن هذه الصفحة: 1 <?xml version="1.0" encoding="utf-8" ?> 2 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 3 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 4 x:Class="XamlDemoApp.XamlDemoPage"> 5 <Label Text="{Binding MainText}" VerticalOptions="Center" HorizontalOptions="Center" /> 6 </ContentPage> يحتوي السطر الأوّل على إصدار XML المُستَخدم، أمّا السطر الثاني فيحتوي على عنصر ContentPage مما يشير إلى أنّنا فعليًّا نضيف صفحة محتوى content page ليس إلّا. يحتوي العنصر ContentPage على سمتين xmlns وxmlns:x وهما سمتان تعريفيّتان، أمّا السمة x:Class فهي تُشير إلى الصنف المرتبط مع هذه الصفحة وهو كما يظهر XamlDemoApp.XamlDemoPage حيث XamlDemoApp هو نطاق الاسم الخاص بالتطبيق. سنرى أين يوجد هذا الصنف بعد قليل. يحتوي السطر 5 على عنصر Label تجريبي يتم توليده بشكل افتراضي. أمّا في السطر السادس فيوجد عنصر الإغلاق لعنصر الفتح الموافق الموجود في السطر 2 وذلك كما هو متبع في XML. في الواقع عندما يضيف Visual Studio الملف XamlDemoPage.xaml فإنّه يضيف بشكل تلقائي الملف XamlDemoPage.xaml.cs أيضًا. واضح أنّ امتداد هذا الملف cs أي أنّه يحتوي على شيفرة سي شارب. انظر إلى الشكل التالي الذي يمثّل مستكشف الحل Solution Explorer بعد إضافة ملف المحتوى: الملف XamlDemoPage.xaml.cs هو ملف الشيفرة البرمجيّة المرافق لملف الواجهة XamlDemoPage.xaml ويحتوي على الصنف XamlDemoApp.XamlDemoPage الذي تحدثنا عنه قبل قليل. يحتوي هذا الصنف على معالجات أحداث event handlers تستجيب للأحداث المختلفة التي قد تحدث ضمن ملف الواجهة والناتجة عن مختلف أنواع العناصر المرئيّة الموجودة ضمنها. لنعد الآن إلى الملف XamlDemoPage.xaml، احذف العنصر Label الافتراضي الموجود في هذا الملف. سنضيف عناصر مرئيّة جديدة. احرص على أن تكون محتويات الملف XamlDemoPage.xaml على الشكل التالي: 1 <?xml version="1.0" encoding="utf-8" ?> 2 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 3 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 4 x:Class="XamlDemoApp.XamlDemoPage"> 5 6 <StackLayout VerticalOptions="CenterAndExpand"> 7 <StackLayout.Children> 8 <Label VerticalOptions="Start" 9 HorizontalOptions="Center" 10 FontSize="Large" 11 TextColor="Accent" 12 x:Name="lblCurrentTime" 13 Text="Click the button!" /> 14 15 <Button VerticalOptions="End" 16 HorizontalOptions="Center" 17 FontSize="Medium" 18 x:Name="btnGetTime" 19 Text="Get Current Time" 20 Clicked="btnGetCurrentTime_Clicked"/> 21 </StackLayout.Children> 22 </StackLayout> 23 24 </ContentPage> يحتوي رماز XAML السابق على العديد من المفاهيم الجديدة ولكن البسيطة والتي سنلخّصها من خلال النقاط التالية: يضم الرماز واجهة بسيطة تتكوّن من لصيقة وزر عند نقره سيعرض الوقت الحالي على اللصيقة الموجودة فوقه. كلّ من هذين العنصرين سيكونان ضمن مخطّط مكدّس StackLayout. يبدأ هذا المخطّط في السطر 6 ويحوي سمة (خاصيّة) وحيدة هي VerticalOptions وتحمل القيمة CenterAndExpand. يمثّل عنصر XAML الموجود في السطر 7 وهو الخاصيّة Children لعنصر المكدّس StackLayout. نضع ضمن هذه الخاصيّة كما اعتدنا من قبل العناصر المرئيّة التي نرغب بوجودها ضمن المكدّس، وهذا ما سنفعله تمامًا بوضع عنصر اللصيقة (الأسطر من 8 حتى 13) وعنصر الزر (الأسطر من 15 حتى 20) ضمنه. وكما هو الحال بالنسبة لأي عنصر XML يجب إغلاق العنصر بوسم إغلاق موافق له، وهذا ما فعلناه في السطر 21. بالنسبة لعنصر اللصيقة، تمثّل السمات الموجودة ضمنها خصائص الصنف Label التي كنّا نتعامل معها في الشيفرة البرمجيّة في السابق. يوجد أمر بسيط هنا، وهو أنّ عنصر اللصيقة لا يوجد له وسم إغلاق مثل . في الحقيقة لا داعي له أبدًا، لأنّ اللصيقة لا يمكنها أن تحوي عناصر أخرى كما هو الحال بالنسبة لمخطّط المكدّس مثلًا. لقد أغلقنا اللصيقة بالرمزين /> فحسب، كما فعلنا مع الزر في السطر 20 تمامًا. توجد سمة اسمها x:Name ضمن عنصر اللصيقة (السطر 12) وأيضًا ضمن عنصر الزر (السطر 18). يمكن من خلال هذه السمة الوصول للعناصر المرئيّة وذلك من خلال الشيفرة البرمجيّة وهذا ما سنراه بعد قليل. في الواقع كان من الممكن ألًّا نستخدم هذه السمة مع عنصر الزر لأنّنا لا نحتاج إلى أن نصل إلى خصائصه بالنسبة لهذا البرنامج. معالج حدث النقر Clicked للزر في السطر 20. أسميته btnGetCurrentTime_Clicked، وهو غير موجود في هذا الملف بالطبع. يحتوي هذا الملف على شيفرة برمجيّة بلغة سي شارب تنفّذ المهمّة المطلوبة عند نقر هذا الزر. سيكون معالج الحدث btnGetCurrentTime_Clicked موجودًا في الملف XamlDemoPage.xaml.cs ضمن الصنف XamlDemoPage. انتقل إلى هذا الملف واحرص أن تكون محتوياته على الشكل التالي: using System; using System.Globalization; using Xamarin.Forms; namespace XamlDemoApp { public partial class XamlDemoPage : ContentPage { public XamlDemoPage() { InitializeComponent(); } private void btnGetCurrentTime_Clicked(object sender, EventArgs e) { lblCurrentTime.Text = DateTime.Now.ToString("h:mm:ss tt", CultureInfo.InvariantCulture); } } } توجد الشيفرة المسؤولة عن إظهار الوقت الحالي على اللصيقة ضمن معالج الحدث btnGetCurrentTime_Clicked كما هو واضح (يمكنك فهم هذه الشيفرة من خلال الانتقال إلى هذا الدرس ومراجعة تطبيق الساعة الرقميّة). انتقل إلى الملف App.cs وتأكّد أنّ بانيته على الشكل التالي: public App() { // The root page of your application MainPage = new XamlDemoPage(); } نفّذ البرنامج باستخدام F5 ثم انقر الزر Get Current Time لتحصل على الوقت الحالي ضمن اللصيقة. انظر إلى الشكل التالي: هناك أمران ملفتان في الشيفرة السابقة: أسندنا قيمة الوقت الحالي إلى الخاصيّة Text من المتغيّر lblCurrentTime الذي يحمل نفس اسم اللصيقة التي صرّحنا عنها في ملف الواجهة. ولكن من أين أتي هذا المتغيّر إذا لم نصرّح عنه مطلقًا في ملف الشيفرة الحالي؟ لاحظ وجود استدعاء للتابع InitializeComponent ضمن بانية الصنف XamlDemoPage. هذا التابع ليس موجودًا ضمن الصنف ContentPage الذي يرث منه صنفنا XamlDemoPage، وهذا يعني أنّه يجب أن يكون ضمن الصنف XamlDemoPage ولكن هذا ما لا نراه أمامنا! الحيلة المستخدمة هنا هو في وجود الكلمة المحجوزة partial ضمن التصريح عن الصنف XamlDemoPage. تشير الكلمة partial إلى وجود جزء آخر لهذا الصنف ولكن في مكان ما! في الواقع هذا الجزء موجود بالفعل ولكن ضمن الملف XamlDemoApp.XamlDemoPage.xaml.g.cs. بما أنَّ هذا الملف له الامتداد cs هذا يعني أنّه يحتوي على شيفرة مكتوبة بلغة سي شارب، كما أنّ اسمه (الطويل نسبيًّا) مكوّن من نطاق الاسم واسم الصنف XamlDemoApp.XamlDemoPage ثم المقطع xaml للإشارة إلى أنّه متعلّق بملف الواجهة الذي يحتوي على رماز XAML والمقطع g الذي يأتي من الكلمة generated للإشارة إلى أنّ هذا الملف يتمّ توليده تلقائيًّا. إذا أردت مشاهدة الملف فاذهب إلى مستكشف الحل Solution Explorer ثم انقل على زر إظهار جميع الملفات Show All Files لتظهر المجلّدات والملفات الموجودة ضمن المشروع. انتقل إلى المجلّد obj ثم انشره لترى محتوياته. ستلاحظ وجود المجلّد Debug فقط ضمنه. انشره أيضًا لتصل إلى الملفات التي ضمنه حيث ستجد عندها الملف XamlDemoApp.XamlDemoPage.xaml.g.cs. انظر إلى الشكل التالي: انقره نقرًا مزدوجًا لترى محتوياته. ستجد الصنف XamlDemoPage مرّة أخرى، وهذا هو الجزء الذي نبحث عنه: 1 public partial class XamlDemoPage : global::Xamarin.Forms.ContentPage { 2 3 [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "0.0.0.0")] 4 private global::Xamarin.Forms.Label lblCurrentTime; 5 6 [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "0.0.0.0")] 7 private global::Xamarin.Forms.Button btnGetTime; 8 9 [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "0.0.0.0")] 10 private void InitializeComponent() { 11 this.LoadFromXaml(typeof(XamlDemoPage)); 12 lblCurrentTime = this.FindByName<global::Xamarin.Forms.Label>("lblCurrentTime"); 13 btnGetTime = this.FindByName<global::Xamarin.Forms.Button>("btnGetTime"); 14 } 15 } سنجد في السطر 4 التصريح عن المتغيّر lblCurrentTime الذي سيعمل التابع InitializeComponent (الأسطر من 10 حتى 15) على ربطه مع اللصيقة المصرّح عنها ضمن ملف الواجهة. كما أشرنا فإنّ هذا الملف يتم توليده تلقائيًّا وذلك بمجرّد إجراء أي عمليّة حفظ على الملف XamlDemoPage.xaml الذي يحتوي على واجهة التطبيق. الخلاصة تعلّمنا في هذه الدرس المبادئ الأساسيّة لبناء الواجهات باستخدام رماز XAML بدلًا عن أسلوب الشيفرة التي اعتمدناه في الدروس السابقة. ستلمس الفائدة العظيمة لبناء الواجهات بهذا الأسلوب عندما تبدأ ببناء تطبيقات واقعيّة، حيث سيجعل هذا الأسلوب من عمليّة بناء التطبيق أمرًا يسيرًا وواضحًا من خلال فصل الواجهة بما تحتويه من عناصر مرئيّة عن الشيفرة البرمجيّة المخصّصة للتفاعل مع هذه الواجهة، وذلك ضمن ملفّين منفصلين. تساعد هذه العمليّة على ترتيب البرامج وجعلها أسهل للفهم وللصيانة وللتطوير المستقبلي، وخصوصًا عندما تصبح البرامج أكبر ومعقّدة أكثر. هنالك الكثير مما يمكن قوله حول هذه التقنيّة سنتناول ذلك تباعًا من خلال الدروس القادمة. سنبني في الدرس القادم تطبيق عملي رياضيّاتيّ مفيد يتمثّل في حل معادلة من الدرجة الثانيّة حيث سنبني واجهة التطبيق من خلال XAML.
  3. نتابع عملنا في سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms. سنتعلّم في هذا الدرس تقنيّات جديدة لبناء واجهات متقدّمة أكثر باستخدام رماز XAML. كما سنتعلّم كيفيّة عرض رسائل تنبيه للمستخدم. يُعتبر هذا الدرس مساعدًا للدرس السابق في فهم كيفية التعامل مع رماز XAML. سنتناول في هذا الدرس تطبيقًا عمليًا مفيدًا، الهدف منه هو حل معادلة من الدرجة الثانيّة Quadratic Equation في مجموعة الأعداد الحقيقية R. كيفيّة حل معادلة من الدرجة الثانية للمعادلة من الدرجة الثانية الشكل العام التالي: لكي نحل هذه المعادلة في المجموعة R نحتاج إلى معرفة قيم المعاملات a وb وc وهي عبارة عن ثوابت حقيقيّة سنطلب من المستخدم أي يزوّد البرنامج بها. بعد ذلك سنطبّق القانون التالي لحساب مميّز هذه المعادلة: من خلال قيمة هذا المميّز نكون أمام ثلاث حالات: الحالة الأولى عندما يكون فيوجد عندئذ حلّين مختلفين للمعادلة المفروضة يمكن الحصول عليهما من العلاقتين التاليتين: الحالة الثانية عندما فيوجد عندئذ حل مضاعف يُعطى بالعلاقة التالية: الحالة الثالثة عندما فلا يكون للمعادلة عندها أي حل في المجموعة R. بناء تطبيق حل المعادلات من الدرجة الثانية ابدأ بإنشاء مشروع جديد من النوع Blank App (Xamarin.Forms Portable) وسمّه SDESolverApp، ثم أبق فقط على المشروعين SDESolverApp (Portable) و SDESolverApp.Droid كما وسبق أن فعلنا في هذا الدرس. بعد ذلك سنضيف صفحة محتوى تعتمد على رماز XAML كما وسبق أن فعلنا في الدرس السابق سنسمّها SDESolverPage. سنقسّم الواجهة إلى ثلاثة أقسام. القسم الأوّل العلوي سيحتوي على رسالة ترحيبيّة، أمّا القسم الأوسط فسيضم المنطقة الخاصّة بإدخال قيم المعاملات a وb وc الذين تحدثنا عنهم قبل قليل مع لصيقة توضيحيّة. أمّا القسم الأخير السفلي فسيحوي زرًا عند نقره سيتم حل المعادلة. انظر الشكل التالي لمعرفة الشكل العام لهذا التطبيق: في البداية أقترح وضع الواجهة السابقة ضمن مخطّط مكدّس يحيط بها بشكل كامل وذلك على الشكل التالي: <StackLayout> <StackLayout.Children> … </StackLayout.Children> </StackLayout> تمثّل النقاط الثلاث التي تظهر في الرماز السابق، المكان المفترض لرماز الواجهة الذي سنتحدّث عنه بعد قليل، حيث ستقع أي عناصر مرئيّة أخرى ضمن العنصر StackLayout.Children كما هو معلوم. لنجهّز الرسالة الترحيبيّة التي ستظهر في الأعلى. في الحقيقة تتألّف هذه الرسالة من عنصر إطار Frame يحوي لصيقة تعرض النص الترحيبي. يمكن استخدام الرماز التالي لتحقيق هذا الشكل: <Frame Padding="5" OutlineColor="Accent" BackgroundColor="#300000FF" VerticalOptions="StartAndExpand"> <Frame.Content> <Label Text="تطبيق حل المعادلات من الدرجة الثانية" FontSize="Large" TextColor="Accent" HorizontalOptions="FillAndExpand" HorizontalTextAlignment="Center" /> </Frame.Content> </Frame> بالنسبة لعنصر الإطار Frame فقد ضبطنا الخاصيّة Padding له لإضافة حشوة صغيرة تحيط بمحتوياته. كما ضبطنا خصائص لون الحد الخارجي OutlineColor لتحمل اللون Accent وخاصيّة لون الخلفية BackgroundColor لتكون "#300000FF". القيمة السابقة هي قيمة ستة عشرية تتكوّن من أربعة مكوّنات. كل مكّون يحجز محرفين. هذه المكوّنات من اليسار إلى اليمين: قيمة الشفافيّة alpha وتتراوح بين 00 و FF (استخدمنا في هذا المثال القيمة 30)، ثم يأتي اللون الأحمر فالأخضر فالأزرق. فمن خلال القيمة "#300000FF" سيظهر معنا لون أزرق لكنّه شفّاف بعض الشيء. ضبطنا أيضًا خاصيّة التموضع الرأسي VerticalOptions لتكون StartAndExpand ليظهر الإطار في البداية (في الأعلى في هذا التطبيق). بعد ذلك تأتي الخاصيّة Frame.Content والتي يمكن إسناد عنصر مرئي واحد إليها. أسندنا لصيقة كما هو واضح في الرماز السابق، وضبطنا خصائصها ليكون حجم الخط كبيرًا ولونه Accent، كما جعلنا محاذاة النص ضمن اللصيقة في الوسط. تنحصر مهمّة عنصر الإطار في إكساب ناحية جمالية للصيقة التي يحتويها. لننتقل الآن إلى القسم الثاني الأوسط من واجهة التطبيق والذي يحتوي كما أشرنا على منطقة مدخلات المستخدم. سنضع هذا القسم بكامله ضمن مخطّط مكدّس. سيحتوي هذا المخطّط على ما يلي: 1- لصيقة ضمن إطار كما فعلنا تمامًا مع الرسالة الترحيبيّة السابقة في القسم العلوي. تحوي هذه اللصيقة نصًّا توضيحيًّا. لننظر إلى الرماز المسؤول عن إظهار اللصيقة التي تحتوي على النص التوضيحي: <StackLayout VerticalOptions="CenterAndExpand"> <StackLayout.Children> <Frame Padding="5" OutlineColor="Green" BackgroundColor="#3000FF00"> <Frame.Content> <Label Text="أدخل المعاملات التالية لإيجاد حل المعادلة " FontSize="Medium" TextColor="Lime" HorizontalOptions="FillAndExpand" HorizontalTextAlignment="End" /> </Frame.Content> </Frame> ... </StackLayout.Children> </StackLayout> النقاط الثلاث الموجودة ضمن الرماز السابق هي مكان الرماز الذي سيُظهِر مربّعات النص المسؤولة عن استقبال الدخل من المستخدم، حيث تجنبّت وضعه صراحةً لكي نركّز الآن على الرماز الحالي. لاحظ عنصر الإطار Frame كيف يحتوي ضمن الخاصيّة Content له على اللصيقة التي تعرض العبارة "أدخل المعاملات التالية لإيجاد حل المعادلة". لقد ضبطت الخاصيّة HorizontalTextAlignment لها لتكون End أي ليظهر النص كما لو أنّه محاذًا نحو اليمين. 2- مخطّط مكدّس آخر لعرض مربّعات الإدخال Entry لاستقبال الدخل من المستخدم، وهو يقع أسفل الإطار الذي يحوي اللصيقة التي تعرض العبارة التوضيحيّة (انظر رقم 1): <StackLayout VerticalOptions="CenterAndExpand"> <StackLayout.Children> ... <StackLayout Orientation="Horizontal"> <StackLayout.Children> <Entry x:Name="txtA" HorizontalOptions="StartAndExpand" Placeholder="المعامل a" FontSize="Medium" Keyboard="Telephone" /> <Entry x:Name="txtB" HorizontalOptions="CenterAndExpand" Placeholder="المعامل b" FontSize="Medium" Keyboard="Telephone" /> <Entry x:Name="txtC" HorizontalOptions="EndAndExpand" Placeholder="المعامل c" FontSize="Medium" Keyboard="Telephone" /> </StackLayout.Children> </StackLayout> </StackLayout.Children> </StackLayout> الأمر الواضح هنا أنّني قد وضعت مربّعات النص Entry لاستقبال الدخل من المستخدم ضمن مخطّط مكدّس آخر، وهذا أمر طبيعي تمامًا في تصميم الواجهات. لقد جعلت خاصيّة الاتجاه Orientation لهذا المكدّس تحمل القيمة Horizontal أي أفقي. كما وضعت ضمن العنصر StackLayout.Children ثلاثة عناصر Entry لاستقبال المعاملات a وb وc على الترتيب. من الملاحظ أنّني استخدمت الخاصيّة x:Name لكلّ من هذه العناصر لكي نستطيع الوصول إلى محتوياتها ضمن ملف الشيفرة البرمجيّة المرافق لملف الرماز كما سنرى بعد قليل. كما استخدمت الخاصيّة Placeholder لكلّ منها لعرض نص توضيحي ضمن عنصر Entry يوضّح وظيفته. الأمر الأخير الملاحظ هنا هو استخدامي للخاصيّة Keyboard للعنصر Entry التي تسمح بتحديد لوحة المفاتيح التي ستظهر للمستخدم عندما يحاول الكتابة ضمن هذا العنصر. توجد عدّة لوحات مفاتيح تدعمها Xamarin.Froms. لقد استخدمت من أجل هذا المثال لوحة المفاتيح Telephone وهي لوحة مفاتيح مخصّصة لإدخال البيانات المتعلّقة بأرقام الهواتف. في الحقيقة توجد لوحة مفاتيح مخصّصة للأرقام اسمها Numeric ولكنّني آثرت لوحة مفاتيح Telephone عليها لأنّها تدعم إدخال إشارة السالب (-) وهذا ما لا توفّره لوحة المفاتيح الرقميّة القياسيّة Numeric. ننتقل الآن إلى القسم الأخير السفلي من الواجهة، وهو يحتوي على زر بسيط عندما ينقره المستخدم سيعمل البرنامج على حل المعادلة ويعرض النتيجة. انظر إلى الرماز الخاص به: <Button Text="حل المعادلة"` VerticalOptions="EndAndExpand" HorizontalOptions="FillAndExpand" HeightRequest="64" Clicked="btnSolve_Clicked"/> استخدمت هذه المرّة الخاصيّة HeightRequest التي يمكن من خلالها ضبط ارتفاع العنصر بشكل تقريبي. لقد جعلت ارتفاعه مساويًا لـ 1 سم تقريبًا. توجد خاصيّة أخرى مشابهة لها لضبط عرض أي عنصر بشكل تقريبي وهي WidthRequest. ضبطّت أيضًا معالج الحدث Clicked الذي سيتم تنفيذه عندما ينقر المستخدم على هذا الزر. اسم هذا المعالج btnSolve_Clicked وسيكون في الطبع ضمن ملف الشيفرة البرمجيّة. إليك الآن الرماز الكامل لهذا لواجهة هذا التطبيق مجمّعًا على الشكل التالي: <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SDESolverApp.SDESolverPage" Padding="5"> <StackLayout> <StackLayout.Children> <Frame Padding="5" OutlineColor="Accent" BackgroundColor="#300000FF" VerticalOptions="StartAndExpand"> <Frame.Content> <Label Text="تطبيق حل المعادلات من الدرجة الثانية" FontSize="Large" TextColor="Accent" HorizontalOptions="FillAndExpand" HorizontalTextAlignment="Center" /> </Frame.Content> </Frame> <StackLayout VerticalOptions="CenterAndExpand"> <StackLayout.Children> <Frame Padding="5" OutlineColor="Green" BackgroundColor="#3000FF00"> <Frame.Content> <Label Text="أدخل المعاملات التالية لإيجاد حل المعادلة " FontSize="Medium" TextColor="Lime" HorizontalOptions="FillAndExpand" HorizontalTextAlignment="End" /> </Frame.Content> </Frame> <StackLayout Orientation="Horizontal"> <StackLayout.Children> <Entry x:Name="txtA" HorizontalOptions="StartAndExpand" Placeholder="المعامل a" FontSize="Medium" Keyboard="Telephone" /> <Entry x:Name="txtB" HorizontalOptions="CenterAndExpand" Placeholder="المعامل b" FontSize="Medium" Keyboard="Telephone" /> <Entry x:Name="txtC" HorizontalOptions="EndAndExpand" Placeholder="المعامل c" FontSize="Medium" Keyboard="Telephone" /> </StackLayout.Children> </StackLayout> </StackLayout.Children> </StackLayout> <Button Text="حل المعادلة" VerticalOptions="EndAndExpand" HorizontalOptions="FillAndExpand" HeightRequest="64" Clicked="btnSolve_Clicked"/> </StackLayout.Children> </StackLayout> </ContentPage> لننتقل إلى ملف الشيفرة البرمجيّة المرافق لملف الرماز السابق. اسم هذا الملف بالطبع هو SDESolverPage.xaml.cs. احرص على أن تكون محتوياته على الشكل التالي: 1 using System; 2 using Xamarin.Forms; 3 4 namespace SDESolverApp 5 { 6 public partial class SDESolverPage : ContentPage 7 { 8 public SDESolverPage() 9 { 10 InitializeComponent(); 11 } 12 13 private void btnSolve_Clicked(object sender, EventArgs e) 14 { 15 double a, b, c; 16 17 string txt_a = txtA.Text; 18 string txt_b = txtB.Text; 19 string txt_c = txtC.Text; 20 21 if (double.TryParse(txt_a, out a) && 22 double.TryParse(txt_b, out b) && 23 double.TryParse(txt_c, out c)) 24 { 25 string result; 26 27 double delta = b * b - 4 * a * c; 28 29 if (delta > 0) 30 { 31 //there are two different solutions. 32 double x1 = (-b + Math.Sqrt(delta)) / (2 * a); 33 double x2 = (-b - Math.Sqrt(delta)) / (2 * a); 34 35 result = string.Format("يوجد حلين مختلفين في المجموعة R\nx1 = {0}\nx2 = {1}", 36 x1.ToString("#0.#"), 37 x2.ToString("#0.#")); 38 } 39 else if (delta == 0) 40 { 41 //there are two equal solutions. 42 double x = -b / (2 * a); 43 44 result = string.Format("يوجد حل مضاعف في المجموعة R\nx1 = x2 = {0}", 45 x.ToString("#0.#")); 46 } 47 else 48 { 49 //there is no solutions in R. 50 result = "المعادلة مستحيلة الحل في المجموعة R"; 51 } 52 53 DisplayAlert("الحل", result, "موافق"); 54 } 55 else 56 { 57 DisplayAlert("خطأ في المدخلات", "بعض المعاملات المدخلة غير صحيحة", "موافق"); 58 } 59 } 60 } 61 } يحتوي الصنف SDESolverPage بشكل أساسيّ على معالج الحدث btnSolve_Clicked الذي سيُستدعى عندما ينقر المستخدم زر حل المعادلة من الواجهة. يحتوي هذا المعالج على كامل منطق التطبيق. يحصل البرنامج على قيم المعاملات في الأسطر من 17 حتى 19 حيث تكون قيم نصيّة بالطبع، ثمّ يحوّل هذه القيم النصيّة إلى قيم من النوع double وذلك في الأسطر من 21 حتى 23 من خلال استخدام التابع الساكن TryParse من الصنف double. يحاول هذا التابع أن يحوّل القيمة النصيّة المُمرّرة إليه في الوسيط الأوّل، فإن نجح في ذلك فإنّه يُسند القيمة المُحوّلة إلى المتغيّر a الذي نمرّره كوسيط ثانٍ إلى هذا التابع ويُرجع التابع القيمة true. أمّا إذا فشلت عمليّة التحويل فإنّ التابع سيُرجع القيمة false فيختلّ شرط عبارة if، وبالتالي لن يُتابع البرنامج عمله في حل المعادلة ويعرض رسالة بهذا الخصوص إلى المستخدم بسبب عدم صلاحيّة أحد العوامل المُدخلة على الأقل. لاحظ أنّنا قد مرّرنا الوسيط الثاني الذي سيحمل نتيجة التحويل في حال نجاحه باستخدام الكلمة المحجوزة out. التي تسمح بتمرير عنوان المتغيّر إلى التابع وليس قيمته. يتابع البرنامج عمله في حال نجحت عمليّة تحويل المعاملات إلى double بحساب مميّز المعادلة ومقارنته بالحالات الثلاث المختلفة التي تحدثنا عنها مسبقًا. توجد ملاحظة أخيرة في أنّ التطبيق يعرض النتائج باستخدام صناديق رسائل قياسيّة في أندرويد، وذلك من خلال استخدام التابع DisplayAlert وهو من الصنف Page (السطران 53 و57) أيّ أنّه ينتمي إلى الصنف SDESolverPage من خلال الوراثة. يخضع التابع DisplayAlert لزيادة التحميل، وقد استخدمت الشكل الذي يتطلّب ثلاثة وسائط وهي بالترتيب عنوان الرسالة، والرسالة المراد عرضها، ثم النص المراد عرضه على الزر. يمكن بالطبع استخدام وسائل مختلفة لعرض النتائج للمستخدم، كعرضها ضمن صفحة منفصلة مثلًا، وقد يكون هذا الأسلوب أفضل. ولكن بالنسبة إلى هذه المرحلة فالحل المستخدم في هذا التطبيق يُعتبر جيّدًا. سنتعلّم مستقبلًا كيفيّة التعامل مع عرض المعلومات المختلفة عن طريق صفحات مستقلّة. الخلاصة بنينا في هذا الدرس تطبيقًا عمليًّا، يوضّح التعامل مع رماز XAML بشكل متقدّم أكثر. في الحقيقة ستحتاج إلى تطوير العديد من البرامج البسيطة كهذا البرنامج كي تتمكّن من تصميم الواجهات بشكل جيّد. سنتابع في الدروس القادمة العمل في تطوير تطبيقات عمليّة بسيطة ومفيدة، يمكن من خلالها تعلّم المزيد عن بناء الواجهات باستخدام XAML.
  4. كنّا قد رأينا في دروس سابقة كيفيّة الاستفادة من الخصائص الموجودة في عناصر مرئية في التحكّم في خصائص عناصر مرئيّة أخرى كما فعلنا مسبقًا في هذا الدرس حينما استطعنا التحكّم بحجم الخط المستخدم ضمن لصيقة باستخدام خاصيّة القيمة Value لعنصر Slider. حيث استخدمنا لهذا الغرض معالجات أحداث تحوي شيفرة برمجيّة. سنتعلّم في هذا الدرس من سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms. كيفيّة الاستغناء عن استخدام الشيفرة البرمجيّة في مثل هذه المهام. وذلك من خلال استخدام تقنيّة ربط البيانات data bindings التي تقدّمها لنا Xamarin. تفيد هذه التقنيّة في التقليل من كتابة الشيفرة البرمجيّة إلى حدٍّ كبير. حيث يمكننا ربط العناصر المرئيّة مع بعضها مباشرةً دون الحاجة لكتابة أي سطر من الشيفرة. سنتناول في هذا الدرس مثالين بسيطين لتوضيح هذه الفكرة. سنعمل في المثال الأوّل على التحكّم في حجم لصيقة باستخدام عنصر Slider. أمّا في المثال الثاني فسنجري تحسينًا بسيطًا على المثال الأوّل، حيث سنضيف لصيقة أخرى ونربطها مع نفس عنصر المنزلق Slider، بحيث ستعرض هذه اللصيقة الجديدة القيمة الحاليّة للخاصيّة Value للعنصر Slider. تطبيق التحكّم بحجم اللصيقة ابدأ بإنشاء مشروع جديد من النوع Blank App (Xamarin.Forms Portable) وسمّه SimpleDataBindingApp، ثم أبق فقط على المشروعين SimpleDataBindingApp (Portable) و SimpleDataBindingApp.Droid كما وسبق أن فعلنا في هذا الدرس. بعد ذلك سنضيف صفحة محتوى تعتمد على رُماز XAML كما وسبق أن فعلنا في هذا الدرس سنسمّها SimpleDataBindingPage. احرص على أن تكون محتويات هذه الصفحة على الشكل التالي: <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SimpleDataBindingApp.SimpleDataBindingPage"> <StackLayout > <StackLayout.Children> <Label x:Name="lblText" VerticalOptions="CenterAndExpand" Text="النص" HorizontalOptions="Center" BindingContext="{x:Reference Name=slider}" Scale="{Binding Path=Value}" /> <Slider x:Name="slider" Maximum="10" Minimum="1" Value="4" VerticalOptions="CenterAndExpand"/> </StackLayout.Children> </StackLayout> </ContentPage> انتقل إلى الملف App.cs واحرص على أن تكون بانية الصنف App على الشكل التالي: public App() { // The root page of your application MainPage = new SimpleDataBindingPage(); } نفّذ البرنامج باستخدام F5 لتحصل على شكل شبيه بما يلي: يمكنك تمرير عنصر المنزلق Slider لترى كيف يتغيّر حجم اللصيقة تلقائيًّا. هناك عدّة نقاط سنتحدّث عنها حول هذا التطبيق البسيط: 1- لم نستخدم الشيفرة البرمجيّة أبدًا. لقد ربطنا الخاصيّة Value لعنصر Slider مع خاصيّة مقياس العنصر Scale للصيقة من خلال رُماز XAML فحسب. 2- في أيّة عمليّة ربط هناك الكائن المصدر source (في مثالنا السابق كان عنصر Slider)، والكائن الهدف target (في مثالنا السابق كان عنصر اللصيقة Label). 3- في مثالنا السابق، ضبطنا الخاصيّة BindingContext للّصيقة (الهدف) لكي تشير إلى عنصر Slider (المصدر)، لاحظ كيف عيّنّا اسم عنصر المنزلق slider وذلك من خلال النص التالي: BindingContext="{x:Reference Name=slider}" ثم أخبرنا Xamarin أنّنا نريد ربط خاصيّة مقياس العنصر Scale للّصيقة مع الخاصيّة Value لعنصر المنزلق Slider عن طريق ضبط النص التالي إلى الخاصيّة Scale للصيقة: Scale="{Binding Path=Value}" تتحكّم الخاصيّة Scale بمقياس العنصر. سنتحدّث عن ذلك في دروس لاحقة ضمن هذه السلسلة. التطبيق المحسّن للتحكّم بحجم اللصيقة أضف إلى التطبيق السابق صفحة محتوى تعتمد على رُماز XAML كما وسبق أن فعلنا في هذا الدرس سنسمّها EnhancedDataBindingPage. احرص على أن تكون محتويات هذه الصفحة على الشكل التالي: <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SimpleDataBindingApp.EnhancedDataBindingPage"> <StackLayout > <StackLayout.Children> <Label x:Name="lblText" VerticalOptions="CenterAndExpand" Text="النص" HorizontalOptions="Center" BindingContext="{x:Reference Name=slider}" Scale="{Binding Path=Value}" /> <Slider x:Name="slider" Maximum="10" Minimum="1" Value="4" VerticalOptions="CenterAndExpand"/> <Label x:Name="lblScale" VerticalOptions="CenterAndExpand" Text="{Binding Path=Value, StringFormat = 'Scale = {0:F1}'}" HorizontalOptions="Center" BindingContext="{x:Reference Name=slider}"/> </StackLayout.Children> </StackLayout> </ContentPage> من الواضح أنّ التعديل الوحيد الملاحظ هنا هو في وجود عنصر اللصيقة الجديد lblScale والذي تنحصر وظيفته كما أوضحنا سابقًا على عرض القيمة الحالية لعنصر المنزلق Slider. إليك تعريف عنصر اللصيقة lblScale: <Label x:Name="lblScale" VerticalOptions="CenterAndExpand" Text="{Binding Path=Value, StringFormat = 'Scale = {0:F1}'}" HorizontalOptions="Center" BindingContext="{x:Reference Name=slider}"/> كما وسبق أن فعلنا مع لصيقة النص lblText، قمنا بتعيين الخاصيّة BindingContext لتشير إلى عنصر المنزلق الذي يحمل الاسم "slider": BindingContext="{x:Reference Name=slider}"/> ثمّ سنربط هذه المرّة الخاصيّة Text لهذه اللصيقة بالخاصيّة Value لعنصر المنزلق على الشكل التالي: Text="{Binding Path=Value, StringFormat = 'Scale = {0:F1}'}" لاحظ كيف أنّ الربط Binding قد جرى بين الخاصيّة Value لعنصر المنزلق، والخاصيّة Text لعنصر اللصيقة الجديد lblScale. لاحظ أيضًا الكلمة الجديدة StringFormat. ستُفسَّر هذه الكلمة في الواقع لتصبح استدعاء إلى التابع String.Format الذي يعمل على تنسيق النص Scale = {0:F1} بحيث يظهر ضمن اللصيقة عدد ذو فاصلة عشرية ثابتة تمتلك رقم واحد فقط على يمين الفاصلة العشريّة. يمكنك تغيير هذا السلوك، بحيث تلغي عمليّة التنسيق هذه لترى الفرق وذلك على الشكل التالي: Text="{Binding Path=Value'}" انتقل الآن إلى الملف App.cs واحرص على أن تكون بانية الصنف App على الشكل التالي: public App() { // The root page of your application MainPage = new EnhancedDataBindingPage(); } عند تنفيذ البرنامج ستحصل على شكل شبيه بما يلي: يمكنك تحريك المنزلق لترى كيف يعمل الربط بين الخاصيّة Value وكل من الخاصيّتين Scale وText للصيقتين الموجودتين في البرنامج. الخلاصة يُعتبر هذا الدرس مثيرًا للانتباه. حيث لم نكتب أي شيفرة برمجيّة في أيّ من التطبيقين اللذين بنيناهما في هذا الدرس. إنّ عمليّة ربط البيانات data bindings بمعزل عن كونها توفّر الوقت والجهد في إنجاز مهام روتينيّة (وهو أمر مهم بالطبع)، تُعتبر حيويةً في أسلوب تصميم التطبيقات MVVM (اختصارًا للكلمات Model View ViewModel) الشائع جدًّا في تصميم تطبيقات الأجهزة المحمولة. سنتناول في الدرس القادم موضوعًا مهمًّا نتعلّم من خلاله كيفيّة تصميم واجهات مستخدم دقيقة من خلال مخطّط الشبكة Grid Layout. وهو أحد أنواع المخطّطات المستخدمة في Xamarin والتي من المؤكّد أنّها ستثير إعجابك.
  5. يُعتبر مخطّط الشبكة Grid Layout من المخطّطات الهامّة والضروريّة في تصميم واجهات المستخدم بالنسبة لتطبيقات الأجهزة المحمولة. فيمكن من خلال هذا المخطّط التحكّم على نحو دقيق نسبيًّا بمواقع جميع العناصر المرئيّة الموجودة في الواجهة، وذلك بخلاف مخطّط المكدّس Stack Layout الذي تناولناه منذ بداية هذه السلسلة حتى الآن. حيث كان من الصعوبة التحكّم بمواقع العناصر المرئيّة بشكل دقيق، وخاصةً فيما يتعلّق بالمحاذاة. في الحقيقة يمكن استخدام كل من هذين المخطّطين معًا في الواجهة الواحدة. سنبدأ في هذا الدرس بالتعرّف على مفهوم هذا المخطّط وطريقة استخدامه. ثمّ نُتْبع هذا الدرس بدرسين آخرين يتناولان تطبيقين عمليين لاكتساب الخبرة اللازمة للعمل مع مخطّط الشبكة بسهولة ويسر أكبر. ما هو مخطّط الشبكة؟ مخطّط الشبكة هو عبارة عن بنية تشبه الجدول من الناحية الشكلية ولكنّها تختلف عنه من الناحية العمليّة. فمخطّط الشبكة قد صُمّم بالأساس ليكون وسيلة لتنظيم العناصر المرئيّة ضمن واجهة المستخدم. أمّا الجدول فهو بنية وظيفتها احتواء بيانات يمكن عرضها للمستخدم. سيكون أغلب تعاملنا مع مخطّط الشبكة من خلال رُماز XAML. إذ أنّه من الممكن أيضًا استخدام الأسلوب البرمجيّ في التعامل معه. ولكن التعامل معه من خلال الرُماز أفضل من ناحية الاستخدام والتنظيم. يمكن تعريف مخطّط الشبكة من خلال الوسم . حيث من الممكن تعيين الأسطر rows والأعمدة columns التي يتألّف منها هذا المخطّط من خلال الخاصيّتين RowDefinitions التي تحتوي على تعاريف الأسطر وColumnDefinitions التي تحتوي على تعاريف الأعمدة. يُدعى مكان تقاطع السطر والعمود بالخليّة Cell. الخاصيّة RowDefinitions هي عبارة عن مجموعة collection من العناصر التي يكون كلّ منها من النوع RowDefinition وتعني تعريف سطر. يمكن من خلال هذا النوع ضبط العديد من خصائص هذا السطر. سنهتم حاليًّا بالخاصيّة Height التي تُعبّر عن ارتفاع السطر. من الممكن تعيين قيمة هذا الارتفاع بثلاثة أنواع من المقاييس: النوع الأوّل هو القيمة العدديّة المباشرة وهي قيمة لا تتعلّق لا تتعلّق بالشاشة device-independent units (انظر هذا الدرس). النوع الثاني هو الكلمة Auto، والتي تعني أنّه ينبغي أن يكون ارتفاع السطر مساويًا لأعلى ارتفاع عنصر مرئي موجود ضمنه. النوع الثالث هو النوع النسبيّ، ويُعبّر عنه رمز النجمة (*)، حيث يكون ارتفاع السطر في هذه الحالة بحيث يشغل باقي الارتفاع المتاح لمخطّط الشبكة ككل. ومن الممكن أن يتعلّق أيضًا بسطر آخر له نفس هذا النوع من القياس، وسنوضّح ذلك في مثال تطبيقي بعد قليل. أمّا بالنسبة للخاصيّة ColumnDefinitions هي عبارة عن مجموعة collection من العناصر التي يكون كلّ منها من النوع ColumnDefinition وتعني تعريف عمود. يمكن من خلال هذا النوع ضبط العديد من خصائص هذا العمود. سنهتم بالخاصيّة Width منه التي تُعبّر عن عرض العمود. من الممكن تعيين قيمة هذا العرض كما هو الحال مع تعريف السطر أيضًا بثلاثة أنواع من المقاييس، تماثل من حيث المبدأ الأنواع الثلاثة السابقة من القياس للسطر: النوع الأوّل هنا هو نفسه النوع الأوّل بالنسبة للسطر، وهو قيمة العدديّة المباشرة التي لا تتعلّق لا تتعلّق بالشاشة device-independent units. النوع الثاني هو الكلمة Auto، والتي تعني أنّه ينبغي أن يكون عرض العمود مساويًا لأعرض عنصر مرئي موجود ضمنه. النوع الثالث هو النوع النسبيّ، ويُعبّر عنه رمز النجمة (*) أيضًا، حيث يكون عرض العمود في هذه الحالة بحيث يشغل باقي العرض المتاح لمخطّط الشبكة ككل. ومن الممكن أن يتعلّق أيضًا بعمود آخر له نفس هذا النوع من القياس. وسنوضّح ذلك أيضًا في المثال التطبيقي الذي سنكتبه بعد قليل. من الجدير ذكره أنّه في حالة القياس النسبيّ في كلّ من حالة السطر والعمود، من الممكن وجود رقم بجوار رمز النجمة يدل على الحصّة التناسبيّة للسطر أو للعمود كما سنرى ذلك بعد قليل. تطبيق عملي بسيط باستخدام مخطّط الشبكة ابدأ بإنشاء مشروع جديد من النوع Blank App (Xamarin.Forms Portable) وسمّه SimpleGridApp، ثم أبق فقط على المشروعين SimpleGridApp (Portable) و SimpleGridApp.Droid كما وسبق أن فعلنا في هذا الدرس. بعد ذلك سنضيف صفحة محتوى تعتمد على رُماز XAML كما وسبق أن فعلنا في هذا الدرس سنسمّها SimpleGridPage. احرص على أن تكون محتويات هذه الصفحة على الشكل التالي: 1 <?xml version="1.0" encoding="utf-8" ?> 2 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 3 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 4 x:Class="SimpleGridApp.SimpleGridPage"> 5 <Grid> 6 <Grid.RowDefinitions> 7 <RowDefinition Height="Auto" /> 8 <RowDefinition Height="100" /> 9 <RowDefinition Height="2*" /> 10 <RowDefinition Height="1*" /> 11 </Grid.RowDefinitions> 12 13 <Grid.ColumnDefinitions> 14 <ColumnDefinition Width="*" /> 15 <ColumnDefinition Width="*" /> 16 </Grid.ColumnDefinitions> 17 18 <Label Text="مخطط الشبكة" 19 Grid.Row="0" Grid.Column="0" 20 FontSize="Large" 21 HorizontalOptions="End" /> 22 23 <Label Text="تجربة مخطط الشبكة" 24 Grid.Row="0" Grid.Column="1" 25 FontSize="Small" 26 HorizontalOptions="End" 27 VerticalOptions="End" /> 28 29 <Image BackgroundColor="Gray" 30 Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Source="icon.png" /> 31 32 <BoxView Color="Green" 33 Grid.Row="2" Grid.Column="0" /> 34 35 <BoxView Color="Red" 36 Grid.Row="2" Grid.Column="1" Grid.RowSpan="2" /> 37 38 <BoxView Color="Blue" 39 Opacity="0.5" 40 Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" /> 41 </Grid> 42 </ContentPage> انتقل الآن إلى الملف App.cs واحرص على أن تكون بانية الصنف App على الشكل التالي: public App() { // The root page of your application MainPage = new SimpleGridPage(); } نفّذ البرنامج لتحصل على شكل شبيه بما يلي: لنحلّل الآن هذا التطبيق البسيط. في البداية أنشأنا مخطّط شبكة عن طريق كتابة الوسم اعتبارًا من السطر 5. بعد ذلك وضعنا تعاريف أسطر الشبكة من السطر 6 حتى السطر 10 على الشكل التالي: <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="100" /> <RowDefinition Height="2*" /> <RowDefinition Height="1*" /> </Grid.RowDefinitions> ويعني ذلك أنّ هذه الشبكة سيكون لها أربعة أسطر (لوجود أربعة وسوم RowDefinition). سيكون ارتفاع السطر الأوّل Auto، ويعني ذلك أنّ هذا السطر سيكون ارتفاعه مساويًا لارتفاع أعلى عنصر موجود ضمنه. أمّا السطر الثاني فارتفاعه 100 وحدة مستقلة عن الجهاز. بالنسبة للسطرين الثالث والرابع فسيكون ارتفاعهما نسبيًّا لوجود رمز النجمة *. لاحظ وجود الرقم 2 بجوار رمز النجمة بالنسبة للسطر الثالث، ووجود الرقم 1 بجوار رمز النجمة بالنسبة للسطر الرابع. الذي سيحدث هنا أنّه وبعد أن يتم تعيين ارتفاعيّ السطرين الأوّل والثاني سيتم اقتسام الارتفاع المتبقي للشبكة بين السطرين الثالث والرابع بحيث تكون حصّة السطر الثالث ضعف (الرقم 2) حصّة السطر الرابع (الرقم 1). بعبارة أخرى ارتفاع السطر الثالث سيكون 2 إلى 1 من ارتفاع السطر الرابع. وضعنا بعد ذلك تعاريف أعمدة الشبكة من السطر 13 حتى السطر 16 على الشكل التالي: <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> من الواضح أنّه سيكون لهذه الشبكة عمودان فقط. كل من هذين العمودين سيكون عرضه نسبيّ (الرمز *). ولكن بسبب عدم وجود أيّ رقم بجوار رمز النجمة في كلّ منهما، سيتمّ اعتبار وجود الرقم 1 بشكل افتراضيّ بجوار كل رمز نجمة (مثل1*). أي بالمحصلة سيكون لكل منهما نفس الرقم، وبالتالي نفس الحصّة النسبيّة لذلك سيكون لهما نفس العرض تمامًا. تأتي بعد ذلك العناصر المرئيّة التي سنضعها ضمن هذه الشبكة. يبدأ ترقيم كل من الأسطر والأعمدة بالرقم صفر. فالسطر رقم صفر هو السطر الأوّل، وكذلك الأمر بالنسبة للأعمدة. إذا نظرت إلى الأسطر من 18 حتى 21 فستجد أنّنا نضيف لصيقة إلى الخليّة المحدّدة بالسطر الأوّل والعمود الأوّل. ويكمن سبب ذلك في السطر 19 حيث نجد الخاصيّتين Grid.Row="0" للسطر رقم 0، وGrid.Column="0" للعمود رقم 0 أيضًا. أي السطر الأوّل والعمود الأوّل. بنفس الأسلوب تمامًا نضيف لصيقة أخرى إلى الخليّة المحدّدة بالسطر الأوّل والعمود الثاني (الأسطر من 23 حتى 27)، وأيضًا عنصر صورة ضمن الخليّة المحدّدة بالسطر الثاني والعمود الأوّل (السطران 29 و30). نلاحظ أيضًا أنّ عنصر الصورة هذا سيشغل بالإضافة إلى العمود الأوّل العمود الثاني أيضًا، وذلك بسبب وجود الخاصيّة Grid.ColumnSpan="2" والتي تشير إلى أنّ الخليّة التي ستحتوي عنصر الصورة يجب أن تمتد على عمودين متتاليين باتجاه اليمين، وهذا ما يُفسّر سبب ظهورها ممتدّة على كامل عرض الشبكة كما هو واضح في الشكل السابق. بعد إضافة عنصر الصورة السابق، سنضيف ثلاثة عناصر BoxView لأغراض العرض فقط (الأسطر من 32 حتى 40). حيث ستشغل هذه العناصر الثلاثة على الترتيب الخلايا المحدّدة بالسطر الثالث والعمود الأوّل، ثم السطر الثالث والعمود الثاني، ثم السطر الرابع والعمود الأوّل. مع ملاحظة أنّ عنصر BoxView الأوسط يحوي الخاصيّة Grid.RowSpan="2" التي تسمح للخلية التي تحتويه بالامتداد إلى سطرين متتاليين. أمّا عنصر BoxView الأخير فيحوي على الخاصية Grid.ColumnSpan="2" لتسمح له بالامتداد على عمودين متتاليين. أي أنّ عنصريّ BoxView الثالث والرابع سيتداخلان في الخليّة المحدّدة بالسطر الرابع والعمود الثاني كما هو واضح من الشكل السابق. الخلاصة تعلّمنا في هذا الدرس كيفيّة التعامل الأساسي مع مخطّط الشبكة Grid Layout. ولعلّ الفهم الأفضل لمثل هذه الأمور تنبع من الممارسّة العمليّة ببناء تطبيقات عمليّة صغيرة ولكنّها مفيدة. سنعمل في الدرسين القادمين على بناء تطبيقين بسيطين يحتويان على العديد من التقنيّات البرمجيّة المفيدة والتي من الممكن استخدامها في التطبيقات الواقعيّة.
  6. سنتناول في هذا الدرس من سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms تطبيقًا عمليًّا مفيدًا حول التعامل مع مخطّط الشبكة Grid Layout وهو عبارة عن تطبيق لوحة أرقام يمكن تطويرها لاحقًا لتصبح مثلًا تطبيق آلة حاسبة متعدّد الوظائف. اطلعنا في الدرس السابق على مخطّط الشبكة وتعلمنا بعض المبادئ الأساسيّة لاستخدامه. يمكنك مراجعة ذلك الدرس، أو من الممكن أن تبدأ مباشرةً في هذا الدرس. فكرة التطبيق سنعمل على كتابة تطبيق لوحة أرقام بسيط، حيث ستظهر أزرار تحمل الأرقام من 0 حتى 9 بالإضافة إلى زر الفاصلة العشرية، مع وجود زر لإلغاء آخر رقم تمّت كتابته. ستبدو واجهة التطبيق كما في الشكل التالي: عندما ينقر المستخدم على أزرار الأرقام ستظهر هذه الأرقام تباعًا بجوار بعضها بالترتيب. أمّا عندما ينقر زر الإلغاء فسيتم إلغاء آخر رقم تمّ نقره. لنستعرض الشيفرة البرمجيّة لهذا التطبيق في الفقرة التالية. الشيفرة البرمجيّة للتطبيق ابدأ بإنشاء مشروع جديد من النوع Blank App (Xamarin.Forms Portable) وسمّه KeypadGrid، ثم أبق فقط على المشروعين KeypadGrid (Portable) و KeypadGrid.Droid كما وسبق أن فعلنا في هذا الدرس. بعد ذلك سنضيف صفحة محتوى تعتمد على رُماز XAML كما وسبق أن فعلنا في هذا الدرس سنسمّها KeypadGridPage. احرص على أن تكون محتويات هذه الصفحة على الشكل التالي: <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="KeypadGrid.KeypadGridPage"> <Grid RowSpacing="2" ColumnSpacing="2" VerticalOptions="Center" HorizontalOptions="Center" WidthRequest="180" Grid.HeightRequest="300"> <Label x:Name="displayLabel" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" FontSize="Large" LineBreakMode="HeadTruncation" VerticalOptions="Center" HorizontalTextAlignment="End" /> <Button x:Name="btnBackspace" Text="⇦" Grid.Row="0" Grid.Column="2" IsEnabled="False" Clicked="btnBackspace_Clicked" /> <Button Text="7" StyleId="7" Grid.Row="1" Grid.Column="0" Clicked="OnDigitButtonClicked" /> <Button Text="8" StyleId="8" Grid.Row="1" Grid.Column="1" Clicked="OnDigitButtonClicked" /> <Button Text="9" StyleId="9" Grid.Row="1" Grid.Column="2" Clicked="OnDigitButtonClicked" /> <Button Text="4" StyleId="4" Grid.Row="2" Grid.Column="0" Clicked="OnDigitButtonClicked" /> <Button Text="5" StyleId="5" Grid.Row="2" Grid.Column="1" Clicked="OnDigitButtonClicked" /> <Button Text="6" StyleId="6" Grid.Row="2" Grid.Column="2" Clicked="OnDigitButtonClicked" /> <Button Text="1" StyleId="1" Grid.Row="3" Grid.Column="0" Clicked="OnDigitButtonClicked" /> <Button Text="2" StyleId="2" Grid.Row="3" Grid.Column="1" Clicked="OnDigitButtonClicked" /> <Button Text="3" StyleId="3" Grid.Row="3" Grid.Column="2" Clicked="OnDigitButtonClicked" /> <Button Text="0" StyleId="0" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Clicked="OnDigitButtonClicked" /> <Button Text="." StyleId="." Grid.Row="4" Grid.Column="2" Clicked="OnDigitButtonClicked" /> </Grid> </ContentPage> من السهل فهم الرُماز السابق مع بعض الملاحظات البسيطة: في البداية لاحظ أنّنا قد أسندنا خصائص إضافيّة لمخطّط الشبكة مثل تباعد الأسطر RowSpacing وتباعد الأعمدة ColumnSpacing وذلك بهدف جعل الأزرار غير ملتصقة ببعضها البعض. كما أسندنا الخيارات الرأسية VerticalOptions والأفقية HorizontalOptions لكي تحمل كل منهما القيمة Center بهدف جعل الشبكة تظهر في مركز الشاشة. لم نستخدم في هذا التطبيق الخاصيّتين RowDefinitions و ColumnDefinitions اللّتان تحدثنا عنهما في الدرس السابق. إذ أنّه من الممكن اعتماد الإعدادات الافتراضيّة الخاصّة بهما، والبدء مباشرة بإضافة العناصر المرئيّة، حيث يمكن من خلال هذه العناصر تحديد السطر والعمود للخليّة التي نودّ وضع العنصر ضمنها كما سنرى بعد قليل. اللصيقة الوحيدة الموجودة في هذا التطبيق تُستَخدم لعرض الرقم الذي أدخله المستخدم. لاحظ أنّنا نريد عرضها في الخلية المحدّدة بالسطر الأوّل والعمود الأوّل، بالإضافة إلى الانتشار لعمودين متتاليين بسبب وجود الخصائص التالية: Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2". كما توجد خاصية أخرى ملفتة للنظر لهذه اللصيقة، وهي الخاصيّة LineBreakMode والتي تحمل القيمة HeadTruncation. هذه القيمة مفيدة في مثل هذه التطبيقات حيث يؤدّي وجودها إلى اقتطاع النص الذي تمّ إدخاله أولًا في حال لم تتسع اللصيقة لعرض كامل النص (يمكنك تجريب ذلك بنفسك فيما بعد). الزر الأوّل الذي يأتي بعد تعريف اللصيقة هو زر إلغاء آخر رقم مُدخل، واسمه btnBackspace، وهو مرتبط مع معالج حدث النقر btnBackspace_Clicked الموجود ضمن ملف الشيفرة البرمجيّة كما سنرى بعد قليل. بالنسبة لبقيّة الأزرار فأمرها بسيط. فكلّ زر نُعيّن له الخاصيّة StyleId للاحتفاظ بالقيمة النصيّة الموافقة للرقم الذي يحمله، فمثًلا الزر الذي يُعبّر عن الرقم 9 ستكون قيمة StyleId له تساوي "9"، وهكذا. الجدير بالملاحظة هنا أننا قد ربطنا جميع هذه الأزرار بنفس معالج الحدث وهو OnDigitButtonClicked، وهو أمر جائز تمامًا، بل ومن المفضّل القيام به. سنطّلع على هذا المعالج بعد قليل. انتقل الآن إلى ملف الشيفرة البرمجيّة KeypadGridPage.xaml.cs الموافق لملف الرُماز السابق، واحرص على أن تكون محتوياته على الشكل التالي: using System; using Xamarin.Forms; namespace KeypadGrid { public partial class KeypadGridPage : ContentPage { public KeypadGridPage() { InitializeComponent(); btnBackspace.IsEnabled = displayLabel.Text != null && displayLabel.Text.Length > 0; } void OnDigitButtonClicked(object sender, EventArgs args) { Button button = (Button)sender; displayLabel.Text += (string)button.StyleId; btnBackspace.IsEnabled = true; } void btnBackspace_Clicked(object sender, EventArgs args) { string text = displayLabel.Text; displayLabel.Text = text.Substring(0, text.Length - 1); btnBackspace.IsEnabled = displayLabel.Text.Length > 0; } } } الشيفرة السابقة بسيطة للغاية، فالصنف KeypadGridPage يحتوي بشكل أساسي على معالجيّ لحدث النقر: المعالج الأوّل هو OnDigitButtonClicked وهو يُستدعى عندما ينقر المستخدم على أحد أزرار الأرقام بالإضافة إلى زر الفاصلة العشرية. وفي هذا المعالج يتم إضافة الرقم الذي نقره المستخدم توًّا إلى محتويات اللصيقة التي تعرض كامل الرقم المُدخَل. نبدأ أولًا بعمليّة تحويل cast للحصول على الزر الذي سبّب استدعاء هذا المعالج: Button button = (Button)sender; ثمّ استخلاص قيمة الخاصيّة StyleId له وضمّها إلى اللصيقة displayLabel: displayLabel.Text += (string)button.StyleId; ومن ثمّ تفعيل زر إلغاء آخر رقم بسبب أنّه قد أصبح هناك رقم واحد على الأقل بشكل مؤكّد ضمن اللصيقة. أمّا المعالج الثاني فهو btnBackspace_Clicked وهو يُستَدعى عندما يتم النقر على زر إلغاء آخر رقم مُدخَل. الشيفرة البرمجيّة ضمن هذا المعالج بسيطة، فهي تعمل أولًا على الحصول على النص الموجود أصلًا ضمن اللصيقة displayLabel وتضعه ضمن المتغيّر النصي text: string text = displayLabel.Text; ومن ثمّ تقوم بحذف آخر رقم عن طريق التابع Substring من المتغيّر text ومن ثمّ إعادة إسناد النص الناتج إلى الخاصيّة Text للصيقة displayLabel. displayLabel.Text = text.Substring(0, text.Length - 1); نقوم في نهاية المطاف بتفعيل زر إلغاء آخر رقم مُدخل أو تعمل على إلغاء تفعيله باستخدام طريقة جميلة تعتمد على تقييم التعبير المنطقي الموجود على يمين عامل الإسناد (=): btnBackspace.IsEnabled = displayLabel.Text.Length > 0; فإذا كانت اللصيقة تحتوي على رقم واحد على الأقل سيكون التعبير displayLabel.Text.Length > 0 مساويًا للقيمة true وبالتالي يتم تفعيل الزر. وإلّا سيعطي هذا التعبير القيمة false ويتم إلغاء تفعيله. حاول إجراء بعض التجارب على هذا التطبيق، ثمّ فكّر كيف يمكن تعديله لإنشاء تطبيق آلة حاسبة فعليّ، يمكنه دعم العمليّات الحسابيّة الأساسيّة الأربع على سبيل المثال. الخلاصة استفدنا من التطبيق الذي تناولناه في هذا الدرس، في كيفيّة استخدام مخطّط الشبكة بشكل عمليّ بغية الاستفادة من مزايا التموضع الدقيقة نسبيًّا التي يوفّرها للعناصر المرئيّة التي تُوضَع ضمنه. سنتابع العمل في الدرس التالي لنبني تطبيقًا عمليًّا مفيدًا أخرًا يستفيد أيضًا من المزايا الرائعة لمخطّط الشبكة في إنشاء مخطّط تكراري إحصائي يشبه مخطّطات الأعمدة التي من الممكن الحصول عليها من تطبيقات مكتبيّة مثل Microsoft Excel مثلًا.
  7. سنتناول في هذا الدرس من سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms تطبيقًا عمليًّا مفيدًا آخرًا، نتعلّم من خلاله المزيد عن مزايا مخطّط الشبكة Grid Layout واستخداماته العمليّة في التطبيقات التي نطوّرها. سيستخدم هذا التطبيق مخطّطًا تكراريًّا Histogram سيكون مصمّمًا باستخدام مخطّط الشبكة. إذا أردت معرفة المزيد عن مخطّط الشبكة فيمكنك مراجعة هذا الدرس. وإلّا، فيمكنك البدء مباشرةً في هذا الدرس. فكرة التطبيق تتلخّص فكرة هذا التطبيق في أنّ المستخدم سيُدخل نصًّا بأيّ لغة يرغبها. ثم ينقر على زر "احسب" ليعمل التطبيق بعد ذلك على حساب تكرار كلّ محرف من محارف النص المُدخل. بعد الانتهاء من ذلك، سيعمل التطبيق على إظهار مخطّط تكراري Histogram على شكل مخطّط أعمدة bars chart يمكن من خلاله ملاحظة الأحرف الأكثر تكرارًا والأقل تكرارًا من نظرة واحدة. سيعتمد التطبيق على عنصر رسومي من النوع Editor لاستقبال الدخل النصيّ من المستخدم. وكذلك على عنصر الزر Button للبدء بإيجاد القيم التكراريّة، وأيضًا سيستخدم مخطط شبكة لعرض النتائج التكراريّة. من أجل كل حرف موجود في النص، سيضيف التطبيق عمود bar سيكون عبارة عن عنصر BoxView يكون ارتفاعه متناسبًا مع تكرار الحرف ضمن النص المُدخَل. بعد إظهار مخطّط الأعمدة سيكون من الممكن النقر على أي عمود (عنصر BoxView) ليعرض التطبيق رسالة للمستخدم تحوي الحرف الذي يمثّله هذا العمود مع عدد مرًّات تكراره ضمن النص المُدخَل. أي بشكل مشابه لما يلي: سنتناول في الفقرة التالية كامل الشيفرة البرمجيّة لهذا التطبيق. الشيفرة البرمجيّة للتطبيق ابدأ بإنشاء مشروع جديد من النوع Blank App (Xamarin.Forms Portable) وسمّه GridBarChart، ثم أبق فقط على المشروعين GridBarChart (Portable) و GridBarChart.Droid كما وسبق أن فعلنا في هذا الدرس. بعد ذلك سنضيف صفحة محتوى تعتمد على رماز XAML كما وسبق أن فعلنا في هذا الدرس سنسمّها GridBarChartPage. احرص على أن تكون محتويات هذه الصفحة على الشكل التالي: 1 <?xml version="1.0" encoding="utf-8" ?> 2 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 3 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 4 x:Class="GridBarChart.GridBarChartPage"> 5 <StackLayout> 6 <StackLayout VerticalOptions="Start"> 7 <Label Text="تطبيق حساب تكرار المحارف" 8 HorizontalOptions="CenterAndExpand" 9 HorizontalTextAlignment="Center" 10 FontSize="Large" 11 TextColor="Green"/> 12 <Editor x:Name="txtInput" /> 13 <Button x:Name="btnCalculate" 14 Text="احسب" 15 HorizontalOptions="FillAndExpand" 16 Clicked="btnCalculate_Clicked" /> 17 </StackLayout> 18 19 <Grid x:Name="grdLettersHistogram" 20 VerticalOptions="FillAndExpand" 21 BackgroundColor="#EBF2F5" 22 Padding="5,5,5,0"> 23 </Grid> 24 </StackLayout> 25 </ContentPage> من الملاحظ أنّ رماز XAML لهذا التطبيق صغير نسبيًّا. يبدأ الرماز بتعريف مخطّط مكدّس StackLayout سيعمل على احتواء كامل واجهة التطبيق. سيحتوي مخطّط المكدّس هذا على عنصرين آخرين: العنصر الأوّل عبارة عن مخطّط مكدّس آخر (الأسطر من 6 حتى 17) يحتوي على لصيقة تعريفيّة بالتطبيق، وعلى عنصر الإدخال النصيّ Editor الذي أسميته txtInput وهو يسمح بكتابة أكثر من سطر نصيّ واحد ضمنه، بالإضافة إلى زر البدء بالحساب الذي أسميته btnCalculate والذي سيؤدّي نقره إلى الانتقال إلى معالج حدث النقر btnCalculate_Clicked. المعالج السابق سيكون بالتأكيد ضمن ملف الشيفرة البرمجيّة الذي سنطّلع عليه بعد قليل. العنصر الثاني هو مخطّط الشبكة (الأسطر من 19 حتى 23) والذي أسميته grdLettersHistogram. لاحظ أنّ هذا المخطّط لا يحوي حاليًّا أيّة عناصر. سنضيف لاحقًا إلى هذا المخطّط عناصر BoxView يمثّل كل منها محرفًا موجودًا ضمن النص المُدخَل، وذلك عن طريق الشيفرة البرمجيّة كما سنرى بعد قليل. انتقل الآن إلى الملف GridBarChartPage.xaml.cs وهو ملف الشيفرة البرمجيّة التابع لملف رماز XAML السابق. احرص على أن تكون محتوياته على الشكل التالي: 1 using System; 2 using System.Collections.Generic; 3 using Xamarin.Forms; 4 5 namespace GridBarChart 6 { 7 public partial class GridBarChartPage : ContentPage 8 { 9 public GridBarChartPage() 10 { 11 InitializeComponent(); 12 } 13 14 private void btnCalculate_Clicked(object sender, EventArgs e) 15 { 16 Dictionary<char, int> freq = new Dictionary<char, int>(); 17 List<View> bars = new List<View>(); 18 TapGestureRecognizer tapGesture = new TapGestureRecognizer(); 19 int maxFreq = -1; 20 21 tapGesture.Tapped += TapGesture_Tapped; 22 23 string s = txtInput.Text; 24 25 foreach (char t in s) 26 { 27 if (char.IsLetterOrDigit(t)) 28 { 29 if (!freq.ContainsKey(t)) 30 { 31 freq.Add(t, 1); 32 } 33 else 34 { 35 freq[t] = ++freq[t]; 36 } 37 } 38 } 39 40 grdLettersHistogram.Children.Clear(); 41 42 //find the max letter frequency. 43 foreach(var v in freq.Values) 44 { 45 if(maxFreq< v) 46 { 47 maxFreq = v; 48 } 49 } 50 51 //calculate measurment unit will apply on bars. 52 double measureUnit = grdLettersHistogram.Height / maxFreq; 53 54 foreach (var c in freq.Keys) 55 { 56 var boxView = new BoxView 57 { 58 HeightRequest = measureUnit * freq[c], 59 BackgroundColor = Color.Accent, 60 VerticalOptions = LayoutOptions.End, 61 StyleId = string.Format("المحرف '{0}' له التكرار {1}", c, freq[c]), 62 }; 63 64 boxView.GestureRecognizers.Add(tapGesture); 65 66 bars.Add(boxView); 67 } 68 69 grdLettersHistogram.Children.AddHorizontal(bars); 70 } 71 72 private void TapGesture_Tapped(object sender, EventArgs e) 73 { 74 DisplayAlert("رسالة", ((BoxView)sender).StyleId, "اغلاق"); 75 } 76 } 77 } تكمن معظم الشيفرة البرمجيّة في هذا الملف ضمن معالج حدث نقر زر حساب التكرارات btnCalculate_Clicked الذي يمتد من السطر 14 حتى السطر 70. في البداية تمّ تعريف المتغيّر freq في السطر 16 ليكون من النوع العمومي Dictionary، وهو عبارة عن بنية معطيات مشهورة تُعرف بالقاموس dictionary. سيحتوي هذا المتغيّر على جميع الأحرف المختلفة الموجودة ضمن النص المُدخَل بالإضافة إلى عدد مرّات التكرار لكلّ حرف. أمّا في السطر 17 فقد تمّ تعريف المتغيّر bars وهو من نوع القائمة List. سيحتوي هذا المتغيّر على جميع عناصر BoxView التي سننشئها لاحقًا من الشيفرة البرمجيّة. بعد ذلك نعرّف المتغيّر tapGesture وهو من النوع TapGestureRecognizer. سنتمكّن من خلال هذا المتغيّر أن نُكسِب لأي عنصر BoxView إمكانيّة أن ينقر عليه المستخدم ليعرض عند ذلك الرسالة التي تحدثنا عنها مسبقًا. أمّا في السطر 19 فقد عرّفنا المتغيّر maxFreq الذي سيحوي التكرار الأكبر من بين الحروف المختلفة.,> ,> نعمل في السطر 21 على إضافة معالج الحدث TapGesture_Tapped (مصرّح عنه في الأسطر من 72 حتى 75) لحدث النقر Tapped للكائن tapGesture. بعد أن نحصل على دخل المستخدم ونضعه ضمن المتغيّر s في السطر 23، سندخل حلقة foreach التكراريّة بحيث نقرأ محتويات النص s محرفًا تلو الآخر، ونختبر كون هذا المحرف هو حرف أو رقم في السطر 27 لأنّنا لا نريد قبول الفراغات على سبيل المثال. في السطر 29 سنختبر في حال كان هذا المحرف موجود فعلًا ضمن القاموس freq من قبل أم لا وذلك عن طريق التابع ContainsKey. فإن كان غير موجود من قبل، سنعمل على إضافته في السطر 31، أمّا إذا كان موجود من قبل فسنعمل على إضافة التكرار الخاص به بمقدار واحد وذلك في السطر 35: freq[t] = ++freq[t]; لاحظ أنّ التعبير freq[t] يسمح لنا بالوصول إلى قيمة المفتاح t الموجودة في القاموس freq. بعد ذلك سنمسح محتويات مخطّط الشبكة في السطر 40، ثم سننتقل إلى حلقة foreach أخرى (الأسطر من 43 حتى 49) تسمح لنا بحساب التكرار الأكبر من بين المحارف الموجودة ضمن القاموس freq. في السطر 52 سنقوم بإجراء عمليّة حسابيّة بسيطة، حيث سنعمل على حساب واحدة القياس الخاصّة بالأعمدة (عناصر BoxView)، وبشكل أدق، واحدة القياس الخاصّة بارتفاع كل عنصر BoxView. وذلك من خلال تقسيم ارتفاع مخطّط الشبكة grdLettersHistogram على التكرار الأكبر maxFreq الذي حسبناه قبل قليل. سنخزّن واحدة القياس هذه ضمن المتغيّر measureUnit كما هو واضح. سندخل أخيرًا ضمن حلقة foreach أخرى (الأسطر من 54 حتى 67) وظيفتها إنشاء عناصر BoxView كلّ منها يمثّل محرف مختلف موجود ضمن القاموس freq، وبارتفاع يتناسب مع تكرار هذا المحرف ضمن النص المُدخل. ستعمل هذه الحلقة على إضافة كل عنصر BoxView إلى القائمة bars (السطر 66). تجدر الملاحظة أنّنا في السطر 61 قد أسندنا نصًّا توضيحيًّا إلى الخاصيّة StyleId لعنصر BoxView. يحتوي هذا النص على الحرف وعلى تكراره ضمن النص المُدخَل. في الواقع يمكننا دومًا استخدام الخاصيّة StyleId مع أيّ عنصر يرث من الصنف View لمثل هذه الأغراض. في النهاية سنعمل على إضافة الأعمدة (عناصر BoxView) الموجودة ضمن القائمة bars دفعةً واحدةً إلى مخطّط الشبكة من خلال العبارة التالي الموجودة في السطر 69: grdLettersHistogram.Children.AddHorizontal(bars); يعمل التابع AddHorizontal من الخاصّية Children لمخطّط الشبكة على إضافة عنصر أو مجموعة من العناصر (كما في مثالنا) إلى مخطّط الشبكة على أن تكون على نفس السطر (أفقيًّا horizontal) وذلك دفعةً واحدةً. يوجد في الواقع تابع آخر مماثل لهذا التابع وهو التابع AddVertical، ولكنّه يضيف العناصر على نفس العمود. من الممكن تحديد السطر أو العمود المراد الإضافة إليه عن طريق توابع ساكنة static مثل Grid.SetRow و Grid.SetColumn ولكنّنا لن نحتاج إلى مثل هذه التوابع لأنّنا سنترك الإعدادات الافتراضيّة كما هي. بقي أن نتحدّث عن معالج حدث النقر TapGesture_Tapped. في الحقيقة الشيء الوحيد الذي يفعله هذا الحدث هو إظهار رسالة تنبيه للمستخدم (السطر 74) عند نقره لأحد الأعمدة ضمن المخطّط (عنصر BoxView) تحتوي هذه الرسالة على النص الذي مرّرناه مسبقًا لعنصر BoxView ضمن الخاصيّة StyleId له. انتقل الآن إلى الملف App.cs واحرص على أن تكون بانية الصنف App على الشكل التالي: public App() { // The root page of your application MainPage = new GridBarChartPage(); } نفّذ البرنامج وأجر بعض الإدخالات النصيّة، وراقب النتائج. حاول نقر بعض الأعمدة لتظهر لك رسالة المعلومات حوله. الخلاصة تناولنا في هذا الدرس تطبيقًا عمليًّا مفيدًا في حساب التكرارات للأحرف ضمن نص ما، ومن ثمّ عرضها بشكل مخطّط تكراري histogram ضمن مخطّط الشبكة. يوضّح لنا هذا التطبيق مدى القوّة والمرونة التي من الممكن الحصول عليها من استخدام مخطّط الشبكة، هذا بالإضافة إلى إمكانيّة المزج بين استخدام مخطّط الشبكة ومخطّط المكدّس بكلّ سهولة ويسر. هناك الكثير من التطبيقات التي يمكن استخدام مخطّط الشبكة معها. وسنعمل في الدروس القادمة على استخدامه بشكل متكرّر في التطبيقات التي نطوّرها.
  8. المخطّط المطلق Absolute Layout هو من المخطّطات المعتمدة في Xamarin، وتقوم فكرته على السماح للعناصر المرئيّة الموجودة ضمنه بأن يكون تموضعها بشكل مطلق، أي من الممكن ضبط مكان التموضع بالإضافة إلى عرض وارتفاع كل عنصر مرئي بشكل دقيق باستخدام الواحدات المستقلة عن الجهاز independent-device units. ولعلّ هذا المفهوم ليس جديدًا، بل هو من المفاهيم القديمة في عالم البرمجة، فقد كان المبرمجون قديمًا (وما زالوا في بعض الأحيان حتى اليوم) يعتمدون على بناء واجهات التطبيقات بتعريف موضع وأبعاد (العرض والارتفاع) كل عنصر بشكل دقيق ضمن الواجهة. سنطّلع في هذا الدرس ضمن سلسلة تعلّم برمجة تطبيقات أندرويد باستخدام Xamarin.Forms على كيفيّة التعامل مع هذا المخطّط باستخدام رماز XAML من خلال تطبيقين بسيطين. تطبيق المربّعات المتداخلة تعتمد فكرة هذا التطبيق على وجود 3 مربّعات متداخلة فيما بينها بحيث يمتلك كلّ منها شفافيّة بمقدار 50%. ستكون هذه المربّعات ضمن مخطّط مطلق Absolute Layout، وكلّ منها عبارة عن كائن ViewBox. سنعيّن مواقع وأبعاد هذه المربّعات باستخدام الواحدات المستقلة عن الجهاز independent-device units. أنشئ مشروعًا جديدًا من النوع Blank App (Xamarin.Forms Portable) وسمّه SimpleAbsoluteLayout، ثم أبق فقط على المشروعين SimpleAbsoluteLayout (Portable) و SimpleAbsoluteLayout.Droid كما وسبق أن فعلنا في هذا الدرس. بعد ذلك سنضيف صفحة محتوى تعتمد على رماز XAML كما وسبق أن فعلنا في هذا الدرس سنسمّيها SimpleAbsoluteLayoutPage. احرص على أن تكون محتويات هذه الصفحة على الشكل التالي: <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="SimpleAbsoluteLayout.SimpleAbsoluteLayoutPage"> <AbsoluteLayout x:Name="absLayout"> <BoxView x:Name="boxAccent" Color="Accent" AbsoluteLayout.LayoutBounds="25, 100, 110, 110" Opacity="0.5"/> <BoxView x:Name="boxRed" Color="Red" AbsoluteLayout.LayoutBounds="50, 125, 110, 110" Opacity="0.5"/> <BoxView x:Name="boxGreen" Color="Green" AbsoluteLayout.LayoutBounds="75, 150, 110, 110" Opacity="0.5"/> </AbsoluteLayout> </ContentPage> محتويات هذه الصفحة بسيطة وواضحة. حيث نعمل على إنشاء مخطّط مطلق باستخدام الوسم . لاحظ كيف وضعنا ضمن هذا العنصر ثلاثة عناصر من النوع BoxView. وقد عيّنّا لكل عنصر منها اسمًا لأنّنا سنحتاجه في القسم الثاني من هذا الدرس. الملفت للنظر أنّ هذه العناصر قد تمّ وضعها مباشرةً ضمن العنصر AbsoluteLayout دون الحاجة إلى وجود الوسم وهذا جائز تمامًا. لاحظ بالنسبة إلى عنصر BoxView الأوّل: <BoxView x:Name="boxAccent" Color="Accent" AbsoluteLayout.LayoutBounds="25, 100, 110, 110" Opacity="0.5"/> الخاصيّة التي تتحكّم بموضعه هي AbsoluteLayout.LayoutBounds حيث أسندنا إليها النص: "25, 100, 110, 110" الذي يحتوي على أربعة أرقام تفصل بينها فواصل، وهي من اليسار إلى اليمين: بعد الزاوية اليسرى العليا من العنصر عن الحافة اليسرى للمخطّط المطلق ورمزه X (القيمة 25). بعد الزاوية اليسرى العليا من العنصر عن الحافة العليا للمخطّط المطلق ورمزه Y (القيمة 100). عرض العنصر ورمزه Width (القيمة 110). ارتفاع العنصر ورمزه Height (القيمة 110). انظر الشكل التخطيطي التالي لمزيد من التوضيح: في الحقيقة يمكنك اعتبار الشكل السابق كجملة إحداثية ديكارتيّة، ولكنّ محور التراتيب y-axis موجّه نحو الأسفل كما هو واضح بدلًا من الاتجاه المألوف نحو الأعلى. ينطبق نفس الأمر تمامًا على عنصريّ BoxView الباقيين: <BoxView x:Name="boxRed" Color="Red" AbsoluteLayout.LayoutBounds="50, 125, 110, 110" Opacity="0.5"/> <BoxView x:Name="boxGreen" Color="Green" AbsoluteLayout.LayoutBounds="75, 150, 110, 110" Opacity="0.5"/> وبما أنّ لكل من هذه العناصر الثلاثة نفس القيمة للعرض وللارتفاع، إذًا فهي عبارة عن مربّعات، وهي طبوقة فيما بينها. إذا نظرت إلى الأرقام الخاصّة بالموضع وبالحجم التي اخترتها "للمربّعات" الثلاث، فستلاحظ أنّني اخترتها بعناية كي تكون متداخلة فيما بينها. وقد جعلت الشفافيّة (الخاصية Opacity) لكلّ منها 50% (القيمة 0.5) لكي تعطي ذلك التأثير اللوني الجميل عندما تتداخل مع بعضها. انتقل الآن إلى الملف App.cs واحرص على ان تكون بانية الصنف App على الشكل التالي: public App() { // The root page of your application MainPage = new SimpleAbsoluteLayoutPage(); } نفّذ التطبيق باستخدام F5، لتحصل على شكل شبيه بما يلي: لنحرّك المربّعات! لنعمل على إضفاء القليل من الإثارة على التطبيق السابق. سنعمل على تحريك المربّعات السابقة بحركة رأسيّة تلقائيّة. أي أنّ المربّعات السابقة ستتحرّك نحو الأعلى حتى تصل إلى الحافة العليا للشاشة، ثم تعكس اتجاه الحركة نحو الأسفل حتى تصل إلى الحافة السفلى من الشاشة. ثم تعود من جديد لتعكس جهة الحركة لتصبح نحو الأعلى، وهكذا دواليك. في الحقيقة لن يطرأ أيّ تغيير على رماز XAML، فكل ما سنفعله هو إضافة الشيفرة البرمجيّة المناسبة لملف الشيفرة البرمجيّة الموافق لملف الرماز السابق. انتقل الآن إلى الملف SimpleAbsoluteLayoutPage.xaml.cs واحرص على أن تكون محتوياته على الشكل التالي: 1 using System; 2 using Xamarin.Forms; 3 4 namespace SimpleAbsoluteLayout 5 { 6 public partial class SimpleAbsoluteLayoutPage : ContentPage 7 { 8 private bool isUpDirection; 9 public SimpleAbsoluteLayoutPage() 10 { 11 InitializeComponent(); 12 13 isUpDirection = false; 14 15 Device.StartTimer(TimeSpan.FromMilliseconds(50), UpdatePositions); 16 } 17 18 private bool UpdatePositions() 19 { 20 Rectangle rect; 21 22 rect = AbsoluteLayout.GetLayoutBounds(boxAccent); 23 if (rect.Y - 10 <= 0) 24 { 25 isUpDirection = false; 26 } 27 28 rect = AbsoluteLayout.GetLayoutBounds(boxGreen); 29 if (rect.Y + rect.Height + 10 > this.Height) 30 { 31 isUpDirection = true; 32 } 33 34 BoxView[] boxViews = { boxAccent, boxRed, boxGreen }; 35 36 for (int i = 0; i < boxViews.Length; i++) 37 { 38 rect = AbsoluteLayout.GetLayoutBounds(boxViews[i]); 39 40 if (isUpDirection) 41 { 42 rect = new Rectangle(rect.X, rect.Y - 10, rect.Width, rect.Height); 43 } 44 else 45 { 46 rect = new Rectangle(rect.X, rect.Y + 10, rect.Width, rect.Height); 47 } 48 49 AbsoluteLayout.SetLayoutBounds(boxViews[i], rect); 50 } 51 52 return true; 53 } 54 } 55 } بالإضافة إلى بانية الصنف SimpleAbsoluteLayoutPage أنشأت تابعًا جديدًا أسميته UpdatePositions يمكنك أن تجده في الأسطر من 18 حتى 53. يُعتبر هذا التابع مسؤولًا عن تحريك المربّعات الثلاثة بالحركة الرأسيّة التي تحدثنا عنها قبل قليل. يتمّ استدعاء هذا التابع بشكل تلقائيّ كل 50 ميللي ثانيّة. التقنيّة التي استخدمناها في هذا الاستدعاء هي نفسها الموجودة في هذا الدرس. حيث استخدمنا التابع StartTimer من الصنف Device لهذا الغرض (انظر السطر 15). لاحظ الحقل isUpDirection من النوع bool المصرّح عنه في السطر 8. يُفيد هذا الحقل في الاحتفاظ بالاتجاه الحالي للمربّعات أثناء الحركة (أي هل الحركة نحو الأعلى أم نحو الأسفل). يبدأ التابع UpdatePositions بالتصريح عن المتغيّر rect من النوع Rectangle (السطر 20). و Rectangle هو عبارة عن بنية struct أي هو من نوع قيمة value type. يُفيد هذا النوع في وصف الأشكال المستطيلة عمومًا. فهو يمتلك أربع خصائص تصف حجم وموضع أيّ مستطيل على الشاشة. هذه الخصائص هي: X و Y و Width و Height وهي تصف على الترتيب: مقدار الإزاحة الأفقيّة عن الحد الأيسر، مقدار الإزاحة الرأسيّة عن الحد الأعلى، وعرض المستطيل، وارتفاع المستطيل. وذلك بمنطق مشابه لما ورد ذكره في الشكل الأوّل الذي تعرضنا له في هذا الدرس. نقوم في السطر 22 بالحصول على المستطيل (المربّع في واقع الأمر) الحاضن للعنصر boxAccent وهو المربّع الأوّل. ثمّ نختبر كون الإحداثي Y له قد أوشك أن يصبح سالبًا وذلك في السطر 23. فإن أوشك على ذلك فهذا يعني أنّ المربّع boxAccent قد ارتطم بالحد الأعلى للشاشة ويجب تغيير اتجاه الحركة ليصبح نحو الأسفل بدلًا من الأعلى، وهذا ما نفعله في السطر 25. نفس المنطق السابق نطبّقه على العنصر boxGreen وهو المربّع الثالث، وذلك في السطر 28. حيث نحصل على المستطيل الحاضن للعنصر boxGreen. ثمّ نختبر كون الحافة السفلى له (تأمل التعبير rect.Y + rect.Height + 10) قد أوشكت أن تتجاوز قيمتها ارتفاع الصفحة (وهو يماثل في مثالنا الارتفاع الكامل للمخطّط المطلق) وذلك في السطر 29. فإن أوشكت على ذلك فهذا يعني أنّ المربّع boxGreen سيتجاوز الحد السفلي للشاشة ويجب تغيير اتجاه الحركة ليصبح نحو الأعلى بدلًا من الأسفل، وهذا ما نفعله في السطر 31. بعد ذلك ننشئ المصفوفة boxViews بحيث تحتوي على عناصر المربّعات الثلاثة boxAccent و boxRed و boxGreen وذلك في السطر 34. ثمّ يدخل البرنامج حلقة for (الأسطر من 36 حتى 50) التي تنحصر وظيفتها في تحديث إحداثيات المربّعات الثلاثة بحيث تحرّكها نحو الأعلى أو نحو الأسفل بحسب قيمة الحقل isUpDirection. سيكون مقدار الانتقال الرأسي 10 وحدات في كل مرّة كما هو واضح في السطرين 42 و 46. ويتم إسناد الإحداثيّات الجديدة للمربّعات الثلاثة عن طريق استدعاء التابع الساكن AbsoluteLayout.SetLayoutBounds في السطر 49. حيث يتطلّب هذا التابع وسيطين. الأوّل هو المربّع المراد تطبيق الإحداثيات الجديدة عليه وهو موجود ضمن المصفوفة boxView، والثاني هو قيمة من النوع Rectange تحوي المستطيل (المربّع في مثالنا هذا) الحاضن الجديد. الخلاصة تحدثنا في هذا الدرس عن المخطّط المطلق Absolute Layout وكيفيّة استخدامه من خلال رماز XAML. يفيد هذا المخطّط كما أشرنا في التحكّم بحجم وتموضع أي عنصر مرئي على الشاشة على نحو دقيق. تناولنا تطبيقين بسيطين يوضّحان كيفيّة التحكّم بحجم وموضع كل عنصر من عناصر BoxView، كما استخدمنا التابع StartTimer من الصنف Device لتوليد استدعاءات تلقائيّة ذات أزمنة منتظمة إلى تابع مخصّص كي يتحكّم بمواضع هذه العناصر. سنتناول في الدرس القادم تطبيقًا عمليًّا هو في واقع الأمر تحديث للتطبيق الثاني في هذا الدرس لكي نتحكّم في مواضع العناصر بشكل مخصّص أكثر.
×
×
  • أضف...