تطبيقات عمليّة حول التعامل مع القياسات في Xamarin


حسام برهان

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

xamarin-008.png

تطبيق رسم المستطيل

أنشئ تطبيقًا جديدًا سمّه 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 لتحصل على الشكل التالي:

fig01.png

حاول أن تستخدم مسطرة عاديّة وتقيس أبعاد هذا المستطيل على الشاشة مباشرةً، ستجد أنّ أبعاده قريبة من الأبعاد التي حدّدناها في الشيفرة (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 ستحصل على شكل شبيه بما يلي:

fig02.png

لنبدأ الآن بمناقشة هذا التطبيق. صرّحنا في السطر 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 hereDateTime.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 في الأعلى. إذا جرّبت تنفيذ البرنامج الآن ستحصل على شكل شبيه بما يلي:

fig03.png

جرّب أن تدوّر الشاشة، سترى أن النص سينسجم تمامًا مع هذه الحالة، ويملأ كامل عرض الشاشة بشكل جميل.

الخلاصة

تناولنا في هذا الدرس تطبيقين عمليين يوضّحان كيفيّة التعامل مع واحدات القياس المستقلّة عن الجهاز independent-device units، كما تعرّفنا على مفاهيم برمجيّة جديدة تتمثّل في التعامل مع المؤقّت الزمني، وفي التعامل مع الإعدادات الإقليميّة. يُعتبر هذا الدرس تطبيقًا عمليًّا للدرس السابق يمكن اللجوء إليه لفهم أفضل حول هذا الموضوع.





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن