BAZRAMIT
Dec 28 2002, 11:30 AM
[ALIGN=CENTER]
الدرس الأول: دخول سريع--صندوق الرسائل [/ALIGN]
إتبع الخطوات التالية من فضلك...
1- قم بتشغيل جهازك.
2- إبدأ برنامج فيجوال سي++
3- إختر الأمر : File--->New
4- تأكد من أنك تقف على الصفحة المعنونة : Projects
5- إختر Win32 Application من الواجهة اليسرى.
6- إملأ اسم المشروع في الحقل النصي المعنون Project Name
7- إنقر OK
8- تأكد من أنك تقف على الخيار An Empty Project
9- مرة أخرى : File--->New
10- تأكد هذه المرة أنك تقف على الصفحة Files
11- عبيء إسم الملف الجديد.
12- إلصق الكود التالي داخله:
[php]#include
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MessageBox(NULL, "Hello, Bazramit!", "Bazramit", MB_OK);
return 0;
}[/php]
13- من القائمة اختر : Build--->Build [ProjectName].exe
14- إنتظر حتى ينهي البرنامج ما يقوم به.
15- ثم إختر : Build--->Execute [ProjectName].exe
16- استمتع بوقتك.. لقد أصبحت في لمح البصر : مبرمج ويندوز!
*****************
التعليق على الكود:
بالطبع لن أخوض في مسائل جانبية وأقوم بشرح ماهية ويندوز وما إلى ذلك...نحن لنا ما نرى!...
فلنبدأ:
بالطبع تذكر أيام كانت كل مداخل البرامج من خلال الدالة
[php]main()[/php]
هل نسيت؟...جيد أنك نسيت..لأننا لن نستخدم هذه الدالة كمدخل تحت منصة ويندوز..ولكن الدالة التي يحب ألا تنساها الآن هي:
[php]WinMain()[/php]
وكما تلاحظ من الكود، يقوم عالم ويندوز المرعب بتمرير مجموعة من الـParameters للدالة WinMain عند بدء البنامج..:
أولا..
HINSTANCE hInstance
وهي المعالج الرئيسي Handle للنسخة العاملة من البرنامج في الذاكرة..ستستخدم هذا الـParameter المفيد جدا في حالة تحميل موارد خاصة بالبرنامج و متضمنة
في ملف الـEXE.
ثانيا..
HINSTANCE hPrevInstance
غير مفيد بالنسبة للبرامج تحت ويندوز 95 فما فوق..
ثالثا..
LPSTR lpCmdLine
عندما تقوم بتشغيل برنامج من خلال أمر RUN في أي نسخة ويندوز..بإمكانك بالطبع تمرير خلاصة أو Argument على هيئة نص.
مثلا جرب معي هذا الأمر في نافذة RUN:
[php]
"G:Program FilesInternet ExplorerIEXPLORE.EXE" ]http://www.bazramit.com[/php]
في هذه الحالة يمكنك القول بلا فخر أنك مررت Argument قيمتها[php]http://www.bazramit.com[/php]
للبرنامج ليفعل بها ما يحلو له!
هذه الـArgument تستقبلها انت داخل برنامجك من خلال المتغير الممرر لك من ويندوز: lpCmdLine
رابعا..
int nCmdShow
ليس من المهم أن تعرف أهمية هذا المتغير في المرحلة الحالية ولكنه قليلا جدا ما يستخدم.
ملحوظة أخرى على الـParameters أنها تتضمن أنواعا من النتغيرات لأول مرة تصادفها..صحيح؟..
HINSTANCE هو نوع المتغير الذي يحمل Handle للوحدة العاملة لأي برنامج محمل في الذاكرة.
LPSTR يعني Long Pointer to String أو مؤشر إلى نص وهو مساو تماما لـchar * (تذكر أتك تبعث بالـArgument على هيئة نصية).
مفهوم...ولكن يجب أن تعرف أن هذه الأنواع (وغيرها كثير) يستخدم تحت منصة win32 (ويندوز 95 فما فوق) فقط..
وكلا النوعين المشار إليهما يتكون من DWORD أي 32 بٍت. تماما كالـint(تحت ويندوز 95 فما فوق أو UNIX).
يلاحظ أيضا استخدام WINAPI قبل استدعاء WinMain وهي كما يقال مجرد عرف متبع ليس إلا وذلك لتحديد نوع الاستدعاء.
الآن لنلق نظرة وداع على الدالة التي تسببت في إظهار الرسالة:
[php]
MessageBox(NULL, "Hello, Bazramit!", "Bazramit", MB_OK);[/php]هذه الدالة تقوم بإخبار ويندوز أن برنامجك يريد أن يظهر رسالة نصها "Hello, Bazramit" و العنوان الئيسي لها "Bazramit" ..
وبالتالي فإن النظام (في أغلب الأحيان) ينزل على رغبتك وينفذ ما طلبت.
إنها واحدة من آلاف الدوال الموجودة بويندوز وتسمى دوال API لتوفر للمبرمج أن يطلب ما يشاء من النظام..
لا تعتقد أنك ستضطر في يوم من الأيام لأن تستذكر آلاف الدوال لتصبح محترفا..على العكس، المهم أن تتعلم كيف تستخدم الـHelp لتخرج منه ما تريد...
خطوة أولى نحو الإحتراف...إذا كنت لا تملك MSDNإنزل هذا الملف المدعو WIN32.hlp من اللنك التالي:
http://212.14.34.87/~devon/down/helpy/winapi32.zip
وابحث عن الدالة MessageBox لتحصل على توقيعها وكيفية إستخدامها...طبعا قد يصعب عليك فهم كل شيء الآن..لكن لا تقلق..أنت قريب جدا!
في الدرس القادم سوف نمضي وقتا ممتعا في تعلم كيفية بناء نافذة Window ونتعرف على أهم مكوناتها.
دمتم بخير
Al-saidi
Dec 28 2002, 12:50 PM
إلى الإمام يا أخي
* الشبح *
Dec 28 2002, 04:14 PM
شكراً اخي على الدرس
أنا لدي كود لم يكتب فيه HINSTANCE بل يوجد فيه HANDLE وهو يقول في Win32 Console Application وليس في Win32 Application هل هذا خطأ ؟
واتمنى لك التوفيق (f)
* الشبح *
Dec 28 2002, 04:22 PM
أخي اليك الكود
هذا بعد تعديل بسيط عنما سبق من رد
[php]#include < windows.h >
long FAR PASCAL _export WndProc ( HWND , UINT , UINT , LONG ) ;
int PASCAL WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance , LPSTR lpszCmdParam , int nCmdShow )
{
static char szAppName [ ] = "Hello" ;
HWND hwnd ;
MSG msg ;
WNDCLASS win ;
if ( ! hPrevInstance )
{
win.style = CS_HREDRAW | CS_VREDRAW ;
win.lpfnWndProc = WndProc ;
win.cbClsExtra = 0 ;
win.cbWndExtra = 0 ;
win.hInstance = hInstance ;
win.hIcon = LoadIcon ( NULL , IDI_APPLICATION ) ;
win.hCursor = LoadCursor ( NULL , IDC_ARROW ) ;
win.hbrBackground = GetStockobject ( WHITE_BRUSH ) ;
win.lpszMenuName = NULL ;
win.lpszClassName = szAppName ;
RegisterClass ( &win ) ;
}
hwnd = CerateWindow ( szAppName ,
"اهلا وسهلا" ,
WS_OVERLAPPEDWINDOW ,
CW_USEDEFAULT ,
CW_USEDEFAULT ,
CW_USEDEFAULT ,
CW_USEDEFAULT ,
NULL ,
NULL ,
hInstance ,
NULL ) ;
ShowWindow ( HWND , nCmdShow ) ;
UpdateWindow ( hwnd ) ;
while ( GetMessage ( &msg , NULL , 0 , 0 ) )
{
TranslateMessage ( &msg ) ;
DispatchMessage ( &msg ) ;
}
return msg.wParam ;
}
long FAR PASCAL _export WndPort ( HWND hwnd , UINT message , UINT wParam , LONG lParam )
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch ( message )
{
case WM_PAINT :
hdc = BeginPanit ( hwnd , &ps ) ;
GetClientRect ( hwnd , &rect ) ;
DrawText ( hdc , "مرحباً بكم في عالم ويندز" , -1 , &rect , DT_SINGLELEING | DT_CENTER | DT_VCENTER ) ;
EndPaint ( hwnd , &ps ) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage ( 0 ) ;
return 0 ;
}
return DefWindowProc ( hwnd , message , wParam , lParam ) ;
}[/php]
واتمنى أن تجدوا لي حلاً واكون شاكر لكم
BAZRAMIT
Dec 28 2002, 05:56 PM
أنا آسف لأنني لم أستخدم سوى فيجوال سي فقط:
هذا الكود سيعمل من خلال فيجوال سي:
[php]
#include < windows.h >
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain ( HINSTANCE hInstance , HINSTANCE hPrevInstance , LPSTR lpszCmdParam , int nCmdShow )
{
static char szAppName [ ] = "Hello" ;
HWND hwnd ;
MSG msg ;
WNDCLASS win ;
if ( ! hPrevInstance )
{
win.style = CS_HREDRAW | CS_VREDRAW ;
win.lpfnWndProc = WndProc ;
win.cbClsExtra = 0 ;
win.cbWndExtra = 0 ;
win.hInstance = hInstance ;
win.hIcon = LoadIcon ( NULL , IDI_APPLICATION ) ;
win.hCursor = LoadCursor ( NULL , IDC_ARROW ) ;
win.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
win.lpszMenuName = NULL ;
win.lpszClassName = szAppName ;
RegisterClass ( &win ) ;
}
hwnd = CreateWindow ( szAppName ,
"ÇåáÇ æÓåáÇ" ,
WS_OVERLAPPEDWINDOW ,
CW_USEDEFAULT ,
CW_USEDEFAULT ,
CW_USEDEFAULT ,
CW_USEDEFAULT ,
NULL ,
NULL ,
hInstance ,
NULL ) ;
ShowWindow ( hwnd , nCmdShow ) ;
UpdateWindow ( hwnd ) ;
while ( GetMessage ( &msg , NULL , 0 , 0 ) )
{
TranslateMessage ( &msg ) ;
DispatchMessage ( &msg ) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch ( message )
{
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect ( hwnd , &rect ) ;
DrawText ( hdc , "ãÑÍÈÇð Èßã Ýí ÚÇáã æíäÏÒ" , -1 , &rect , DT_CENTER | DT_VCENTER ) ;
EndPaint ( hwnd , &ps ) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage ( 0 ) ;
return 0 ;
}
return DefWindowProc ( hwnd , message , wParam , lParam ) ;
}[/php]
BAZRAMIT
Dec 28 2002, 06:17 PM
على فكرة.. Win32 Console Appliaction مخصصة للبرامج المكتوبة كي تعمل
تحت الــConsole Window الموجودة بويندوز والشبيهة بدوس..
لكي تكتب تطبيقا لويندوز تختار Win32 Application. حت تستطيع عرض النوافذ.
وكذلك Console تحتاج إلى دالة main وليس WinMain.
* الشبح *
Dec 28 2002, 08:32 PM
شكراً أخي أنا ايضاً استخدم الفجوال سي لكن أنا لا اعرف كيف شغله من قبل
لدي مشكله هوه لا يوجد فيه خطأ عدما اريد تشغيله يرسل لي رسالة خطأ
اليك الرساله :
[php]Compiling...
Win32 Application.cpp
Linking...
LIBCD.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
Debug/Win32 Application.exe : fatal error LNK1120: 1 unresolved externals
Error executing link.exe.
Win32 Application.exe - 2 error(s), 0 warning(s)[/php]
هذه الرساله لم اجد اليها حل
شكرك مره أخرى
BAZRAMIT
Dec 28 2002, 08:55 PM
أخي الشيخ..أنت قد اخترت مشروعا من نوع Win32 Console Application
وهو يتطلب دالة main كمدخل للبرنامج وليس WinMain كما هي الحال في
الـويندوز..
إبدأ مشروعا جديدا Win32 Application ثم أضف له الملف الذي قمت بكتابته
وسيعمل إن شاء الله.
BAZRAMIT
Dec 28 2002, 09:04 PM
قررت أن أنشر الدرس الثاني الآن لأن الدرس القادم قد يتأخر قليلا..وأرجو مخلصا أن تكون هذه الشروحات بسيطة وخالية من التهويل.
[ALIGN=CENTER]الدرس الثاني:أغلق الباب وافتح النافذة: نافذة عامة[/ALIGN]
1- إبدأ مشروعا جديدا Win32 Application.
2- أضف ملفا جديدا إلى مشروعك.
3- ألصق الكود التالي داخله:
[php]
include
//WndProc signature
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//My Window Class name
const char m_ClassName[] = "BazramitClass";
//Main Program Entry
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
//Fill Window Class Structure Members
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = m_ClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
//Register What you just filled
RegisterClassEx(&wc);
//Create the window from the defined class
hwnd = CreateWindowEx(
NULL,
m_ClassName,
"Bazramit Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
//Display The Window on desktop
ShowWindow(hwnd, nCmdShow);
//Refresh the client area
UpdateWindow(hwnd);
//Enter Message Loop
while(GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
//The WndProc Which Processes Messages
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg) //
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
//Call The Default Handler
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}[/php]
4- Build المشروع ثم Executeٌــه.
تعليق على الكود:
قد يبدو الكود المرفق أعلاه غريبا بعض الشيء للوهلة الأولى..ولكنه في الحقيقة أسهل مما تتصور. إنها مراحل دائما ما تتكرر بنفس الطريقة في معظم
برامج ويندوز.
المرحلة الأولى: ملء بنية WNDCLASSEXWNDCLASSEX هو نوع من الـStructures أو البنى تستخدم لتخزين البيانات الخاصة بالنافذة وتسمى هذه البنية Window Class .
دعونا نخصص مساحة مستحقة للكود الخاص بملء هذه البنية:
في البداية نقوم بتعريف بنية جديدة من نوع WNDCALSSEX داخل الدالة WinMain.
[php]WNDCLASSEX wc;[/php]
ثم نملأ هذه البنية كالتالي:
[php] //Fill Window Class Structure Members
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = m_ClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);[/php]
أعضاء هذه البنية كما يتضح من الكود أعلاه هم:
cbSize ويحمل حجم هذه البنية، نستخدم الدالة sizeof للقيام بعملية حساب الحجم.
style ويحدد ملامح البنية التي ستستخدمها نافذتنا الرئيسية فيما بعد
lpfnWndProc ونملؤه بعنوان الدالة التي ستقوم بمتابعة عمل النافذة التي ستستخدم هذه البنية، في هذه الحالة قررنا أن نستخدم الدالة WndProc.
cbClsExtra ويحدد عدد البايتات التي الزيادة التي نود أن نضيفها إلى البنية.(غير مهم بالنسبة لأغراضنا الحالية)
cbWndExtra ويحدد عدد البايتات الزيادة التي نحتاجها في النافذة نفسها (لاحظ أن النافذة ليست هي بنية WNDCLASSEX،
النافذة هي من تستخدم البنية كما)
hInstance ويملأ بقيمة الرابط Instance الممرر من ويندوز إلى WinMain.
hIcon ويملأ برابط (Handle) الأيقونة التي ستستخدمها النافذة. نستخدم لملء هذه الخلية دالة LoadIcon.
hCursor نفس الشيء ولكن بالنسبة إلى المشيرة Cursor.
hbrBackground لون الخلفية التي ستظهر بها النافذة التي ستستخدم هذه البنية.
lpszMenuName إذا كان هناك قائمة نريد أن نصلها بانافذة، من الممكن إضافة اسمها هنا.
lpszClassName إسم الـبنية (أي نص نختاره، في حالتنا أنا اخترت النص "BazramitClass" وخزنته في المتغير m_ClassName)
hIconSm هنا نضع قيمة رابط الأيقونة الصغرى. هناك عدة أحجام من الأيقونات، ويعرضها النظام في مكانها المناسب، عندما نختار NULL قيمة
لهذه الخلية فإن النظام يقوم بالبحث عن الأيقونة بنفسه. (من الممكن أن يتضمن رابط الأيقونة الأساسية للنافذة عدة أحجام من الأيقونات)
بعد كل هذا الشرح..يظل السؤال عالقا..هل يجب أن أفهم كل هذا الهراء؟..والجواب، طبعا لا ( في الوقت الحالي)..مع تقدمك في القدرة على التطوير
بلغة سي، فإن كثيرا من الأمور الغامضة ستنجلي بإذن الله..
المهم الآن أن تعي أهمية هذه البنية بالنسبة للنافذة لأنها تحمل البيانات الأساسية التي تحتاجها الأخيرة. وستقوم دوما بملء البنية بنفس الطريقة
تقريبا..
المرحلة الثانية: إنشاء النافذة
بعد ذلك نقوم باستخدام الدالة CreateWindowEx لإنشاء نافذتنا ذات الطلة البهية:
[php] //Create the window from the defined class
hwnd = CreateWindowEx(
NULL,
m_ClassName,
"Bazramit Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL)
;[/php]
ولكي نأخذ قسطا أكبر من المعرفة، دعنا نعرض توقيع الدالة المأخوذ رأسا من WIN32.hlp:
[php]
HWND CreateWindowEx(
DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // handle to menu, or child-window identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data
);[/php]
وهذا شرح لكل Parameter على حدة:
dwExStyle نستخدمه كي نخصص ملامح إضافية للنافذة، نحن لا نريد مواصفات إضافية لذلك نمرر NULL هنا.
lpClassName أيضا نمرر اسم البنية التي نود أن نستخدمها m_ClassName لأن كل نافذة لابد أن ترتكز على بنية من نوع WNDCLASSEX.
lpWindowName نمرر هنا الإسم الذي سيظهر كعنوان للنافذة (الاسم المكتوب على الشريط الذي يحتوي على أزرار التصغير والتكبير والإغلاق).
dwStyle هنا يمكنك أن تختار مظهر النافذة (ستايل) من خلال تمرير عدد من الثوابت المعرفة خصيصا لهذا الغرض. في حالتنا استخدمنا أكثر
الستايلات شيوعا وهو WS_OVERLAPPEDWINDOW الذي يتيح عدة خصائص منها إظهار الأزرار تصغير وتكبير وإغلاق..إذا جربت
أن تمرر NULL إلى هذا الباراميتر، فإن النافذة لن تظهر الأزرار المذكورة.
X,Y المكان الذي ستظهر فيه النافذة.. يمكنك أن تدع للنظام أن يقرر المكان المناسب بأن تمرر القيمة الثابتة:CW_USEDEFAULT
nWidth, nHeight العرض والطول، أيضا استخدمنا CW_USEDEFAULT.
hWndParent إن كانت هناك نافذة أم لنافذتنا..نمرر قيمة الهاندل أو الرابط للنافذة الأم في هذا الباراميتر. في حالتنا لا يوجد لأنها النافذة الأم بحد
ذاتها ولذلك مررنا NULL
hMenu قيمة الرابط المخصص للقائمة (Menu)، نافذتنا لا يوجد بها menu ولذلك مررنا NULL.
hInstance قيمة رابط الوحدة في الحافظة الممرر إلينا من ويندوز في الدالة WinMain.
lpParam أي مؤشر لقيم أو وحدات بنيوية إضافية نريد زيادتها، في الغالب سيكون NULL.
نقطة أخيرة قبل الانتقال إلى المرحلة التالية، الدالة CreateWindowEX تعيد إلينا قيمة الرابط من نوع HWND
الذي نستخدمه كثيرا عندما نستدعي دوال API.
بعد أن نقوم بإنشاء النافذة، يجب أن نظهرها باستخدام الدالة ShowWindow
ثم نقوم بطلب "إنعاش" مناطق النافذة ورسمها بواسطة UpdateWindow
[php]
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);[/php]
المرحلة الثالثة: دخول الحلقة التكرارية لتلقي الرسائلهنا يكمن قلب عمل النافذة:
[php] while(GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}[/php]
الدالة GetMessage تقوم باستقبال كل الرسائل Messages المبعوثة إلى النافذة..الرسائل المبعوثة هي بالضرورة
مثل الضغط على لوحة المفاتيح أو تحريك الماوس أو تغيير حجم النافذة..إلخ.وحتى تتحول النافذة إلى تطبيق حقيقي
يجب أن تتلقف الأخيرة الرسائل و تقوم يمعالجتها..
ومع وجود برامج عديدة ممكن أن تعمل على نظام ويندوز في وقت واحد..فإن النظام يقوم بتجميع الرسائل فيما يسمى
طابور الرسائل أو Message Queue ويرسل بها حسب ترتيب دخولها الطابور إلى كل نافذة صدرت عنها.
وبالتالي فإن نافذتنا يجب أن تتلقف هذه الرسائل كمثيلاتها من النوافذ، وإلا أصبحت خبرا منسيا، كلما بعث لها المستخدم
برسالة عبر النظام لا ترد ولا تستجيب، شكل جميل بلا وظيفة أو يسمونه جمال الروح.
من هنا كان استخدام الدالة GetMessage مهما في داخل حلقة تكرارية لتقوم باستمرار بتفقد بريدها من النظام.
أما الدالة TranslateMessage فتقوم بتحويل كبسات لوحة المفاتيح إلى رسائل يمكن تقبلها ومعالجتها.
وتكمن أهمية DispatchMessage في أنها تقوم بتمرير الرسالة إلى الدالة WndProc لتقوم بمعلجتها.
المرحلة الرابعة: متخذ الإجراءات WndProc
حسبما بينا في شرح الخانات المختلفة لـWNDCLASSEX..كان أحد الخانات هو عنوان الدالة التي يلقى على عاتقها
إتخاذ الإجراءات ومعالجة الرسائل. لقد أصبح عرفا منذ زمن في أوساط مبرمجي فيجوال سي، أن يسموا هذه الدالة:WndPro،
لك حرية الاختيار أن تلتزم بالعرف أولا..ولكن يجب أن تراعي أن يكون توقيع الدالة على الشكل التالي:
[php]
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);[/php]
الباراميتر الأول هو هاندل النافذة من نوع HWND، والثاني هو رقم الرسالة من نوع UINT. والثالث والرابع نوعي
WPARAM, LPARAM ويحتويان على بيانات خاصة مع كل رسالة.
في داخل الــWndProc وجدنا الكود التالي:
[php]LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg) //
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
//Call The Default Handler
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}[/php]
كما نلحظ، تقوم الدالة بعملية تنقيح للرسائل باستخدام switch-case ففي كل مرة تستقبل فيها النافذة رسالة جديدة..يتم تفعيل الدالة
وبالتالي تفعيل الجزء الخاص بمعالجة الرسالة..
كمبرمج سيكون أغلب عملك في هذه المنطقة..حيث إن اهتمامك الأساسي سينصب على التعامل مع عدد من الرسائل التي يطلقها النظام
حسب استخدام البرنامج. مثلا الرسالة WM_CLOSE تطلق عندما يقوم المستخدم بطلب الخروج من البرنامج.
في الكود أعلاه تقوم الدالة في حالة تلقيها للرسالة WM_CLOSE باستدعاء الدالة DestroyWindow، التي بدورها تتسبب في وضع
رسالة WM_DESTROY إفي طابور الرسائل بالنظام..ومن ثم يقوم النظام بترشيحها إلى الحلقة التكرارية التي تستقبل الرسائل في
WinMainداخل النافذة، وبدورها ترسل الدالة WinMain بالرسالة إلى WndProc الذي يستقبل الرسالة ويتعرف عليها فيستدعي الدالة
PostQuitMessage التي تتسبب في الخروج من البرنامج...
من الممكن جدا أن كثير من الأمور لم تتضح حتى الآن..لكن المهم قبل أن ننتقل إلى درس آخر أن تفهم أساسيات المراحل المختلفة
المشروحة في هذه الحصة...وعلى العموم فإن أهم المراحل وأكثرها استهلاكا للوقت مرحلة معالجة واستقبال الرسائل لأن الكود فيما يسبقها
لا يختلف كثيرا بين برنامج وآخر.
Developer .NET
Dec 29 2002, 05:27 AM
ممتاز ....
اشكرك على هذه المشاركة ...
تقبل تحياتي ...
* الشبح *
Dec 29 2002, 06:11 PM
شكراً لك أخي و الى الامام
مبرمج المستقبل
Dec 29 2002, 09:42 PM
مشكوووووووورين على المجهود (gift)
تحياتي (f)
إسماعيل عنجريني
Dec 30 2002, 01:03 AM
رااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااااائع
أرجو المزيد ولكن ضمن برنامج كامل
اا الفاروق اا
Dec 30 2002, 06:14 AM
السلام عليكم
مشكور وإلى مزيد من التقدم ( بإذن الله )
إسماعيل عنجريني
Dec 31 2002, 12:45 AM
الحق يقال منذ أن بدأت في هذا المنتدى لم أستفد كما استفدت من هذا الموضوع فأرجو المتابعة على نفس المنوال
Al-saidi
Dec 31 2002, 12:57 AM
المزيد .......... بارك الله فيك
BAZRAMIT
Dec 31 2002, 10:29 AM
أولا أشكر كل من اهتم بهذه الشروحات..
وأتمنى من كل قلبي أن تكون هذه الدروس مفيدة لجميع المهتمين
بالدخول إلى هذا العالم الرحب.
ولكني أتأسف لكم أيضا بسبب عدم وجود مواعيد ثابتة لتقديم الدروس
راجيا أن تقدروا ظروف العمل...وأتمنى لكم جميعا التوفيق.
[ALIGN=CENTER]الدرس الثالث: نحو معالج نصوص[/ALIGN]
أنشيء مشروعا جديدا وأضف ملفا جديدا تلصق به الكود التالي:
[php]#include
//WndProc signature
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//My Window Class name
const char m_ClassName[] = "BazramitClass";
char m_Chars[100];
int m_Col;
//Main Program Entry
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
//Fill Window Class Structure Members
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = m_ClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
//Register What you just filled
RegisterClassEx(&wc);
//Create the window from the defined class
hwnd = CreateWindowEx(
NULL,
m_ClassName,
"Bazramit Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
//Display The Window on desktop
ShowWindow(hwnd, nCmdShow);
//Refresh the client area
UpdateWindow(hwnd);
//Enter Message Loop
while(GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
//The WndProc Which Processes Messages
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
switch(msg) //
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_CHAR:
hdc=GetDC(hwnd);
m_Chars[m_Col]=(char)wParam;
m_Col++;
TextOut(hdc,0,0,m_Chars,m_Col);
ReleaseDC(hwnd, hdc);
break;
default:
//Call The Default Handler
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}[/php]
نفذ البرنامج، اكتب بعض الجمل، ولاحظ النتيجة..
ــــــــــــــــــــــــــــــ
تعليق على الكود:
ما نود فعله في هذا البرنامج..إخراج الحروف التي يدخلها المستخدم..
كما ترى..لم يتغير الكود كثيرا هذه المرة..
أضفنا بعض المتغيرات في البداية قبل WinMain:
[php]char m_Chars[100];
int m_Col;[/php]
m_Chars سنضيف فيه كل الأحرف التي يضغط عليها المستخدم لنخرجها له فيما بعد...لاحظ أن الحد الأقصى للحروف
في هذا البرنامج 100.
و m_Col نضع فيه عدد الحروف التي أدخلها حتى اللحظة..
كذلك أضفنا متغيرا من نوع HDC جديد نراه لأول مرة داخل حدود WndProc:
[php]HDC hdc;[/php]
HDC هو نوع يستخدم بواسطة ويندوز لكي يحدد السطح الذي سيخرج إليه الحروف أو حتى الرسومات...
وهو اختصار لــHandle to a Device Context.
لكل نافذة أيا كان نوعها Device Context خاص بها..وعندما نخرج رسوما إلى هذه النافذة، نحتاج إلى Device Context لكي يعرف
النظام أي نافذة نريد أن نرسل بخرجنا إليها.
كذلك استخدمنا الدالة TextOut داخل WndProc وهي إحدى أشهر الدوال المستخدمة لإخراج النصوص.
الآن لنراجع ما تغير داخل WndProc:
[php]LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
switch(msg) //
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_CHAR:
hdc=GetDC(hwnd);
m_Chars[m_Col]=(char)wParam;
m_Col++;
TextOut(hdc,0,0,m_Chars,m_Col);
ReleaseDC(hwnd, hdc);
break;
default:
//Call The Default Handler
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}[/php]
لأننا نريد أن نتعامل مع كبسات لوحة المفاتيح المتولدة عن المستخدم، فإننا قمنا بمعالجة الرسالة:WM_CHAR..
عندما يبعث النظام لنا بهذه الرسالة فإنه يخبرنا أن المستخدم قد ضغط على أحد المفاتيح ويمرر لنا قيمة الــASCII code
الخاص بالمفتاح في الباراميتر: wParam.
في هذه الحالة يقوم برنامجنا بتلقف الرسالة وعمل الآتي:
[php] case WM_CHAR:
hdc=GetDC(hwnd);
m_Chars[m_Col]=(char)wParam;
m_Col++;
TextOut(hdc,0,0,m_Chars,m_Col);
ReleaseDC(hwnd, hdc);
break;[/php]
بما أننا سنستخدم الدالة TextOut في إخراج النصوص..لا بد أن نحصل على الباراميترات التي تحتاجها لكي يعمل
توقيع TextOut من داخل Win32.hlp هو:
[php]BOOL TextOut(
HDC hdc, // handle of device context
int nXStart, // x-coordinate of starting position
int nYStart, // y-coordinate of starting position
LPCTSTR lpString, // address of string
int cbString // number of characters in string
);[/php]
hdc هو قيمة الرابط إلى الـDevice Context للنافذة التي نود إخراج النص إليها، ويمكن الحصول عليه من خلال الدالة GetDC
nXStart المكان على المحور الأفقي للنافذة الذي يبدأ عنده النص.
nYStart المكان على المحور العمودي للنافذة الذي يبدأ عنده النص.
lpString قيمة pointer يشير إلى مكان النص في الذاكرة.
cbString عدد الأحرف التي نود إخراجها..
وكما قلنا في السابق..البرنامج يخزن الأحرف لحد 100 حرف في المتفير من نوع Array of chars المسمى m_Chars.
كما أننا سوف نزيد قيمة المتغير m_Col في كل مرة نستقبل فيها الرسالة وهذا المتغير يكون بمثابة العدّاد الذي يخزن
عدد الأحرف التي أدخلها المستخدم.
ولكي نحصل على قيمة الـDevice Context فإننا نستخدم GetDC التي نمرر إليها قيمة رابط النافذة hwnd.
[php]hdc=GetDC(hwnd);[/php]
ولكن يجب أن نضع في اعتبارنا أنه من المهم أن نعفي الـ Device Context مباشرة بعد أن ننهي عملنا (بإخراج النص)
بواسطة الدالة:
[php]ReleaseDC(hwnd, hdc);[/php]
أعتقد أن الأمور أصبحت الآن واضحة تماما...
[php]TextOut(hdc,0,0,m_Chars,m_Col);[/php]
تخرج كل الحروف المخزنة على نافذتنا في المكان المحدد (0,0).
طيب...عظيم، ولكن جرب معي الأتي في البرنامج..
قم بكتابة أي نص على الشاشة..ثم قم بتصغيرها...
أرجعها إلى الواجهة الآن...ماذا ترى؟..
لقد انمحى النص الذي أدخلته في السابق...وعندما تبدأ في الكتابة مرة أخرى..يعود النص إلى ما كان عليه...
السبب فيما حدث، أننا قمن في البرنامج يمعالجة الرسالة WM_CHAR..فإخراج النصوص يتم عندما يضغط
المستخدم على لوحة المفاتيح ...
وعندما نقوم يتصغير النافذة ثم نعيدها مرة أخرى يبعث النظام برسالة WM_PAINT التي للأسف لم نضعها
في حسباننا في البداية...
الرسالة WM_PAINT دائما ما سنحتاجها عند إعادة إنتاج محتويات الشاشة..ويرسلها لنا النطام في حالة أن
النافذة تغير حجمها أو أصبحت في خلفية نافذة أخرى مثلا...
هذه هي النسخة المعدلة من WndProc التي تعالج هذه السقطة:
[php]LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
switch(msg) //
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_CHAR:
hdc=GetDC(hwnd);
m_Chars[m_Col]=(char)wParam;
m_Col++;
TextOut(hdc,0,0,m_Chars,m_Col);
ReleaseDC(hwnd, hdc);
return 0 ;
break;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
TextOut(hdc,0,0,m_Chars,m_Col);
EndPaint (hwnd, &ps) ;
return 0 ;
break;
default:
//Call The Default Handler
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}[/php]
كما اتضح تلقفنا هذه المرة الرسالة WM_PAINT..
هناك طريقة للحصول على الــHDC لا تستخدم إلا في داخل هذه الرسالة:
case WM_PAINT:
[php] //
hdc = BeginPaint (hwnd, &ps) ;
// أجر كل عمليات الرسم هنا
//
EndPaint (hwnd, &ps) ;
return 0 ;
break;[/php]
والتفسير:
-إبدأ عملية رسم المحتويات بالحصول على قيمة HDC بواسطة BeginPaint (لا نحتاج إلى GetDC)
2-أجر كل عمليات الرسم المطلوبة
3-أنهي العملية بواسطة EndPaint (لا نحتاج إلى ReleaseDC).
الدالتان BeginPaint و EndPaint يجب أن نمرر لها متغيرا من نوع PAINTSTRUCT وهي بنية تملؤها لنا الدالة بالمعلومات المهمة
المتعلقة بخصائص منطقة الرسم Client Area داخل النافذة.
بهذا ينتهي الدرس الثالث...
أريد منك حتى الدرس المقبل أن تفكر في الأمور التالية:
1-كيف نستطيع معالجة كبسات المستخدم على BackSpace.؟
2-كيف لنا أن نعالج كبسات المستخدم على Enter لإضافة سطر جديد.؟
* الشبح *
Dec 31 2002, 05:17 PM
thanks for a lesson but i connot read your lesson now
(f) thanks
إسماعيل ابراهيم
Dec 31 2002, 06:43 PM
ما شاء الله... بارك الله فيك أخي BAZRAMIT على هذه الدروس القيّمة...
لكن لدي سؤال: هل تخطط لبناء دروسك على موضوع win API أم انك تريد ان تكمل بـ MFC ذلك لأنني بدأت سلسلة, كنت أود الاكمال. وأنا خططت للسلسلة ان تكون عن MFC فلكي لا يحدث تكرار ارجو إخباري عن أي موضوع ستكتب (Win API أم MFC )....
وتحياتي.
BAZRAMIT
Jan 1 2003, 06:09 AM
أخي إسماعيل ابراهيم،
لا أخي أنا كما يقولون Anti-MFC ، ولم أستخدمها من قبل...
ولذلك لن يكون هناك تعارض...
أرجوك أن تكمل دروسك، فهي مفيدة لي أنا أيضا(yess)
تحياتي لك
* الشبح *
Jan 1 2003, 03:12 PM
شكراً أخي على الدرس فعلاً جميل واتمنى منك المزيد قد قرات الدرس
بس للاسف في الدرس دى ولي رح من دروسك تواجهني مشاكل ( قصدي شي ما فهمته عدل )
واذا استطعت ان تشرح الره الجايه بشرح ممل جداً جداً
BAZRAMIT
Jan 1 2003, 03:25 PM
* الشبح *
في الحقيقة هذه أول مرة أكتب فيها شروحات...
ومن المفيد جدا أن أسمع رأيك..وما الذي لم تفهمه..
من الصعب جدا أن يكتب الواحد درسا جيدا...ولهذا أتمنى أن تخبرني
عن الأشياء التي ترى أنها غير مكتملة..
BraveHeart
Jan 1 2003, 08:19 PM
والله يا BAZRAMIT انك (gift) من الله
ايوة كذا الدروس والا بلاش
المبرمج الذهبى
Jan 2 2003, 03:50 AM
اخي العزيز :
تسلم علي مجهودك وعلي هذا اليرنامج
بس لي طلب كيف اعمل للبرنامج امتداد EXE.
اي اشغل البرنامج بدون compilier
ولك جزيل الشكر
BAZRAMIT
Jan 2 2003, 07:10 AM
أخي المبرمج الذهبى، أنا لم أفهم قصدك جيدا،
ولكن.. إذا أردت أن تحول البرنامج المكتوب بلغة سي إلى exe
لابد أن تحوله عن طريق compiler للغة سي...زد على ذلك أن برامج ويندوز
تتطلب مواصفات معينة للcompiler"، ولذلك أنا أستخدم
Visual C++ (Visual Studio) compiler.....
لذلك بلا شك تحتاج إلى compiler لكي تجمع exe file.
إسماعيل ابراهيم
Jan 2 2003, 04:52 PM
أخي المبرمج الذهبى.. عندما تقوم بتشغيل برنامجك من داخل فيجيوال ستوديو, فانك تكون قد أنشأت ملف exe "دون ان تدري" وستجده في مكتبه باسم Debug داخل مكتبة مشروعك..
أرجو ان أكون قد فهمت سؤالك صح.
* الشبح *
Jan 2 2003, 09:24 PM
إقتباس
hdc هو قيمة الرابط إلى الـDevice Context للنافذة التي نود إخراج النص إليها، ويمكن الحصول عليه من خلال الدالة GetDC
nXStart المكان على المحور الأفقي للنافذة الذي يبدأ عنده النص.
nYStart المكان على المحور العمودي للنافذة الذي يبدأ عنده النص.
lpString قيمة pointer يشير إلى مكان النص في الذاكرة.
cbString عدد الأحرف التي نود إخراجها
أنا اقصد كما سبق من اقتبس هذا الشرح لا يعجبني ( اعتذر اخي العزيز )
لكن الحمد الله ( يقلون الاساتده اقرى السؤال مره مرثين تعرف الجواب ) فهمت ما تقصد بتمام لكن لم ثتبت تماماً في ذاكرتي

