إنشاء مصمم للشيتات، التقارير، الفواتير، ونحوها

This article is available in English too, check it out here.

المشروع: Geming.Sisc.msi

المحتويات

محتويات هذا الموضوع:

  • المحتويات
  • نظرة خاطفة
  • مقدمة
  • المشكلة Problem
  • المتطلبات Requirements
    • متطلبات المستخدم User Requirements
    • المتطلبات العملية Functional Requirements
  • الحل Solution
  • لقطات من المشروع Snapshot
  • المكونات Components
  • الأصناف Classes
  • تكوين قاعدة البيانات
  • حل المشكلات
  • نظرة على الأكواد
  • التحميل

نظرة خاطفة

اليوم لا نتكلم عن درس أو شرح لموضوع معين! اليوم معنا مثال بسيط عبارة عن برنامج يستطيع المستخدم من خلاله تصميم الشيتات والتقارير الخاصة به.

سيكون دأبنا في هذا الموضوع مختلف كثيرا عما تعودنا عليه. فسوف نبدأ بإذن الله تعالى بشرح فكرة المشروع ومتطلباته وكيف يقوم بحل المشكلة التي وضع لها. ثم بعد ذلك سوف نأخذ جولة سريعة في خلال الكود ونتعرف على الأفكار التي اعتمد عليها البرنامج. فالدرس اليوم هو عبارة عن دراسة جدوى للمشروع مبسطة إلى أبعد درجة (أقترح عليك ألا تعتمد عليها كمرجع :P.)

بالطبع البرنامج مفتوح المصدر يمكنك التعديل فيه كما تشاء.

مقدمة

معنا في هذا اليوم برنامج بسيط عبارة عن مصمم شيتات أو تقارير. أولا، يجب علينا أن نفصل ماذا نقصد بمصطلح “شيت Sheet”. نقصد بهذا المصطلح “شيت Sheet” هو كل ما يمكن عرضه أو طباعته من تقارير وفواتير وغيرها. فالشيت Sheet هو عبارة عن قالب Template للفاتورة مثلا والتي من خلال الكود نستطيع ملء بياناتها.

برنامجنا في هذا اليوم هو عبارة عن أداة بسيطة توفر للمستخدم فرصة إنشاء الشيتات الخاصة به عن طريق أدوات بسيطة مثل مربعات النصوص، العناوين، الصور، الأشكال الهندسية، وغيرها. البرنامج أو المثال هو فقط عينة بمعنى أنه مجرد مثال بسيط يشرح الفكرة وبإمكان المبرمج التطوير فيه أكثر وأكثر أو حتى يقوم بإنشائه بالطريقة التي تناسبه.

المشكلة Problem

المشكلة Problem الداعية إلى هذا المشروع هو أن العميل دائما مقيد بأشكال التقارير أو الشيتات التي يحددها المبرمج له. فالمطلوب هو جعل العميل قادرا على إنشاء قوالب Templates لتقاريره أو حتى التعديل في التقارير الحالية.

المتطلبات Requirements

المتطلبات Requirements، وقسمناها إلى متطلبات للمستخدم User Requirements والمتطلبات العملية Functional Requirements متطلبات المبرمج.

