BeginPaint/EndPaint أم GetDC/ReleaseDC؟

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

اليوم نطرح سؤال: هل تستخدم دوال BeginPaint/EndPaint أم تستخدم GetDC/ReleaseDC؟

الإجابة تكون حسب الكود الذي تقوم بكتابته. ففي حالة كونك تقوم بمعالجة الرسالة WM_PAINT فحينها يفضل أن تستخدم BeginPaint/EndPaint. ما عدا ذلك، فيفضل استخدام GetDC/ReleaseDC. لماذا؟

الإجابة بسيطة. كما تعرف فإن نظام التشغيل يقوم بإرسال الرسالة WM_PAINT إلى النافذة حال كون هناك مساحة معينة من النافذة تحتاج إلى عملية إعادة الرسم (مثلا عندما يكون هذا الجزء مغطى بجزء آخر)، وهذه المساحة تسمى Invalidated Area.

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

وهذه هي فائدة BeginPaint/EndPaint. فدالة BeginPaint() أول ما تقوم به هي عملية التصحيح Validation لهذا الجزء من الشاشة المطلوب رسمه، وبالطبع تقوم BeginPaint() بعمليات إنشاء مقبض الرسم DC والتجهيز اللازم لعملية الرسم. وبالتالي فعند ندائك لدالة EndPaint() وانتهاء عملية الرسم يكون نظام التشغيل متأكدا أن عملية الرسم تمت بشكل ناجح ولا يحتاج إلى إرسال هذه الرسالة مرة أخرى (إلا إذا احتاج جزء من النافذة عملية إعادة الرسم مرة أخرى.)

وإذا تخيلنا ماذا يحدث بداخل الدالة BeginPaint() يمكننا أن نكتب السودوكود (Pseudo-code) هذا:

HDC BeginPaint(...)
{
    . . .
    validate client area
    e.g. call ValidateRect()
    initialize the DC
    do the necessary initialization
    . . .
    return handle to the DC
}

من الناحية الأخرى فإن لا GetDC() ولا ReleaseDC() تقوم بهذه العملية الهامة من عملية تصحيح الجزء المطلوب إعادة رسمه. لذلك فإنك عند استخدامها في WM_PAINT فإن نظام التشغيل سوف يستمر بإرسال WM_PAINT واحدة تلو الأخرى طالما هذا الجزء لم يتم تصحيحه، إلا إذا قمت بالنداء على دالة مثل ValidateRect() مثلا. ولهذا فإنه يفضل (إن لم يكن يجب) عدم استخدام GetDC/ReleaseDC في كود WM_PAINT.

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

يرجى ملاحظة أنه يجب عليك ألا تخلط الدوال ببعضها. فيجب عليك مثلا ألا تبدأ عملية الرسم بالدالة BeginPaint() وتنهيها بـ ReleaseDC() فهذا يعتبر خاطئ جدا ومع الوقت سيسبب مشاكل في ذاكرة الجهاز (ما يعرف بـ Memory Leaks ونحوها.) وبالطبع تعرف أنه يجب عليك ألا تقم ببدء عملية الرسم دون إنهائها، فيجب ألا يحتوي الكود الخاص بك على BeginPaint() أو GetDC() فقط.

أيضا، مما يجب مراعاته كود مثل التالي، فإنه يسبب مشكلة خطيرة بالنسبة لواجهة برنامجك:

    switch (uMsg)
    {
        . . .
        case WM_PAINT:
            return 0;
        . . .
    }

ما المشكلة في هذا الكود إنه لم يقم بأي عملية يمكنها أن تحدث خطأ؟؟؟؟!!!! المشكلة هي في الكود نفسه المشكلة هي أنك لم تقم بعمل أي شيئ!

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

لو نظرنا للحظة في كود معالجة WM_PAINT الخاص بنظام التشغيل (والذي ينفذ طالما أن الرسالة أرسلت إلى DefWindowProc() ولم يقم البرنامج بمعالجتها) ستجد أن الكود يحوي فقط سطرين وهما كالتالي:

        case WM_PAINT:
            BeginPaint(hWnd, &ps);
            EndPaint(hWnd, &ps);
            return 0;

كما عرفت، فإن BeginPaint() تقوم بإعلام نظام التشغيل بأن عملية الرسم تتم بشكل صحيح، ويؤكد هذا الأمر EndPaint() والذي ينهي عملية الرسم بشكل صحيح.

لهذا فإنه يجب عليك ألا تقم بمعالجة WM_PAINT إطلاقا إلا إذا قمت بعمل تصحيح Validation للمساحة المطلوبة سواء باستخدام BeginPaint/EndPaint أو باستخدام ValidateRect() مثلا. وإذا اضطررت إلى ذلك فيمكنك تحويل الأمر إلى DefWindowProc() أو DefFrameProc() ونحوهما.

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

اخترنا لك:

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

هل أعجبتك؟ شارك بها...