الحفظ مشله عويصه

mustafa
Jan 4 2003, 02:41 PM
أريد أن أتعرف على لغة السى++
من حيث الدوال و
class
(f)
BAZRAMIT
Jan 6 2003, 06:33 PM
أخي * الشبح *
أشكرك على تعليقك على الشرح...
بالفعل..هذه الطريقة، أنا متأكد أنها لن تعجب الكثيرين...
لأنها تعتمد على ايضاحات نصية فقط..من الممكن بالطبع أن تكون أفضل
بالإستعانة برسومات..أو أشكال بيانية..
ولكن، صدقني..أنا أفضل مكان تعلمت فيه برمجة ويندوز..ليس الكتب وإنما
شروحات مبنية بنفس الطريقة تقريبا...مثلا لو كنت تعرف عن شروحات إيكسيليون Iczelion..
أنا بنيت عليها شروحاتي لأنني لم أستفد في حياتي من أي كتاب مثلما استفدت من الشروحات المماثلة.
بالنسبة للحفظ...
أنصحك بألا تحفظ أي شيء!
أعتى المبرمجين في العالم يستخدمون الــHelp
في حالة برمحة ويندوز بالذات...يجب أن تتعلم كيفية استخدام
الــWin32API help
أو الــMSDN WIN32 SDK
أما ما تعجز عن فهمه..فيما يتعلق بويندوز...لا تستعجله..
هي مراحل نمر بها...والتطور نحو الأفضل هو المنظور دائما....
أشكرك.
BAZRAMIT
Jan 6 2003, 06:45 PM
mustafa
إذا كنت تعرف اللغة الانجليزية...اطلع على الصفحة التالية:
http://www.tcfb.com/freetechbooks/bookcpp.html
BAZRAMIT
Jan 6 2003, 06:46 PM
[ALIGN=CENTER]الدرس الرابع:الحاسة البرمجية- نحو معالج نصوص (تكملة 1)[/ALIGN]
كنا قد تساءلنا في نهاية الدرس السابق عن كيفية معالجة الـBackspace والــEnter.
الحل دوما يكون في يد المبرمج..سأقوم هنا بتقديم حل سهل جدا..ولكن يجب أن تعرف أنه كلما مر الوقت وأنت تطور باستخدام فيجوال سي،
كلما زادت الخيارات أمامك..ولذلك لا تعتبر كل ما أقدمه الآن بأنه أفضل طريقة ممكنة...
الكود التالي يقوم بمعالجة ما ذكرناه..
[php]#include
//WndProc signature
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
const char m_ClassName[] = "BazramitClass";
char m_Chars[100][100];
int m_Col[100];
int m_Row;
//Main Program Entry
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
//Fill Window Class Structure Members
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = m_ClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
//Register What you just filled
RegisterClassEx(&wc);
//Create the window from the defined class
hwnd = CreateWindowEx(
NULL,
m_ClassName,
"Bazramit Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
//Display The Window on desktop
ShowWindow(hwnd, nCmdShow);
//Refresh the client area
UpdateWindow(hwnd);
//Enter Message Loop
while(GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
//The WndProc Which Processes Messages
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
int i;
RECT rc;
switch(msg) //
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_CHAR:
switch(wParam)
{
case(13):
m_Row++;
m_Col[m_Row]=0;
return 0;
break;
case(8) :
if(m_Col[m_Row]>0)
{
m_Col[m_Row]--;
GetClientRect(hwnd,&rc);
InvalidateRect(hwnd,&rc,true);
}
else if (m_Col[m_Row]==0 && m_Row>0)
{
m_Row--;
GetClientRect(hwnd,&rc);
InvalidateRect(hwnd,&rc,true);
}
return 0;
break;
default:
hdc=GetDC(hwnd);
m_Chars[m_Row][m_Col[m_Row]]=(char)wParam;
m_Col[m_Row]++;
TextOut(hdc,0,m_Row*20,m_Chars[m_Row],m_Col[m_Row]);
ReleaseDC(hwnd, hdc);
return 0 ;
break;
}
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
for (i=0;i<=m_Row;i++)
{
TextOut(hdc,0,i*20,m_Chars[i],m_Col[i]);
}
EndPaint (hwnd, &ps) ;
return 0 ;
break;
default:
//Call The Default Handler
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}[/php]
جرب الآن كتابة بعض الحروف واستخدم Enter و Backspace.
التعليق على الكود:
هل حدث تغير كبير عن المرة السابقة؟...
أولا نحن فكرنا في طريقة أخرى لتخزين الحروف المررة لنا من قبل المستخدم...
[php]char m_Chars[100][100];[/php]
استخدمنا منظومة ثنائية الفهرس Two Dimensional Array لتخزين الحروف..
السبب في ذلك أننا نريد تخزين كل سطر في منظومة واحدة...
وكلما ضغط المستخدم على "إدخال"..ليبدأ سطرا جديدا..نبدأ في تخزين الحروف في منظومة أخرى..
تخيل أن الحروف يتم تخزينها في جدول يشبه جدول الكلمات المتقاطعة..هناك رقم لكل صف (سطر) و رقم لكل عمود.
مثال:
المستخدم يدخل:
Hello I am new to Visual C
ثم يضغط على الزر Enter ثم يكمل:
Now, I am starting anew line
عنها يكون شكل المنظومة m_Chars كالتالي:
[php]m_Chars[0]="Hello I am new to Visual C"
m_Chars[1]="Now, I am starting anew line"[/php]
وإذا كان بالإمكان الدخول إلى الفهرس الثاني في المنظومة الثنائية سنجد فيها:
[php]m_Chars[0][0]='H'
m_Chars[0][1]='e'
m_Chars[0][2]='l'
m_Chars[0][3]='l'
m_Chars[0][4]='o'
m_Chars[0][5]=' '
....
.....
......[/php]
وبذلك يكون المفهرس (index) الأول عنوانا لرقم السطر، والمفهرس الثاني عنوانا لرقم العمود.
هذا كود افتراضي يبين كيفية عرض كل الأسطر المخزنة (أرجو ألا تكون قد نسيت فائدة الدالة TextOut):
[php]for (i=0;i<100;i++)
{
TextOut(hdc,0,0,m_Chars[i],100);
}[/php]
من ناحية أخرى، لابد لبرنامجنا من معرفة رقم السطرالذي يقف عنده المستخدم...
[php]int m_Row;[/php]
بالطبع سنقوم بزيادة m_Row كلما ضغط المستخدم على Enter.
وبما أن كل سطر سيخزن عدد مختلف من الأعمدة(عدد الأحرف لكل سطر) وأقصى حد من عدد الأسطر هو 100:
[php]int m_Col[100];[/php]
كلما أدخل المستخدم حرفا جديدا، نقوم بزيادة عدد الأعمدة أو الأحرف للسطر الحالي:
[php]m_Col[m_Row]++;[/php]
إذن دعنا الآن نراجع التصميم الذي أعددناه لمعالجة الرسالة WM_CHAR :
1-كلما ضغط المستخدم على Enter نقوم بزيادة عدد الأسطر m_Row.
2-كلما ضغط المستخدم على Backspace نقوم بإنـــقـــاص عدد الأعمدة (الحروف) في السطر الحالي وبذلك لا يتم عرض الحرف
السابق.
3-كلما ضغط المستخدم على أي حرف آخر نقوم بـــزيـــادة عدد الأعمدة (الحروف) في السطر الحالي ونخزن الحرف في m_Chars.
قبل أن ننتقل إلى تحليل الكود في WndProc دعونا نمضي بعض الوقت مع ضيف جديد.
لكل نافذة يوجد مساحة تخضع للرسم والمعالجة تسمى Client Area Rectangle. دعنا نسميها مربع العمليات...
مثلا مربع العمليات في Internet Explorer هو المساحة التي يتم عليها رسم محتويات صفحات الويب المختلفة.
ومثلا مربع العمليات في MS WORD هو المساحة التي يتم عليها كتابة النصوص..
إذن Client Area Rectangle هو المربع الذي نرسم عليه جميع المحتويلت للنافذة...أي أن هذا المربع يشمل النافذة كلها ما عدا
الإطار، وقضيب العنوان (Title Bar)، والمزلاجان ScrollBars الرأسي والأفقي....
لكي نحصل على مربع العمليات الخاص بأي نافذة نستخدم الدالة: GetClientRect.
ولم لا نلق نظرة على التوقيع الخاص بهذه الدالة لنعرف كيفية استخدامها:
[php]BOOL GetClientRect(
HWND hWnd, // handle of window
LPRECT lpRect // address of structure for client coordinates
);[/php]
واضح!...نمرر للدالة قيمة المعالج الخاص بالنافذة..HWND
وكذلك عنوان لمتغير من نوع:LPRECT .
LPRECT في حد ذاته هو مؤشر لبنية Structure من نوع RECT...وهذا هو تعريفها:
[php]typedef struct _RECT { // rc
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT; [/php]
كالترجمة الحرفية للكلمة RECT..مربــــع..له الخصائص التالية:
left المكان الذي سيبدأ عنده المربع على المحور الأفقي النافذة...صفر 0 تعني أن المربع سيبدأ من حدود الـClient Area اليسرى.
top المكان الذي سيبدأ عنده المربع على المحور الرأسي...صفر 0 تعني أن المربع سيبدأ من حدود الـClient Area العليا.
right المكان الذي سينتهي عنده المربع على المحور الأفقي النافذة
bottom المكان الذي سينتهي عنده المربع على المحور الرأسي النافذة
باستخدام هذه البنية نستطيع تجديد أي مربع نريد الرسم عليه في النافذة...
ولكن إذا أردنا ملأ هذه البنية بالقيم الخاصة بكل الـClientArea، نستخدم الدالة GetClientRect لتملأه لنا.
وفي الوقت الذي نريد أن نعيد رسم محتويات المربع بامكاننا استخدام الدالة: InvalidateRect.
[php]BOOL InvalidateRect(
HWND hWnd, // handle of window with changed update region
CONST RECT *lpRect, // address of rectangle coordinates
BOOL bErase // erase-background flag
);[/php]
نمرر للدالة قيمة عنوان البنية RECT.
الدالة InvalidateRect تقوم في الواقع بإعادة رسم محتويات أي مربع نمرره لها...
وعندما نمرر المربع الذي تم ملؤه بواسطة GetClientRect، فإن InvalidateRect تتسبب في إعادة رسم جميع
محتويات النافذة...وبالتالي كأننا نقوم بإرسال رسالة WM_PAINT للنافذة.
شكرا!
المرحلة الحاية أيضا تقتضي أن ننعش الذاكرة فيما يتعلق بالدالة TextOut إحدى الدوال المسؤلة عن إخراج النصوص، وهذا تعريفها
مرة أخرى:
[php]BOOL TextOut(
HDC hdc, // handle of device context
int nXStart, // x-coordinate of starting position
int nYStart, // y-coordinate of starting position
LPCTSTR lpString, // address of string
int cbString // number of characters in string
);[/php]
نحتاج لتشغيل هذه الدالة...أن نحدد المعالج Handle الخاص بسطح النافذة...أو ما يسمى HDC...ونمرره في الباراميتر الأول.
وفي الباراميتر الثاني نمرر المكان الذي سيبدأ عنه النص المطلوب كتابته على المحور الأفقي.
وفي الباراميتر الثالث..نمرر المكان على المحور الأفقي..
في الرابع..نمرر عنوان النص String الذي نود كتابته...لاحظ أننا من الممكن أن نمرر منظومة من الحروف Array of Chars في هذا
الباراميتر..
في الباراميتر الخامس..نمرر عدد الأحرف التي نود اخرجها!
الآن نظرة شاخصة إلى الكود الخاص بمعالجة ضغطات المستخدم على لوحة المفاتيح:
[php] case WM_CHAR:
switch(wParam)//
{
//Enter في حالة الضغط على
case(13):
m_Row++; //زيادة عدد الأسطر
m_Col[m_Row]=0; //أعد قيمة الأحرف المخزنة في السطر
الجديد إلى صفر
return 0;
break;
// Backspace في حالة الضغط على
case(8) :
if(m_Col[m_Row]>0) //لا بد ان يكون عدد الأحرف في السطر أكبر من صفر
{
m_Col[m_Row]--; //في هذه الحالة أنقص عدد الأحرف
وبالتالي يلغى الحرف السابق
GetClientRect(hwnd,&rc); //إحصل عبى
قيمة مربع الرسم الذي نظهر عليه الحروف في النافذة
InvalidateRect(hwnd,&rc,true); //قم بإعادة رسم المنطقة أو
المربع الذي تظهر عليه الحروف في النافذة
}
else if (m_Col[m_Row]==0 && m_Row>0)//أما إذا كان عدد الحروف
في السطر مساو للصفر
//وفي نفس الوقت يكون عدد الأسطر أكبر من الصفر
{
m_Row--;
//قم بإلغاء السطر الحالي وعدسطرا إلى الوراء
GetClientRect(hwnd,&rc); //أيضا
أعطني قيمة مربع العمليات
InvalidateRect(hwnd,&rc,true); //أرغم البرنامج على إعادة
رسم المربع وإخراج الحروف عند السطر الجديد
}
return 0;
break;
// في حالة الضغط على أي مفتاح آخر في لوحة المفاتيح
default:
hdc=GetDC(hwnd);
//لو سمحت، أعطني قيمة معالج الأسطح الخاص بالنافذة
m_Chars[m_Row][m_Col[m_Row]]=(char)wParam; //
m_Charsخزن الحرف الجديدفي المتغير
//m_Row في السطر رقم
//m_Col[m_Row]والعمود (الحرف) رقم
m_Col[m_Row]++;
// قم بزيادة رقم المفهرس الذي يحتفظ بعدد الحروف في السطر الحالي
TextOut(hdc,0,m_Row*20,m_Chars[m_Row],m_Col[m_Row]);
//أخرج كل الحروف في المكان المحدد
ReleaseDC(hwnd, hdc); //انتهينا
return 0 ;
break;
}
[/php]
هذه ترجمة مفصلة لطريقة عمل الكود:
عندما يضغط المستخدم علىEnter يقوم البرنامج بالتالي:
-زيادة عدد الأسطر
-تنصيب عدد الأعمدة في هذا السطر الجديد على أنها صفر..حيث تزداد هذه القيمة كلما ضغط المستخدم على حرف جديد
كما تقل كلما ضغط المستخدم على Backspace.
[php] //Enter في حالة الضغط على
case(13):
m_Row++; //زيادة عدد الأسطر
m_Col[m_Row]=0; //أعد قيمة الأحرف المخزنة في السطر
الجديد إلى صفر
return 0;
break; [/php]
وعندما يضغط المستخدم على Backspace:
-إذا كان عدد الأحرف المخزنة في السطر الحالي أكبر من صفر..يقوم البرنامج بإنقاص عدد الأحرف..وبالتالي عندما
نقوم برسم محتويات النافذة مرة أخرى (بواسطة InvalidateRect)..لا يظهر الحرف الأخير..
-إذا كان السطر الحالي أكبر من صفر، في الوقت الذي يكون عدد الأحرف في السطر صفر....
يقوم البرنامج بإلغاء السطر الحالي بالكامل...
بمعنى: إذا ضغط المستخدم على Enter نقوم بإضافة سطر جديد...ولكن إذا قام المستخدم بعد ذلك مباشرة
بالضغط على Backspace، نقوم بإلغاء السطر...
[php] // Backspace في حالة الضغط على
case(8) :
if(m_Col[m_Row]>0) //لا بد ان يكون عدد الأحرف في السطر أكبر من صفر
{
m_Col[m_Row]--; //في هذه الحالة أنقص عدد الأحرف
وبالتالي يلغى الحرف السابق
GetClientRect(hwnd,&rc); //إحصل عبى
قيمة مربع الرسم الذي نظهر عليه الحروف في النافذة
InvalidateRect(hwnd,&rc,true); //قم بإعادة رسم المنطقة أو
المربع الذي تظهر عليه الحروف في النافذة
}
else if (m_Col[m_Row]==0 && m_Row>0)//أما إذا كان عدد الحروف
في السطر مساو للصفر
//وفي نفس الوقت يكون عدد الأسطر أكبر من الصفر
{
m_Row--;
//قم بإلغاء السطر الحالي وعدسطرا إلى الوراء
GetClientRect(hwnd,&rc); //أيضا
أعطني قيمة مربع العمليات
InvalidateRect(hwnd,&rc,true); //أرغم البرنامج على إعادة
رسم المربع وإخراج الحروف عند السطر الجديد
}
return 0;
break;[/php]
أما عندما يضغط المستخدم على أي حرف آخر:
-نخزن الحرف...
-نقوم بزيادة عدد الأحرف للسطر الحالي..
-نرسم جميع الحروف في السطر الحالي بما فيها الحرف الجديد...
[php] // في حالة الضغط على أي مفتاح آخر في لوحة المفاتيح
default:
hdc=GetDC(hwnd);
//لو سمحت، أعطني قيمة معالج الأسطح الخاص بالنافذة
m_Chars[m_Row][m_Col[m_Row]]=(char)wParam; //
m_Charsخزن الحرف الجديدفي المتغير
//m_Row في السطر رقم
//m_Col[m_Row]والعمود (الحرف) رقم
m_Col[m_Row]++;
// قم بزيادة رقم المفهرس الذي يحتفظ بعدد الحروف في السطر الحالي
TextOut(hdc,0,m_Row*20,m_Chars[m_Row],m_Col[m_Row]);
//أخرج كل الحروف في المكان المحدد
ReleaseDC(hwnd, hdc); //انتهينا
return 0 ;
break;[/php]
ولكن هناك ملاحظة على مكان إخراج الحروف في الدالة: TextOut::
[php]TextOut(hdc,0,m_Row*20,m_Chars[m_Row],m_Col[m_Row]);[/php]
المكان اللأفقي لإخراج أي سطر يكون 0.
ولكن المكان العمودي يختلف حسب رقم السطر...m_Row*20
أي أننا سنترك 20 وحدة قياسية بين كل سطر وآخر..
طبعا هذه ليست طريقة محببة لعمل ذلك...من المفترض أن يعرف المرء بالضبط متوسط طول الحرف على الشاشة كي نقوم بإخراج السطر
في المكان الصحيح....
تصور مثلا أن طول الحرف عموديا هو X وأننا استطعنا الحصول عليه...
في هذه الحالة تكون الطريقة المناسبة لإظهار السطور:
[php]TextOut(hdc,0, (m_Row * X) + 2 ,m_Chars[m_Row],m_Col[m_Row]);[/php]
بالطبع يوجد دالة تستطيع إخبارنا بمقدار علو الحرف...ولكن لنترك هذه الدالة الآن للدرس القادم!
ملحوظة أخرى...
عندما يقوم المستخدم بالضغط على Backspace فإننا نقوم برسم محتويات مربع العمليات بالكامل...وطبعا هذه ليست أفضل طريقة..
بإمكاننا إعادة رسم المنطقة المتضررة فقط..وذلك بتعيين مربع يشمل الحرف الملغي فقط....
على العموم..هو ليس بالأمر اليسير...ولكن لا شيء لا يمكن عمله باستخدام Visual C.
فقط بقي أن نسدل الستار على طريقة عمل الكود بأن نحدد طريقة تعامل البرنامج مع الرسالة WM_PAINT
التي تصل للبرنامج في حالة تغيير وضع النافذة...أو حجمها..أو عندما نستدعي الدالة InvalidateRect...أو أي حالة أخرى
يكون البرنامج مطالبا فيها بإعادة رسم محتويات النافذة...
[php] case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
for (i=0;i<=m_Row;i++)
{
TextOut(hdc,0,i*20,m_Chars[i],m_Col[i]);
}
EndPaint (hwnd, &ps) ;
return 0 ;
break;[/php]نقوم بإعادة إخراج كل السطرو المخزنة في m_Chars
أعرف أن هذا الدرس قد يثير العديد من التساؤلات حول التكنيك المتبع...
ولكن لكي تفهم بالكامل ما تقدم...يجب أن تقوم بمحاولات شخصية وتلعب بالكود وحوله إلى أقصى حد...
إذا كانت لك أي ملاحظات حول الكود...من فضلك..لا تتردد في السؤال....
* الشبح *
Jan 6 2003, 07:52 PM
شكراً أخي على الدرس
Al-saidi
Jan 12 2003, 11:18 PM
نتمنى ألا تنقطع عن تقديم هذه الدروس القيمة و المفيدة
هاني الأتاسي
Jan 12 2003, 11:27 PM
أخي BAZRAMIT بارك الله فيك .. هل تستطيع جمعها في درس أو عدة دروس في ملف HTML .. حتى نضعها في قسم الدروس .. وأكون لك من الشاكرين ..
بالنسبة للكود اكتب الكود حول العنصر التالي :
كود
<CODE language=cpp>
put the code here
</CODE>
وشكرا جزيلا