متطلبات المستخدم User Requirements

  • البرنامج يجب أن يسمح للمستخدم بعرض، إنشاء، تعديل، أو إزالة القوالب (الشيتات.) كما يجب أن يسمح أيضا بطريقة لتنظيم Grouping القوالب حسب نوعها (فواتير، تقارير عملاء، إلخ.)
  • لأجل البساطة في المشروع، المجموعات Groups سوف تكون مستوى واحد One-Level أي أنه لن يكون هناك مجموعات متداخلة أو تدرج Hierarchy في المجموعات.
  • البيانات يجب أن تكون دائمة، يجب على البرنامج حفظ هذه البيانات وعرضها متى طلبها المستخدم.
  • عندما يحاول المستخدم استخدام القالب في البرنامج، يجب على البرنامج ملء هذا بالبيانات المناسبة والصحيحة. فمثلا عند محاولة المستخدم طباعة فاتورة معينة باستخدام قالب معين، يجب على البرنامج ملء الخانات الخاصة بهذه الفاتورة بالبيانات الصحيحة.
  • كي يستطيع المستخدم تصميم القالب الخاص به، يجب أن يكون لديه أدوات (مربعات نصوص، عناوين، صور، إلخ) يقوم برسمها على الشاشة ويقوم البرنامج بملء هذه الأدوات بالبيانات المناسبة.
  • كي يستطيع البرنامج ملء الأدوات أو الخانات بالبيانات المناسبة سوف يكون لكل أداة أو خانة اسم وسوف يقوم المستخدم بربط الخانات مع البيانات. فهناك مثل خانة تسمى الاسم Name وخانة أخرى تسمى السن Age. فعند تشغيل البرنامج يتم ملأ هذه الخانات بالبيانات المناسبة.
  • يجب أن يكون هناك مسطرة كي يستطيع المستخدم قياس المسافات وتنظيم الأدوات بطريقة صحيحة.
  • يجب على المستخدم أن يكون قادرا على تعديل جميع بيانات القالب نحو الألوان وحجم الورق ونحوها.
  • يجب على برنامج التصميم أن يكون عاما بحيث يعمل في أي مكان أو مع أي نظام.
  • يجب أن يكون البرنامج قابلا للتفاعل مع بقية أجزاء النظام.
  • التجريد Abstraction لمكونات المشروع، وكذلك تنظيم المكونات حسب الارتباط.
  • القابلية للامتداد Extensibility وهذا لدعم التطوير المستقبلي للمشروع سواء من فريق العمل أو من فريق آخر.
  • جميع الأدوات والقالب نفسه هي عبارة عن أدوات Controls في المشروع ليتحقق عامل التكاملية مع بقية أفراد المشروع. فيقوم المبرمج بإضافة أداة التصميم إلى النافذة Form الخاصة به كي يوفر للمستخدم الإمكانيات المطلوبة.
  • تسمى الأدوات التي يرسمها المستخدم “أشكال Shapes.”
  • القالب Sheet هو عبارة عن حاوية Container للأدوات بداخله.
  • للتبسيط، سوف تسجل بيانات القالب Sheet والأدوات Shapes بصيغة XML في قاعدة البيانات، وسوف تكون هذه القاعدة SQL Server.
  • كل أداة داخل القالب يجب أن يكون لها اسما مميزا، تماما مثل الأدوات الخاصة بالنموذج Form.

المتطلبات العملية Functional Requirements

  • يجب على برنامج التصميم أن يكون عاما بحيث يعمل في أي مكان أو مع أي نظام.
  • يجب أن يكون البرنامج قابلا للتفاعل مع بقية أجزاء النظام.
  • التجريد Abstraction لمكونات المشروع، وكذلك تنظيم المكونات حسب الارتباط.
  • القابلية للامتداد Extensibility وهذا لدعم التطوير المستقبلي للمشروع سواء من فريق العمل أو من فريق آخر.
  • جميع الأدوات والقالب نفسه هي عبارة عن أدوات Controls في المشروع ليتحقق عامل التكاملية مع بقية أفراد المشروع. فيقوم المبرمج بإضافة أداة التصميم إلى النافذة Form الخاصة به كي يوفر للمستخدم الإمكانيات المطلوبة.
  • تسمى الأدوات التي يرسمها المستخدم “أشكال Shapes.”
  • القالب Sheet هو عبارة عن حاوية Container للأدوات بداخله.
  • للتبسيط، سوف تسجل بيانات القالب Sheet والأدوات Shapes بصيغة XML في قاعدة البيانات، وسوف تكون هذه القاعدة SQL Server.
  • كل أداة داخل القالب يجب أن يكون لها اسما مميزا، تماما مثل الأدوات الخاصة بالنموذج Form.

الحل Solution

كان الحل هو في مجموعة مكونات Components بسيطة تحقق أهداف ومتطلبات المشروع والتي سوف تسمى مكونات بناء القالب Sheet Infrastructure Components أو SISC كاختصار.

لقطات من المشروع Snapshots

الأشكال التالية هي لقطات من مشروعنا. الشكل الأول يوضح قالب فاتورة أثناء وقت التصميم، بينما الشكل الثالث يوضح نموذج لسيرة ذاتية Resume.

شكل 1 - نموذج فاتورة في SISC

شكل 1 - نموذج فاتورة في SISC

شكل 2 - نموذج سيرة ذاتية في SISC

شكل 2 - نموذج سيرة ذاتية في SISC

كما علمنا، عند تشغيل البرنامج وعند طلب المستخدم لذلك سوف تملأ الخانات بالبيانات المناسبة.

