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

حسام برهان

الأعضاء
  • المساهمات

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

  • تاريخ آخر زيارة

  • عدد الأيام التي تصدر بها

    31

كل منشورات العضو حسام برهان

  1. مبدأ آخر من مبادئ التصميم الكائنيّ التوجّه ضمن مبادئ SOLID يُطلق عليه اسم مبدأ ليسكوف للاستبدال Liskov Substitution Principle ويُرمز له اختصارًا بالرمز LSP. سنبدأ هذا المبدأ بشيء من المفاهيم النظريّة. صاحبة هذا المبدأ هي البروفسور باربارا ليسكوف، وقد طرحته أوّل الأمر عام 1987 وكان ينص على ما يلي: قد يبدو الكلام السابق مبهمًا بعض الشيء، يمكننا توضيحه بالشكل التالي: "إذا استطعنا استبدال كل كائن O1 مكان كائن O2 فمن الممكن الجزم بأنّ S هو نوع فرعي (نوع ابن) للنوع T". ولكن في بعض الأحيان رغم أنّ S هو نوع فرعي للنوع T ولكن لا يمكن الاستبدال بين كائناتهما بالصورة الموضّحة قبل قليل وهذا بالطبع أمر غير جيّد. أعادت باربارا ليسكوف بالاشتراك مع جانيت وينغ Jeannette Wing صياغة المبدأ السابق بشكل مختصر أكثر في عام 1994 ليصبح على الشكل التالي: المقصود هنا أنّه إذا كانت خاصيّة أو دالّة (طريقة method) تعمل ضمن كائنات من النوع T، فينبغي أن تعمل أيضًا وبنفس الصورة ضمن كائنات من النوع S بحيث أنّ النوع S هو نوع ابن للنوع T. وهذا هو مبدأ ليسكوف للاستبدال الذي وصفه روبرت مارتن لاحقًا بصورة أكثر عمليّةً في إحدى مقالاته على الشكل التالي: بعد كلّ هذه المقدّمة النظريّة ثقيلة الظلّ بعض الشيء، ينبغي أن نُدعّم المفاهيم النظريّة السابقة ببعض الأمثلة التي توضّحها بالشكل المناسب. واحد من هذه الأمثلة التي تخرق هذا المبدأ هو تمثيل العلاقة بين المستطيل والمربّع بشكل كائنيّ. من البديهي تمامًا أن يكون لدينا صنف اسمه Square (مربّع) يكون صنفًا ابنًا للصنف Rectangle (مستطيل)، فمن الناحية الرياضيّة يُعتبر المربّع حالة خاصّة من المستطيل، فهو مستطيل تساوى فيه بعداه. لنعبّر في البداية عن الصنف Rectangle برمجيًّا بالشكل التالي: class Rectangle { int width; int height; public: int getWidth() { return width; } int getHeight() { return height; } virtual void setWidth(int value) { width = value; } virtual void setHeight(int value) { height = value; } }; وبما أنّ المربّع هو حالة خاصّة من المستطيل كما أسلفنا، فيمكن كتابة الصنف Square مع إعادة تعريف الطريقتين setWidth و setHeight: class Square : public Rectangle { public: void setWidth(int value) { width = value; height = value; } void setHeight(int value) { width = value; height = value; } }; سيضمن هذا التعديل على الطريقتين setWidth و setHeight ضمن الصنف Square أنّ أي كائن (مربّع) ننشئه من الصنف Square ستكون أضلاعه الأربعة متساوية الطول. لننظر الآن إلى الدّالة التالية التي سنستخدمها لتجريب البنية الكائنيّة السابقة: bool test(Rectangle &rectangle) { rectangle.setWidth(2); rectangle.setHeight(3); return rectangle.getWidth() * rectangle.getHeight() == 6; } تقبل الدّالة test تمرير كائن من النوع Rectangle أو كائن من النوع Square، وهذا جائز بالطبع لأنّ Square هو نوع ابن للنوع Rectangle. السؤال هنا هو ماذا سيحدث عند تمرير كائن من النوع Square إلى الدّالة test؟ ستُعيد الدّالة القيمة false رغم أنّ التقييم يجري على مرجع من النوع Rectangle (وسيط الدّالة). المشكلة هنا أنّه رغم أنّ المربّع هو مستطيل من الناحية الرياضيّة إلّا أنّه لا يتشارك السلوك نفسه معه. وهذا يُعتبر خرقًا واضحاً لمبدأ الاستبدال. فالكائنات من النوع Rectangle لا يمكن أن يتمّ استبدالها بكائنات من النوع Square رغم أنّ النوع Square هو نوع ابن للنوع Rectangle، لأنّ ذلك سيؤدّي إلى تغيّر في سلوك الطرائق الموجودة ضمن الصنف Rectangle كما هو واضح. لقد أوضح برتراند ماير هذه المسألة أيضًا على الشكل التالي: الإجراء من النص السابق قد يكون دالة أو طريقة method. الشروط البادئة هي الشروط التي يجب أن تتحقّق كي يتم استدعاء الطريقة وتنفيذها، أمّا الشروط اللّاحقة فهي محقّقة دومًا بعد انتهاء تنفيذ الطريقة. فالّذي قصده برتراند ماير هو أنّه عند إعادة كتابة تابع في صنف ابن يرث من صنف أب، يجب ألّا تكون الشروط البادئة أقوى من الشروط البادئة للطريقة الأصليّة (الموجودة في النوع الأب)، كما يجب ألّا تكون الشروط الّلاحقة أضعف من الشروط الّلاحقة للطريقة الأصليّة. في مسألتنا السابقة (مسألة المربّع والمستطيل)، لم تكن هناك أيّ شروط بادئة، ولكن كان هناك شرط لاحق للطريقة setHeight وهو أنّ هذه الطريقة يجب ألّا تُغيّر العرض width، وهذا الشرط تمّ خرقه (أصبح أضعف) عندما أعدنا تعريف الطريقة setHeight ضمن النوع Square. تُعتبر الوراثة في الحقيقة مفهومًا قويًّا ومحوريًّا في التصميم كائنيّ التوجّه، ولكن من السهل الوقوع في الأخطاء إذا لم نطبّق مبدأ ليسكوف للاستبدال، فقد تجد أنّ تصميمك الكائنيّ جيّد ومقنع، ولكن سرعان ما ستقع في المتاعب إذا لم تراعي هذا المبدأ. ترجمة -وبتصرّف- للمقال Liskov Substitution Principle لصاحبه Radek Pazdera.
  2. تعتبر تقنيّة Linq من التقنيّات الجديدة نسبيًّا في سي شارب ولغات دوت نت عمومًا. تسمح هذه التقنيّة بإجراء عمليّات استعلام معقّدة لاستخلاص البيانات بشكل سلس وسهل بسبب شكلها المألوف كما سنرى لاحقًا في هذا الدرس. لتقنيّة Linq أشكال متعدّدة: Linq to Objects: للتعامل مع البيانات الموجودة في ذاكرة البرنامج. Linq To XML: للتعامل مع البيانات النصيّة الموجودة بتنسيق XML. Linq To SQL: وهي تقنيّة خاصّة بالحصول على البيانات من خادم SQL Server. في الحقيقة تمّ التخلّي عن هذه التقنيّة رغم حداثتها، وذلك لصالح تقنيّة أحدث وأكثر تطوّرًا وهي Entity Framework. كما يتحدّث هذا الدرس عن تعابير Lambda وهي من المزايا المفيدة والتي تسهّل عمل المبرمجين إلى حدٍّ كبير. ستناول في هذا الدرس الشكل الأوّل من Linq، وهو استخدام Linq مع الكائنات Objects. ولكن قبل ذلك لنتحدّث قليلًا عن تعابير Lambda. تعابير Lambda تستطيع تخيّل تعابير Lambda على أنّها دوال functions صغيرة ليس لها اسم، تعمل على إجراء عمليّات حسابيّة بسيطة، ومن الممكن أن ترجع نتيجة. في الواقع يمكن للنوّاب Delegates أن تغلّف تعابير Lambda ضمن شروط محدّدة. سنتناول مثالًا بسيطًا يوضّح كيفيّة التعامل معها. انظر إلى الشيفرة التالية: class Program { delegate int Square(int x); static void Main(string[] args) { Square square = (x) => x * x; int result = square(5); } } صرّحنا في الشيفرة السابقة عن النائب Square الذي يتطلّب وسيطًا واحدًا من النوع int ويُرجع قيمة من نفس النوع. يُفترض بهذا النائب بأن يُغلّف التوابع التي تعمل على إيجاد مربّع عدد صحيح. إذا نظرت الآن إلى التابع Main ستجد أنّنا في السطر الأوّل منه نصرّح عن المتغيّر square من نوع النائب Square، حيث نُسند إليه ما يلي: (x) => x * x التعبير السابق هو تعبير Lambda بسبب وجود السهم <= ضمنه. فهم هذا التعبير بسيط، فهو يطلب وسيطًا وحيدًا (x) على يسار السهم، ويضرب قيمة هذا الوسيط بنفسها: x * x على يمين السهم، سيُرجع هذا التعبير قيمة x مضروبةً بنفسها. ولكن الملفت في الأمر أنّنا قد أسندنا هذا التعبير إلى متغيّر من نوع النائب Square. السبب في ذلك أنّ تعبير Lambda السابق يتوافق مع النائب Square في أنّه يحتاج إلى وسيط وحيد من النوع int ويُرجع قيمة من نفس النوع. ولكنّنا لم نوضّح في تعبير Lambda نوع الوسيط أو نوع القيمة المُعادة! لا مشكلة في ذلك، فسيتم استخلاص النوع من الوسيط المُمرّر وذلك بشكل تلقائي. كمثال آخر على استخدام تعبير Lambda يمكن كتابة تعبير يتطلّب وسيطين ولكن لا يُرجع أي قيمة. انظر إلى الشيفرة التالية: class Program { delegate void SumAndPrinting(int a, int b); static void Main(string[] args) { SumAndPrinting sumAndPrnt = (a, b) => Console.WriteLine(a + b); sumAndPrnt(3, 4); } } النائب SumAndPrinting يقبل الآن وسيطين من النوع int لكنّه لا يُرجع أي قيمة (void). انظر إلى تعبير Lambda كيف أصبح: (a, b) => Console.WriteLine(a + b) لاحظ كيف أنّ الوسائط الموجودة بين قوسين تفصل بينها فواصل عاديّة دون تحديد الأنواع. كما يمكنك أن تلاحظ أيضًا بأنّ هذا التعبير مهمّته جمع قيمتي الوسيطين والطبع إلى الشاشة دون إرجاع أيّ قيمة. ملاحظة: يمكننا الاستغناء عن القوسين في تعبير Lambda، إذا أردنا تمرير وسيط واحد فقط. مثل: x => x * x استعلاماتLinq للاستفادة من Linq يجب إضافة نطاق الاسم System.Linq باستخدام الكلمة المحجوزة using. أفضل وسيلة لفهم Linq هي من خلال مثال تطبيقي بسيط، في البرنامج Lesson15_01 سنستخدم الصنف Student الذي استخدمناه في درس سابق ولكن سنجري فيه بعض التعديلات البسيطة، حيث أصبحنا نفصل اسم الطالب FirstName عن كنيته LastName، بالإضافة إلى إضافة حقل جديد اسمه Id من النوع int، والذي يُعبّر عن رقم الطالب: class Student { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public int Mark { get; set; } } سننشئ 10 كائنات من الصنف Student ونخزّنها ضمن مجموعة عموميّة <List<Student ثم نُجري على هذه المجموعة بعض "الحيل" باستخدام Linq. الهدف من هذا البرنامج هو إجراء عمليّة تصفية على هؤلاء الطلّاب بحيث نحصل على الطلّاب الذين تكون درجاتهم أكبر تمامًا من 60. سيحتوي البرنامج Lesson15_01 على أفكار جديدة ولكن مفيدة فكن مستعدًّا: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 5 namespace Lesson15_01 6 { 7 class Student 8 { 9 public int Id { get; set; } 10 public string FirstName { get; set; } 11 public string LastName { get; set; } 12 public int Mark { get; set; } 13 } 14 15 class Program 16 { 17 static void Main(string[] args) 18 { 19 List<Student> studentsList = new List<Student>() 20 { 21 new Student {Id = 1, FirstName = "Ahmad", LastName = "Morad" , Mark = 80}, 22 new Student {Id = 2, FirstName = "Husam", LastName = "Sayed" , Mark = 75}, 23 new Student {Id = 3, FirstName = "Nour", LastName = "Hasan" , Mark = 65}, 24 new Student {Id = 4, FirstName = "Bssel", LastName = "Shamma" , Mark = 30}, 25 new Student {Id = 5, FirstName = "Ahmad", LastName = "Khatib" , Mark = 90}, 26 new Student {Id = 6, FirstName = "Maryam", LastName = "Burhan" , Mark = 95}, 27 new Student {Id = 7, FirstName = "Sarah", LastName = "Burhan" , Mark = 100}, 28 new Student {Id = 8, FirstName = "Mansour", LastName = "Khalid" , Mark = 50}, 29 new Student {Id = 9, FirstName = "Omran", LastName = "Barrak" , Mark = 45}, 30 new Student {Id = 10, FirstName = "Hasan", LastName = "Anis" , Mark = 56}, 31 }; 32 33 Console.WriteLine("Full List:"); 34 Console.WriteLine("----------"); 35 PrintList(studentsList); 36 37 38 IEnumerable<Student> students = from student in studentsList 39 where student.Mark > 60 40 select student; 41 42 Console.WriteLine(); 43 Console.WriteLine("After applying Linq:"); 44 Console.WriteLine("----------"); 45 PrintList(students); 46 } 47 48 private static void PrintList(IEnumerable<Student> students) 49 { 50 Console.WriteLine("{0,-5}{1,-15}{2,-15}{3,-10}", "Id", "First Name", "Last Name", "Mark"); 51 52 foreach (Student s in students) 53 { 54 Console.WriteLine("{0,-5}{1,-15}{2,-15}{3,-10}", s.Id, s.FirstName, s.LastName, s.Mark); 55 } 56 } 57 } 58 } نفّذ البرنامج السابق لتحصل على شكل شبيه بما يلي: لاحظ كيف استثنى البرنامج في القائمة الثانية الطلاب الذين تقل درجاتهم عن 60 أو تساويها. قارن بين القائمتين واكتشف العناصر المستثناة. يبدأ البرنامج في التابع Main بإنشاء 10 كائنات من النوع Student تمثّل بيانات عشرة طلّاب وإسناد هذه الكائنات فورًا إلى المجموعة القائمة studentsList بشكل مختصر (الأسطر من 19 إلى 31). الملفت هنا هو طريقة إنشاء كل من هذه الكائنات. انظر إلى السطر 21 مثلًا: new Student {Id = 1, FirstName = "Ahmad", LastName = "Morad" , Mark = 80} هذا شكل مختصر لإنشاء كائن من النوع Student حيث استخدمنا حاضنة {} بعد اسم الصنف Student مباشرةً وكتبنا أسماء الخصائص التي نريد تهيئتها ضمن هذه الحاضنة. بالنسبة لبانيّة الصنف Student فستُستدعى بكلّ تأكيد. يعتبر هذا الشكل من الإنشاء والإسناد المباشر للخصائص مفيدًا للغاية (أستخدمه بكثرة في برامجي الخاصّة) حيث يقلّل من أسطر الشيفرة البرمجيّة إلى حدٍّ كبير. سيطبع البرنامج بعد ذلك القائمة التي أنشأناها قبل قليل من باب التوضيح، وذلك من خلال استدعاء التابع الساكن PrintList (السطر 35) بالشكل التالي: PrintList(studentsList); مرّرنا لهذا التابع القائمة الكاملة studentsList. التصريح عن التابع الساكن موجود في الأسطر بين 48 و 56 وسنتكلّم عنه بعد قليل. يحتوي السطر 38 على عبارة برمجيّة تستخدم استعلام Linq: IEnumerable<Student> students = from student in studentsList where student.Mark > 60 select student; يقع استعلام Linq على يمين عامل الإسناد (=)، وفي الحقيقة إذا كان لديك اطّلاع على لغة SQL فسيكون هذا الاستعلام مألوفًا بالنسبة إليك. لنركّز الآن على هذا الاستعلام فحسب: from student in studentsList where student.Mark > 60 select student يبدأ الاستعلام بالكلمة المحجوزة from يتبعه اسم متغيّر جديد يمكنك تسميّته بأيّ اسم ترغبه. اخترت الاسم student لأنّني وجدتّه معبّرًا. بعد اسم المتغيّر الجديد نجد الكلمة المحجوزة in وبعدها اسم المجموعة التي نريد تطبيق الاستعلام عليها. إذًا أصبح بإمكاننا قراءة السطر الأوّل من الاستعلام على الشكل التالي: يبدأ السطر الثاني بالكلمة المحجوزة where وهي اختياريّة ومن الممكن عدم كتابتها، وهي تسمح بكتابة شرط من ممكن تطبيقه على عناصر المجموعة studentsList. بالنسبة لمثالنا هذا، اخترت تطبيق الشرط: where student.Mark > 60 أي أنّني أريد أن تكون درجة كل طالب (student) أكبر تمامًا من 60. أمّا السطر الثالث select student فهو يخبر Linq عن شكل البيانات التي نريد الحصول عليها بنتيجة تنفيذ الاستعلام. في مثالنا هذا نريد الحصول على مجموعة كل عنصر من عناصرها هو كائن من النوع Student. بنتيجة تنفيذ الاستعلام السابق سيحتوي المتغيّر students على مرجع لكائن مجموعة يحقّق الواجهة <IEnumerable<Student ولا يهمّك في الحقيقة ما هو النوع الفعليّ لهذه المجموعة. يمكن لاستعلام Linq أن يُنتج مرجعًا لكائن مجموعة يحقّق الواجهة <IQueryable<Student ولكنّ الحديث عن هذا الموضوع هو خارج مجال الدرس. في الواقع يمكن استخدام شروط أكثر تعقيدًا كأن نرغب بالحصول على جميع الطلّاب الذين تتراوح درجاتهم بين 60 و90 ضمنًا على سبيل المثال، وذلك باستخدام العامل && بالشكل التالي: from student in studentsList where student.Mark >= 60 && student.Mark <= 90 select student كما من الممكن أنّ نرتّب البيانات حسب رقم الطالب id، أو بحسب اسمه FirstName أو كنيته LastName أو بمزيج منها، وذلك باستخدام الكلمة المحجوزة orderby الخاصّة بـ Linq: from student in studentsList where student.Mark >= 60 && student.Mark <= 90 orderby student.FirstName, student.LastName select student سيقوم الاستعلام السابق بترتيب العناصر التي توافق الشرط الموجود في القسم where حسب الاسم ثمّ حسب الكنيّة. توجد في الحقيقة الكثير من المزايا القويّة التي تتمتّع بها استعلامات Linq والتي لا يتّسع هذا الدرس لذكرها. بالنسبة للتابع PrintList (الأسطر من 50 حتى 58) فيقتصر دوره على طباعة جدول للقائمة التي نمرّرها كوسيط إليه. لاحظ أنّ الوسيط الوحيد الذي يقبله يحقّق الواجهة <IEnumerable<Student لذلك فيمكننا تمرير أي وسيط إليه يحمل مرجعًا إلى كائن من أيّ صنف يحقّق هذه الواجهة بما فيه بالطبع الصنف <List<Student. الأمر الوحيد الجديد في هذا التابع هو استخدامه لتنسيق مختلف في إظهار البيانات بشكل جدوليّ. انظر السطر 52. ستجد النص التنسيقي: "{0,-5}{1,-15}{2,-15}{3,-10}" يسمح هذا النص التنسيقي بعرض البيانات بشكل جدوليّ أنيق على الشاشة، حيث يسمح التنسيق التالي {0, -5} بعرض الوسيط ذو الموقع 0 (من التابع WriteLine) ضمن حقل عرضه 5 فراغات بحيث تكون المحاذاة نحو اليسار. أمّا التنسيق {1, -15} فيسمح بعرض الوسيط ذو الموقع 1 ضمن حقل عرضه 15 فراغ بحيث تكون المحاذاة نحو اليسار أيضًا. بإزالة إشارة السالب (-) من التنسيقين السابقين ستصبح المحاذاة نحو اليمين. هل تريد المزيد من الإثارة؟ أضف السطر التالي إلى السطر 43 من البرنامج السابق (أي بعد العبارة التي تستخدم استعلام Linq): double average = students.Average(s => s.Mark); تستخدم هذه العبارة التابع Average من المتغيّر students الذي يحتوي على قائمة الطلّاب بعد التصفيّة كما نعلم. وكما يُوحي اسمه يعمل هذا التابع على حساب معدّل الطلاب (كائنات Student) الموجودين ضمن students. ولكن كيف سيعرف التابع Average الحقل الذي سيتمّ بموجبه حساب المعدّل؟ يتمثّل الحل في استخدام تعبير Lambda يتطلّب وسيطًا واحدًا (الوسيط s) الذي سيمثّل كائن Student، ويُرجع قيمة الخاصيّة Mark له: s => s.Mark سيستخدم التابع هذا التعبير للمرور على جميع العناصر الموجودة ضمن المجموعة students ليحصل على درجة كلّ منها باستخدام تعبير Lambda السابق ثمّ يحسب المعدّل، ليعمل البرنامج على إسناده إلى المتغيّر average وهو من النوع double كما هو واضح. بعد تنفيذ العبارة السابقة وعلى فرض أنّ نسخة البرنامج Lesson15_01 الأساسيّة هي التي استُخدمت، ستكون قيمة average تساوي 77.5، ويمثّل هذا الرقم معدّل الطلاب الذين تكون درجاتهم أكبر تمامًا من 60. قد يبدو كلّ ما قدّمناه جميلًا وممتعًا، لكنّك لن تستمتع بشكل فعليّ بهذه المزايا الرائعة التي توفّرها Linq و تعابير Lambda ما لم تستخدمها في برامجك الخاصّة. أستخدم مثل هذه المزايا في برامجي التي أطوّرها، ولن تتصوّر مدى سعادتي عندما أستخلص بيانات من قاعدة بيانات أو من خدمة ويب web service قد تحتوي على المئات أو الآلاف من البيانات الخام، ثمّ أُطبّق عليها وصفات Linq السحريّة فأحصل على ما أريده بكتابة عبارة برمجيّة واحدة فقط! تمارين داعمة تمرين 1 أجرِ تعديلًا على البرنامج Lesson15_01 بحيث نحصل على جميع الطلّاب الذين تكون درجاتهم أقل تمامًا من 50. تمرين 2 أجرِ تعديلًا آخرًا على البرنامج Lesson15_01 بحيث نحصل على جميع الطلّاب الذين يكون الحرف الأوّل من اسمهم هو "A". (تلميح: أحد الحلول المقترحة هو استخدام التابع StartWith من الخاصيّة النصية FirstName للكائن student أي على الشكل التالي: student.FirstName.StartsWith("A") وذلك بعد الكلمة where في استعلام Linq ). الخلاصة تعرّفنا في هذا الدرس على تعابير Lambda واستعلامات Linq. حيث صمّمنا عدّة برامج توضّح هاتين التقنيّتين المهمّتين في البرمجة باستخدام سي شارب. ستصادف كلًّا منهما كثيرًا في حياتك البرمجيّة، وستكون سعيدًا باستخدامها نظرًا للاختصار الكبير الذي ستحصل عليه في الشيفرة البرمجيّة، هذا فضلًا عن الأداء عالي المستوى الذي لن تستطيع مجاراته باستخدامك للشيفرة التقليديّة.
  3. لدينا كما تعلم نوعين أساسيين من المطوّرين في تطوير تطبيقات الويب: 1- مطوّر تطبيقات خلفيّة back-end developer وهذا النوع من المطوّرين يهتم بتطبيقات تعمل على الخادوم Server مثل ASP.NET MVC (تستخدم لغة سي شارب)، أو لغة PHP، أو Ruby on Rails وغيرها. 2- مطوّر تطبيقات أماميّة front-end developer وهذا النوع من المطوّرين يهتم بالتطبيقات التي تعمل ضمن متصفّح الويب. ولعلّ أكثرها شهرةً تلك المكتوبة بلغة JavaScript، بالإضافة إلى اهتمامهم بطبيعة الحال ب HTML و CSS. وهناك بكل تأكيد مجال التصميم الذي يعمل به مصمّمون ربما لا يعرفون شيئًا عن البرمجة. نصيحتي إليك: في الحقيقة ليس لديك تصنيف محدّد الآن، ولكم أعتقد أنّك ربما ستميل لأن تكون مطور تطبيقات خلفيّة back-end developer والتي من الممكن أن تستخدم تقنية ASP.NET MVC لإنشاء مثل هذه التطبيقات. وإذا أحببت يمكنك تعلّم PHP بالطبع. أرجو أن تنتبه إلى أنّك لست بحاجة في الوقت الحالي أن تعرف كيف تصمّم موقع انترنت، وفي الحقيقة على الغالب لن تحتاج لذلك أبدًا. لأنّه توجد قوالب وتصاميم جاهزة يمكنك شراؤها والاستفادة منها. ستحتاج لأن تتخصّص بشيء واحد فقط، مع الإلمام ببعض الأمور من باقي الاختصاصات. فإن كنت مبرمج ASP.NET MVC أو PHP مثلًا، فعليك أن تعرف كيفية التعامل مع JavaScript و HTML و CSS بالحدود الدنيا على الأقل. توجد سلسلة دروس ممتازة على موقع PluralSight ولكنها ليست مجانية مع الأسف. انظر إلى الدرس الأوّل: https://app.pluralsight.com/library/courses/full-stack-dot-net-developer-fundamentals/table-of-contents المؤلّف: Mosh Hamedani
  4. تؤدّي الشيفرة البرمجيّة السابقة إلى الحصول على جميع الكائنات (السجلات) التي يكون السعر Price في كلّ منها أصغر من أو يساوي القيمة p. تفيد مثل هذه العبارة في عمليّة فلترة مجموعة من البيانات حسب شرط أو شروط محدّدة. أنصحك بقراءة المزيد حول تعابير lambda من هذا الرابط على افتراض أنّك تستخدمين لغة سي شارب: https://msdn.microsoft.com/en-us/library/bb397687.aspx
  5. سنتحدث في هذا الدرس من سلسلة برمجة تطبيقات الأجهزة المحمولة باستخدام Xamarin عن موضوع مهم يسبّب في الكثير من الأحيان بعض الارتباك لمبرمجيّ تطبيقات الأجهزة المحمولة، وهو موضوع التعامل مع القياسات والحجوم على الشاشة. سنبدأ أولًا بلمحة تاريخيّة سريعة، ننتقل من خلالها إلى فهم واحدات القياسات المستخدمة في Xamarin.Forms. لمحة تاريخيّة يتكوّن أيّ جهاز عرض من مصفوفة مستطيلة من البيكسل Pixels. فأيّ جسم يُعرض على الشاشة سيكون له مساحة تُقدّر بالبيكسل. تعامل المبرمجون منذ البداية مع البيكسل كواحدة قياس معتمدة لرسم الأجسام والأشكال المختلفة على الشاشة. وعلى الرغم من أنّه لا ينبغي على المبرمج عادةً أن يعتمد على قياسات ثابتة للعناصر المرئيّة في التطبيقات التي يكتبها، إلّا أنّه في كثير من الأحيان يكون من الضروري القيام بذلك حسب متطلّبات التطبيق. فمع اختلاف أحجام الشاشات واختلاف كثافة البيكسل لكل شاشة نظرًا للتقدّم التقنيّ الذي شهده هذا المجال، أصبح أن يكون للتطبيق نفس الشكل تقريبًا على مختلف أنواع الشاشات أمرًا فيه تحدّ كبير للمبرمجين. لشاشات سطح المكتب طيف واسع من قياسات البيكسل، من القياس القديم 640x480 (أي 640 بيكسل للعرض، و480 بيكسل للطول) حتى بلغت هذه الأيّام بضعة آلاف لكل بُعد. كما تتمتّع الشاشات أيضًا بقياس فيزيائي يُقدّر عادةً بالبوصة Inch، وهو المسافة القُطريّة لمستطيل الشاشة. فمن خلال قياس البيكسل لأيّ شاشة والقياس الفيزيائي لها، نستطيع حساب دقّة الشاشة resolution أو ما يُعرف بكثافة البيكسل في كلّ بوصة PPI وغالبًا ما يسمّى بعدد النقاط في كلّ بوصة DPI (اختصار لـ Dots Per Inch). فمثلًا من أجل شاشة قديمة لها قياس بكسل 800x600، يمكننا ببساطة حسب نظرية فيثاغورث في المثلث القائم، أن نستنتج أنّ قطر هذه الشاشة يساوي 1000 بيكسل. حيث استخدمنا العلاقة التالية في حساب القطر: فإذا كان لهذه الشاشة القياس الفيزيائي 13 بوصة مثلًا فعندها يمكننا بسهولة أن نحسب كثافة البيكسل في البوصة أو DPI لها بالعلاقة البسيطة التالية: بالمقابل ومن أجل نفس هذه الشاشة ذات 13 بوصة، يمكننا أن نجد في هذه الأيّام قياسات بيكسل مثل 2560x1600 وهذا ما يعطي DPI لهذه الشاشة يُقدّر بـ 230 تقريبًا. فهذا يعني أنّ أيّ جسم على الشاشة الجديدة، ولنقل أنّ مساحته 100 بيكسل مربّع مثلًا، سيشغل ثلث المساحة الظاهرية التي يشغلها الجسم نفسه ولكن على الشاشة القديمة، مما سيؤدّي بالطبع إلى اختلاف كبير في أشكال التطبيقات على الشاشات المختلفة. بدأت تظهر حلول عمليّة في الواقع مع تطوّر أنظمة التشغيل الخاصّة بالحواسيب المكتبيّة، والتي تمّ تكييفها لاحقًا مع الأجهزة المحمولة. حيث عملت شركات مثل مايكروسوفت وآبل على ابتكار أنظمة قياس تعتمد على واحدات لا تتعلّق بالجهاز (الشاشة) device-independent units وذلك بدلًا من البيكسل. حيث يقع على عاتق نظام التشغيل أن يحوّل هذه القياسات المصمّمة بنظام القياس الجديد إلى قياسات بيكسل مناسبة. وهكذا يستطيع المبرمجون كتابة تطبيقات تعتمد نظام قياس مستقل، بحيث تظهر تطبيقاتهم بقياس موحّد تقريبًا على مختلف أنواع الشاشات. سلكت غوغل في أندرويد نفس السلوك لضمان أن تظهر الأشكال ذات القياسات المحدّدة بشكل موحّد على جميع شاشات أجهزة أندرويد المتنوّعة أصلًا. الحل الذي توفّره Xamarin.Forms وفّرت Xamarin.Forms حلّا جيّدًا لهذه المسألة من خلال الفرضيّة البسيطة التالية: كل 160 وحدة قياس (مستقلة عن الجهاز) تقابل بوصة واحدة. وهذا ما يُعادل 64 وحدة قياس لكل سنتيمتر. والفرضيّة السابقة صالحة من أجل أي تطبيق يعمل على أندرويد أو iOS أو Windows Phone. أيّ عنصر مرئي يظهر على الشاشة يرث من الصنف VisualElement. يمتلك هذا الصنف خاصيّتين مفيدتين: Width وHeight. تُعبّر هاتين الخاصيتين عن عرض Width وارتفاع Height أي عنصر مرئي على الشاشة بواحدات مستقلة عن الجهاز كما اتفقنا. تكون القيمة الابتدائيّة لكل منهما -1 في البداية، وتعطينا قيمًا صحيحة فقط عندما يُجهَّز التخطيط الذي سيظهر على الشاشة ويأخذ كلّ عنصر مكانه. يُعرّف الصنف VisualElement أيضًا حدثّا اسمه SizeChanged والذي يحدث عندما تتغيّر قيمة إحدى الخاصيتين Width أو Height. قد تتغيّر قيمتي هاتين الخاصيتين لأسباب متنوّعة منها تدوير الشاشة مثلًا. من الممكن تثبيت معالج للحدث SizeChanged من أجل أي عنصر مرئي يظهر على الصفحة بما فيها الصفحة نفسها. سنتناول في الفقرة التالية برنامجًا بسيطًا يُظهر قياس الشاشة التي يعمل عليها ولكن بالواحدات المستقلّة عن الجهاز. برنامج الحصول على قياس الشاشة أنشئ تطبيقًا جديدًا من النوع Blank App (Xamarin.Forms Portable) وسمّه GetSize. وكما اتفقنا احذف جميع المشاريع باستثناء المشروعين GetSize (Portable) وGetSize.Droid. أضف صفحة محتوى جديدة ContentPage إلى المشروع GetSize (Portable) وسمّها GetSizePage واحرص على أن يكون الصنف GetSizePage على الشكل التالي: 1 1 public class GetSizePage : ContentPage 2 { 3 private Label label; 4 5 public GetSizePage() 6 { 7 this.label = new Label 8 { 9 FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)), 10 HorizontalOptions = LayoutOptions.Center, 11 VerticalOptions = LayoutOptions.Center, 12 }; 13 14 Content = this.label; 15 16 SizeChanged += GetSizePage_SizeChanged; 17 } 18 19 private void GetSizePage_SizeChanged(object sender, EventArgs e) 20 { 21 label.Text = String.Format("{0} \u00D7 {1}", this.Width, this.Height); 22 } 23 } لاحظ في البداية أنّنا صرّحنا عن الحقل الخاص label وهو من النوع Label في السطر 3، حيث سنُسند إليه مرجع للصيقة جديدة سننشئها لاحقًا في بانية الصنف GetSizePage في السطر 7. سبب إنشاء هذا الحقل، هو الحاجة للوصول إلى اللصيقة ضمن معالج الحدث كما سنرى بعد قليل. أسندنا خياري التموضع الأفقي HorizontalOptions والتموضع الرأسي VerticalOptions في السطرين 10 و11 على الترتيب لهذه اللصيقة الجديدة لتظهر في وسط الصفحة. لاحظ أنّنا لا نستخدم مخطّط مكدّس في هذا البرنامج، بل نُسند اللصيقة مباشرةً إلى الخاصيّة Content للصفحة في السطر 14. الأمر الجديد بالنسبة إلينا في هذه السلسلة هو إسناد معالج حدث event handler للحدث SizeChanged للصفحة في السطر 16. ومعالج الحدث الذي أسميته GetPageSize_SizeChanged مصرّح عنه في الأسطر من 19 حتى 22. إذا نظرت إلى السطر 21 ستجد أنّنا نولّد نصًّا تنسيقيًّا لعرضه ضمن اللصيقة (نُسنده إلى الخاصيّة Text لها). لاحظ الرمز \u00D7 الذي يُستخدم لتوليد إشارة الضربxكما سنرى بعد قليل. كما تجدر الملاحظة أنّنا نقرأ خاصيّتي العرض Width والارتفاع Height للصفحة وندرجها ضمن النص التنسيقي المولّد. انتقل إلى بانية الصنف App واحرص على أن تكون كما يلي: public App() { // The root page of your application MainPage = new GetSizePage(); } نفّذ البرنامج باستخدام F5 لتحصل على شكل شبيه بما يلي: أود أن أؤكّد على أنّ القياس الذي تراه في الشكل السابق هو بالواحدات المستقلّة عن الجهاز وليس بالبيكسل. وهو لا يتضمّن شريط الحالة status bar العلويّ، ولا يتضمّن أيضًا المساحة المخصّصة لظهور الأزرار في الأسفل (في حال كان الجهاز يدعم ذلك). إذا حاولت تدوير الشاشة 90 درجة. ستحصل على قياس مختلف: ملاحظة لقد نفّذت البرنامج السابق على جهاز Samsung Galaxy Core Prime. الخلاصة يُعتبر هذا الدرس أساسيًّا لفهم كيفيّة التعامل مع القياسات في تطبيقات Xamarin.Forms حيث تحدثنا عن القياسات بمفهوميها القديم والحديث. وسبل التعامل مع الواحدات المستقلّة عن الجهاز device-independent units ودورها الأساسيّ في توحيد قياسات العناصر المرئيّة ذات الأبعاد الثابتة على مختلف أنواع الشاشات. سنتناول في الدرس القادم أمثلة عمليّة مفيدة حول استخدام الواحدات المستقلّة عن الجهاز في كتابة تطبيقين بسيطين. يتناول التطبيق الأوّل رسم شكل بسيط ذو مساحة محدّدة على الشاشة، في حين يتناول التطبيق الثاني ساعة رقميّة بسيطة توائم النص المعروض بحسب حجم الشاشة التي يعمل عليها التطبيق.
  6. تحدثنا في الدرس السابق من هذه السلسلة عن المبادئ الأساسيّة للتعامل مع مخطّط المكدّس StackLayout. سنتابع في هذا الدرس العمل معه، حيث سنقدّم مفاهيم أساسيّة لتموضع العناصر ضمن مخطّط المكدّس. سننشئ تطبيق بسيط يمكننا من خلاله فهم خيارات التموضع الرأسيّة والأفقيّة للعناصر ضمن مخطّط مكدّس StackLayout. أنشئ تطبيقًا جديدًا (كما تعلّمنا في الدروس السابقة) وسمّه LabelPositionsApp. وأبق فقط على المشروعين LabelPositionsApp (Portable) و LabelPositionsApp.Droid ضمنه. فهم خيارات التموضع الرأسيّة انقر بزر الفأرة الأيمن على المشروع LabelPositionsApp (Portable) وأضف صفحة محتوى ContentPage جديدة (كما تعلّمنا من الدرس السابق) سمّها VerticalOptionsPage. سننشئ ضمن بانية الصنف VerticalOptionsPage مخطّط مكدّس وسنضيف إليه ثلاث لُصيقات. ستبدو هذه البانية على الشكل التالي: public VerticalOptionsPage() { Content = new StackLayout { Children = { new Label { Text = "LayoutOptoins.Start", VerticalOptions = LayoutOptions.Start, BackgroundColor = Color.Accent, TextColor = Color.Black }, new Label { Text = "LayoutOptoins.Center", VerticalOptions = LayoutOptions.Center, BackgroundColor = Color.Aqua, TextColor = Color.Black }, new Label { Text = "LayoutOptoins.End", VerticalOptions = LayoutOptions.End, BackgroundColor = Color.Yellow, TextColor = Color.Black } } }; } لاحظ أنّنا قد استخدمنها الإسناد المباشر للخصائص أثناء إنشاء كائنات اللُصيقات. استخدمنا هذه المرّة الخاصيّة VerticalOptions لكل لصيقة أنشأناها. تُحدّد هذه الخاصيّة التموضع الرأسي لكل لصيقة ضمن مخطّط المكدّس. هذه الخاصيّة هي من نوع البنية LayoutOptions التي تحتوي على 8 حقول ساكنة static تُعبّر عن جميع خيارات التموضع الممكنة للعناصر المرئيّة. يتناول تطبيقنا البسيط السابق ثلاثةً من هذه الحقول وهي: LayoutOptions.Start للصيقة الأولى، وLayoutOptions.Center للصيقة الثانية، وLayoutOptions.End للصيقة الثالثة كما هو واضح من الشيفرة البرمجيّة. بالنسبة للخاصيتين BackgroundColor (لون الخلفية) وTextColor (لون النص) فهما موجودتان من باب تمييز اللُصيقات على الشاشة فحسب. هناك أمرٌ آخر، وهو أنّ مخطّط المكدّس في الشيفرة السابقة سيُعتبر رأسيًّا بشكل افتراضي، وذلك لأنّ خاصية الاتجاه Orientation له تحمل القيمة StackOrientation.Vertical بشكل افتراضيّ. انتقل إلى الملف App.cs واحرص على أن تكون بانيته على الشكل التالي: public App() { // The root page of your application MainPage = new VerticalOptionsPage(); } شغّل التطبيق باستخدام F5 لتحصل على شكل شبيه بما يلي: أضف الصورة fig01 كما وسبق أن أوضحنا في درس سابق أنّنا عندما نُسند القيمة LayoutOptions.Start للخاصية VerticalOptions للصيقة فإنّها ستظهر أعلى الشاشة، أمّا عند إسناد القيمة LayoutOptions.Center لهذه الخاصية فستظهر اللصيقة في المنطقة الوسط للشاشة، أمّا القيمة LayoutOptions.End فستودّي إلى إظهار اللصيقة في المنطقة السفلية للشاشة، والمسافات الفارغة بين اللُصيقات السابقة هي نتيجة القيمة الافتراضيّة للخاصية Spacing لمخطّط المكدّس. ولكن هذا لا يحدث تمامًا بالنسبة لتطبيقنا السابق، فكلّ اللُصيقات تظهر كما لو أنّها تقع في جهة واحدة من الشاشة، وكلّها ذات ارتفاع ثابت، وواضح أيضًا أنّ هناك مساحة فارغة تمامًا غير مستخدمة (مساحة حرّة) تقع في الأسفل. سنتعلّم كيفيّة التحكم بمواضع بهذه اللُصيقات بصورة أكبر، ولكن من أجل هذا التطبيق علينا أن نفهم الفرق بين Start وCenter وEnd. لنجري الآن بعض التعديلات البسيطة على تطبيقنا السابق. استبدل محتويات بانية الصنف VerticalOptionsPage بالشيفرة التالية: public VerticalOptionsPage() { Content = new StackLayout { Children = { new Label { Text = "LayoutOptoins.StartAndExpand", VerticalOptions = LayoutOptions.StartAndExpand, BackgroundColor = Color.Accent, TextColor = Color.Black }, new Label { Text = "LayoutOptoins.Center", VerticalOptions = LayoutOptions.Center, BackgroundColor = Color.Aqua, TextColor = Color.Black }, new Label { Text = "LayoutOptoins.End", VerticalOptions = LayoutOptions.End, BackgroundColor = Color.Yellow, TextColor = Color.Black } } }; } أجريت تعديلًا بسيطًا على اللصيقة الأولى، حيث أسندت القيمة LayoutOptions.StartAndExpands للخاصية VerticalOptions لها. عندما تنفّذ التطبيق هذه المرّة ستحصل على الشكل التالي: أضف الصورة fig02 القيمة StartAndExpand تعني ببساطة: تموضع أولًا ثم تمدّد. ستتموضع اللصيقة الأولى أوّل الشاشة كما حدث مع التطبيق السابق، ولكنّها ستشغل المساحة الحرّة (الفارغة) التي كانت موجودة مسبقًا، أي أنّ التّمدّد يكون على هذه المساحة الحرّة في حين أنّ النص الخاص باللصيقة سيكون بالأعلى (بسبب وجود Start). ودليل ذلك أنّ اللصيقتين التاليتين قد ظهرتا أسفل هذه المساحة الفارغة والتي أصبحت مشغولة الآن من قِبَل اللصيقة الأولى، ولو لم يصبح لون الخلفيّة لهذه المساحة الحرّة مماثلًا للون الخلفية للصيقة الأولى. لكي نفهم الموضوع بشكل أفضل عدّل الشيفرة الموجودة ضمن بانية الصنف VerticalOptionsPage لتحمل اللصيقة الثانية القيمة LayoutOptions.CenterAndExpand للخاصية VerticalOptions. في حين تحمل اللصيقتين الأولى والثالثة القيمتين Start وEnd على الترتيب (عدّل النص الذي سيظهر على اللُصيقات للتوضيح). لتحصل على الشكل التالي: أضف الصورة fig03 انظر كيف شغلت اللصيقة الثانية المساحة الحرّة بالكامل هذه المرّة، في حين أنّ النص الخاص باللصيقة سيكون هذه المرّة بالوسط (بسبب وجود Center). ولعلّك تستطيع الآن تخمين الشكل الذي ستحصل عليه في حال أسندت القيمة LayoutOptions.EndAndExpand للخاصية VerticalOptions للصيقة الثالثة وأعدت اللصيقتين الأولى والثانية إلى القيمتين Start وCenter على الترتيب. ستحصل على شكل شبيه بما يلي: أضف الصورة fig04 أصبحت اللصيقة الثالثة تشغل المساحة الحرّة بالكامل، ويظهر نصّها في الأسفل (بسبب وجود End). وهكذا نكون قد ناقشنا ثلاث حالات يكون في كلّ منها إحدى اللُصيقات فقط هي من تحمل ميزة التّمدّد Expand. ولكن ماذا لو حملت لصيقتين أو أكثر ميزة التّمدّد Expand بنفس الوقت؟ الجواب بسيط، ستتقاسم هذه اللُصيقات المساحة الحرّة فيما بينها وتتموضع حسب قيم Start وCenter وEnd الخاصّة بها. عدّل بانية الصنف VerticalOptionsPage لتحتوي على الشيفرة البرمجيّة التالية: public VerticalOptionsPage() { Content = new StackLayout { Children = { new Label { Text = "LayoutOptoins.StartAndExpand", VerticalOptions = LayoutOptions.StartAndExpand, BackgroundColor = Color.Accent, TextColor = Color.Black }, new Label { Text = "LayoutOptoins.CenterAndExpand", VerticalOptions = LayoutOptions.CenterAndExpand, BackgroundColor = Color.Aqua, TextColor = Color.Black }, new Label { Text = "LayoutOptoins.EndAndExpand", VerticalOptions = LayoutOptions.EndAndExpand, BackgroundColor = Color.Yellow, TextColor = Color.Black } } }; } نفّذ البرنامج لتحصل على الشكل الجميل التالي: أضف الصورة fig05 ستقسّم المساحة الحرّة الآن بين اللُصيقات الثلاث بالتساوي، ويتموضع النص في كل لصيقة بحسب القيم Start وCenter وEnd لكلّ منها. بقيت حالتان لم نناقشهما بعد، وهما Fill وFillAndExpand. ليس للقيمة LayoutOptions.Fill في الواقع أيّ دور عند إسنادها للخاصيّة VerticalOptions في حال كان الاتجاه Orientation لمخطّط المكدّس رأسيًا (كما في الأمثلة التي تناولناها حتى الآن)، في حين تعمل القيمة LayoutOptionsFillAndExpand بنفس الأسلوب ولكن بإشغال المساحة الحرّة المتاحة بحيث يمتد لون الخلفيّة في هذه المرّة ليشمل جميع المساحة المشغولة. انظر إلى الأشكال الأربعة التالية التي أعددتها للمقارنة، والتي توضّح تأثير وضع مزيج من قيم LayoutOptions معًا في نفس التطبيق السابق: أضف الصورة fig06 الفرق الوحيد بين الشكلين السابقين هو في قيمة الخاصية VerticalOptions للصيقة الثالثة في كل منهما. ففي الشكل A تكون هذه القيمة Fill لذلك فهي لا تملك أيّ تأثير كما أسلفنا، في حين أنّه في الشكل B تصبح FillAndExpand لذلك تملأ هذه اللصيقة كامل المساحة الفارغة المتبقيّة. لننتقل الآن إلى المجموعة الثانية: أضف الصورة fig07 في الشكل C تقتسم اللُصيقات الثلاث المساحة الحرّة فيما بينها لأنّ كلّا منهما يحمل الميزة Expand. أمّا في الشكل D فيتكرّر نفس الأمر مع ملاحظة أنّ اللصيقة الثانية أصبحت FillAndExpand والثالثة EndAndExpand، وهذا كلّ شيء. ملاحظة من الواضح أنّ النص يظهر دومًا في أعلى اللصيقة التي تحمل خاصيّتها VerticalOptions القيمة FillAndExpand. يعود سبب ذلك إلى خاصيّة أخرى في اللصيقة وهي التي تتحكّم بمحاذاة النص رأسيًّا في هذه الحالة. اسم هذه الخاصيّة VerticalTextAlignment وتقبل قيمًا من المعدودة TextAlignment. لا تعمل هذه الخاصيّة في حال كان اتجاه مخطّط المكدّس رأسيًّا، إلّا إذا كانت اللصيقة FillAndExpand. فهم خيارات التموضع الأفقيّة إذا كانت خيارات التموضع الرأسيّة واضحة بالنسبة إليك، فستكون خيارات التموضع الأفقيّة سهلة الفهم وبسيطة للغاية. فكل الكلام السابق لخيارات التموضع الرأسية سيبقى صحيحًا ولكن بالشكل المناسب لخيارات التموضع الأفقيّة. أضف صفحة محتوى ContentPage جديدة للمشروع LabelPositionsApp (Portable) وسمّها HorizontalOptionsPage. سننشئ ضمن بانية الصنف HorizontalOptionsPage مخطّط مكدّس وسنضيف إليه أيضًا ثلاث لُصيقات. ستبدو هذه البانية على الشكل التالي: public HorizontalOptionsPage() { Content = new StackLayout { Orientation = StackOrientation.Horizontal, Children = { new Label { Text = "Start", HorizontalOptions = LayoutOptions.Start, BackgroundColor = Color.Accent, TextColor = Color.Black }, new Label { Text = "Center", HorizontalOptions = LayoutOptions.Center, BackgroundColor = Color.Aqua, TextColor = Color.Black }, new Label { Text = "End", HorizontalOptions = LayoutOptions.End, BackgroundColor = Color.Yellow, TextColor = Color.Black } } }; } لاحظ كيف أسندنا القيمة StackOrientation.Horizontal للخاصيّة Orientation لمخطّط المكدّس لكي يُصبح اتجاهه أفقيًّا. ولاحظ أيضًاً أنّنا نستخدم في هذه المرّة الخاصية HorizontalOptions (بدلًا من VerticalOptions) لكل لصيقة. عند تنفيذ البرنامج ستحصل على شكل شبيه بما يلي: أضف الصورة fig08 لاحظ هنا أنّ عرض كل لصيقة يتناسب مع طول النص الموجود فيها، وهذا أمر لم نكن نصادفه في مخطّط المكدّس الرأسيّ. كما تجدر ملاحظة أنّني قد استخدمت في هذا التطبيق الأسماء Start وCenter وEnd لعرضها ضمن محتويات اللُصيقات (ضمن الخاصيّة Text لها) بدون اسم البنية LayoutOptoins طلبًا للاختصار. سنجري الآن بعض المقارنات بإسناد مزيج من خيارات LayoutOptions عندما يكون مخطّط المكدّس أفقيًّا. يمكنك التعديل على الشيفرة البرمجيّة للبرنامج السابق بحسب ما تراه ضمن الأشكال التالية لتحصل على نفس النتيجة. أضف الصورة fig09 أضف الصورة fig10 ملاحظة تحدثنا مسبقًا عن محاذاة النص ضمن اللصيقة. في الحقيقة توجد قاعدة مهمّة ولكنّها بسيطة في طريقة التعامل مع محاذاة النصوص ضمن اللُصيقات. تنص هذه القاعدة على أنّه إذا كان مخطّط المكدّس رأسيًّا عندها لن تعمل إلّا الخاصيّة HorizontalTextAlignment لمحاذاة النص أفقيًّا. أمّا إذا كان المكدّس أفقيًّا فلن تعمل عندها إلّا الخاصيّة VerticalTextAlignment لمحاذاة النص رأسيًّا أي دومًا بعكس اتجاه مخطّط المكدّس. ويُستثنى من هذه القاعدة حالتان: الحالة الأولى إذا كان مخطط المكدّس رأسيًّا وكانت اللصيقة FillAndExpand فعندها يمكن استخدام الخاصيّة VerticalTextAlignment معها. الحالة الثانية إذا كان مخطط المكدّس أفقيًّا وكانت اللصيقة FillAndExpand فعندها يمكن استخدام الخاصيّة HorizontalTextAlignment معها. الخلاصة تعلّمنا في هذا الدرس كيفيّة التعامل المتقدّم مع مخطّط المكدّس، حيث تناولنا كيفيّة تموضع اللُصيقات ضمن مخطّط المكدّس سواءً كانت اتجاهه رأسيًّا أم أفقيًّا. في الحقيقة، يمكن تطبيق المعلومات الواردة هنا على أيّ عنصر مرئي تقريبًا، وليس فقط على اللُصيقات. أنصحك بأن تجري الكثير من التجارب على هذا الموضوع، وأن تضع أي استفسار قد يصادفك كسؤال ضمن التعليقات على هذا الدرس، لكي يتسنّى للجميع المشاركة فيه. .~lock.06-xamarin-stacklayout-part02.docx#
  7. تعاملنا في الدروس السابقة مع عنصر واحد فقط يظهر على الشاشة. كان هذا العنصر عبارة عن لصيقة Label نُسندها إلى الخاصيّة Content لصفحة محتوى content page. في البرامج الحقيقية كما هو معروف، سنحتاج بالتأكيد إلى العديد من العناصر التي ستظهر على الشاشة لتلبية متطلّبات البرنامج. المشكلة التي تصادفنا هنا أنّ الخاصية Content لصفحة المحتوى لا تقبل سوى عنصر واحد يرث من الصنف View. أوجدت Xamarin حلًا بسيطًا لهذه المشكلة يتمثّل في استخدام المخطّطات layouts. يمكن لأيّ مخطّط أن يستوعب أي عدد من العناصر التي يرث كل منها من الصنف View. في الحقيقة يُعتبر المخطّط بحدّ ذاته يرث من الصنف Layout<View> وهذا الصنف بدوره يرث بشكل غير مباشر من الصنف View. لذلك يمكن إسناد أيّ مخطط للخاصية Content مباشرةً. تدعم Xamarin أربعة أنواع من المخطّطات وهي: المخطّط المطلق AbsoluteLayout مخطّط الشبكة GridLayout المخطّط النسبي RelativeLayout مخطّط المكدّس StackLayout. سنتناول ثلاثة مخطّطات في هذه السلسلة وهي: المطلق والشبكي والمكدّس. وسنبدأ في هذا المقال بمخطّط المكدّس StackLayout. مخطّط المكدّس StackLayout يمكن إضافة أي عدد من العناصر إلى هذا المخطّط. وسبب تسميته بهذا الاسم هو أنّه يرتّب العناصر المضافة إليه بشكل متكدّس stacked. يمتلك الصنف StackLayout خاصيتين إضافيتين عن باقي المخطّطات هما: Orientation وتُعبّر عن الاتجاه (أفقي أو رأسي) وSpacing وتمثّل التباعد بين العناصر المُضافة إلى المخطّط ولها القيمة الافتراضيّة 6.0. لنتناول الآن مثال بسيط يوضّح آلية التعامل مع هذا المخطّط المفيد. تطبيق ColorsApp لعرض بعض الألوان أنشئ تطبيقًا جديدًا كما تعلّمنا من الدروس السابقة بحيث يكون من النوع Blank App (Xamarin.Forms Portable) وسمّه ColorsApp. بعد إنشاء التطبيق في Visual Studio أبق فقط على المشروعين ColorsApp (Portable) و ColorsApp.Droid. أضف إلى المشروع ColorsApp (Portable) صفحة محتوى سمّها ColorsListPage (انقر بزر الفأرة الأيمن على المشروع واختر Add ثم اختر New Item. وبعد ظهور النافذة، اختر من الجهة اليسرى Cross-Platform، ومن وسط الشاشة اختر Forms ContentPage). بعد إنشاء هذه الصفحة سنلاحظ أنّها تحتوي بالفعل على مخطّط مكدّس جاهز. انظر إلى محتويات هذه الصفحة: using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using System.Text; using Xamarin.Forms; namespace ColorsApp { public class ColorsListPage : ContentPage { public ColorsListPage() { Content = new StackLayout { Children = { new Label { Text = "Hello ContentPage" } }, }; } } } لاحظ كيف أنّنا نُسند كائنًا جديدًا من الصنف StackLayout إلى الخاصيّة Content ضمن البانية ColorsListPage. لاحظ أيضّا كيف أنّ المخطّط StackLayout يحتوي على خاصيّة اسمها Children تُعبّر عن العناصر الأبناء (كلّ منها يرث من الصنف View) التي نرغب بإضافتها إلى المكدّس. استبدل محتويات الملف ColorsListPage.cs بالشيفرة التالية: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Reflection.Emit; 5 using System.Text; 6 7 using Xamarin.Forms; 8 9 namespace ColorsApp 10 { 11 public class ColorInfo 12 { 13 public Color Color { get; set; } 14 public string Name { get; set; } 15 } 16 17 public class ColorsListPage : ContentPage 18 { 19 public ColorsListPage() 20 { 21 ColorInfo[] colors = new ColorInfo[] 22 { 23 new ColorInfo {Color= Color.Aqua,Name = "Aqua" }, 24 new ColorInfo {Color=Color.Blue,Name ="Blue" }, 25 new ColorInfo {Color=Color.Gray,Name ="Gray" }, 26 new ColorInfo {Color=Color.Black,Name ="Black" }, 27 new ColorInfo {Color=Color.Silver,Name ="Silver" }, 28 new ColorInfo {Color=Color.Red,Name ="Red" }, 29 new ColorInfo {Color=Color.Maroon,Name ="Maroon" }, 30 new ColorInfo {Color=Color.Yellow,Name ="Yellow" }, 31 new ColorInfo {Color=Color.Olive,Name ="Olive" }, 32 new ColorInfo {Color=Color.Lime,Name ="Lime" }, 33 new ColorInfo {Color=Color.Green,Name ="Green" }, 34 new ColorInfo {Color=Color.Navy,Name ="Navy" }, 35 new ColorInfo {Color=Color.Teal,Name ="Teal" }, 36 new ColorInfo {Color=Color.Pink,Name ="Pink" }, 37 new ColorInfo {Color=Color.Fuchsia,Name ="Fuchsia" }, 38 new ColorInfo {Color=Color.Purple,Name ="Purple" } 39 }; 40 41 StackLayout stackLayout = new StackLayout(); 42 43 for (int i = 0; i < colors.Length; i++) 44 { 45 stackLayout.Children.Add(new Label 46 { 47 Text = colors[i].Name, 48 TextColor = colors[i].Color, 49 FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)) 50 }); 51 } 52 53 Padding = new Thickness(5, 5, 5, 5); 54 55 Content = stackLayout; 56 } 57 } 58 } لقد أضفنا صنفًا بسيطًا جديدًا اسمه ColorInfo إلى هذا الملف (الأسطر من 11 حتى 15). ورغم أنّ وجود صنفين في نفس الملف هو عادة برمجيّة سيئة، إلّا أنّني آثرت ذلك طلبًا للتبسيط. تنحصر وظيفة هذا الصنف في الاحتفاظ بمعلومات بسيطة عن أيّ: قيمة اللون Color واسمه Name. أنشأنا في السطر 21 مصفوفة من الصنف ColorInfo وأسندناها إلى المتغيّر colors. ستحتوي هذه المصفوفة كما هو واضح على 16 لون. ننشئ بعد ذلك كائن جديد من الصنف StackLayout ونسنده إلى المتغيّر stackLayout (السطر 41)، ثم ندخل حلقة for (السطر 43) وظيفتها المرور على عناصر المصفوفة colors، وبحيث تُنشئ في كل دورة لصيقة Label جديدة وتُعيّن لها النص ولونه وحجمه، وتضيف هذه اللصيقة مباشرةً إلى مخطّط المكدّس stackLayout عن طريق التابع Add للخاصيّة Children منه. بعد ذلك نُحدّد مقدار الحشوة padding للصفحة (السطر 53) وفي النهاية نُسند المتغيّر stackLayout إلى الخاصية Content للصفحة (السطر 55). لتجربة هذا التطبيق، انتقل أولًا إلى الملف App.cs واحرص أن تكون بانية الصنف App على الشكل التالي: public App() { // The root page of your application MainPage = new ColorsListPage(); } نفّذ البرنامج باستخدام F5 لتحصل على شكل شبيه بما يلي: نلاحظ هنا أمرين مهمّين. الأوّل أنّ بعض الألوان لا تظهر بشكل جيّد على الخلفية السوداء (وهي الخلفية الافتراضية لتطبيقات Xamarin في أندرويد) لأنّ التباين اللوني ليس جيّدًا، بل إنّ الكلمة Black لن تظهر لأنّ لونها يماثل لون الخلفية الأسود. أمّا الأمر الثاني، فلدينا بالأساس 16 لون إلّا أنّه ظهر 12 لون منها فقط. من الممكن أن يختلف عدد الألوان الظاهرة عندك بحسب الشاشة التي تستخدمها، فقد تظهر جميعها مثلًا، ولكن على كلّ الأحوال ستواجه بالتأكيد تطبيقات في المستقبل لن تظهر فيها جميع العناصر دفعةً واحدة على الشاشة. في الواقع نحتاج إلى آلية لتمرير المحتويات scrolling مثل أيّ تطبيق أندرويد آخر. سنعالج الآن كلًّا من الأمرين السابقين. تطبيق ColorsApp المحسّن سنعالج في البداية مسألة التباين اللوني التي من الممكن حلّها بإضافة تابع جديد إلى الصنف ColorsListPage واسمه GetSuitableBackground. تنحصر وظيفة هذا التابع بحساب مقدار الإنارة luminance وفق ثوابت عدديّة متعارف عليها على أنّها مثالية. فإذا كانت الإنارة للّون المراد إظهاره أكبر من 0.5 فستكون الخلفية سوداء، أمّا إذا كانت أقل من أو تساوي 0.5 فستكون الخلفية بيضاء. مما يمنح تباينًا مناسبًا لعرض الألوان على الشاشة. انظر الشيفرة البرمجيّة الخاصّة بهذا التابع: private Color GetSuitableBackground(Color color) { double luminance = 0.30 * color.R + 0.59 * color.G + 0.11 * color.B; return luminance > 0.5 ? Color.Black : Color.White; } تُعبّر الخصائص color.R و color.G و color.B عن المكوّنات الأحمر والأخضر والأزرق على الترتيب للّون الممرّر لهذا التابع والمراد إيجاد لون الخلفية المناسب له. أمّا الثوابت 0.30 و 0.59 و 0.11 فهي ثوابت تُعتبر مثالية لإيجاد إنارة اللون ويمكنك تغييرها إذا أحببت. أمّا بالنسبة لمسألة تمرير محتويات الشاشة فحلّها بسيط، ويتمثّل في استخدام الصنف ScrollView حيث نُنشئ كائن جديد منه، ونُسند المتغيّر stackLayout إلى الخاصيّة Content من ذلك الكائن، ثم نُسند ذلك الكائن الجديد إلى الخاصيّة Content لصفحة المحتوى ColorsListPage بدلًا من إسناد stackLayout مباشرةً إليها كما كنّا نفعل في البرنامج السابق. انظر إلى الشيفرة التالية: Content = new ScrollView { Content = stackLayout }; انظر الآن إلى الشيفرة الكاملة بعد إجراء التعديلات السابقة عليها: using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using System.Text; using Xamarin.Forms; namespace ColorsApp { public class ColorInfo { public Color Color { get; set; } public string Name { get; set; } } public class ColorsListPage : ContentPage { public ColorsListPage() { ColorInfo[] colors = new ColorInfo[] { new ColorInfo {Color= Color.Aqua,Name = "Aqua" }, new ColorInfo {Color=Color.Blue,Name ="Blue" }, new ColorInfo {Color=Color.Gray,Name ="Gray" }, new ColorInfo {Color=Color.Black,Name ="Black" }, new ColorInfo {Color=Color.Silver,Name ="Silver" }, new ColorInfo {Color=Color.Red,Name ="Red" }, new ColorInfo {Color=Color.Maroon,Name ="Maroon" }, new ColorInfo {Color=Color.Yellow,Name ="Yellow" }, new ColorInfo {Color=Color.Olive,Name ="Olive" }, new ColorInfo {Color=Color.Lime,Name ="Lime" }, new ColorInfo {Color=Color.Green,Name ="Green" }, new ColorInfo {Color=Color.Navy,Name ="Navy" }, new ColorInfo {Color=Color.Teal,Name ="Teal" }, new ColorInfo {Color=Color.Pink,Name ="Pink" }, new ColorInfo {Color=Color.Fuchsia,Name ="Fuchsia" }, new ColorInfo {Color=Color.Purple,Name ="Purple" } }; StackLayout stackLayout = new StackLayout(); for (int i = 0; i < colors.Length; i++) { stackLayout.Children.Add(new Label { Text = colors[i].Name, TextColor = colors[i].Color, BackgroundColor = GetSuitableBackground(colors[i].Color), FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)) }); } Padding = new Thickness(5, 5, 5, 5); Content = new ScrollView { Content = stackLayout }; } private Color GetSuitableBackground(Color color) { double luminance = 0.30 * color.R + 0.59 * color.G + 0.11 * color.B; return luminance > 0.5 ? Color.Black : Color.White; } } } تأمّل الشيفرة السابقة، وتأكّد من فهمها بشكل جيّد. ثم نفّذ البرنامج باستخدام F5 لتحصل على شكل شبيه بما يلي، علمًا أنّني قد استخدمت ميزة التمرير الجديدة لإظهار بقية العناصر التي لم تكن تظهر معنا من قبل: ستلاحظ أنّ كلّ لون يظهر في هذه القائمة، له لون خلفيّة مناسب له لإظهاره بالشكل الأمثل. ملاحظة يمكنك استخدام الخاصيّة Spacing لمخطّط المكدّس لزيادة أو إنقاص مقدار التباعد بين اللصائق labels التي تظهر على الشاشة. للخاصيّة Spacing القيمة الافتراضيّة 6.0 كما أسلفنا، جرّب أن تعيّن القيمة 0 لها مثلًا وانظر على ماذا ستحصل. يمكنك تعيين هذه الخاصيّة بالنسبة للبرنامج السابق بأن تضيف السطر التالي: stackLayout.Spacing = 0; بعد السطر التالي مباشرةً: StackLayout stackLayout = new StackLayout(); الخلاصة تعرّفنا في هذا الدرس على مبادئ التعامل مع مخطّط المكدّس StackLayout الذي يُستخدَم في تطبيقات Xamarin على نحو واسع لترتيب العناصر المرئية على الشاشة بشكل متكدّس. تعرّفنا أيضًا على كيفيّة إكساب مخطّط المكدّس ميزة التمرير لكي نستطيع عرض العناصر التي لا تظهر على الشاشة. سنتابع عملنا مع مخطّط المكدّس في الدرس التالي، حيث سنتحدّث عن المزيد من المزايا المهمّة التي يتمتّع بها هذا المخطّط.
  8. سؤالك عام جدًا أخي. هل من الممكن أن تكون أكثر تحديدًا من فضلك؟
  9. عندما تعمل على حاسوبك الشخصي، أو حتى على هاتفك الذكيّ، أو ربّما ساعتك الذكيّة. فأنت تستخدم الأحداث آلاف المرّات. الحدث هو وسيلة جميلة للتعبير عن أمر طارئ حدث لكائن برمجيّ. قد يكون هذا الأمر الطارئ عبارة عن نقرة زر فأرة، أو عن لمسة على شاشة جهازك الذكي. أو ضغطة مفتاح من لوحة المفاتيح الخاصّة بي وأنا أكتب هذا المقال، أو أن يكون أمرًا طارئًا يُعبّر عن حالة داخليّة ضمن نظام التشغيل. باختصار، هناك عدد كبير جدًّا من المصادر المختلفة أو المحتملة للأحداث. العلاقة بين الأحداث Events والنوّاب Delegates تعتمد الأحداث بشكل كليّ على النوّاب. وفي الحقيقة هي وسيلة لتهذيبها! لنستعير الصنف Car من البرنامج Lesson13_02 من الدرس السابق: 1 public class Car 2 { 3 public delegate void SpeedNotificatoinDelegate(string message); 4 5 private SpeedNotificatoinDelegate speedNotificationHandler; 6 7 public void RegisterWithSpeedNotification(SpeedNotificatoinDelegate handler) 8 { 9 this.speedNotificationHandler = handler; 10 } 11 12 public int CurrentSpeed { get; set; } 13 public int MaxSpeed { get; set; } 14 15 public Car() 16 { 17 CurrentSpeed = 0; 18 MaxSpeed = 100; 19 } 20 21 public Car(int maxSpeed, int currentSpeed) 22 { 23 CurrentSpeed = currentSpeed; 24 MaxSpeed = maxSpeed; 25 } 26 27 public void Accelerate(int delta) 28 { 29 CurrentSpeed += delta; 30 31 if (CurrentSpeed > MaxSpeed) 32 { 33 if(this.speedNotificationHandler != null) 34 { 35 string msg = string.Format("You exceed the maximum speed! (Current = {0}, Max = {1})", 36 CurrentSpeed, MaxSpeed); 37 38 speedNotificationHandler(msg); 39 } 40 } 41 } 42 } كنت قد طلبت منك في التمرين الداعم رقم 2 من الدرس السابق أن تجري تعديلًا على الصنف Car بحيث تستغني عن التابع RegisterWithSpeedNotification، ولمّحت بأن تجعل الحقل speedNotificationHandler ذي محدّد وصول public. بعد إجراء هذا التعديل سيصبح الصنف Car على الشكل التالي: public class Car { public delegate void SpeedNotificatoinDelegate(string message); public SpeedNotificatoinDelegate speedNotificationHandler; public int CurrentSpeed { get; set; } public int MaxSpeed { get; set; } public Car() { CurrentSpeed = 0; MaxSpeed = 100; } public Car(int maxSpeed, int currentSpeed) { CurrentSpeed = currentSpeed; MaxSpeed = maxSpeed; } public void Accelerate(int delta) { CurrentSpeed += delta; if (CurrentSpeed > MaxSpeed) { if (this.speedNotificationHandler != null) { string msg = string.Format("You exceed the maximum speed! (Current = {0}, Max = {1})", CurrentSpeed, MaxSpeed); speedNotificationHandler(msg); } } } } سيعمل هذا الصنف بشكل ممتاز، حيث من الممكن أن نُسند نائبًا للحقل speedNotificationHandler من خارج الصنف Car بالشكل التالي: Car car = new Car(100, 0); car.speedNotificationHandler = new Car.SpeedNotificatoinDelegate(OnExceedMaxSpeedHandler); المشكلة هنا أنّ الحقل speedNotificationHandler أصبح مكشوفًا تمامًا، حتى أنّه من الممكن استدعاء التابع الذي يغلّفه هذا النائب، من خارج الصنف Car، وهذا يعدّ خرقًا لمبدأ مهم في البرمجة كائنيّة التوجّه ألا وهو التغليف Encapsulation. لا ينبغي أن يتمكّن أيّ أحدٍ من استدعاء التابع الذي يغلّفه الحقل السابق إلّا من داخل الصنف Car حصرًا، لأنّ النائب المُسند لذلك الحقل يُعبّر عن حالة داخليّة ضمن الصنف Car وهي تجاوز السرعة القصوى. توفّر لنا سي شارب حلًا عمليًّا وأنيقًا لهذه المشكلة تتمثّل في استخدام الأحداث. بالنسبة للصنف Car السابق (بعد التعديل) يكفيك أن تَسِم الحقل speedNotificationHandler بالكلمة المحجوزة event ليتحوّل إلى حدث لا يمكن استدعاؤه إلّا من داخل الصنف Car. لكن سيكون هناك اختلاف صغير في طريقة إسناد النوّاب إلى هذا الحقل. يحتوي البرنامج Lesson14_01 على النسخة الجديدة للصنف Car مع تعديل بسيط ضمن التابع Main لندعم استخدام الأحداث: 1 using System; 2 3 namespace Lesson14_01 4 { 5 public class Car 6 { 7 public delegate void SpeedNotificatoinDelegate(string message); 8 9 public event SpeedNotificatoinDelegate speedNotificationHandler; 10 11 public int CurrentSpeed { get; set; } 12 public int MaxSpeed { get; set; } 13 14 public Car() 15 { 16 CurrentSpeed = 0; 17 MaxSpeed = 100; 18 } 19 20 public Car(int maxSpeed, int currentSpeed) 21 { 22 CurrentSpeed = currentSpeed; 23 MaxSpeed = maxSpeed; 24 } 25 26 public void Accelerate(int delta) 27 { 28 CurrentSpeed += delta; 29 30 if (CurrentSpeed > MaxSpeed) 31 { 32 if (this.speedNotificationHandler != null) 33 { 34 string msg = string.Format("You exceed the maximum speed! (Current = {0}, Max = {1})", 35 CurrentSpeed, MaxSpeed); 36 37 speedNotificationHandler(msg); 38 } 39 } 40 } 41 } 42 43 class Program 44 { 45 static void Main(string[] args) 46 { 47 Car car = new Car(100, 0); 48 49 car.speedNotificationHandler += new Car.SpeedNotificatoinDelegate(OnExceedMaxSpeedHandler); 50 51 for (int i = 0; i < 5; i++) 52 { 53 Console.WriteLine("Increasing speed by 30"); 54 car.Accelerate(30); 55 } 56 } 57 58 static void OnExceedMaxSpeedHandler(string message) 59 { 60 Console.WriteLine(message); 61 } 62 } 63 } انظر كيف وضعنا الكلمة المحجوزة event بعد كلمة public في التصريح عن الحقل speedNotificationHandler في السطر 9. كما أرجو أن تلاحظ أيضًا التعديل الذي طرأ في السطر 49 على كيفيّة إسناد النائب الجديد إلى الحقل speedNotificationHandler: car.speedNotificationHandler += new Car.SpeedNotificatoinDelegate(OnExceedMaxSpeedHandler); لاحظ كيف استخدمنا العامل (=+) بدلًا من العامل (=). في الحقيقة سيؤدي استخدام العامل (=) في هذه الحالة إلى حدوث خطأ أثناء ترجمة البرنامج. ولكن ماهي الفائدة من العامل (=+)؟ لهذا العمل فائدة كبيرة، فمن خلاله يمكن تسجيل عدّة نوّاب (وبالتالي عدّة توابع) ضمن الحدث speedNotificationHandler بنفس الوقت. مما يعني أنّ عبارة مثل تلك الموجودة في السطر 37: speedNotificationHandler(msg); ستؤدّي إلى استدعاء أي نائب (وبالتالي أي تابع) مسجّل في الحدث speedNotificationHandler بشكل متسلسل يراعي الترتيب الذي سُجّلت ضمنه هذه النوّاب ضمن الحدث speedNotificationHandler باستخدام العامل =+. نسمّي التابع OnExceedMaxSpeedHandler اصطلاحًا بمعالج الحدث. الآن سنذهب أبعد من ذلك ونجري تغييرًا على عبارة التسجيل في الحدث في السطر 49 لتصبح على الشكل التالي: car.speedNotificationHandler += OnExceedMaxSpeedHandler; لاحظ هنا أنّنا قد أزلنا التعبير الذي ينشئ كائن من النائب SpeedNotificatoinDelegate، ووضعنا بدلًا من ذلك اسم التابع OnExceedMaxSpeedHandler مباشرةً بعد العامل (=+). في الحقيقة إنّ مترجم سي شارب ذكيّ كفاية ليعرف أنّه ينبغي عليه أن يُنشئ كائنًا جديدًا من النائب SpeedNotificatoinDelegate بشكل تلقائيّ يغلّف التابع OnExceedMaxSpeedHandler. إذا نفّذت البرنامج ستحصل على نفس الخرج المتوقّع. ملاحظة: تتمتّع النوّاب أيضًا بميّزة التسجيل المتعدّد باستخدام العامل =+. ولكن هذه الميّزة تُستخدم مع الأحداث بشكل أكبر. التوابع مجهولة الاسم Anonymous Methods التوابع مجهولة الاسم هي من المزايا التي تسمح باختصار الشيفرة إلى حدٍّ كبير. فمن اسمها، يظهر أنَّه لا يوجد لمثل هذه التوابع اسم، وإنّما جسم فقط يحوي الشيفرة المطلوب تنفيذها. كمثال بسيط على التوابع مجهولة الاسم سنجري تعديلًا على البرنامج Lesson14_01 السابق ليستخدم تابعًا عديم الاسم بدلًا من التابع OnExceedMaxSpeedHandler. سيكون هذا التعديل في السطر 49 ليصبح على الشكل التالي: car.speedNotificationHandler += delegate(string message) { Console.WriteLine(message); }; لاحظ هنا أنّنا قد استخدمنا الكلمة المحجوزة delegate بعد العامل =+ بعد ذلك الوسائط التي يقبلها هذا التابع ثم جسم التابع المحاط بالحاضنة. الشيء الوحيد الناقص هو اسم التابع. من الواضح أنّ تعريف هذا التابع عديم الاسم يجب أن يتطابق مع تعريف النائب الذي صُرّح الحدث speedNotificationHandler بناءً عليه. الآن يمكن التخلّص من التابع OnExceedMaxSpeedHandler. انظر إلى البرنامج Lesson14_02 الكامل: 1 using System; 2 3 namespace Lesson14_02 4 { 5 public class Car 6 { 7 public delegate void SpeedNotificatoinDelegate(string message); 8 9 public event SpeedNotificatoinDelegate speedNotificationHandler; 10 11 public int CurrentSpeed { get; set; } 12 public int MaxSpeed { get; set; } 13 14 public Car() 15 { 16 CurrentSpeed = 0; 17 MaxSpeed = 100; 18 } 19 20 public Car(int maxSpeed, int currentSpeed) 21 { 22 CurrentSpeed = currentSpeed; 23 MaxSpeed = maxSpeed; 24 } 25 26 public void Accelerate(int delta) 27 { 28 CurrentSpeed += delta; 29 30 if (CurrentSpeed > MaxSpeed) 31 { 32 if (this.speedNotificationHandler != null) 33 { 34 string msg = string.Format("You exceed the maximum speed! (Current = {0}, Max = {1})", 35 CurrentSpeed, MaxSpeed); 36 37 speedNotificationHandler(msg); 38 } 39 } 40 } 41 } 42 43 class Program 44 { 45 static void Main(string[] args) 46 { 47 Car car = new Car(100, 0); 48 49 car.speedNotificationHandler += delegate(string message) 50 { 51 Console.WriteLine(message); 52 }; 53 54 for (int i = 0; i < 5; i++) 55 { 56 Console.WriteLine("Increasing speed by 30"); 57 car.Accelerate(30); 58 } 59 } 60 } 61 } ملاحظة: يوجد نائب جاهز موجود ضمن مكتبة FCL اسمه EventHandler وظيفته توفير الدعم للأحداث الجديدة التي نعرّفها، بحيث لا نضطّر إلى التصريح عن نائب جديد في كلّ مرّة نريد فيها التصريح عن حدث جديد. يغلّف النائب EventHandler أي تابع يتطلّب وسيطين الأوّل من النوع object والذي يمثّل الكائن الذي أصدر الحدث، والثاني من النوع EventArgs وهو كائن يحتوي على بعض المعلومات الإضافيّة عن الحدث. تمارين داعمة تمرين 1 ليكن لدينا الصنف التالي: class Counter { private int currentValue = 0; public void Increase() { currentValue++; } public void Decrease() { currentValue--; } } أجرِ تعديلًا على هذا الصنف بحيث تصرّح عن الحدث Notification الذي يُفعَّل عندما تصبح قيمة الحقل currentValue من مضاعفات العدد 5 فقط. (تلميح: الأعداد السالبة ليست من مضاعفات 5. والعدد 5 هو مضاعف لنفسه). تمرين 2 أجرِ تعديلًا على البرنامج Lesson14_02 السابق لتستغني عن النائب SpeedNotificatoinDelegate تمامًا، بحيث تستخدم النائب الجاهز EventHandler عوضًا عنه. (تلميح: سيتطلّب الأمر تعديل الوسائط الممرّرة إلى معالج الحدث لتتطابق مع الوسائط التي يحتاجها النائب EventHandler) الخلاصة تعرّفنا في هذا الدرس على الأحداث Events. تلك التقنيّة المهمّة التي تعتمد عليها تطبيقات سطح المكتب desktop applications بشكل أساسيّ، فضلًا عن باقي أنواع التطبيقات مثل تطبيقات الويب، وتطبيقات الأجهزة المحمولة، وأي نوع من أنواع التطبيقات التي تتطلّب التفاعل الداخلي مع نظام التشغيل أو الخارجيّ مع المستخدم.
  10. يُعتبر التعامل مع النصوص من المهام البرمجيّة الأساسيّة في جميع أنواع التطبيقات بما فيها تطبيقات الأجهزة المحمولة. توفّر Xamarin تقنيّات جيّدة للتعامل مع النصوص، سنتناول العديد منها من خلال هذا الدرس، وأيضًا من خلال دروس لاحقة في هذه السلسلة. سنعالج في هذا الدرس بعضًا من الحالات التي قد تواجه المطوّر عند التعامل مع النصوص في التطبيقات التي يُنشئها. التعامل مع المقاطع النصية نحتاج في الكثير من الأحيان أن نعرض نصًّا طويلًا بعض الشيء على الشاشة. قد يأتي هذا النص من مستند أو من خدمة ويب web service أو من غيرها من المصادر. المتنوّعة، وينبغي أن يتمكّن التطبيق من التعامل مع نصوص بمثل هذا الحجم. كما فعلنا من الدرس السابق، أنشأ مشروعًا جديدًا من النوع (Blank App (Xamarin.Forms Portable وسمّه TextManipulationApp. أبق فقط على المشروعين (TextManipulationApp (Portable و TextManipulationApp.Droid. سنضيف الآن صفحة محتوى content page جديدة ولكن بطريقة مختلفة ومختصرة عما فعلناه في الدرس السابق. انقر بزر الفأرة الأيمن على المشروع (TextManipulationApp (Portable واختر Add ثم من القائمة الفرعية التي ستظهر اختر New Item. اختر من الجهة اليسرى للنافذة التي ستظهر العقدة Cross-Platform، وبعد تحديث محتويات القسم الأوسط من النافذة، اختر نوع الملف Forms ContentPage وامنحه الاسم ParagraphPage من مريع الاسم Name في الأسفل، وبعد ذلك انقر Add. كما في الشكل التالي: بعد أن يضيف Visual Studio هذا الملف، ستكون محتوياته على الشكل التالي: using System; using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using System.Text; using Xamarin.Forms; namespace TextManipulationApp { public class ParagraphPage : ContentPage { public ParagraphPage() { Content = new StackLayout { Children = { new Label { Text = "Hello ContentPage" } } }; } } } توجد نطاقات أسماء غير ضرورية هنا ولكن لا بأس بها حاليًا. لاحظ أنّ الصنف ParagraphPage يرث من الصنف ContentPage بشكل افتراضي، كما لاحظ أنّ بانية الصنف ParagraphPage جاهزة، وتحتوي على مثال بسيط جاهز. احذف محتويات هذه البانية، لنبدأ العمل على تطبيقنا. سننشئ لصيقة Label تحتوي على مقطع نصيّ مكتوب باللغة العربية. اكتب الشيفرة التالية ضمن بانية الصنف ParagraphPage: Content = new Label { VerticalOptions = LayoutOptions.Center, Text = "تُعتبر منصّة Xamarin في الوقت الحالي، من أهمّ منصّات تطوير تطبيقات الأجهزة المحمولة المتوفّرة،" + " والتي يبدو أنّها ستبقى على الواجهة لوقت ليس بالقليل، " + "خصوصًا بعد استحواذ شركة مايكروسوفت على الشركة المنتجة لها والتي تحمل أيضًا نفس الاسم." + "سنتعلّم من خلال هذه السلسلة كتابة تطبيقات عمليّة من خلال استخدام هذه التقنيّة الواعدة." }; انتقل إلى الملف App.cs ضمن المشروع (TextManipulationApp (Portal واحرص على أن تكون بانية الصنف App على الشكل التالي: public App() { // The root page of your application MainPage = new ParagraphPage(); } نفّذ البرنامج باستخدام F5 لتحصل على شكل شبيه بما يلي: تجدر الملاحظة أنّه تتم مُحاذاة كامل النص رأسيًّا في منتصف الشاشة. سبب ذلك هو إسناد القيمة LayoutOptions.Center إلى الخاصيّة VerticalOptions (خيارات التموضع الرأسيّة) للصيقة Label. لاحظ أيضًأ أنّ النص في الشكل السابق تمّت مُحاذاته نحو اليسار، وليس إلى اليمين كما هو متوقّع بالنسبة للنصوص العربيّة. يعود سبب ذلك إلى أنّ الخاصيّة HorizontalTextAlignment من الصنف Label والمسؤولة عن المحاذاة الأفقيّة للنص ضمن اللصيقة سيكون لها القيمة الافتراضيّة TextAlignment.Start عند تشغيل البرنامج. و TextAlignment عبارة عن معدودة enum تحتوي على ثلاثة قيم هي: Start و Center و End. وبما أنّ شاشة الجهاز المحمول تُقسم إلى ثلاثة مناطق أساسيّة أفقيًا: Start للمنطقة اليسرى و Center للمنطقة الوسطى (المركزية) و End للمنطقة اليمنى من الشاشة لذلك ستتم محاذاة النص إلى اليسار بدلًا من اليمين. إذا أردنا أن تمم مُحاذاة النص إلى اليمين، فكل ما عليك فعله هو إسناد القيمة TextAlignment.End للخاصية HorizontalTextAlignment، وهذا ما سنفعله بعد قليل. وهناك أمر آخر نلاحظه أيضًا من الشكل السابق، وهو أنّ النص يقترب كثيرًا من حواف الشاشة حتى يكاد يلتصق بها. حلّ هذا الموضوع بسيط ويتمثّل في إضافة حشوة padding على حواف الشاشة، وهي عبارة عن مساحة صغيرة يمكن تعيينها برمجيًا للشاشة، نستطيع من خلالها التحكم بمساحة الهامش على الأطراف الأربعة لها. انظر الآن إلى بانية الصنف ParagraphPage بعد التعديلات المطلوبة: public ParagraphPage() { Content = new Label { VerticalOptions = LayoutOptions.Center, Text = "تُعتبر منصّة Xamarin في الوقت الحالي، من أهمّ منصّات تطوير تطبيقات الأجهزة المحمولة المتوفّرة،" + " والتي يبدو أنّها ستبقى على الواجهة لوقت ليس بالقليل، " + "خصوصًا بعد استحواذ شركة مايكروسوفت على الشركة المنتجة لها والتي تحمل أيضًا نفس الاسم." + "سنتعلّم من خلال هذه السلسلة كتابة تطبيقات عمليّة من خلال استخدام هذه التقنيّة الواعدة.", HorizontalTextAlignment = TextAlignment.End }; Padding = new Thickness(5, 5, 5, 5); } أضفنا أولًا الخاصيّة HorizontalTextAlignment إلى اللصيقة Label وأسندنا إليها القيمة TextAlignment.End لمحاذاة النص ضمن اللصيقة نحو اليمين (لاحظ أنّ الفاصلة العادية تفصل بين الخصائص). كما أضفنا الخاصية Padding (من الصنف ParagraphPage) وأسندنا إليها كائنًا جديدًا من الصنف Thickness (السماكة). تخضع بانية الصنف Thickness لزيادة التحميل حيث لها أكثر من شكل. استخدمت الشكل الأخير الذي يقبل أربعة وسائط تمثّل مقادير الحشوة padding من اليسار left والأعلى top واليمين right والأسفل bottom على الترتيب. قد تتساءل عن واحدة القياس المستخدمة هنا، ولكنني سنؤجل النقاش حولها إلى درس لاحق. عند تنفيذ البرنامج ستحصل على شكل شبيه بما يلي: لاحظ الآن كيف تمّت مُحاذاة النص نحو اليمين (ضمن اللصيقة Label)، ولاحظ أيضًا كيف ابتعد النص عن حواف الشاشة بعد تعيين الخاصية Padding لصفحة المحتوى ParagraphPage. ملاحظة: أرجو التمييز بين الخاصيتين HorizontalOptions و HorizontalTextAlignment للصيقة Label. فالأولى تمثّل خيارات التموضع الأفقية للصيقة ضمن الشاشة. أمّا الثانية فتمثّل محاذاة النص الأفقية ضمن اللصيقة. وبنفس الأسلوب، يجب التمييز بين الخاصيتين VerticalOptions و VerticalTextAlignment للصيقة Label ولكن من الناحية الرأسية بدلًا من الأفقية. تنسيق النص ضمن اللصيقة يمكن تنسيق النص الموجود ضمن لصيقة Label بالشكل الذي نرغبه عن طريق استخدام خاصيّة أخرى من خصائص اللصيقة وهي FormattedText. الخاصية FormattedText هي من الصنف FormattedString، والذي يحتوي بدوره على خاصية اسمها Spans من النوع <IList<Span فهي عبارة عن مجموعة (قائمة) من كائنات من النوع Span. أيُّ كائن من الصنف Span ينسّق جزءًا محدّدًا من النص الكلي، ويتم التحكم بهذا التنسيق من خلال ست خصائص من الصنف Span هي: جزء النص المراد تنسيقه Text واسم الخط FontFamily وحجم الخط FontSize وسمات الخط FontAttributes ولون النص ForegroundColor ولون الخلفية BackgroundColor. إذا شعرت ببعض الارتباك من الكلام السابق فلا بأس! سنوضّح هذه الأمور من خلال مثال بسيط يعمل على إضافة بعض التأثيرات على النص الذي يظهر على الشاشة. انظر أولًا إلى الشكل الذي أود الحصول عليه ثم لنوضّح كيفيّة فعل ذلك برمجيًّا: قد لا يبدو التنسيق السابق جميلًا، ولكنّه كفيل بتوضيح الفكرة. لاحظ في البداية أنّ كلمة "المجد" قد لوّنت باللون الأزرق مع خلفية صفراء. أمّا عبارة "لن تبلغ المجد حتى تلعق الصبر" فتم تلوينها باللون Aqua مع ملاحظة أنّ هذه العبارة ذات حجم نص أكبر من حجم النص للعبارة التي قبلها. لقد أضفت صنف محتوى جديد Forms ContentPage سميته FormattedParagraphPage إلى نفس المشروع (TextManipulationApp (Portable السابق، وبأسلوب مماثل للأسلوب الذي أضفنا فيه الصنف ParagraphPage في بداية هذا الدرس. انظر إلى محتويات الملف FormattedParagraphPage.cs: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Reflection.Emit; 5 using System.Text; 6 7 using Xamarin.Forms; 8 9 namespace TextManipulationApp 10 { 11 public class FormattedParagraphPage : ContentPage 12 { 13 public FormattedParagraphPage() 14 { 15 FormattedString formattedString = new FormattedString(); 16 17 formattedString.Spans.Add(new Span 18 { 19 Text = "لا تحسبنّ ", 20 }); 21 22 formattedString.Spans.Add(new Span 23 { 24 Text = "المجد ", 25 BackgroundColor = Color.Yellow, 26 ForegroundColor = Color.Blue, 27 }); 28 29 formattedString.Spans.Add(new Span 30 { 31 Text = "تمرًا أنت آكله ", 32 }); 33 34 formattedString.Spans.Add(new Span 35 { 36 Text = "لن تبلغ المجد حتى تلعق الصبر.", 37 ForegroundColor = Color.Aqua, 38 FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)) 39 }); 40 41 Content = new Label 42 { 43 VerticalOptions = LayoutOptions.Center, 44 FormattedText = formattedString, 45 HorizontalTextAlignment = TextAlignment.End, 46 FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)) 47 }; 48 49 Padding = new Thickness(5, 5, 5, 5); 50 } 51 } 52 } بدأنا في البانية (السطر 15) بإنشاء كائن من الصنف FormattedString وإسناده إلى المتغيّر formattedString. بعد ذلك بدأنا بإضافة النصوص المنسّقة إلى القائمة Spans باعتماد الأسلوب البسيط التالي (الأسطر من 17 حتى 20): formattedString.Spans.Add(new Span { Text = "لا تحسبنّ " }); استخدمنا التابع Add من القائمة Spans لإضافة كائن من النوع Span حيث أنشأنا هذا الكائن وأسندنا الخاصية Text له مباشرة عند الإنشاء. النص في الشيفرة السابقة لا يحمل أي تنسيق خاص. في الحقيقة لقد كرّرنا هذا الأسلوب من أجل كل جزء من النص الكامل. ولكنّنا اعتمدنا بعض التنسيقات النصيّة المختلفة أحيانًا. انظر مثلًا إلى الشيفرة الموجودة في الأسطر من 22 حتى 27: formattedString.Spans.Add(new Span { Text = "المجد ", BackgroundColor = Color.Yellow, ForegroundColor = Color.Blue, }); هذه المرّة نسّقنا النص "المجد" بحيث أسندنا اللون الأصفر كلون للخلفية BackgroundColor، واللون الأزرق للون النص ForegroundColor. انظر أيضًا إلى جزء النص الذي أضفناه في الأسطر من 34 حتى 39: formattedString.Spans.Add(new Span { Text = "لن تبلغ المجد حتى تلعق الصبر.", ForegroundColor = Color.Aqua, FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)) }); هذه المرّة سيكون لون الخط Aqua وحجم الخط كبير Large. لاحظ كيف أسندنا قيمة حجم الخط إلى الخاصية FontSize. استخدمت لهذا الغرض التعبير التالي: Device.GetNamedSize(NamedSize.Large, typeof(Label)) الصنف Device يمثّل الجهاز الحالي الذي يعمل عليه تطبيقنا، ويحتوي على العديد من التوابع الساكنة المفيدة. من هذه التوابع استخدمنا التابع GetNamedSize الذي يُرجع حجم الخط المطلوب حسب الوسائط الممرّرة له. الوسيط الأول هو NamedSize.Large أي أنّنا نريد حجم كبير للخط (NamedSize هي معدودة)، والوسيط الثاني هو نوع العنصر المراد تطبيق هذا الخط عليه. في حالتنا نريد تطبيق هذا الخط على لصيقة Label لذلك مرّرنا (typeof(Label كوسيط ثانٍ حيث يُرجع النوع الخاص Type باللصيقة Label. هذا هو الأسلوب المفضّل في تعيين القياسات المناسبة لأيّ شيء يخطر ببالك، وذلك لأنّ الأجهزة التي نعمل عليها ستكون شاشاتها مختلفة القياسات بكلّ تأكيد، لذلك فنرغب أن نترك للتابع GetNamedSize مهمة القيام بالعمليات الحسابية المناسبة لإرجاع حجم الخط المناسب بالنسبة لحجم الشاشة التي يعمل عليها البرنامج حاليًا، وللحجم المراد الحصول عليه (في مثالنا هذا أردنا الحصول على حجم خط كبير NamedSize.Large وتوجد بالتأكيد قياسات أخرى). ما تبقى من البرنامج سهل للغاية، حيث نقوم في السطر 44 بإسناد المتغيّر formattedString إلى الخاصية FormattedText للصيقة. يكفل ذلك بعرض النص منسّقًا على الشاشة، مع الانتباه إلى أنّنا لم نستخدم في هذه الحالة الخاصية Text للصيقة. هناك أمر أخير يجب الانتباه إليه. لاحظ أنّنا في السطر 46 نُسند حجم خط جديد إلى الخاصية FontSize الخاصة باللصيقة. سيكون هذا الخط متوسّط الحجم (لاحظ الوسيط الأوّل NamedSize.Medium). الفكرة هنا هو أنّ هذا الخط سيطبّق على كل جزء من النص (موجود ضمن كائن Span) في حال لم تُعيّن الخاصية FontSize الخاصة بهذا الجزء. فمثلًا لن يطبّق هذا الحجم على جزء النص المعيّن في الأسطر بين 34 و 39. وذلك لأنّ هذا الجزء قد عيّن حجم خط خاص به (NamedSize.Large) كما رأينا قبل قليل. ملاحظة: ستحتاج لتجربة هذا البرنامج إلى تعديل بسيط ضمن بانية الصنف App ضمن الملف App.cs. احرص على أن تكون البانية على الشكل التالي لتستخدم صنفنا الجديد FormattedParagraphPage: public App() { // The root page of your application MainPage = new FormattedParagraphPage(); } الخلاصة تعرّفنا في هذا الدرس على مبادئ التعامل مع النصوص في Xamarin وبنينا للمرّة الأولى تطبيقين بسيطين يعرضان بعض المعلومات على المستخدم. تعلّمنا كيفيّة محاذاة النصوص وإجراء بعض عمليّات التنسيق عليها مثل تغيير لون النص ولون الخلفية لأيّ كلمة أو عبارة. لم يتناول هذا الدرس كيفيّة معالجة الحالة التي يكون فيها النص كبيرًا ويحتاج إلى وسيلة لتمريره لعرض محتوياته كاملة. سنعالج هذه المسألة في درس لاحق.
  11. أهلًا وسهلًا بك أخي، يسعدني سماع ذلك منك. سيتم طرح المقالات تباعًا إن شاء الله. يمكنك الانتقال إلى المقالة التالية من هذا الرابط. ويمكنك الاطلاع على الجديد من هذه السلسلة من هنا.
  12. برأيي أنّ طول الناتج ليس مشكلة أبدًا. وعلى العموم إذا وجدت شيئًا أقصر يضمن عدم التكرار، فسأخبرك عنه إن شاء الله.
  13. يمكنك استخدام التابع Guid.NewGuid(). انظر كمثال على ذلك في سي شارب: // This code example demonstrates the Guid.NewGuid() method. using System; class Sample { public static void Main() { Guid g; // Create and display the value of two GUIDs. g = Guid.NewGuid(); Console.WriteLine(g); Console.WriteLine(Guid.NewGuid()); } } /* This code example produces the following results: 0f8fad5b-d9cb-469f-a165-70867728950e 7c9e6679-7425-40de-944b-e07fc1f90ae7 */
  14. يُعتبر مبدأ الفتح والإغلاق Open/Closed Principle أو اختصارًا OCP، من المبادئ التي تساعد مطوّري البرمجيّات على تحقيق تصاميم برمجيّة عالية الجودة. على أيّة حال، قد يكون من الصعب أحيانًا أن نوضّح ما الذي نعنيه بالبرمجيّات عالية الجودة. بالعودة إلى المبدأ OCP، يعود الفضل إلى برتراند ماير في وضع مصطلح مبدأ الفتح والإغلاق، حيث ظهر أوّل الأمر في كتابه البنية كائنيّة التوجّه للبرمجيّات "Object Oriented Software Construction" ينص هذا المبدأ على ما يلي: الذي يعنيه هذا المبدأ، هو أنّنا عندما نُصمّم جزءً من تطبيق برمجي، فإنّه من الضروري أن نضع في حسباننا إمكانيّة التوسّع المستقبليّ، فكلّنا يعلم أنّ المتطلّبات الخاصّة بالزبائن تتغيّر على الدوام وبسرعةٍ كبيرة. لذلك فإنّ الشيفرة البرمجيّة ستتغيّر وتتوسّع لتلبّي المزيد من المتطلّبات والمزايا، وقد لا يؤدّي هذا الأمر على الدوام إلى عواقب حميدة على الصعيد البرمجي. الهدف الذي يُنشده هذا المبدأ هو أن ننظر إلى المستقبل (ابنِ الآن، وخطّط للمستقبل) بحيث نصمّم تطبيقاتنا البرمجيّة بحيث لا تحتاج إلى تغيير في الشيفرة المكتوبة مسبقًا عند إضافة مزايا ووظائف جديدة إليها. لندع الشيفرة البرمجيّة تُعبّر عن نفسها مع المثال التالي: def area(geometric_entity): if geometric_entity.type() == SQUARE: return geometric_entity.a * geometric_entity.a elif geometric_entity.type() == CIRCLE: return PI * geometric_entity.r * geometric_entity.r else: raise UnknownEntityError("I literally have no idea.") قد توحي الشيفرة السابقة بالبساطة أوّل الأمر، ولكنّها تُظهر جانبًا أساسيًّا من مبدأ OCP. فإذا أردنا مثلًا أن تدعم الدالّة السابقة إمكانية حساب مساحة مستطيل فيمكن ذلك بسهولة وذلك بإضافة مقطع elif جديد. ولكن بالمتابعة على هذا المنوال، وفي حالة حساب مساحة شكل هندسي غير قياسي، فستتحول الأسطر البرمجيّة البسيطة السابقة إلى ما يزيد عن 1500 سطر برمجي لحساب مساحة هذا الشكل باستخدام تكامل ريمان Riemann Integral، مما سيجعل هذه الأسطر كوحش برمجيّ إذا لم يلتهمك، فإنّ مدير المشاريع سيفعل ذلك حتمًا! النقطة التي نريد الوصول إليها، أنّه في كلّ مرّة نريد فيها إحداث تغيير في البرنامج لدعم مزايا جديدة، فإنّه من الممكن أن يؤدّي هذا التغيير إلى مشاكل في عمل المزايا القديمة التي كانت تعمل بشكل جيّد أصلًا، وهذا أمر غير مرغوب بالطبع. وهذا ما يحذّرنا منه مبدأ OCP، فيجب أن تكون العناصر البرمجيّة مفتوحة للتوسعة (دعم مزايا إضافيّة) ولكنها مغلقة للتعديل (عدم الحاجة إلى تعديل الشيفرة التي تدعم المزايا القديمة). قد يتبادر إلى ذهن البعض أنّه في حالة حدوث مشاكل جرّاء هذا التعديل فمن الممكن إصلاحها باستخدام وحدات الاختبار unit tests ومنقّحات الأخطاء debuggers، ولكن لماذا نلجأ لمثل هذه الأساليب إذا كان بإمكاننا تجنّبها أصلًا؟ فدرهم وقاية خير من قنطار علاج. وكما أنّه من الصعب وضع تعريف رسميّ لمعيار الجودة للبرمجيّات، فكذلك الأمر بالنسبة لهذه المبادئ. فلا توجد قواعد صارمة للتقيّد بها، فكل شيء يعود للخبرة الشخصيّة والتخطيط الجيّد. ترجمة -وبتصرّف- للمقال Open/Closed Principle in Software Design لصاحبه Radek Pazdera.
  15. سنتابع العمل في سلسلة برمجة تطبيقات الأجهزة المحمولة باستخدام Xamarin، حيث سنعمل في هذا الدرس على تنزيل وتنصيب برنامج Visual Studio 2015 Community الذي يأتي بشكل مجّاني من مايكروسوفت. حيث أنّ منصّة Xamarin أصبحت تأتي مع Visual Studio كميّزة من المزايا الاختياريّة التي من الممكن اختيارها أثناء تنصيب Visual Studio، وذلك بعد استحواذ مايكروسوفت على شركة Xamarin. تنزيل وتنصيب Visual Studio 2015 Community أنصح أن يكون التنصيب على ويندوز 10 أو ويندوز 8.1. انتقل إلى صفحة تنزيل بيئة التطوير Visual Studio 2015. كما في الشكل التالي: سنختار الإصدار Community من اليسار، لذلك فانقر الزر Download Community Free. سيعمل المتصفّح على تحميل ملف تنفيذي صغير اسمه vs_community.exe وهو برنامج الإعداد الذي سيعمل على تنصيب Visual Studio Community. سيشّغل المتصفّح هذا البرنامج بعد تنزيله فورًا، لذلك سيعطيك ويندوز تحذير أمان أنّه برنامج تنفيذي، ويعرض عليك تشغيله أو إلغاء العمليّة. اقبل تشغيله من خلال نقر الزر Run كما في الشكل التالي: سيعمل برنامج الإعداد ويبدأ بجلب بيانات التنصيب من الإنترنت. قد يستغرق ذلك القليل من الوقت قبل أن تحصل على النافذة في الشكل التالي، التي تخيّرنا بين خيارين للتنصيب: افتراضي Default ومخصّص Custom: انقر الخيار المخصّص Custom ثمّ انقر Next. لينتقل برنامج الإعداد إلى نافذة المزايا المطلوب تنصيبها. انتقل إلى العقدة Cross Platform Mobile Development وانشرها لتصل إلى الميّزة (C#/.NET (Xamarin v4.0.4 وهو الإصدار الحالي لمنصّة Xamarin حين كتابة هذا الدرس. قد يختلف هذا الإصدار بالنسبة إليك. انقر صندوق الاختيار بجوار هذه الميّزة، سيؤدّي ذلك إلى اختيار مزايا أخرى بشكل تلقائي. في الحقيقة سيكون حجم حزمة البيانات التي ستُنزّل من الإنترنت كبيرة نسبيًّا. بالنسبة لهذه السلسلة لن نحتاج إلى جميع هذه المزايا، لذلك انتقل إلى العقدة Programming Languages وانشرها، وأزل الإشارة من صندوق الاختيار الموجود بجانب ++Visual C. انقر الزر Next لنصل إلى المرحلة النهائيّة قبل البدء بعمليّة التنصيب، وهي المرحلة التي تلخّص ما سيقوم به برنامج الإعداد. انقر الزر Install لتبدأ عمليّة التنصيب التي ستأخذ بعض الوقت بحسب سرعة الإنترنت لديك. ستتضمّن عملية التنصيب تحميل حزمة التطوير البرمجيّة SDK الخاصة بأندرويد، بالإضافة إلى تثبيت واجهتين برمجيّتين أو أكثر افتراضيًّا مثل API 19 و API 21. على العموم يمكن تثبيت الواجهات البرمجيّة التي ترغبها بعد انتهاء التنصيب وذلك من خلال مدير الحزم والواجهات في أندرويد Android SDK Manager الذي يأتي مع حزمة التطوير البرمجيّة SDK. اقرأ هذا المقال هنا على أكاديميّة حسّوب للمزيد من المعلومات حول الواجهات البرمجيّة API ودعمها للأجهزة المشغّلة لأندرويد. التشغيل الأول لبيئة التطوير Visual Studio عند تشغيل بيئة التطوير Visual Studio 2015 للمرّة الأولى، سيطلب منك Visual Studio تسجيل الدخول باستخدام حساب بريد إلكتروني من مايكروسوفت (كحساب بريد إلكتروني على Hotmail مثلًا) كما في الشكل التالي: انقر الزر Sign in لتسجيل الدخول، لتحصل على النافذة الخاصّة بإدخال البريد الإلكتروني. أدخل البريد الإلكتروني ثم انقر Continue وإذا طلب منك Visual Studio أن تحدّد نوع البريد الإلكتروني، فاختر شخصيّ Personal Email. بعد ذلك ستصل إلى نافذة تطلب منك كلمة المرور لحساب البريد الإلكتروني الذي أدخلته قبل قليل. أدخل كلمة المرور، ثم انقر Sign in، لتصل إلى النافذة الرئيسيّة لتطبيق Visual Studio كما في الشكل التالي: إنشاء مشروع جديد من النافذة الرئيسيّة لبيئة التطوير Visual Studio، انقر القائمة File من الأعلى، واختر منها New ثم Project لإنشاء مشروع جديد، ستحصل على النافذة التالية: اختر من الشجرة التي تظهر على الجانب الأيسر الخيار: Cross-Platform (قد تحتاج لنشر بعض العقد لتصل إليه). ثم اختر نوع المشروع Blank App Xamarin.Forms) Portable) من القسم الأوسط للنافذة وذلك من أجل اختيار مكتبة الأصناف المحمولة PCL مع هذا التطبيق. ثمّ أدخل الاسم HelloWorld في حقل الاسم Name من الأسفل، ثم انقر OK. سيستغرق الأمر وقتًا قليلًا ليعمل Visual Studio على إنشاء عدّة مشاريع ضمن الحل Solution الحالي. بالنسبة إليّ (أستخدم نظام التشغيل Windows 10) فقد حصلت على خمسة مشاريع ضمن هذا الحل وهي: (HelloWorld (Portable HelloWorld.Droid HelloWorld.iOS (HelloWorld.Windows (Windows 8.1 (HelloWorld.WinPhone (Windows Phone 8.1 تظهر هذه المشاريع ضمن مستكشف الحل Solution Explorer في الجانب الأيمن من النافذة (إذا لم يكن ظاهرًا فيمكنك إظهاره من القائمة View ثم اختيار Solution Explorer). انظر الشكل التالي: في الحقيقة لن نهتم في هذه السلسلة سوى بالتطبيقات التي تعمل على أندرويد، لذلك سنحتفظ بالمشروعين (HellowWorld (Portable و HellowWorld.Droid ونحذف باقي المشاريع. انقر بزر الفأرة الأيمن على المشروع HelloWorld.iOS ثم اختر Remove لإزالته. كرّر نفس العمليّة بالنسبة للمشروعين (HelloWorld.Windows (Windows 8.1 و (HelloWorld.WinPhone (Windows Phone 8.1. ملاحظة: قد تختلف المشاريع التي تظهر عندك بشكل طفيف. على أيّة حال احرص على وجود مشروعين فقط وهما HelloWorld.Droid و (HelloWorld (Portable. تشغيل تطبيق أندرويد الأول لتشغيل تطبيقات أندرويد فإنّنا نحتاج إلى جهاز ذكي يشغّل أندرويد (بصرف النظر عن الإصدار) أو أن يتوفّر لدينا محاكي Emulator يعمل على محاكاة عمل هذا الجهاز ولكن على حاسوبنا الشخصي. توفّر مايكروسوفت محاكٍ Emulator خاص بها: Visual Studio Emulator For Android، وذلك لمحاكاة عمل تطبيقات أندرويد على جهاز الحاسوب بدون الحاجة إلى وجود جهاز فيزيائي متصل بالحاسوب. يُعتبر هذا المحاكي برأيي أفضل من المحاكي الافتراضي الذي يأتي مع حزمة التطوير الخاصّة بأندرويد من حيث الأداء. وعلى أية حال، فستحتاج إلى تفعيل ميزة HAXM التي تأتي مع معالجات Intel، والتي يحتاجها المحاكي الافتراضي لتسريع أدائه. انظر إلى هذا الرابط لتعرف المزيد عن هذا الموضوع. لنشغّل تطبيقنا الأوّل ضمن المحاكي الخاص بمايكروسوفت، سيظهر لك أعلى نافذة بيئة التطوير شريط صغير يحتوي على اسم المحاكي الذي سيتم تشغيله. انظر إلى الشكل التالي: لاحظ بأنّ المحاكي الذي لديّ يدعم الواجهة البرمجيّة API 19 ولا بأس في ذلك. يمكنك دعم أي واجهة برمجيّة ترغبها، باستخدام Android SDK Manager. كلّ ما فعلته أنّني تركت الإعدادات الافتراضيّة كما هي. انقر السهم الأخضر الصغير للتشغيل في وضع التنقيح debugging mode (أو يمكنك اختيار الأمر Start Debugging من القائمة Debug أو اضغط F5). سيستغرق الأمر قليلًا من الوقت حتى تحصل على شكل شبيه بما يلي: لاحظ الرسالة الترحيبيّة !Welcome to Xamarin Forms في وسط الشاشة. مبروك لقد حصلت على برنامجك الأوّل! أوقف الآن تشغيل البرنامج باختيار الأمر Stop Debugging من القائمة Debug (أو اضغط Shift+F5). انتقل إلى مستكشف الحل Solution Explorer وانشر المشروع (HelloWorld (Portable ثمّ انقر على الملف App.cs وهو الملف الأساسيّ في أيّ تطبيق من تطبيقات Xamarin. ستجد ضمنه الشيفرة التالية: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 using Xamarin.Forms; 7 8 namespace HelloWorld 9 { 10 public class App : Application 11 { 12 public App() 13 { 14 // The root page of your application 15 MainPage = new ContentPage 16 { 17 Content = new StackLayout 18 { 19 VerticalOptions = LayoutOptions.Center, 20 Children = { 21 new Label { 22 XAlign = TextAlignment.Center, 23 Text = "Welcome to Xamarin Forms!" 24 } 25 } 26 } 27 }; 28 } 29 30 protected override void OnStart() 31 { 32 // Handle when your app starts 33 } 34 35 protected override void OnSleep() 36 { 37 // Handle when your app sleeps 38 } 39 40 protected override void OnResume() 41 { 42 // Handle when your app resumes 43 } 44 } 45 } لاحظ الصنف App في السطر 10. يرث هذا الصنف من الصنف Application الذي يمثّل التطبيق في Xamarin.Forms. أيّ تطبيق ننشئه في Xamarin.Forms يجب أن يرث من هذا الصنف. تقع بانية الصنف App في الأسطر من 12 حتى 28 وفيها يتمّ تعيين الصفحة الرئيسيّة التي سيظهرها التطبيق عند تشغيله. أيّ صفحة من صفحات التطبيق ستكون عبارة عن كائن من صنف يرث من الصنف ContentPage (صفحة محتوى). يمكن أن تحتوي هذه الصفحة على أيّ شيء يخطر ببالك من مربّعات النص والأزرار وأشرطة التمرير وغيرها من الأدوات. لاحظ أنّنا في السطر 15 (ضمن بانية الصنف App) قد أسندنا كائنًا جديدًا من الصنف ContentPage إلى الخاصيّة MainPage وهي خاصيّة تتبع الصنف Application (الذي يرث منه الصنف App). تستخدم الشيفرة الموجود هنا أسلوب الإنشاء المختصر للكائنات، فكلّ شيء يتمّ هنا من خلال عبارة برمجيّة واحدة. حيث نسند الخصائص للكائنات الجديدة المُنشأة مباشرةً عند إنشاء هذه الكائنات. فمن خلال الشيفرة الموجودة في الأسطر بين 15 و 27 كرّرنا هذا الأسلوب ثلاث مرّات، وذلك من أجل كائنات جديدة من الأصناف ContentPage و StackLayout و Children. ملاحظة يُعتبر الإسناد المختصر للخصائص عند إنشاء كائنات جديدة، من التقنيّات المهمّة التي تبسّط الشيفرة البرمجيّة إلى حدّ كبير. وكيف تنعش ذاكرتك، إليك المثال البسيط التالي. بفرض أنّه لدينا الصنف Student الذي يحتوي على الخصائص التالية: FirstName و LastName و Age. عند إنشاء كائن جديد من الصنف Student يمكننا استخدام العبارة البرمجيّة التالية: Student student = new Student { FirstName = "Ahmad", LastName = "Shareef", Age = 16 }; ففي هذه الحالة ننشئ كائن جديد من الصنف Student وبنفس الوقت نُسند القيم المناسبة لخصائصه. سنؤجّل الحديث عن التوابع OnStart و OnSleep و OnResume لنتحدّث عنها لاحقًا في هذه السلسلة، رغم أنّ أسمائها توحي بوظائفها التي تُعتبر مفيدة ومهمّة في عمل التطبيق. الخلاصة تعلّمنا في هذا الدرس كيفيّة تنزيل وتنصيب Visual Studio 2015 Community مع إضافة قابليّة تطوير تطبيقات لأندرويد باستخدام Xamarin. لاحظ أنّني قد تركت الأمور تسير بشكلها الافتراضيّ قدر المستطاع، لأنّه كما هو واضح هناك العديد من الإجراءات كي تصبح بيئة التطوير جاهزة للعمل، وللبدء بتطوير تطبيقات تعمل على أندرويد وغيره من أنظمة التشغيل باستخدام سي شارب #C و Xamarin.
  16. 1- في الغالب الأعم يتم تخزين token الاستعادة في قاعدة البيانات لكل مستخدم يطلب إعادة تعيين كلمة المرور الخاصة به. وهناك حالتين للتخزين. فإمّا أن يكون ضمن نفس جدول المستخدمين عن طريق حقل إضافي. أو أن يكون ضمن جدول منفصل مرتبط بجدول المستخدمين، والحالة الأولى هي الأبسط بالطبع. 2- يتم تحديث الـ token ضمن قاعدة البيانات في كل مرة يطلب فيها المستخدم استعادة كلمة مروره. 3- بالنسبة للدوال التي تولّد مثل هذا الـ token فيعود ذلك إلى لغة البرمجة المستخدمة، أو إلى قاعدة البيانات المستخدمة في حال أردت استخدام Stored Procedure مثلًا. 4- لا أتصور أنّك قد تحتاج إلى طرق أخرى لفعل ذلك إلّا إذا كان لديك سبب وجيه.
  17. سنبدأ في هذا الدرس من سلسلة برمجة تطبيقات الأجهزة المحمولة باستخدام Xamarin بالتعرّف على بنية التطبيق ضمن الحل Solution في Visual Studio 2015، بالإضافة إلى إنشائنا لتطبيق بسيط نتعرّف من خلاله على آلية عمل التطبيقات المبنية بواسطة Xamarin والتي تشبه في العديد من الجوانب مثيلاتها المنشأة باستخدام التقنيّات الأساسيّة المعتمدة في إنشاء تطبيقات أندرويد (لغة جافا مع Android Studio). بنية التطبيق المنشأ باستخدام Xamarin شغّل برنامج Visual Studio 2015، وبعد ظهور النافذة الرئيسيّة انتقل إلى القائمة File ومنها إلى New ثم اختر من القائمة الفرعيّة التي ستظهر الخيار Project. وكما فعلنا في الدرس السابق، اختر من الجهة اليسرى للنافذة التي ستظهر Cross-Platform، ثمّ اختر المشروع (Blank App (Xamarin.Forms Portable من القسم الأوسط للنافذة. امنح هذا المشروع الاسم SimpleTextApp ثم انقر الزر OK. سنبني تطبيقًا بسيطًا يوضّح كيفيّة استخدام لُصيقة Label وحيدة في إظهار النص في التطبيقات المبنيّة باستخدام Xamarin. من مستكشف الحل Solution Explorer الذي يوجد عادةً في الجهة اليمنى لنافذة بيئة التطوير Visual Studio، أبقِ فقط على المشروعين SimpleTextApp.Droid و (SimpleTextApp (Portable لأنّنا سنتعامل مع تطبيقات أندرويد فحسب (كما اتفقنا أيضًا من الدرس السابق). احذف باقي المشاريع الإضافيّة، يجب أن تحصل على شكل شبيه بما يلي: يحتوي المشروع (SimpleTextApp (Portable على معظم الشيفرة البرمجيّة، وهذا سلوك عام في المشاريع ذات النمط PCL. حيث تُعتبر الشيفرة الموجودة ضمن المشروع (الذي يحمل المقطع Portable بالإضافة لاسمه) مشتركة لجميع أنواع التطبيقات العاملة على أيّ نظام تشغيل بما فيه أندرويد. نلاحظ الملف App.cs الأساسي الذي سيكون موجودًا في أيّ تطبيق Xamarin وقد تحدثنا عنه في الدرس السابق ورأينا كيف يحتوي على الصنف App الذي يرث من الصنف Application، وكيف أنّ بانية هذا الصنف هي المسؤولة عن إنشاء الصفحة الرئيسيّة للتطبيق. انشر العقدة References لترى المراجع الخاصّة بهذا المشروع. ستلاحظ وجود عدّة مكتبات يهمّنا منها: Xamarin.Forms.Core و Xamarin.Forms.Platform و Xamarin.Forms.Xaml هذه هي المكتبات الرئيسيّة المشكّلة لـ Xamarin.Forms. بالنسبة للمشروع SimpleTextApp.Droid فهو يحتوي على مجلّدين هما: Assets و Resources. بالنسبة للمجلّد Resources فيحتوي على ملفات الصور وملفات معلومات التخطيطات layouts descriptions وغيرها من الملفات التي تُعتبر بمثابة المصادر resources لتطبيقك. انشر المجلّد Resources لترى بنيته. أمّا بالنسبة للمجلّد Assets فتُوضَع ضمنه الملفات العامّة الأخرى التي ترغب بتضمينها مع تطبيقك. يحتوي هذا المشروع أيضًا على الملف MainActivity.cs الذي يحوي ضمنه صنف له نفس الاسم MainActivity وهو يمثّل الفعاليّة Activity الأساسيّة لتطبيق أندرويد. افتح هذا الملف لتجد أنّ هذا الصنف يحتوي على التابع OnCreate الذي يُنفَّذ عند بدء تشغيل تطبيق أندرويد. لاحظ العبارة البرمجيّة التالية ضمن هذا التابع: LoadApplication(new App()); من الواضح أنّها تنشئ كائنًا جديدًا من الصنف App الموجود ضمن المشروع (SimpleTextApp (Portable، ثم تمرّره إلى التابع LoadApplication مباشرةً ليعمل على تحميل التطبيق وإظهاره للمستخدم. إذًا فعند تشغيل تطبيق أندرويد، سينفّذ التابع OnCreate الموجود في الصنف MainActivity أولًا، حيث يعمل هذا التابع على إنشاء كائن جديد من الصنف App فينفّذ بانيته وهي المسؤولة عن إنشاء الصفحة الرئيسيّة للتطبيق كما أشرنا. على أيّة حال ستتوضّح الفكرة بشكل جيّد بعد أن نبدأ بكتابة الشيفرة البرمجيّة. كتابة الشيفرة البرمجية انقر بزر الفأرة الأيمن على اسم المشروع (SimpleTextApp (Portable في مستكشف الحل Solution Explorer لتظهر قائمة سياق، اختر منها Add ثمّ من القائمة الفرعيّة اختر Class. ستظهر النافذة الخاصّة بإضافة صنف جديد. من حقل الاسم Name من الأسفل اكتب الاسم التالي لهذا الصنف الجديد وهو MainPage، ثم انقر Add. سيعمل Visual Studio على إضافة ملف جديد إلى المشروع اسمه MainPage.cs يحتوي على صنف بنفس اسمه ومحتواه شبيه بما يلي: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SimpleTextApp { class MainPage { } } لن نحتاج إلى معظم نطاقات الأسماء الموجودة في الشيفرة السابقة في هذا البرنامج، سنحتاج حاليًا إلى نطاق الاسم Xamarin.Forms. وسنجعل الصنف MainPage يرث من الصنف ContentPage كما سنضيف البانية إليه. عدّل الشيفرة السابقة لتصبح على الشكل التالي: using Xamarin.Forms; namespace SimpleTextApp { class MainPage : ContentPage { public MainPage() { } } } لاحظ أنّني تخلصت من جميع نطاقات الأسماء غير الضرورية (على أية حال سيظهر أي نطاق اسم غير مُستَخدَم في الشيفرة البرمجيّة بلون باهت في بيئة التطوير Visual Studio 2015). سيلعب الصنف MainPage دور صفحة محتوى content page رئيسيّة للتطبيق SimpleTextApp. سنضع ضمن بانية هذا الصنف الشيفرة البرمجيّة اللازمة لإنشاء لُصيقة واحدة Label تحوي نصًّا بسيطًا. ثم سنجري بعض التجارب البسيطة على هذه اللُّصيقة. يُعتبر هذا البرنامج بسيطًا في الواقع، ومهمته جعلك أكثر تآلفًا مع Xamarin. سنبدأ بإنشاء لُصيقة من الصنف Label وذلك ضمن بانية الصنف MainPage. تُستخدم اللُصيقات عادةً لعرض النصوص إلى المستخدم. انظر إلى الشيفرة البسيطة التالية: Label lblMain = new Label(); lblMain.Text = "Welcome to Hsoub Academy!"; ننشئ لُصيقة كما ننشئ أيّ كائن من صنف ما. أنشأنا في الشيفرة السابقة كائنًا جديدًا من الصنف Label وأسندناه إلى المتغيّر lblMain. ثم أسندنا النص "!Welcome to Hsoub Academy" إلى الخاصيّة Text من المتغيّر lblMain. تُستَخدم الخاصية Text لضبط وقراءة المحتوى النصيّ للُّصيقة. كي نستطيع عرض اللُّصيقة السابقة على الصفحة الرئيسيّة للتطبيق، علينا إسناد المتغيّر lblMain إلى الخاصيّة Content من الصنف ContentPage. وبما أنّ الصنف MainPage يرث من ContentPage فستكون الخاصيّة Content موجودة تلقائيًّا ضمنه أيضًا. ستكون عمليّة الإسناد على الشكل التالي: this.Content = lblMain; تكون عمليّة الإسناد السابقة ممكنة لأنّ الصنف Label يرث من الصنف View الذي يمثّل واجهة عرض view بمفهومها العام. وبما أنّ الخاصيّة Content هي من النوع View أيضًا فتكون عمليّة الإسناد هذه ممكنة. ستكون محتويات الملف MainPage.cs شبيهة بما يلي: using Xamarin.Forms; namespace SimpleTextApp { class MainPage:ContentPage { public MainPage() { Label lblMain = new Label(); lblMain.Text = "Welcome to Hsoub Academy!"; this.Content = lblMain; } } } هذا كلّ شيء! علينا الآن القيام بخطوة صغيرة أخيرة، وهي في جعل تطبيقنا يُنشئ كائن من صنفنا الجديد MainPage وذلك ضمن الملف App.cs. انتقل إلى هذا الملف (موجود ضمن المشروع (SimpleTextApp (Portable واحرص على أن تكون بانية الصنف App على الشكل التالي: public App() { // The root page of your application MainPage = new MainPage(); } اضغط الآن المفتاح F5 لتنفيذ التطبيق، لتحصل على شكل شبيه بما يلي: ملاحظة يمكنك وصل جهازك المحمول الذي يعمل بنظام أندرويد إلى الحاسوب لتجريب تشغيل التطبيق عليه مباشرةً. ولكن في هذه الحالة ينبغي أن تتوفّر واجهة برمجيّة API منصّبة على حاسوبك مناسبة لنسخة أندرويد الموجودة على الجهاز المحمول. على أية حال يمكنك تنصيب هذه الواجهة في حال عدم وجودها من خلال Android SDK Manager كما أشرنا في الدرس السابق. كما أنصح بوصل الجوال قبل تشغيل بيئة التطوير Visual Studio، أو أن تعيد تشغيلها إذا كانت تعمل من قبل. تنسيق الشيفرة البرمجية بشكل أفضل في الحقيقة لا تُعتبر الشيفرة البرمجيّة الموجودة ضمن الملف MainPage.cs مثاليّةً رغم بساطتها. ربما لا تشعر بمشكلة الآن، ولكن عندما تصبح برامجك أكبر، ستقع في مشاكل حقيقيّة لأنّ الأسلوب السابق بكتابة الشيفرة يجعلها كبيرة جدًّا. سأستخدم ميّزة الإسناد المباشر للخصائص أثناء إنشاء الكائنات. تفيد هذه الميزة في الاستغناء عن معظم المتغيّرات في البرنامج، التي ينحصر دورها في لعب دور الوسيط المؤقّت. سنعيد صياغة الشيفرة الموجودة في الملف MainPage.cs بشكل أنيق وعمليّ أكثر: using Xamarin.Forms; namespace SimpleTextApp { class MainPage : ContentPage { public MainPage() { this.Content = new Label{ Text = "Welcome to Hsoub Academy!" }; } } } لاحظ أنّني استغنيت عن المتغيّر lblMain لأنّنا أسندنا كائن اللُّصيقة مباشرةً إلى الخاصيّة Content. مزايا إضافية على التطبيق ستلاحظ من التطبيق السابق أنّ اللُّصيقة تظهر في الزاوية اليسرى العليا من النافذة. سنجعلها تظهر في مركز الشاشة. ينبغي ضبط خاصيّتين من خصائص اللُّصيقة لهذا الغرض، وهما HorizontalOptions (لخيارات المحاذاة الأفقيّة) و VerticalOptions (لخيارات المحاذاة الرأسيّة). كل من هاتين الخاصيّتين من النوع LayoutOptions، وهي بنية struct موجودة ضمن نطاق الاسم Xamarin.Forms. تحتوي هذه البنية على عدّة حقول جاهزة تُفيد في خيارات التموضع الأفقيّة والرأسيّة للُّصيقة، وهي خيارات مهمّة للغاية كما سنرى لاحقًا في هذه السلسلة. لنجري الآن التعديل المطلوب على اللُّصيقة الموجودة ضمن بانية الصنف MainPage. ستصبح محتويات الملف MainPage.cs على الشكل التالي: using Xamarin.Forms; namespace SimpleTextApp { class MainPage : ContentPage { public MainPage() { this.Content = new Label{ Text = "Welcome to Hsoub Academy!", HorizontalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center }; } } } لاحظ إسناد القيمة LayoutOptions.Center لكل من الخاصّتين HorizontalOptions و VerticalOptions وذلك للتوسيط الأفقي والرأسي على الترتيب. نفّذ البرنامج مرّة أخرى ستلاحظ أنّ اللُّصيقة أصبحت في مركز الشاشة كما هو واضح من الشكل التالي: أمّا إذا أحببت أن يكون النص ضمن اللُّصيقة بلون آخر، وليكن الأحمر مثلًا، فعليك في هذه الحالة إسناد القيمة Color.Red للخاصيّة TextColor لكائن اللُّصيقة، علمًا أنّ Color هو بنية موجودة ضمن نطاق الاسم Xamarin.Forms أيضًا. انظر كيف نفعل ذلك في الشيفرة: this.Content = new Label{ Text = "Welcome to Hsoub Academy!", HorizontalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center, TextColor = Color.Red }; نفّذ البرنامج مرّة أخرى، لترى النص وقد أصبح لونه أحمر. الخلاصة تعرّفنا في هذا الدرس على البنية العامة لتطبيق Xamarin ضمن بيئة التطوير Visual Studio 2015. كما قمنا ببناء تطبيق بسيط للغاية أسميناه SimpleTextApp. تنحصر وظيفة هذا التطبيق في التّعرف أكثر على Xamarin. سنبدأ اعتبارًا من الدرس القادم ببناء تطبيقات متدرّجة في الصعوبة، وذات مزايا إضافيّة من الممكن أن تستفيد منها كمبرمج تطبيقات محمولة في حياتك العمليّة.
  18. هل تستخدم trigger ضمن قاعدة البيانات مرتبط بالجدول الذي تحدثه؟
  19. شكرًا لك أخ خليل. بالفعل تبدو كلمة المفوّض معبّرة أيضًا. علمًا أنّني لم أستخدم كلمة التنويب.
  20. كأن الصورة التي أرفقتها لا تعبر عن الاستعلام الموجود اعلاها. هل من الممكن تزويدنا بالاستعلام الذي يعطي النتيجة الظاهرة في الصورة؟
  21. تُعتبر منصّة Xamarin في الوقت الحالي، من أهمّ منصّات تطوير تطبيقات الأجهزة المحمولة المتوفّرة، والتي يبدو أنّها ستبقى على الواجهة لوقت ليس بالقليل، خصوصًا بعد استحواذ شركة مايكروسوفت على الشركة المنتجة لها والتي تحمل أيضًا نفس الاسم (Xamarin). قد تكون متحفّزًا للدخول في عالم تطوير تطبيقات الأجهزة المحمولة (كما كنت عندما تعرّفت للمرّة الأولى على Xamarin) ومستعدًّا لكتابة الشيفرة مباشرةً، ولكن لنصبر قليلًا، ولنفهم بدايات الأمور، ومبدأ عمل Xamarin بشكل أفضل ضمن هذا المقال، الذي يُعدّ الأوّل في سلسلة مقالات سترشدك إلى كيفيّة تطوير تطبيقات باستخدام منصّة Xamarin مع إنشاء تطبيقات عمليّة من خلالها. مقدمة تمّ تأسيس شركة Xamarin (وتُقرأ زامارين) في عام 2011، من قِبَل نفس المهندسين الذين صمّموا مشروع Mono. ومشروع Mono وإخوانه مثل Mono for android و Mono Touch، هي عبارة عن منصّات لتشغيل تطبيقات مكتوبة بلغة سي شارب C# على أنظمة التشغيل: Linux و Android و iOS على الترتيب. يمكن باستخدام Xamarin إنشاء تطبيقات أصليّة Native Apps لأجهزة Android و iOS و Mac وويندوز بلغة برمجة ليست معتمدة رسميًا بالنسبة إليها. سنركّز في هذه السلسلة على بناء تطبيقات تعمل على نظام التشغيل Android. لمن هذه السلسلة؟ إذا كان لديك إلمام جيّد بلغة سي شارب ومكتبة الأصناف الأساسيّة BCL ضمن إطار العمل دوت نت بالإضافة إلى معرفة أوليّة بطريقة استخدام بيئة التطوير Visual Studio 2015، وتمتلك الشغف والصبر لتعلّم كيفيّة برمجة تطبيقات للأجهزة المحمولة باستخدام Xamarin فإنّ هذه السلسلة هي بالتأكيد لك. لدينا هنا في أكاديميّة حسوب سلسلة تعليميّة خاصّة بلغة سي شارب يمكن الاستفادة منها. الحاجة إلى Xamarin برزت الحاجة إلى Xamarin بسبب طبيعة تطبيقات الأجهزة المحمولة وعملها على أنظمة تشغيل مختلفة تعود إلى شركات متنافسة. توجد في الوقت الحالي ثلاثة أنظمة تشغيل مسيطرة على السوق بصورة متفاوتة وهي: iOS لشركة Apple وAndroid لشركة Google وWindows Phone لشركة Microsoft. تختلف هذه الأنظمة في العديد من النواحي، التي سنناقشها فيما يلي. تجربة المستخدم بالنسبة إلى تجربة المستخدم يوجد تشابه في هذه الأنظمة من ناحية تقديم الواجهات الرسوميّة للمستخدم والتفاعل مع الجهاز من خلال اللمس أو اللمس المتعدّد، ولكنّ هناك اختلافات في التفاصيل. فلكلّ نظام تشغيل وسائل مختلفة في التنقّل بين صفحات التطبيق، وفي تقديم البيانات، وفي العمل مع القوائم، وغيرها من التفاصيل الأخرى التي تتطلّب أن يسلك المطوّر developer منحىً مختلفًا من أجل كلّ نظام تشغيل. بيئات تطوير ولغات برمجة مختلفة أمّا بالنسبة للغات البرمجة وبيئات التطوير، فهذا أمر آخر. فلكلّ نظام تشغيل متطلّباته الخاصّة التي ألخّصها بشكل سريع فيما يلي: لإنشاء تطبيقات على أنظمة iOS فأنت تحتاج إلى إتقان لغة البرمجة Objective-C أو لغة البرمجة Swift. وأن تمتلك حاسوب MacBook (أي إصدار) مع بيئة التطوير Xcode. لإنشاء تطبيقات على أنظمة Android فستحتاج إلى لغة جافا Java مع بيئة التطوير Android Studio التي تعمل على العديد من أنظمة التشغيل. أمّا لإنشاء تطبيقات تعمل على Windows Phone أو Windows 10 Mobile فأنت بحاجة إلى لغة البرمجة سي شارب C# مع حاسوب يشغّل ويندوز، وإلى بيئة التطوير Visual Studio. واجهات برمجية مختلفة تعتمد جميع أنظمة التشغيل السابقة على واجهات تطبيق برمجيّة API مختلفة. مع أنّه يوجد بعض الشبه بالنسبة للكائنات المتعلّقة بواجهة المستخدم user-interface. فعلى سبيل المثال، جميع الأنظمة السابقة توفّر وسيلة للمستخدم لاختيار حالة منطقيّة Boolean والتي يمكن تمثيلها بـ True أو False ففي iOS يصنّف هذا الكائن على أنّه view اسمه UISwitch، أمّا في أندرويد فهو widget اسمها Switch، وفي ويندوز فهو control ويسمّى ToggleSwitch. الحل الذي توفره Xamarin يمكن تجاوز جميع النقاط السابقة من خلال Xamarin لأنّها توفّر لغة برمجة وحيدة وهي سي شارب يمكن استخدامها لكتابة تطبيقات على أيّ نظام تشغيل. كما أنّها توفّر بيئة تطوير متقدّمة ووحيدة وهي Visual Studio 2015 لكتابة هذه التطبيقات (يمكن استخدام بيئة التطوير Xamarin Studio أيضًا بالنسبة لنظام Mac). بالإضافة إلى أنّها وحّدت الواجهات البرمجيّة المختلفة API ضمن واجهة برمجيّة وحيدة يتعامل معها المطوّر. لغة سي شارب غنيّة عن التعريف. فهي لغة قويّة ومرنة وغنيّة جدًا ودائمة التطوير. لقد أصبحت بحق من أكثر لغات البرمجة تطوّرًا وحداثةً. ولسنا هنا بصدد تفضيل لغة برمجة على أخرى، ولكن بحسب خبرتي الشخصيّة، واطّلاعي على العديد من لغات البرمجة الأخرى، تستطيع القول بأنّ سي شارب تحتلّ موقعًا مرموقًا بينها. يستطيع المطوّرون من أجل تطبيق محدّد، كتابة شيفرة واحدة مشتركة لجميع أنظمة التشغيل السابقة بدون أيّة تعديلات تُذكر عليها فيما يتعلّق بمنطق العمل business logic داخل التطبيق وبما يتضمنّه من عمليّات برمجيّة لا تتعلّق بنوع الجهاز المحمول أو نظام التشغيل الذي يعمل عليه. نسمّي هذه الشيفرة بالشيفرة المستقلة عن نظام التشغيل platform independent. أمّا إذا تطلّب الأمر من التطبيق أن يتعامل مع العتاد الصلب للجهاز الذي يعمل عليه (مثل الكاميرا أو حسّاس GPS مثلًا)، فيمكن عندها كتابة أجزاء من الشيفرة التي تراعي خصوصيّة كل نظام تشغيل، نسمّي مثل هذه الشيفرة بالشيفرة المرتبطة بنظام التشغيل platform dependent. المكونات الرئيسية لمنصة Xamarin ركّزت شركة Xamarin منذ نشأتها على التكنولوجيا الخاصّة بالمترجمات Compilers. وقد أصدرت الشركة ثلاث مجموعات أساسيّة من مكتبات دوت نت .NET وهي: Xamarin.Mac و Xamarin.iOS و Xamarin.Andorid التي تشكّل بمجموعها منصّة Xamarin. تسمح هذه المكتبات للمطوّرين بكتابة تطبيقات أصليّة native apps لكلّ من أنظمة التشغيل الموافقة. ذكرنا قبل قليل أنّ هناك جزء من شيفرة التطبيق البرمجيّة يتكرّر عادةً من أجل كل نظام تشغيل، يمكن عزل هذا الجزء ووضعه ضمن مشروع منفصل في برنامج Visual Studio. لهذا المشروع نوعين: مشروع الأصول المشتركة Shared Asset Project أو اختصارًا SAP. وهو عبارة عن مجموعة من ملفات الشيفرة بالإضافة إلى ملفّات الأصول assets مثل الصور وغيرها الموجودة ضمن المشروع والتي يمكن مشاركتها مع باقي المشاريع الأخرى ضمن نفس الحل Solution في Visual Studio. مكتبة الأصناف المحمولة Portable Class Library أو اختصارًا PCL. وهي تضمّ الشيفرة التي نرغب بمشاركتها وذلك ضمن مكتبة ربط ديناميكي dynamic link library أو اختصارًا DLL. سنركّز في هذه السلسلة على هذا النوع. أمّا بالنسبة للشيفرة التي تتعلّق بنظام التشغيل، فتوضع ضمن مشروع منفصل ضمن Visual Studio لكلّ نظام تشغيل نرغب بأن ندعمه. ففي الحالة العامّة، يمكن أن يكون لدينا ثلاثة مشاريع منفصلة لكل من Android و iOS و Windows Phone. تستفيد جميعها من الشيفرة المشتركة والمستقلّة عن نظام التشغيل الموجودة في PCL أو SAP. وبالمجمل تكون جميع تلك المشاريع ضمن نفس الحل Solution ضمن Visual Studio. انظر إلى الشكل التالي الذي يوضّح كيفيّة تفاعل التطبيق المكتوب لأنظمة التشغيل المختلفة مع مكوّنات Xamarin: يمثّل الشكل السابق تطبيقًا واحدًا. نلاحظ في السطر الثاني الأشكال المختلفة لهذا التطبيق على أنظمة التشغيل المختلفة. انظر كيف تتفاعل هذه الأشكال المختلفة مع مشروع PCL أو SAP للاستفادة من الشيفرة المشتركة التي لا تتعلّق بنظام تشغيل محدّد. انظر إلى تطبيق iOS مثلًا كيف يتفاعل أيضًا مع المكتبة Xamarin.iOS التي توفّر وسيلة للربط bindings بينه وبين واجهة التطبيقات البرمجيّة API لنظام التشغيل iOS. ينطبق نفس الأمر تماماً بالنسبة لتطبيق أندرويد. الاستثناء الوحيد هو بالنسبة لتطبيق ويندوز الذي لا يحتاج إلى وسيط (وسيلة للربط) في هذه الحالة، حيث يمكنه الاتصال مباشرةً مع واجهة API لنظام التشغيل ويندوز (بصورة أدق Windows Phone). نماذج Xamarin ابتكرت شركة Xamarin في عام 2014 ما يُعرف بنماذج Xamarin أو Xamarin Forms. تسمح هذه المنصّة للمطوّرين بأن يكتبوا شيفرة برمجيّة لواجهة المستخدم user interface بحيث يمكن تحويل هذه الشيفرة مباشرةً إلى تطبيقات تعمل على أجهزة أندرويد و iOS وويندوز. في الحقيقة أصبحت Xamarin Forms تدعم إنشاء تطبيقات عامّة Universal Windows Platform على الأجهزة التي تشغّل نسخ ويندوز المختلفة مثل Windows Phone و Windows 10 و Windows 10 Mobile و Windows 8.1 و Windows 8.1 Phone. بالنسبة لبنية الحل Solution في Visual Studio فلن يتغيّر كثيرًا، باستثناء أنّ المشاريع المنفصلة الخاصّة بأنظمة التشغيل السابقة ستكون صغيرة على نحو ملحوظ بسبب وجود كميّة قليلة من الشيفرة البرمجيّة ضمنها. سيتضمّن مشروع PCL أو SAP في هذه الحالة الشيفرة المشتركة والمستقلّة عن أنظمة التشغيل كما اتفقنا على ذلك من قبل، بالإضافة إلى الشيفرة البرمجيّة المسؤولة عن الإظهار والتعامل مع واجهة المستخدم. أي أنّ منصّة Xamarin Forms تسمح لنا بكتابة شيفرة برمجيّة واحدة تعمل مباشرةً على أنظمة التشغيل المختلفة. انظر الشكل التالي لفهم آليّة عمل التطبيقات التي تعتمد Xamarin Forms. تعتمد التطبيقات المكتوبة لأنظمة التشغيل المختلفة في هذه الحالة على مشروع PCL أو SAP بشكل كليّ في التواصل مع الواجهات البرمجيّة API. وهكذا من الممكن في الكثير من التطبيقات كتابة شيفرة واحدة فقط تعمل على جميع الأجهزة! باستثناء الحالات التي يكون من الضروري فيها كتابة شيفرة مخصّصة لنظام تشغيل محدّد. في المستقبل قد يتغيّر هذا الأمر، فقد يكون من الممكن كتابة شيفرة واحدة فقط تعمل على جميع الأجهزة مهما كان نوع هذه الشيفرة. سنتحدّث في هذه السلسلة عن Xamarin Forms بشكل أساسيّ. كيفية عمل Xamarin بالنسبة لتطبيقات iOS فيعمل مترجم سي شارب الخاص بـ Xamarin على ترجمة الشيفرة البرمجيّة إلى لغة مايكروسوفت الوسيطيّة MSIL، ثمّ يُستخدم مترجم Apple على نظام تشغيل Mac لتوليد رُماز أصليّ native code يعمل على iOS كما لو أنّه تطبيق مكتوب بلغة Objective-C تمامًا. أمّا بالنسبة لتطبيقات Android، فسيولّد المترجم أيضًا لغة MSIL التي ستعمل في هذه الحالة على بيئة تنفيذ مشتركة CLR مخصّصة للعمل على أندرويد. وستكون التطبيقات الناتجة في هذه الحالة تشبه أيضًا إلى حدّ كبير تلك المنشأة باستخدام لغة جافا وبيئة التطوير Android Studio الخاصّة بأندرويد. وأخيرًا بالنسبة للتطبيقات التي تعمل على Windows Phone و Windows 10 Mobile، فالتطبيقات مدعومة بشكل واضح، وستعمل كالتطبيقات الأصليّة المنشأة باستخدام Visual Studio بدون استخدام Xamarin. الخلاصة منصّة Xamarin واعدة، ولها تاريخ عريق وتجارب غنيّة من قبل أن تظهر شركة Xamarin إلى الوجود. ولعلّ مايكروسوفت قد أدركت الأهميّة الكبيرة لها، فتمّت عمليّة الاستحواذ التي كانت متوقعّة. أنصحك بأن تبادر إلى تعلّم Xamarin وخصوصًا في حال كنت مبرمج سي شارب، أو لديك معلومات أوليّة عنها. ولعلّ الأيّام القادمة قد تحمل المزيد من الدعم والمفاجآت لمنصّة Xamarin التي كان أوّلها هو جعلها مجّانيّة للاستخدام الشخصيّ أو للفرق البرمجيّة الصغيرة. سنبدأ اعتبارًا من الدرس القادم في هذه السلسلة التعليميّة، تنصيب برنامج Visual Studio 2015 وبدء العمل مع Xamarin.
  22. يُعتبر مبدأ المسؤوليّة الواحدة Single Responsibility Principle (أو اختصارًا SRP) المبدأ الأوّل من مبادئ التصميم SOLID، وهو مفيد بصورة خاصّة في التصميم كائنيّ التوجّه object-oriented design. يعتمد هذا المبدأ على تجزئة مكوّنات النظام البرمجي بحيث يكون لكلّ جزء منه مهمّة (مسؤوليّة) واحدة ووحيدة. ينص هذا المبدأ على ما يلي: لا يظهر من النص السابق أي إشارة مباشرة إلى المسؤوليّة الواحدة. لتوضيح الربط بين المسؤولية الواحدة وبين نص المبدأ السابق، لننظر إلى المثال التالي الذي يحوي صنفًا مكتوبًا بلغة ++C ويُستخدم للاتصال بخادوم قواعد بيانات MySQL. اسم هذا الصنف MySQL أيضًا، ويمتلك واجهة لتأسيس الاتصال مع خادوم MySQL وإغلاقه، وإرسال استعلامات SQL إلى الخادوم واستقبال ومعالجة النتائج: class MySQL { public: bool connect(); void disconnect(); bool executeQuery(std::string queryString); MySQLResult* getQueryResult(); }; من الواضح أنّ لهذا الصنف مهمّتان أساسيّتان، الأولى هي إدارة عملية الاتصال مع خادوم MySQL (فتح وإغلاق الاتصال) والثانية هي التواصل مع الخادوم في إجراء الاستعلامات واستقبال النتائج (تنفيذ استعلامات SQL). لو افترضنا الآن حدوث السيناريو التالي: أصبح خادوم MySQL يقبل الاتصالات المشفّرة فقط. حدثت بعض التغييرات ضمن الخادوم بحيث أنّه بدأ بالاستجابة بشكل مختلف لبعض الاستعلامات. سيؤدي ذلك بالطبع إلى حدوث تغييرين ضمن صنف MySQL السابق، أو بمعنى آخر، سيكون هناك سببان لتغيير الصنف MySQL. ويُعدّ هذا خرقًا لمبدأ المسؤولية الواحدة كما هو واضح. يُعتبر وضع أكثر من مهمّة قابلة للتغيير (من أجل سبب ما) لأحد الأصناف خطأً تصميميًّا. قد لا تبدو تلك مشكلةً في الوقت الحالي، ولكن أيّ نظام برمجي يتغيّر ويتطوّر. فما يبدو حلًّا مقبولًا في الوقت الحاضر، قد يُفضي إلى نتائج سيّئة في المستقبل. يمكن استخدام الحل التالي لمشكلتنا السابقة: class MySQLConnection { public: bool open(); /* former connect() */ void close(); /* former disconnect() */ }; class MySQLQuery { MySQLConnection* session; public: bool execute(std::string queryString); MySQLResult* getResult(); }; يبدو مبدأ المسؤوليّة الواحدة بسيطًا، ولكنّه في الحقيقة صعب التطبيق. والسبب في ذلك، هو أنّ وضع المسؤوليّات المتعدّدة لصنف ما معًا، هو أمر بديهي ومألوف بالنسبة إلينا، أمّا عملية الفصل والتجزئة إلى أصناف أصغر لكلٍّ منها مسؤوليّة واحدة، فقد لا تبدو جذّابةً أوّل الأمر. بالنسبة لي، عندما عدت وراجعت بعض تصميمات الأصناف القديمة لديّ، قلّما وجدت صنفًا من الممكن جعله يراعي هذا المبدأ. ولكن عندما أمعنت النظر والتفكير وجدت أنّ الفصل في المهام سيقلّل من تعقيد التصميم، وسيجعل الشيفرة أيسر للقراءة والفهم. بالمقابل، فإنّ تطبيق هذا المبدأ بشكل صارم، ليس فكرةً جيّدة. فعلى المرء أن يكون حكيمًا في تحديد متى يمكن تطبيق هذا المبدأ، وخصوصًا عندما يجد أنّ الملف الخاص بأحد أصنافه بات يحتوي على أكثر من 500 سطر من الشيفرة البرمجيّة! ترجمة -وبتصرّف- للمقال Single Responsibility Principle لصاحبه Radek Pazder.
  23. نتابع عملنا في سلسلة تنصيب شير بوينت بتجهيز خادوم التطبيق والويب. يُعتبر خادوم التطبيق والويب الخادوم الأخير في مزرعة خواديم شير بوينت ذات الطبقتين التي تحدّثنا عنها في درس فهم البنية الهيكليّة لشير بوينت. من الملاحظ أنّ هذا الخادوم سيلعب دورين: دور خادوم تطبيقات Application Server ودور خادوم ويب Web Server. يُعتبر شير بوينت نهمًا للعتاد الصلب بحق. تنصح مايكروسوفت أن يحتوي هذا الخادوم على معالج رباعي النوى بمعمارية 64 بت، مع ذاكرة وصول عشوائي لا تقل عن 24 جيحا بايت، على فرض أنّنا نريد تشغيل جميع الخدمات التي تأتي مع شير بوينت. في الحقيقة يمكنك استخدام متطلّبات عتاديّة أقل في حال كنت تريد تجريب شير بوينت فحسب، ولكن في بيئة العمل الحقيقيّة أنصحك بأن تلتزم بنصائح الشركة المصنّعة لهذا التطبيق للوصول إلى بيئة عمل مستقرّة تضمن تشغيل شير بوينت من قِبَل المستخدمين بشكلٍ سلس وسريع. تنصيب نظام التشغيل وتغيير اسم الخادوم وعنوانه سنعمل على تنصيب نظام التشغيل Windows Server 2012 R2 كما وسبق أن فعلنا في هذا الدرس مع إجراء التحديثات اللازمة له من الإنترنت. بعد الانتهاء انقر زر ابدأ لتصل إلى صفحة البداية، ثم انقر بزر الفأرة الأيمن على اللوح This PC ثم اختر Properties من القائمة. بعد أن تظهر نافذة الخصائص لهذا الخادوم انقر زر Change Settings لتظهر نافذة أخرى انقر منها الزر Change كما مرّ معنا سابقًا. ستظهر النافذة التالية، غيّر اسم هذا الخادوم ضمن الحقل Computer name ليصبح HSB-SP: انقر زر موافق، واقبل إعادة التشغيل. بعد الانتهاء من عمليّة إعادة التشغيل، سجّل الدخول إلى ويندوز، ثم انتقل إلى صفحة البداية (بنقر الزر ابدأ) واكتب مباشرةً: Network and Sharing Center انقر على النتيجة التي ستظهر، وبعد ظهور نافذة مركز الشبكة والمشاركة، انقر على الاتصال المناسب من الجهة اليمنى، ستظهر نافذة الحالة لهذا الاتصال، انقر منها الزر Properties لتظهر نافذة جديدة تمثّل خصائص هذا الاتصال. اختر البروتوكول Internet Protocol Version 4 (TCP/IPv4) ثم انقر الزر Properties من الأسفل. أدخل عنوان IP لهذا الخادوم: 192.168.25.5، واحرص على أن تكون باقي العناوين مطابقة لما هو موجود في الشكل التالي: يمكننا الآن ضمّ هذا الخادوم إلى المجال hsoub-sp.com بخطوات مماثلة تمامًا لما فعلناه في هذا الدرس (راجع فقرة ضمّ الخادوم إلى المجال hsoub-sp.com). بعد إعادة التشغيل وظهور نافذة تسجيل الدخول، سجّل الدخول إلى المجال hsoub-sp.com بدلًا من تسجيل الدخول إلى الخادوم المحلّي فحسب، وذلك عن طريق نقر زر السهم الصغير الموجود في الناحية العلوية اليسرى، ثم اختيار Other User. ستظهر نافذة لإدخال بيانات تسجيل الدخول كما في الشكل التالي: لاحظ وجود العبارة Sign in to: HSOUB-SP وهذا مؤشّر جيّد إلى أنّ عمليّة الضم إلى المجال hsoub-sp.com قد نجحت تمامًا. اضغط المفتاح Enter لتسجيل الدخول باسم مدير المجال SPAdmin. تنزيل نسخة تجريبيّة من SharePoint Server 2013 يمكنك تنزيل نسخة تجريبيّة من SharePoint Server 2013 من هنا. حيث ستحصل على شكل شبيه بما يلي. لاحظ أنّ المدّة التجريبيّة المتاحة هي 180 يوم: انقر الزر Sign in لتسجيل الدخول بأحد حسابات مايكروسوفت مثل حساب البريد الإلكتروني على هوتميل مثلًا. ستنتقل إلى الصفحة التالية التي تخبرك بأنّه عليك التسجيل أولًا كي تستطيع تنزيل شير بوينت: انقر زر Register to continue. في الحقيقة لا تتعدّى عمليّة التسجيل كونها عمليّة بسيطة لجمع معلومات حول الغرض الذي ستستخدم شير بوينت من أجله، حيث ستحصل على نموذج يتطلّب منك تعبئة بعض البيانات كما في التالي: ستحتاج إلى استخدام شريط التمرير للوصول إلى الزر Continue. انقر هذا الزر للانتقال إلى عمليّة التحميل. بعد ذلك ستصل إلى مرحلة التنزيل التلقائي، حيث سيتم تنزيل الملف SharePointServer_x64_ar-sa.img بشكل تلقائيّ. إذا لم يعمل التنزيل التلقائي فيمكنك نقر الزر Download لتنزيل الملف بشكل يدوي: بعد تنزيل هذا الملف يمكنك حرقه burning على قرص DVD ومن ثمّ استخدامه لتنصيب شير بوينت. تنصيب المتطلّبات الأوليّة لشير بوينت أدخل القرص الخاص بشير بوينت 2013 ليعمل برنامج التشغيل التلقائي AutoRun حيث ستحصل على الشكل التالي: انقر Run splash.hta لتشغيل برنامج الإعداد. سيعرض عليك ويندوز رسالة تأكيد اقبلها لتظهر نافذة برنامج الإعداد كما يلي: سيتمّ تنصيب شير بوينت على مرحلتين. المرحلة الأولى ستكون عبارة عن تنصيب متطلّبات شير بوينت الأوليّة software prerequisites. أمّا المرحلة الثانية، فستكون تنصيب شير بوينت نفسه. احرص على وجود اتصال بالإنترنت ثم انقر Install software prerequisites لتبدأ عمليّة تنصيب المتطلّبات الأوليّة حيث ستظهر النافذة التالية لتخبرك بالمهام التي سيتمّ تنفيذها: انقر الزر Next لتبدأ هذه العمليّة، سيستغرق الأمر بعضًا من الوقت حتى تصل إلى النافذة التالية التي تطلب منك إعادة تشغيل الخادوم: انقر الزر Finish وسيعمل ويندوز على إعادة تشغيل الخادوم حيث ستجري عمليّة تطبيق بعض التحديثات على نظام التشغيل لديك كما في الشكل التالي: قد يستغرق هذا الأمر بعضًا من الوقت، وبعد إعادة التشغيل، سيخبرك ويندوز بأنّ البرنامج المسؤول عن تنصيب المتطلّبات الأوليّة لشير بوينت يحاول إجراء تغييرات على نظام التشغيل: انقر زر Yes. ثم تابع تسجيل الدخول إلى المجال hsoub-sp.com. سيكمل برنامج الإعداد عمله تلقائيًّا كما في الشكل التالي: بعد الانتهاء، سيطلب منك برنامج الإعداد إعادة التشغيل أيضًا. انقر زر Finish لإعادة التشغيل. بعد إعادة التشغيل وتسجيل الدخول إلى المجال hsoub-sp.com سيستأنف برنامج الإعداد عمله مجّددًا. وعند الانتهاء ستحصل على رسالة بأنّ عمليّة تنصيب المتطلّبات الأوليّة لشير بوينت قد تمّت بنجاح. انظر إلى الشكل التالي: انقر زر Finish لإنهاء هذه العمليّة، وبهذا نصبح مستعدّين للمرحلة الأخيرة وهي تنصيب شير بوينت. أنصح عند هذه النقطة البحث عن تحديثات جديدة لويندوز. الخلاصة جهّزنا في هذا الدرس خادوم التطبيق والويب، ونصّبنا المتطلّبات الأوليّة اللازمة لتنصيب شير بوينت. قد تبدو هذه العمليّة طويلة ومعقّدة. في الحقيقة قد تكون طويلة وصعبة بعض الشيء وتحتاج إلى خبرات جيّدة في العديد من المجالات التي ليس علاقة بشير بوينت بشكل مباشر. لكن بالممارسة الجيّدة والانتباه إلى دقائق الأمور ستضمن الوصول إلى نسخة مستقرّة من شير بوينت يمكن استثمارها بالشكل الأمثل. سنتابع عملنا في الدرس التالي، ونقوم بالتنصيب النهائي لشير بوينت.
  24. ستحتاج إلى وضع السمة OnItemDataBound="Fetcher" ضمن عنصر الـ repeater على الشكل التالي: <asp:Repeater ID="tasks_repeater" runat="server" OnItemDataBound="Fetcher"> <ItemTemplate> <asp:Panel ID="offerHelpVisitor_panel" runat="server"><asp:Panel> </ItemTemplate> </asp:Repeater> و Fetcher هنا هو اسم تابع يجب أن يكون موجودًا ضمن ملف الكود الخاص بالصفحة على الشكل التالي: public void Fetcher(object sender, RepeaterItemEventArgs e) { panels.Add((Panel)e.Item.FindControl("offerHelpVisitor_panel")); } أمّا بالنسبة للمتغيّر panels فهو من النوع List<Panel> حيث سيحتفظ بمرجع reference لكل عنصر Panel يتم توليده ضمن الـ repeater. عليك أن تصرّح عن هذا المتغيّر ضمن الصنف الخاص بالصفحة على الشكل التالي: private List<Panel> panels = new List<Panel>(); عند تحميل الصفحة سيتم استدعاء التابع Fetcher عددًا من المرّات مساويًا لعدد عناصر الـ Panel التي سيولّدها الـ repeater وبالتالي يمكن أن تصل إليها جميعها عن طريق المتغير panels.
×
×
  • أضف...