سنتناول في هذا الدرس تطبيقين عمليّين حول كيفيّة التعامل مع القياسات في Xamarin. لقد تناولنا مفهوم القياسات في Xamarin.Forms في الدرس السابق من هذه السلسلة، وأوضحنا أهميّة هذا المفهوم في التنسيق الصحيح لواجهة المستخدم عند استخدام شاشات متنوّعة لأجهزة أندرويد. سيكون التطبيق الأوّل بسيطًا للغاية، حيث ينحصر عمله في رسم مستطيل صغير بالأبعاد الحقيقيّة، وإظهاره بنفس المساحة على مختلف أنواع الشاشات. أمّا التطبيق الثاني فهو عبارة عن ساعة رقميّة بسيطة يتغيّر حجمها بحسب حجم الشاشة، وبحسب وضع الشاشة أيضًا (أفقي أم عمودي).
تطبيق رسم المستطيل
أنشئ تطبيقًا جديدًا سمّه RectangleApp من النوع Blank App (Xamarin.Forms Portable)، وكما جرت العادة أبقِ على المشروعين RectangleApp (Portable) وRectangleApp.Droid فقط. ثم أضف صفحة محتوى جديدة سمّها RectanglePage واحرص على أن تكون بانيتها على الشكل التالي:
public RectanglePage() { BoxView rectangle = new BoxView { HorizontalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center, BackgroundColor = Color.Aqua, WidthRequest = 1 * 64, HeightRequest = 3 * 64 }; Content = rectangle; }
الشيفرة البرمجيّة الموجودة في البانية RectanglePage
بسيطة. حيث ننشئ كائنًا جديدًا من الصنف BoxView وهو عنصر مرئي تنحصر وظيفته في إكساب ناحية جماليّة لواجهة المستخدم. ضبطنا الخاصيّتين HorizontalOptions
وVerticalOptions
ليظهر هذا العنصر في وسط الشاشة. كما أسندنا الخاصيّتين WidthRequest
وHeightRequest
له كي نعيّن العرض والارتفاع على الترتيب.
لاحظ هنا أنّه كان من الممكن أن يكون اسميّ كل من الخاصيتين السابقتين على الشكل Width
وHeight
فحسب. ولكن تمّ إضافة الكلمة Request
لكلٍّ منهما، لكي يوحي ذلك أنّ القيم التي نُسندها إليهما قد لا يتمّ الالتزام بها بشكل دقيق. لاحظ أنّنا نُسند القيمة 1 * 64 لخاصيّة WidthRequest
وهذا يعني بالطبع القيمة 64 بالواحدات المستقلّة عن الجهاز، أمّا الخاصيّة HeightRequest
فقد أسندنا إليها القيمة 3 * 64 وهذا يعطينا 192 وحدة مستقلة عن الجهاز. تذكّر معي من الدرس السابق أنّ كل 64 وحدة مستقلة عن الجهاز تعادل 1 سنتيمتر، إذًا نرغب بأن يكون عرض المستطيل 1 cm وطوله 3 cm.
انتقل إلى الملف App.cs
وتأكّد أنّ بانيته على الشكل التالي:
public App() { // The root page of your application MainPage = new RectanglePage(); }
نفّذ البرنامج باستخدام المفتاح F5 لتحصل على الشكل التالي:
حاول أن تستخدم مسطرة عاديّة وتقيس أبعاد هذا المستطيل على الشاشة مباشرةً، ستجد أنّ أبعاده قريبة من الأبعاد التي حدّدناها في الشيفرة (1 cm x 3 cm). إذا نفّذت هذا التطبيق على أيّ جهاز محمول ستحصل على نفس الشكل تقريبًا. إذًا استطعنا رسم نفس الشكل تقريبًا باستخدام الواحدات المستقلّة عن الجهاز وبصرف النظر عن دقّة الشاشة المستخدمة.
تطبيق الساعة الرقميّة
سيكون هذا التطبيق متقدّمًا أكثر ويحتوي على مفاهيم مفيدة في البرمجة بشكل عام. الغاية من هذا التطبيق هو إنشاء ساعة رقميّة ذات تصميم بسيط تعرض الوقت بشكل آني كلّ ثانية. الميزة فيها أنّها ستشغل كامل عرض الشاشة تقريبًا بصرف النظر عن الجهاز المستخدم أو وضع تدوير الشاشة (أفقي أم عمودي). أي ستكون الساعة مرنة لتتناسب مع أيّ شاشة يعمل عليها التطبيق.
أنشئ تطبيقًا جديدًا سمّه DigitalClockApp من النوع Blank App (Xamarin.Forms Portable)، وأبقِ على المشروعين DigitalClockApp (Portable) وDigitalClockApp.Droid فقط. ثم أضف صفحة محتوى جديدة سمّها DigitalClockPage. عدّل محتويات الملف DigitalClockPage.cs ليبدو على الشكل التالي:
1 using System; 2 using Xamarin.Forms; 3 4 namespace DigitalClock 5 { 6 public class DigitalClockPage : ContentPage 7 { 8 private Label lblClock; 9 10 public DigitalClockPage() 11 { 12 lblClock = new Label 13 { 14 HorizontalOptions = LayoutOptions.Center, 15 VerticalOptions = LayoutOptions.Center 16 }; 17 18 SizeChanged += DigitalClockPage_SizeChanged; 19 20 Device.StartTimer(TimeSpan.FromSeconds(1), OneSecondPassed); 21 22 Content = lblClock; 23 } 24 25 private void DigitalClockPage_SizeChanged(object sender, EventArgs e) 26 { 27 if (this.Width > 0) 28 { 29 lblClock.FontSize = this.Width / 10.0 * 2; 30 } 31 } 32 33 private bool OneSecondPassed() 34 { 35 lblClock.Text = DateTime.Now.ToString("h:mm:ss tt"); 36 37 return true; 38 } 39 } 40 }
انتقل إلى الملف App.cs واحرص على أن تكون بانية الصنف App.cs على الشكل التالي:
public App() { // The root page of your application MainPage = new DigitalClockPage(); }
نفّذ البرنامج باستخدام F5 ستحصل على شكل شبيه بما يلي:
لنبدأ الآن بمناقشة هذا التطبيق. صرّحنا في السطر 8 عن حقل خاص من نوع Label
لصيقة اسمه lblClock
، وقمنا في السطر 12 ضمن بانية الصنف DigitalClockPage
بإنشاء كائن جديد وأسندناه إلى هذا الحقل. سنستخدم في هذا التطبيق وللمرّة الأولى حدثين. الأوّل هو الحدث SizeChanged
للصفحة DigitalClockPage
والثاني هو الحدث الذي ينتج من استخدام التابع الساكن StartTimer
من الصنف Device
، وهو حدث يتمّ تفعيله بعد مرور فترة زمنيّة يمكن تحديدها سلفًا أي أنّه سيلعب دور مؤقّت زمني. بالنسبة للحدث SizeChanged
فقد عيّنا معالج حدث له في السطر 18. اسم معالج الحدث هو DigitalClockPage_SizeChanged
وقد صرّحنا عنه في الأسطر من 25 حتى 31. تحدثنا عن الحدث SizeChanged
في الدرس السابق، وكيف أنّه يُفعَّل كلّما تغيّر عرض أو ارتفاع العنصر المرئي المُسنَد إليه معالج الحدث. إذًا كلّما يتغيّر عرض أو ارتفاع الصفحة سيتم استدعاء معالج الحدث DigitalClockPage_SizeChanged
. يتم في البداية ضمن معالج الحدث هذا اختبار كون عرض الصفحة أكبر من الصفر أم لا وذلك في السطر 27، لأنّ عرض الصفحة من الممكن أن يحمل قيمة تمهيديّة سالبة (-1) وهي قيمة لا نرغب بالتعامل معها بالطبع. يتم بعد ذلك ضبط حجم النص المُستخدَم والملائم للشاشة التي يعمل عليها التطبيق، وإليك كيف يحدث ذلك.
بمعزل عن Xamarin، يقيس حجم النص font size عادةً المسافة الرأسيّة المشغولة بين خطّين وهميين، يقع الخط الأوّل فوق أعلى النص بقليل، أمّا الخط الثاني فيقع تحت أسفل النص بقليل. الواحدة المستخدمة مع حجم الخط هي النقطة Point. حيث يُعتبر أنّ كل بوصة تحتوي على 72 نقطة. فالخط الذي قياسه 72 مثلًا يعني بأنّ ارتفاع أي حرف ضمنه لن يتجاوز البوصة الواحدة. أمّا بالنسبة لعرض أي حرف، فهو يساوي تقريبًا نصف قياس الخط المستخدم. فالخط الذي قياسه 12 نقطة مثلًا، سيكون عرض أي حرف فيه يساوي 6 نقاط تقريبًا.
يبقى الكلام السابق صحيحًا تمامًا عند البرمجة في Xamarin. ففي تطبيقنا هذا، سيُعرَض على الشاشة 9 أو 10 محارف بحسب الساعة الحالية. تتضمّن أرقام الساعة وعلامتي (:) بالإضافة إلى فراغ واحد وحرف واحد (“م” أو “ص”) يُشير إلى المساء أو الصباح. سنعتبر عدد المحارف 10 وهكذا فعندما نقسّم عرض الصفحة Width على عدد المحارف، سينتج معنا عرض المحرف الواحد من عرض الصفحة. وبما أنّ قياس الخط هو ضعف عرض المحرف تقريبًا كما وسبق أن أشرنا قبل قليل، لذلك يؤدي ضرب عرض المحرف الواحد بالعدد 2 إلى إيجاد قيمة تقريبيّة ولكنّها مقبولة إلى حدٍّ كبير لحجم الخط مُقاسًا بالواحدات المستقلّة، وهذا ما تُعبّر عنه العبارة البرمجية في السطر 29. وهكذا يتم ضبط حجم الخط للساعة الرقميّة ليتلاءم تمامًا مع عرض الصفحة على أيّ جهاز محمول.
بالنسبة للحدث الذي يتمّ تفعليه كل ثانية، فنحصل عليه من استدعاء التابع الساكن StartTimer
من الصنف Device
كما أسلفنا. يتطلّب هذا التابع وسيطين الأوّل هو الفترة الزمنيّة اللازمة لكي يتم تفعيل هذا الحدث، وهذا الوسيط من النوع TimeSpan
وهي بنية struct
تمثّل فترة زمنيّة محدّدة. وبما أنّنا نريد أن يتم تفعيل الحدث كل ثانية، لذلك فاستخدامنا التابع الساكن FromSeconds
من البنية TimeSpan
على الشكل التالي:
TimeSpan.FromSeconds(1)
الذي يُرجع قيمة من البنية TimeSpan تمثّل ثانية واحدة فقط لأنّنا مرّرنا القيمة 1 لهذا التابع.
أمّا الوسيط الثاني فهو عبارة عن نائب delegate يُعبّر عن معالج هذا الحدث. هذا النائب هو من النوع Func وهو نوع نائب موجود ضمن نطاق الاسم System
، يُغلّف هذا النائب تابع لا يتطلّب أيّ وسيط، ولكن يُرجع قيمة من النوع bool
. وهذا ما يتحقّق بالنسبة لمعالج الحدث OnSecondPassed
الذي أنشأناه خصيصًا لهذا الغرض (الأسطر من 33 حتى 38). ومرّرنا اسمه كوسيط ثانٍ إلى التابع StartTimer
.
لاحظ أنّ معالج الحدث OnSecondPassed
يُرجع دومًا القيمة true (السطر 37) وهو شرط استمرار عمل المؤقّت الزمني. نلاحظ في السطر 35 كيف يتم إسناد النص التنسيقي الذي يمثّل الوقت الحالي إلى الخاصية Text من اللصيقة lblClock
. استخدمنا البنية DateTime
للحصول على الوقت الحالي من خلال الخاصيّة Now. ثمّ استدعينا التابع ToString
من Now
ومرّرنا له نصًّا تنسيقيًّا للحصول على الوقت الحالي بالشكل الذي يلائم تطبيقنا:enter code here
DateTime.Now.ToString(“h:mm:ss tt”);
إذا انتبهت إلى النص التنسيقي ستجد أنّنا قد كتبنا الحرف h الذي يُعبّر عن الساعة مرّة واحدة فقط، وسبب ذلك أنّني لا أريد من البرنامج أن يحجز خانتين في حال كانت الساعة قبل العاشرة (أي مكوّنة من خانة واحدة فقط). أمرٌ آخر، يُشير الحرفان tt
إلى الوقت الحالي هل هو صباحًا أم مساءً. يظهر من صورة التطبيق عندما نفّذته على جهازي أنّ هناك حرفًا واحدًا فقط يظهر وهو “م” أي مساءً (وربما يظهر “ص” صباحًا حسب وقت التجريب). سبب ظهور هذا الشكل من الأحرف هو الإعدادات الإقليميّة الموجودة على جهازي. من الممكن أن نستخدم في تطبيقنا الحرفين AM بدلًا من “ص” أو أن نستخدم الحرفين PM بدلًا من “م” ولكنّنا نحتاج إلى تغيير الإعدادات الإقليمية الافتراضيّة في هذه الحالة.
سنجري تعديلًا على تطبيقنا هذا بحيث يعتمد الحرفين AM (قبل الظهر – صباحًا) أو الحرفين PM (بعد الظهر – مساءً). أجرِ التعديل البسيط التالي على العبارة البرمجيّة الموجودة في السطر 35 لتصبح على الشكل التالي:
lblClock.Text = DateTime.Now.ToString("h:mm:ss tt", CultureInfo.InvariantCulture);
لقد مرّرنا وسيطًا إضافيًّا إلى التابع ToString
(يخضع لزيادة التحميل overloading
) يضمن بأن يستخدم هذا التابع الإعدادات الإقليميّة العامّة InvariantCulture
(تشبه تلك الخاصّة بالولايات المتحدّة) عند تنسيق النص. هناك أمرٌ صغير آخر، لاحظ أنّ تطبيقنا سيستخدم محرفًا إضافيًّا لعرض الساعة، لأنّ AM أو PM مكوّنة من حرفين، وليس من حرف واحد كما كان عليه الحال مع “م” أو “ص”. هذا بالإضافة إلى كون AM أو PM عبارة عن أحرف طباعيّة كبيرة لذلك ستأخذ عرضًا أكبر من باقي المحارف. لذلك لا مشكلة إذا اعتبرنا أنّه لدينا الآن 13 محرف كحد أقصى بدلًا من 10 كما كان عليه الحال في النسخة القديمة من هذا التطبيق، وهي في الحقيقة قيمة تجريبيّة حصلت عليها بالتجريب. لذلك سنجري تعديلًا بسيطًا آخر في العبارة البرمجيّة في السطر 29 حيث سنستبدل القيمة 13 بالقيمة القديمة 10 كما يلي:
lblClock.FontSize = this.Width / 13.0 * 2;
كما ستحتاج أيضًا إلى إضافة نطاق الاسم System.Globalization
في الأعلى. إذا جرّبت تنفيذ البرنامج الآن ستحصل على شكل شبيه بما يلي:
جرّب أن تدوّر الشاشة، سترى أن النص سينسجم تمامًا مع هذه الحالة، ويملأ كامل عرض الشاشة بشكل جميل.
الخلاصة
تناولنا في هذا الدرس تطبيقين عمليين يوضّحان كيفيّة التعامل مع واحدات القياس المستقلّة عن الجهاز independent-device units، كما تعرّفنا على مفاهيم برمجيّة جديدة تتمثّل في التعامل مع المؤقّت الزمني، وفي التعامل مع الإعدادات الإقليميّة. يُعتبر هذا الدرس تطبيقًا عمليًّا للدرس السابق يمكن اللجوء إليه لفهم أفضل حول هذا الموضوع.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.