المكونات Components

المكونات Components الخاصة بمشروعنا:

شكل 3 - مكونات SISC

شكل 3 - مكونات SISC

هناك ثلاث مكتبات في مشروعنا:

  • Geming.Sisc.Infrastructure:
    وفيها جميع الأدوات الخاصة بالواجهة UI مثل الشيت (أداة بالتصميم) والأدوات (الأشكال Shapes) التي يتم إضافتها إلى هذا الشيت.
  • Geming.Sisc.Data:
    وهي المسؤولة عن ربط المشروع بقاعدة البيانات.
  • Geming.Sisc.Driver:
    وهو برنامج WinForms لتوضيح المشروع.

الأصناف Classes

قابلية الامتداد Extensibility هي واحدة من أهم المطالب في المشروع. ولهذا قمنا بتطوير العديد من الأصناف Classes والتي لكل واحدة منها هدفها الخاص وقمنا بمراعاة الـ Extensibility في هذه الأصناف عن طريق تطوير الدوال القابلة للتوريث Virtual وغيرها.

الشكل التالي يوضح التكوين العام للأدوات الرئيسية في المشروع:

شكل 4 - الأصناف الرئيسية في SISC

شكل 4 - الأصناف الرئيسية في SISC

كما نرى، جميع الأصناف نتحدر -بشكل مباشر أو غير مباشر- من الأداة System.Windows.Forms.Control والتي هي الأب لجميع الأدوات التي يمكن إضافتها إلى النموذج وتظهر عليه.

نلاحظ أيضا أن هناك مسطرتان، واحدة أفقية والثانية رأسية.

أما شكل 5 فيوضح تركيب وتكوين بقية العناصر:

شكل 5 - الأدوات في SISC

شكل 5 - الأدوات في SISC

كما لاحظنا لدينا العديد من الأدوات مشابهة كثيرا لأدوات الويندوز مثل الصورة مربع النص Text Box،الصورة Image، ومربع الاختيار Check Box.

جميع الأصناف أو العناصر Classes تم تطبيق الـ Interface المسماة System.Runtime.Serialization.ISerializable كي تتم عملية التحويل إلى XML بسهولة.

تكوين قاعدة البيانات

قاعدة البيانات هي قاعدة بسيطة جدا جدا، ويوضحها الشكل التالي:

شكل 6 - تصميم قاعدة البيانات

شكل 6 - تصميم قاعدة البيانات

نلاحظ أن العمود Shape.Value هو من نوع xml.

حل المشكلات

الآتي هو عبارة عن مشكلات واجهتنا أثناء التصميم وكيفية حل كل مشكلة:

  • الرسم

كيف سيتم رسم الأدوات؟ يتم ذلك عن طريق الإمكانيات التي توفرها لنا بيئة الدوت نت في عملية الرسم من العناصر الموجودة في System.Drawing وكذلك عناصر رسم الأدوات الموجودة في System.Windows.Forms.VisualStyles. وأيضا فالإمكانيات الموجودة في الأداة الأم System.Windows.Forms.Control ساعدت كثيرا.

  • جودة الرسم

يجب أن نجعل المستخدم قادرا على تحديد جودة الرسومات أو الأشكال. وتم ذلك عن طريق العنصر System.Drawing.Graphics والذي يحوي خصائص Properties خاصة بجودة الرسم مثل Antialiasing.

  • التحويل إلى XML

وتم حل هذه المشكلة عن طريق تطبيق الـ Interface المسماة System.Runtime.Serialization.ISerializable على جميع الأدوات القابلة للحفظ. وأيضا حصلنا على مساعدة كبيرة من الأدوات الموجودة في System.Runtime.Serialization.

  • قاعدة البيانات

قاعدة البيانات هي قاعدة SQL Server والربط كان من طبقتين two-tiers أي أنه لم يتم استخدام العناصر مثل DataSet أو DataAdapter بل استخدمنا فقط Connection، Command، و DataReader.

  • دعم وقت التصميم Design-Mode Support

بالطبع كان يجب مراعاة أن القالب Sheet هو حاوية Container للعناصر بداخله. وأيضا فهناك العديد من العناصر التي تظهر وقت التصميم فقط مثل شبكة Grid في خلفية الشيت لمساعدة المستخدم على تحديد مكان العناصر.

