2 [ALIGN=CENTER]الدرس الثاني[/ALIGN].
[ALIGN=CENTER]البرمجة بواسطةWin32 API كمقدمةلـMFC:[/ALIGN]
برنامجك الاول في الويندوز:
هذا البرنامج المكتوب بلغة سي, والذي يعرض لنا على النافذة العبارة الشهيرة : “Hello World” ...
-------------------------------
كود
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR szCmdLine, int iCmdShow)
{
const char SzAppName[] = "ClassName";
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass;
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW|CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = SzAppName;
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wndclass);
hwnd = CreateWindow(SzAppName,
"Hello Word Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
RECT rect;
PAINTSTRUCT ps;
switch(iMsg)
{
case WM_PAINT:
hdc= BeginPaint(hwnd,&ps);
GetClientRect(hwnd,&rect);
DrawText(hdc,"Hellow World!",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
EndPaint(hwnd,&ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
برنامج كبير فقط من أجل انشاء نافذة, وكتابة كلمتين !!
هذا تقريبا أصغر برنامج في ويندوز.. سأشرح الكود بالتفصيل بعد عدة جمل مهمة.. كل برنامج ويندوز يحب ان يحتوي على شيئين غاية في الاهمية:
1. دالة WinMain() : وهي نقطة المدخل الى كل برنامج ويندوز..
2. دالة الرد على رسائل النظام: في حالتنا هذه اسمها:WndProc , وتسمى "دالّة النافذة", أو "إجراء النافذة".
بشكل عام دالة WinMain تعرّف صفات النافذة العامة, وتحدد اسم دالة الرد على رسائل النظام.. ومن ثم تشغّل حلقة تلقي الرسائل وترجمتها وارسالها الى الدالة التي تعالج الرسائل... هذا شرح عام عن البرنامج.. واللآن الشرح المفصّل:
قمنا في البداية بإحتواء #include للملف windows.h وهو أهم ملف في ويندوز ففيه كل الدوال الاساسية لويندوز.
بعد ذلك قمنا باعلان عن الدالةWndProc ثم أتبعنا هذا بجسم الدالة WinMain .
شرح الدالة WinMain سطر بسطر:
Static char szAppName[]=”hello” – تعريف مصفوفة وتعبئتها بكلمة.
HWND hwnd; تعريف متغير من نوع HWND (هذا النوع الجديد يحمل قيم من نوع مقبض نافذة)
MSG msg; تعريف متغيّر من نوع MSG (هذا النوع الجديد هو مبنى يحمل قيم رسائل النظام).
WNDCLASSEX wndclass – تعريف كائن من المبنى WNDCLASSEX: هذا المبنى يسمى مبنى النافذة وهو مهم جدًا.. ففيه توضع كل القيم التي يرتكز عليها البرنامج في بناء النافذة.
من السطر wndclass.cbsize=sizeof(wndclass); الى السطر wndclass.hIconSm=LoadIcon(NULL,IDI_APPLICATION) قمنا بتعبئة القيم الخاصة بمبنى النافذة. وهي صفات النافذة مثل : نوع النافذة style والايقونة .. ومؤشر الماوس... الخ... أهم شيء هنا هو الحقل : lpfnWndProc والذي يحدد للبرنامج اسم الدالة التي ترد على رسائل النظام لكي "يعرف" الى اين يرسل هذه الرسائل..و lpszClassName وهو اسم فئة النافذة.. وبواسطة هذا الاسم يتم التعرف على البرنامج.
شرح بسيط عن كل حقل من حقول:
cbSize يُحَدَّد فيه الحجم , بالبايتات, لهذا المبنى(مبنى WNDCLASSEX)
style نسق الكلاس, وفيه توضع صفات النافذة التي ستنشأ.. في مثالنا هذا CS_HREDRAW تعني ان النافذة سوف يعاد رسمها اذا تم تغيير عرض النافذة.و CS_VREDRAWتعني ان النافذة سوف يعاد رسمها اذا تم تغيير ارتفاع النافذة. (رسم النافذة, واعادة رسمها هو من خواص الويندوز, فكل ما تراه امامك كنوافذ ما هو الا خداع

, فأنت عندما ترى نافذة قامت بتغطية نافذة اخرى ثم هذه النافذة الاخرى عند استدعائها تخرج, وكأنها كانت مختبئة,كل هذا يتم بواسطة رسم واعادة رسم, وتحديث النوافذ.)
lpfnWndProc يحدد اسم دالة النافذة, وهي الدالة التي تعالج الرسائل.
cbClsExtra يحدد عدد البايتات الزائدة التي تريد ان تحجزها في الذاكرة خلف المبنى WNDCLASSEX.
cbWndExtra يحدد عدد البايتات الزائدة التي تريد ان تحجزها في الذاكرة , خلف الذاكرة التي تحجز عند بناء النافذة.
hInstance قيمة يتلقاها البرنامج من النظام وهي قيمة مقبض "الظهور" الذي نافذتنا هذه أنشأت بداخله... لا تهتم به!
hIcon مقبض لايقونة الكلاس..
hCursor مقبض مؤشر الفارة.
hbrBackground مقبض الى لون الخلفية, هذا اللون سيكون لون خلفية النافذة.
lpszMenuName مؤشر الى اسم المصدر(Recource) الذي يمثل القائمة(Menu ).
lpszClassName وهو اسم فئة النافذة.. وبواسطة هذا الاسم يتم التعرف على البرنامج
hIconSm مقبض للايقونة الصغيرة التي تظهر في زاوية النافذة وعلى شريط المهام.
--
أهم شيء هنا هو الحقل : lpfnWndProc والذي يحدد للبرنامج اسم الدالة التي ترد على رسائل النظام لكي "يعرف" الى اين يرسل هذه الرسائل.
و lpszClassName وهو اسم فئة النافذة.. وبواسطة هذا الاسم يتم التعرف على البرنامج.
هذه معلومات عامّة , قد لا تستخدمها بعد الان!, لأننا نبرمج بـ MFC. .
RegisterClassEx(&wndclass) –هذه عملية تسجل المبنى وبدونها لا يمكن استخدام المبنى لاحقا عند استدعاء دالة النظام CreatWindow ونقوم بتسجيل المبنى بواسطة تمرير عنوان الكلاس كبراميتر للدالة RegisterClassEx..
بعد هذا قمنا باستدعاء دالة النظام : CreateWindow() كما يلي:
[ALIGN=LEFT] hwnd = CreateWindow(SzAppName,
"Hello Word Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);[/ALIGN]
القيمة التي ترجعها لنا هذه الدالة هي مقبض النافذة التي انشأناها. راجع درس الاخ BAZRAMIT لتعرف القيم التي مررناها للدالة..
حتى هنا قمنا ببناء النافذة, لكن لو شغلنا البرنامج لن نحصل على شيء لأننا لم نطلب من النظام ان يظهر لنا النافذة... ولكي نفعل هذا كتبنا الدالة التالية:
[ALIGN=LEFT]ShowWindow(hwnd, iCmdShow);[/ALIGN]
حيث حددنا مقبض النافذة الذي نريد اظهارها hwnd وحددنا بعدها كيفية الاظهار iCmdShow وهي قيمة نتلقاها من النظام, لكن نستطيع ان نحددها نحن, فلو اردنا مثلا اظهار النافذة مكبّرة Maximize سنكتب:
[ALIGN=LEFT] ShowWindow(hwnd, SW_MAXIMIZE);[/ALIGN]
ولو كانت نافذة ظاهرة واردت اخفائها سأختار كيفية الاظهار مخفي هكذا:
[ALIGN=LEFT] ShowWindow(hwnd, SW_HIDE);[/ALIGN]
وهذه مجموعة من الصفات تسلى في تجربتها:
SW_FORCEMINIMIZE
SW_MINIMIZE
SW_RESTORE
SW_SHOW
SW_SHOWDEFAULT
SW_SHOWMAXIMIZED
SW_SHOWMINIMIZED
SW_SHOWMINNOACTIVE
SW_SHOWNA
SW_SHOWNOACTIVATE
SW_SHOWNORMAL
----------
بعد انشاء النافذة واظهارها, قمنا بتحديثها, واهمية التحديث كي يرسل النظام الى النافذة رسالة WM_PAINT المسؤوالة عن" دهن" النافذة.
الان يبدأ العمل الرئيسي, لأي نافذة تظهر امامنا , وهو حلقة الرسائل:
كود
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
وهذه الحلقة تنتظر رسائل من النظام, وعندما تتلقى رسالة من النظام, تقوم بترجمتها: TranslateMessage(&msg) وبعد ترجمة الرسالة تقوم بارسالها الى دالة النافذة هكذا: [ALIGN=LEFT] DispatchMessage(&msg);.[/ALIGN]
وماذا تفعل دالة النافذة وكيف ترد على رسائل النظام؟
أولا رسائل النظام لها اسماء, ونعرف كل رسالة وماذا تعني , وهي كثيرة, وتتميز بأن اسمها يبدأ بـ WM_ مثل الرسالة: WM_PAINT وهي رسالة تتلقاها النافذة عند الحاجة الى "دهنها" ومتى تحتاج النافذة الى دهن ( دهن اقصد به تعبئتها باللون – حتى الان نتحدث عن نافذة فارغة)؟ تحتاج النافذة الى دهن عند اظهارها للمرة الاولى و عندما يتم إخفاءها خلف نافذة اخرى ثم اظهارها, أو عند تصغيرها, واعادة تكبيرها, بإختصار عندما يكون هناك حاجة الى رسمها من جديد..
ودالة النافذة تعمل غالبا بطريقة switch لفحص الرسالة, فمثلا اذا كانت رسالة دهن, ماذا سيحدث, النظام "لا يفهم"! عندما يريد دهن النافذة سوف يمسح كل ما فيها من نصوص, ورسوم, بما في ذلك جملتنا الحبيبة "hello world" , اذا سنطلب من النظام عند كل عملية دهن ان يقوم بإعادة كتابة هذه الجملة, فيظن المستخدم انها لم تمسح.. لم اقل لكم كله خداع

, وبلغة سي نقول:
كود
case WM_PAINT:
hdc= BeginPaint(hwnd,&ps);// بداية الدهن
GetClientRect(hwnd,&rect); //
DrawText(hdc,"Hellow World!",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER); // ما يهمنا هو جملتنا الحبيبة
EndPaint(hwnd,&ps);// الدهن.
return 0; // كما تعرف فانه عندما يتحقق الشرط فكل ما يليه سينفذ, ولكي لا ننفذ الشرط التالي, وضعنا هذه الجملة.
وأخيرا قمنا بالرد على الرسالة WM_DESTROY وهي الرسالة التي نتلقاها عند اغلاق النافذة... الرد:
كود
case WM_DESTROY:
PostQuitMessage(0);//سالة تخبر نظام التشغيل بان البرنامج يطلب الانتهاء.. وهي عادة الرد المناسب لهذه الرسالة.
break
DefWindowProc(hwnd, iMsg, wParam, lParam); للرد على الرسائل التي لم تذكر في البرنامج...
========
كلمة حول الرسائل:
علمنا ان النظام يبعث برسائل للبرنامج , لكن هل تعلم ان برنامجنا يمكنه ان يرسل رسالة الى برنامج آخر؟ نعم, هناك طريقتين لكي تجعل برنامجك يرسل رسالة لبرامج أخرى:
1- أن يبعث بها الى سرب الرسائل (لتنتظر في الدور), وعندما يأتي دورها تصل الى البرنامج , وذلك بواسطة: الدالة :
كود
BOOL PostMessage(HWND hWnd, مقبض النافذة المستهدفة
UINT Msg, اسم الرسالة
WPARAM wParam, بارامتر يصف الرسالة , وهو يختلف معناه حسب الرسالة
LPARAM lParam مثل البارامتر السابق .. لكنه يحمل صفة أخرى..
);
2- ارسال رسالة مباشرة الى دالة النافذة المستهدفة .. وذلك بواسطة دالة SendMessage.
لا تخف كل هذه المعلومات لن تهمك منها الا معرفتها (نظريا). ففي المستقبل ومع الممارسة ستعرفها جميعا...
وهناك الكثير لنقوله حول البرمجة بـ WINAPI لكن كما تعلمون الدروس في MFC , لهذا سأنتقل مباشرة الى MFC ..
===============
3 [ALIGN=CENTER]
اسس البرمجة بـ MFC ...[/ALIGN]
بناء البرنامج الاول بواسطة MFC ... الان سترى الفرق

..
كما لا بد وقد لاحظت لم نأت على ذكر الفئات في البرمجة بواسطة WinAPI حتى عندما قلت كلاس, فقد كان الاستعمال الفعلي هو مبنى struct وليس كلاس, أو فئة (فئة=كلاس )... والسبب ان WinAPI هو بلغة سي. أما الان فسنستخدم الفئات والوراثة بكثرة, لأن MFC بلغة سي++, هي تحوي, أو تغلّف في داخلها الـ WinAPI فكل ما تفعلة ب WinAPI يمكنك فعله بـ MFC بجهد أقل, وحتى داخل برنامجك المكتوب بواسطة MFC يمكنك استخدام دوال WINAPI المجرّدة... و...... يبدو أن كلامي كثير لهذا تعالوا للتطبيق ودعكم من هذا الكلام...
... يتكون كل برنامج MFC على الاقل من قسمين اساسيين.. قسم البرنامج, وقسم الاطار, وغالبا كل " الافعال" تتم في في قسم الاطار, اما قسم البرنامج فغالبا لا يتغير. وبناء برنامج MFC هو عبارة عن بناء فئة البرنامج , وبناء فئة الاطار..
البرنامج الاول... نافذة فارغة:
كود
#include <afxwin.h>
class mywin : public CFrameWnd
{
public: mywin()
{
Create(NULL, "ArabTeam2000");
}
};
class app : public CWinApp
{
BOOL InitInstance()
{
m_pMainWnd = new mywin();
m_pMainWnd->ShowWindow(m_nCmdShow);
return TRUE;
}
};
app myApp;
لتشغيل الكود:
1- افتح فيجيوال سي++6 .. واختر file ثم New ثم حدد : Win32 Application واكتب اسم المشروع ثم اضغط OK.
2- اختر file ثم New ثم C++ Source File , واكتب اسم الملف ثم أو كي...
3- انسخ الكود والصقه في هذا الملف الجديد...
4- تحتاج الان ان تخبر المترجم انك تستخدم الـ MFC وذلك كالتالي إختر project , ثم Setting ستخرج لك نافذة وفيها بند باسم : Not Using MFC غيره الى Use MFC In static library..
5- شغل الكود!
نافذة جميلة .. بتعب اقل... اليس كذلك؟!
ماذا فعلنا؟؟؟ :
اولا: قمنا بتضمين الملف afxwin.h ... ((#include
وهذا الملف الزامي لكي نستخدم MFC ...
أطلقنا الاسم mywin على فئة الاطار.. وهي ترث من الفئة المعرفة في مكتبات MFC وهي الفئة : CFrameWnd... وبكلمات أخرى.. عرفنا فئة جديدة بإسم mywnd وهي ترث من الفئة CframeWnd ... وفئة CframeWnd هي فئة معرّفة في مكتبات MFC لتعريف إطار (نافذة)..
وفي تعريف هذه الفئة عرفنا دالة الباني mywin() وفي هذه الدالة استدعينا الدالة Create لكي ننشئ نافذة.. الدالة Create تقابل دالة API المسماه CreateWindow() لكن كما ترى استخدام Create أسهل...
الان نريد تعريف فئة البرنامج.. وهي دائما ترث من الفئة CwinApp ..
class app : public CwinApp... اطلقنا على فئتنا اسم App ..
وفي الدالة InitInstance() التي تستدعى فور تشغيلنا البرنامج, في هذه الدالة وضعنا الاوامر الخاصة بإظهار النافذة وهي:
m_pMainWnd = new mywin();.. اصبح m_pMainWnd يحمل مؤشر الى فئة الاطار..
m_pMainWnd->ShowWindow(m_nCmdShow); عملية اظهار النافذة.
وفي النهاية قمنا بتعريف كائن من برنامجنا (app myApp;) وذلك لكي نحث البرنامج على الابتداء.
أي أسئلة حول هذا الدرس؟؟ ارجو أن أكون قد وفقت في شرحه...