تطوير الفورم في الدوت نت باستخدام +GDI – الكلاس Skinner
اللغة المستخدمة: الفيجوال بيسك
التطبيق: فيجوال استوديو 2005 و 2008
المستوي: التقييم متروك للقارئ
إعداد: مهندس / عمر أمين إبراهيم
مقدمة
من المؤكد أننا جميعا قد لاحظنا كيف يبدو شكل الفورم في الويندوز فيستا ولقد أتاحت شركة مايكروسوفت لمستخدمي نظام الفيستا إمكانية التعديل علي ألوان الفورم والمقصد هنا تحديدا هو التعديل في ألوان الجزء الذي يطلق عليه Non Client Area وكما تعلمون جميعا فإن هذه الخاصية ليست مدعومة أو ليست متاحة لمصممي البرامج من خلال الفيجوال دوت نت و بما أن مايكروسوفت لم تسمح بالتعديل علي ألوان Non Client Area من خلال الفيجوال استوديو لذا علينا أن نصمم ذلك بأن نضيف خصائص الي برامجنا تتيح لنا التغيير في شكل الفورم.
عموما أو بشكل عام كل ذلك يحدث باستخدام GDI+ ومن المؤكد أننا جميعا قد قرأنا عن GDI+ أو علي الأقل نعلم معني هذه الكلمة ولنعطي فكرة سريعة عن ماهية هذه الكلمة أو معناها
كلمة GDI+ هي نسخة محسنة أو نسخة مطورة من GDI التي تمثل الثلاثة الأحرف الأولي لكلمة Graphics Device Interface و GDI+ هي جزء من نظام التشغيل في الويندوز إكس بي Windows XP وهي المسئولة عن التعامل مع الرسم والصور وفي الدوت نت يعتبر System.Drawing Namespace هو المسئول عن إعطاؤنا الإمكانيات للدخول الي GDI+ والتعامل معها لذلك أفترض أن القارئ الذي سيتابع هذا الموضوع لدية دراية أو خلفية مناسبة عن كيفية التعامل مع +GDI بالإضافة الي ما يمكن أن نطلق عليه Dot Net Drawing Library وهي مجموعة من الكلاس الموجودة داخل System Drawing Namespace وهي التي سوف تساعدنا في أن نتعامل مع كثير من الأشياء مثل الرسم و الصور و الفونت و التكست و الألوان و الأيقونات و أيضا الأشكال الهندسية المختلفة الي أخره من هذه الأشياء. إذن كيف نغير شكل الفورم؟ أو كيف نصنع شيئا مماثلا للفيستا فورم ؟ هذا هو هدفنا هنا من هذا الموضوع وسنحاول الإجابة علي هذا السؤال من خلال طرح ومناقشة مجموعة من الأفكار التي ستساهم بشكل كبير في مساعدتنا علي تصميم الفورم
رسم الفورم
كما تعلمون فإن الرسم علي الفورم يحدث داخل الحدث Paint أو عن طريق عمل Override للحدث OnPaint أو للحدث OnPaintBackground وليس شرطا أن نستخدم أي من هذه الأحداث مباشرة ولكن أيضا يمكننا أن نرسم علي الفورم من أي مكان نريده داخل الكود و مجموعة الأمثلة التالية توضح بعض الأفكار عن كيفية الرسم داخل الفورم
المثال الأول: وهنا سوف نرسم مستطيل داخل الفورم ولتنفيذ المثال افتح مشروع جديد وفي داخل الحدث Paint الخاص بالفورم ضع الكود التالي
Dim rect As New Rectangle(0, 0, 100, 100)
Dim sb As New SolidBrush(Color.Aqua)
e.Graphics.FillRectangle(sb, rect)
End Sub
Private Sub Form1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
ولنلقي نظرة علي الكود أعلاه وهو كود بسيط جدا حيث هنا نرسم مستطيل ما حيث عرض المستطيل = 100 وطول المستطيل يساوي 100 ونقطة بداية رسم المستطيل علي الفورم هي النقطة صفر وصفر وهي النقطة التي تقع في اعلي يسار الفورم ثم قمنا بتعريف فرشاة SolidBrush حيث سنستخدمها في تلوين المستطيل باللون المطلوب ثم رسمنا المستطيل داخل الفورم
المثال الثاني: وهنا سوف نقوم بتعريف مستطيلا واحدا ثم سنرسم هذا المستطيل في مكانان مختلفان داخل الفورم ولتنفيذ المثال افتح مشروع جديد وفي داخل الحدث Paint الخاص بالفورم ضع الكود التالي
Dim rect As New Rectangle(0, 0, 100, 100)
Dim sb As New SolidBrush(Color.Aqua)
e.Graphics.FillRectangle(sb, rect)
e.Graphics.TranslateTransform(100, 100)
e.Graphics.FillRectangle(sb, rect)
e.Graphics.ResetTransform()
End Sub
Private Sub Form1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
ولنلقي نظرة علي الكود أعلاه وهو أيضا كود بسيط جدا حيث هنا نرسم مستطيل ما حيث عرض المستطيل = 100 وطول المستطيل يساوي 100 ونقطة بداية رسم المستطيل علي الفورم وهي ما يمكن أن نطلق عليها نقطة الأصل وهي النقطة صفر وصفر ثم قمنا بتعريف فرشاة SolidBrush حيث سنستخدمها في تلوين المستطيل باللون المطلوب ثم رسمنا المستطيل داخل الفورم ثم بعد ذلك نقلنا نقطة الرسم علي الفورم الي النقطة 100 و 100 وذلك باستخدام TranslateTransform ثم رسمنا نفس المستطيل بنفس الأبعاد ولكن نقطة الرسم هنا هي النقطة 100 و 100ثم استخدمنا ResetTransform وذلك لكي نعود الي نقطة الأصل علي الفورم وهي النقطة صفر وصفر ولماذا نفعل ذلك لأننا لو حاولنا الاستمرار في الرسم بدون عمل ResetTransform فإن الرسم سيتم ولكن نقطة الصفر الجديدة ستكون هي النقطة 100 و 100
المثال الثالث: وهو صورة أخري للكود أعلاه ولكننا فقط رسمنا المستطيل مائلا بزاوية 30 وما أريد توضيحه هنا هو أننا عندما نستخدم RotateTransform أو TranslateTransform يجب أن لا ننسي أبدا أن نستخدم ResetTransform لكي نعود دائما بالرسم الي نقطة الأصل ولتنفيذ المثال افتح مشروع جديد وفي داخل الحدث Paint الخاص بالفورم ضع الكود التالي
Dim rect As New Rectangle(0, 0, 100, 100)
Dim sb As New SolidBrush(Color.Aqua)
e.Graphics.FillRectangle(sb, rect)
e.Graphics.TranslateTransform(100, 100)
e.Graphics.RotateTransform(30)
e.Graphics.FillRectangle(sb, rect)
e.Graphics.ResetTransform()
End Sub
Private Sub Form1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
المثال الرابع: وبما أننا هنا نتحدث هنا عن تطوير الفورم لذا لنكتب نفس الكود الموجود في المثال الثالث أعلاه بطريقة أخري بحيث نربط الكود مع أبعاد الفورم نفسه مثل عرض الفورم مثلا وأيضا بدلا من استخدام SolidBrush لنستخدم LinearGradientBrush ثم نرسم الفورم في نقطة الأصل وهي النقطة التي تقع في أقصي يسار أعلي الفورم ثم بعد ذلك و بدلا من استخدام TranslateTransform سنقوم بتعريف Matrix أخري لعكس اتجاه الرسم بحيث نجعل نقطة الصفر للرسم تبدأ من أقصي يسار أسفل الفورم ثم نرسم المستطيل مرة أخري ولتنفيذ المثال افتح مشروع جديد وفي داخل الحدث Paint الخاص بالفورم ضع الكود التالي
Dim rect As New Rectangle(0, 0, Me.ClientRectangle.Width, 24)
Dim lgb As New Drawing2D.LinearGradientBrush(rect, Color.Blue, Color.Aqua, 90, True)
Dim m_matrix As New Drawing2D.Matrix(1, 0, 0, -1, 0, Me.ClientRectangle.Height)
e.Graphics.FillRectangle(lgb, rect)
e.Graphics.Transform = m_matrix
e.Graphics.FillRectangle(lgb, rect)
e.Graphics.ResetTransform()
End Sub
Private Sub Form1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
سنلاحظ أثناء تجربة الكود الموجود بالمثال أعلاه وتحديدا عند محاولة تكبير الفورم أو تغيير أبعاده أن الفورم لا يعيد رسم نفسه بالشكل الصحيح لذلك سنقوم بتعديل الكود أعلاه قليلا بحيث نجبر الفورم علي إعادة رسم نفسه مرة أخر عند تغيير أبعاده لذلك وفي الحدث Resize علينا أن نستخدم Invalidate وذلك لكي نجبر الفورم علي رسم نفسه بالشكل الصحيح والكود سيكون كالتالي
Invalidate()
End Sub
Private Sub Form1_Resize(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Resize
ملحوظة هامة:
لو قمت عزيزي القارئ بتغيير قيمة Opacity للفورم وجعلتها مثلا 85 % ستكتشف شيئا طريفا جدا وهو أن الفورم وبالرغم من أنه يعيد رسم نفسه حيث أننا استخدمنا Invalidate في الحدث Resize ولكن عند تغيير قيمة Opacity ستلاحظ أن الفورم لا يرسم المستطيلات التي حددناها سابقا بالضبط وخصوصا عندما يكون في حالة Maximize لذلك ستلاحظ فرق ضئيل جدا في أقصي اليمين لا يتم رسمه تماما وأيضا ستلاحظ أن الرسم يهتز قليلا أي أن هناك ما يسمي Flickering أثناء محاول تغيير أبعاد الفورم ولكي نتخلص من هذه الأشياء البسيطة كل ما علينا أن نفعله هو تغيير قيمة الصفة DoubleBuffered وهي عبارة عن Boolean بأن نجعل قيمتها تساوي True
أيضا لو قمت عزيزي القارئ بإضافة كنترول ما مثل Panel علي سبيل المثال الي الفورم ولنجعل قيمة الصفة Dock لهذه Panel تساوي Fill ثم نقوم بتجربة نفس المثال أعلاه سنكتشف وبالرغم من أننا رسمنا داخل الفورم فإن Panel وهي في حالة Dock = Fill قد قامت بتغطية ما رسمناه داخل الفورم ولحل هذه المشكلة البسيطة كل ما عليك أن تفعله هو أن تتلاعب قليلا بقيمة الصفة Padding الخاصة بالفورم ويتم ذلك إما عن طريق التعامل مع الفورم في مرحلة Design أو أننا نكتب ذلك في الحدث Load الخاص بالفورم
المثال الخامس: لنعيد صياغة الكود الموجود بالمثال الرابع ونجعله أكثر فعالية بحيث نرسم الشكل النهائي للفورم ولتنفيذ المثال افتح مشروع جديد ثم اكتب الكود بالشكل التالي
Dim rectTop As New Rectangle(0, 0, Me.ClientRectangle.Width, 24)
Dim rectbottom As New Rectangle(0, 0, Me.ClientRectangle.Width, 5)
Dim rectLeft As New Rectangle(0, 5, 5, Me.ClientRectangle.Height - 5)
Dim rectRight As New Rectangle(Me.ClientRectangle.Width - 5, 5, 5, Me.ClientRectangle.Height - 5)
Dim lgb As New Drawing2D.LinearGradientBrush(rectTop, Color.Maroon, Color.Red, 90, True)
Dim lgbt As New Drawing2D.LinearGradientBrush(rectLeft, Color.Maroon, Color.Red, 90, True)
Dim m_matrix As New Drawing2D.Matrix(1, 0, 0, -1, 0, Me.ClientRectangle.Height)
e.Graphics.Transform = m_matrix
e.Graphics.FillRectangle(lgbt, rectLeft)
e.Graphics.FillRectangle(lgbt, rectRight)
e.Graphics.ResetTransform()
e.Graphics.Transform = m_matrix
e.Graphics.FillRectangle(lgb, rectbottom)
e.Graphics.ResetTransform()
e.Graphics.FillRectangle(lgb, rectTop)
Dim ico As New Icon(Me.Icon, 16, 16)
Dim mFontSize As Single
mFontSize = Me.Font.Size
mFontSize += 2.0F
Dim mfont As New Font(Me.Font.Name, mFontSize, Me.Font.Style, Me.Font.Unit)
e.Graphics.DrawIcon(ico, rectTop.X + 2, 4)
e.Graphics.DrawString(Me.Text, mfont, New SolidBrush(Color.White), rectTop.X + 18, 4)
End Sub
Private Sub Form1_Resize(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Resize
Invalidate()
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Padding = New System.Windows.Forms.Padding(5, 24, 5, 5)
Me.DoubleBuffered = True
Me.Opacity = 0.85
Me.Left = (Screen.PrimaryScreen.WorkingArea.Width - Me.Width) / 2
Me.Top = (Screen.PrimaryScreen.WorkingArea.Height - Me.Height) / 2
Me.FormBorderStyle = Windows.Forms.FormBorderStyle.None
End Sub
Private Sub Form1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
اعتقد أن الكود اعلاه واضح فما هو إلا عبارة عن تجميع أفكار للكود التي كتباه في المثال الأول حتى المثال الرابع وعلي العموم الخطوات التالية نوضح سريعا فكرة الكود بالمثال الخامس
في الحدث Paint
تم تعريف مجموعة من المتغيرات وهي عبارة عن أربعة مستطيلات وهم علي التوالي rectTop و rectBottom و rectLeft و rectRight وتم ربط أبعاد هذه المستطيلات بأبعاد الفورم أو تحديدا مع المستطيل الذي يعبر عن أبعاد الفورم وهو ما يطلق عليه اسم ClientRectangle
تم تعريف متغيران عبارة عن فرشاة ألوان وهما من النوع LinearGradientBrush وتم استخدامهم في تلوين المستطيلات الأربعة
تم تعريف Matrix حيث ستستخدم في نقل نقطة الأصل للرسم لكي نرسم بعض المستطيلات في أماكنها علي الفورم
تم رسم المستطيلات الأربعة علي الفورم
وأخيرا تم رسم أيقونة الفورم وأيضا الفورم تكست
في الحدث Resize
تم استخدام Invalidate لكي نجبر الفورم علي إعادة رسم نفسه
في الحدث Load
تم تحديد بعض الصفات الخاصة بالفورم وتم تحديد قيمتها مثل DoubleBuffered و Opacity وأيضا مكان الفورم ليكون في وسط شاشة الكمبيوتر تحديدا وذلك عن طريق استخدام Me.Left و Me.Top ثم أخيرا قمنا بإلغاء FormBorder
لربما أن الفورم حتى هذه المرحلة قد يفي باحتياجات بعض القراء ولكن لنفكر قليلا ماذا نريد أيضا من الفورم لكي يظهر بشكل احترافي ولو قليلا اعتقد مبدئيا أننا نريد أن نغير Form Edges أو أطراف الفورم ونجعلها تأخذ شكل القوس ولو قليلا وأيضا يجب أن نعطي المستخدم إمكانية تحريك الفورم علي الشاشة وأيضا يجب أن نعطي المستخدم إمكانية إغلاق الفورم أو أيضا إمكانية وضع الفورم في Task Bar عندما يكون في الحالة Minimized مثلا ولربما أيضا نريد تتغير ألوان الفورم عندما يكون الماوس خارج حدود الفورم أو في حالة أن المستخدم قد ترك الفورم واستخدم برنامج أخر أعتقد انه مازال هناك الكثير من الأشياء التي يجب إضافتها الي الكود أعلاه لكي نجعل شكل الفورم أكثر إحترافية ولكن قبل أن نفعل كل ذلك لنسأل أنفسنا سؤالا هاما جدا وهو هل يجب علينا في كل مرة نصمم برنامجا ما أن نكتب مثل هذا الكود مع كل فورم ؟ من المؤكد أن ذلك ليس منطقيا والأفضل لنا أن نبني كلاس ما ثم يمكننا أن نضيفه الي أي مشروع بعد ذلك كنوع من Reference وهنا سيجب علينا فقط أن نكتب القليل جدا من الكود مع كل فورم وهذا بالتأكيد سيوفر علينا الكثير من الجهد والوقت. أو من الممكن أن نبني هذا الكلاس علي شكل Component يتم إضافتها الي الفورم وهذا بالتأكيد سيكون عمليا أكثر حيث هنا يمكن إضافته الي الفيجوال استوديو Toolbox ومن ثم يمكن أن نستخدمه وقتما نريد مع أي فورم
والأن لنبني معا مثل هذا الكلاس حيث سيكون هو النواة الأساسية التي من الممكن التطوير بها حينما نريد وأيضا يمكن استخدامه بشكل عام في تغيير شكل الفورم وسوف أطلق علي هذا الكلاس إسم Skinner مثلا أو يمكنك أنت عزيزي القارئ أن تختار أي اسم تريده للكلاس
يتبع في المشاركة التالية....................