ولهذا فإن العنصر Sheet تم تحديد وراثته من الأداة System.Windows.Forms.Design.ParentControlDesigner ليوفر دعم أكبر لوقت التصميم Design-Mode.

نظرة على الأكواد

في ما يلي أهم النقاط والأكواد التي يمكن أن تكون محور اهتمام للقارئ.

  • كود الرسم

الكود التالي هو كود دالة الـ OnPaint() التي تم استخدامها في العنصر الأم ShapeBase والذي منه تنحدر جميع الأدوات التي يمكن إضافتها إلى الشيت.

protected override void OnPaint(PaintEventArgs e)
{
    GraphicsManager.SetQuality(e.Graphics, PaintingQuality);
    ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle, this.ForeColor, ButtonBorderStyle.Dotted);
    PaintShape(e.Graphics);
    if (Selected)
        DrawSelectionFrame(e.Graphics);
    ControlPaint.DrawSizeGrip(e.Graphics, this.BackColor, GetResizeGripRect());
}

الكود يقوم بالنداء على دالة داخلية تسمى SetQuality() لتحديد جودة الأدوات بناءا على ما اختاره المستخدم.

يقوم الكود بالنداء على دالة داخلية أخرى تسمى PaintShape() والتي يتم التعديل عليها Override في كل أداة منحدرة من العنصر الأم ShapeBase.

فهذا مثلا كود الدالة PaintShape() الخاصة بأداة مربع النص TextBoxShape:

public override void PaintShape(Graphics dc)
{
    base.PaintShape(dc);
    using (Brush b = new SolidBrush(this.BackColor))
        dc.FillRectangle(b, this.ClientRectangle);
    using (Pen p = new Pen(SystemColors.ActiveCaption, 1))
    {
        p.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
        Rectangle r = this.ClientRectangle;
        dc.DrawRectangle(p, r);
    }
    Rectangle rect = this.ClientRectangle;
    rect.Offset(2, 2);
    rect.Width -= 4; rect.Height -= 4;
    StringFormat format = new StringFormat();
    format.LineAlignment = this.Alignment;
    if (this.RightToLeft == RightToLeft.Yes)
        format.FormatFlags = StringFormatFlags.DirectionRightToLeft;
    using (Brush b = new SolidBrush(this.ForeColor))
        dc.DrawString(this.Text, this.Font, b, rect, format);
}

بعد النداء على دالة PaintShape() الكود الخاص بالعنصر الأم، يتم تطبيق الكود الخاص بأداة مربع النص.

  • جودة الرسومات

قمنا بإنشاء دالة مساعدة تقوم بالتعديل على عنصر الرسم System.Drawing.Graphics على خصائصه لكي توفر للمستخدم طريقة تحديد جودة الرسم من سيئة Low إلى ممتازة High مرورا بالمتوسطة Normal.

public static void SetQuality(System.Drawing.Graphics g, PaintingQuality quality)
{
    if (g == null)
        throw new ArgumentNullException("g");
    if (quality == PaintingQuality.High)
    {
        g.CompositingQuality = CompositingQuality.AssumeLinear;
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.PixelOffsetMode = PixelOffsetMode.Half;
        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.TextRenderingHint =
            System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
    }
    else if (quality == PaintingQuality.Medium)
    {
        g.CompositingQuality = CompositingQuality.HighQuality;
        g.InterpolationMode = InterpolationMode.Bilinear;
        g.PixelOffsetMode = PixelOffsetMode.HighQuality;
        g.SmoothingMode = SmoothingMode.HighQuality;
        g.TextRenderingHint =
            System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
    }
    else
    {
        g.CompositingQuality = CompositingQuality.Default;
        g.InterpolationMode = InterpolationMode.Default;
        g.PixelOffsetMode = PixelOffsetMode.Default;
        g.SmoothingMode = SmoothingMode.Default;
        g.TextRenderingHint =
            System.Drawing.Text.TextRenderingHint.SystemDefault;
    }
}

التحميل

تحميل المشروع SISC

مواضيع مشابهة:

اخترنا لك:

أحدث المواضيع:

هل أعجبتك؟ شارك بها...
  • مودي

    شرح ولا اروع يالغالي يعطيك العافيه اخوي محمد وعاشت هالايادي

  • مودي

    شرح ولا اروع يالغالي يعطيك العافيه اخوي محمد وعاشت هالايادي

  • Pingback: Creating a Simple Sheet Designer in C# | Just Like a Magic()