سلام
انشالله که دوستان همگی موفق به راه اندازی ال سی دی و تاچ شده باشند. این آخرین پست منه و درباره Window Manager توضیحاتی خدمتتون میگم:
Window Manager
مدیرپنجره های emwinکارش رسم پنجره ها و Widgetها، ریفرش اونها و همچنین تولید رویدادهای مربوط به آنهاست.
برای شروع کار باید هدرش رو include کنید:
#include “WM.h”
شروع تابع MainTask همیشه با GUI_Init است. همونطور که قبلا گفتم کارش راه اندازی ال سی دی و تاچه. برای راه اندازی مدیرپنجره دستور WM_Init رو بلافاصله پس از GUI_Init بنویسید.
کد:
void MainTask(void);
void MainTask(void){
GUI_Init();
WM_Init();
GUI_CURSOR_Show();
WM_SetDesktopColor(GUI_BLACK);
#if GUI_SUPPORT_MEMDEV
WM_SetCreateFlags(WM_CF_MEMDEV);
#endif
…
کدهایی که ملاحظه می کنید تقریبا در تمام برنامه ها باید نوشته بشه. خط سوم GUI_CURSOR_Show همونطور که از نامش پیداست برای اینه که ماوس نمایش داده بشه. اگه شما ماوس رو نمی بینید دلیلش اینه که تاچ درست کالیبره نشده. خط بعدی رنگ دستکاپ رو مشخص می کنه. و در خط بعد درصورتیکه در فایل GUI_Conf.h مقدار ماکروی GUI_SUPPORT_MEMDEVICE رو یک کرده باشید اجرا میشه و پرچم مربوطه رو ست می کنه تا WM از حافظه مجازی استفاده کنه.
در WM پنجره ها از نوع ساختاری به نامWM_HWIN هستند. پنجره ای به نام DESKTOP به محض این که WM_Init اجرا بشه ساخته میشه. این پنجره همیشه وجود داره و مثل دستکاپ ویندوز پایین ترین پنجره است. اگه با برنامه نویسی تحت ویندوز آشنایی دارید کار راحتی با emwin خواهید داشت. پنجره ها دارای خواصی مانند پنجره های ویندوز هستند. یکی از اون خواص ارتفاع پنجره هاست. Desktop پایین ترین پنجره است و همیشه اولین پنجره ای است که در فرایند WM_PAINT ترسیم میشه. هندل این پنجرهWM_HBKWIN است. هر پنجره می تونه Parent یا Child باشه. توجه کنید که همه Widget ها از نوع پنجره Child هستند. پس دستوراتی که در emwin مربوط به روابط فرزند و والد هست رو از راهنماش بخونید. پنجره های Parent دارای تابعی به نام Callback هستند که در واقع تمامی رویدادهای مربوط به اون پنجره و فرزندانش رو باید در تابع Callback مربوط به اون پنجره مدیریت بفرمایین.
برای اینکه آموزش روالی داشته باشه بیایید یکی از مثالهای emwin رو باهم مرور کنیم.
این کارهارو در keil انجام بدهید:
در شاخه Application اگر فایلی وجود داره حذفش کنید. در عوض فایل WM_Sample.C رو از مسیر Sample\Tutorial به شاخه Application اضافه کنید.
می تونید کامپایلش کنید و ببینید که چه کارهایی انجام میده.
خب شرح توضیحات رو از تابع MainTask شروع می کنیم.
در این تابع ابتدا GUI و WM راه اندازی میشه.
کد:
void MainTask(void) {
GUI_Init();
WM_Init();
GUI_SetBkColor(GUI_BLACK);
WM_SetCreateFlags(WM_CF_MEMDEV);
WM_EnableMemdev(WM_HBKWIN);
while (1) {
_DemoSetDesktopColor();
_DemoCreateWindow();
_DemoCreateWindowAsChild();
_DemoInvalidateWindow();
_DemoBringToTop();
_DemoMoveTo();
_DemoBringToBottom();
_DemoMoveWindow();
_DemoHideShowParent();
_DemoHideShowChild();
_DemoClipping();
_DemoRedrawing();
_DemoResizeWindow();
_DemoSetCallback();
_DemoDeleteWindow();
}
}
در تابع فوق پس از مقدار دهی های اولیه دستوری مشاهده می کنید که استفاده از حافظه مجازی رو برای پنجره Desktop مجاز می کنه. درواقع این مساله رو برای همه پنجره ها باید مشخص کنید. توابع حلقه while رو ببینید:
کد:
_DemoSetDesktopColor();
برای اینکه رنگ پنجره Desktop رو عوض کنه:
static void _DemoSetDesktopColor(void) {
GUI_SetBkColor(GUI_BLUE);
GUI_Clear();
_DrawInfoText("WM_SetDesktopColor()");
GUI_Delay(SPEED*3/2);
WM_SetDesktopColor(GUI_BLACK);
GUI_Delay(SPEED/2);
/* Set background color and invalidate desktop color.
This is needed for the later redrawing demo. */
GUI_SetBkColor(GUI_BLACK);
WM_SetDesktopColor(GUI_INVALID_COLOR);
}
دستورات کاملا گویا است. دو دستور GUI_SetBkColor وWM_SetDesktopColor باهم فرق می کنند. اولی رنگ پس زمینه و دومی رنگ پنجره Desktop است. داستان مربوط به رنگها رو باید خودتون از راهنما بخونید. خیلی ساده است. نکته جالب اینه که اگه برای دسکتاپ رنگ مشخص نکنید، اون نمیتونه خودش رو ریفرش کنه. بنابراین اثر سایر پنجره ها روش می مونه.
برای ایجاد تاخیر از دستور GUI_Delay یا GUI_X_Delay استفاده کنید.
دستور GUI_Clear وظیفه اش پاک کردن صفحه نمایش یا پنجره است. درصورتیکه پنجره ای Active باشه پاک میشه وگرنه دستکتاپ پاک خواهد شد. درصورتیکه WM استفاده نکنید این دستور صفحه رو پاک می کنه (با رنگBkColor پر می کنه)
خط بعد تابع _DrawInfoText("WM_SetDesktopColor()");رو فراخوانی کرده:
کد:
static void _DrawInfoText(char* pStr) {
GUI_SetColor(GUI_WHITE);
GUI_SetFont(&GUI_Font24_ASCII);
GUI_DispStringHCenterAt("WindowManager - Sample", 160, 5);
GUI_SetFont(&GUI_Font8x16);
GUI_DispStringAtCEOL(pStr, 5, 40);
}
در این تابع ابتدا رنگ قلم رو سفید معرفی کرده سپس فونت رو انتخاب کرده (فونت رو باید با ابزار FontConvertor که در فولدر tools هست بسازید وبه پروژه add کنید. دستور بعدی میگه که یک متنی رو در مختصات 160و5 بنویس با این شرط که از نظر افقی مختصات داده شده وسط متن بیافته. HCenter
سپس فونت عوض میشه و دستور بعدی هم متنی که ورودی تابع هست رو در مختصات 5و40 مینویسه البته طوری که هم بصورت افقی و هم عمودی در مرکز این مختصات قرار گرفته باشه . عبارت EOL یعنی بقیه خط رو با SPACE تا انتها پر کنه.
تابع بعدی که خیلی هم مهمه اینه. با یادگیری اون همه چیز درباره پنجره هاروشن میشه:
کد:
static void _DemoCreateWindow(void) {
/* Set callback for background window */
_cbBkWindowOld = WM_SetCallback(WM_HBKWIN, _cbBkWindow);
/* Create windows */
_ChangeInfoText("WM_CreateWindow()");
GUI_Delay(SPEED);
_hWindow1 = WM_CreateWindow( 50, 70, 165, 100, WM_CF_SHOW | WM_CF_MEMDEV, _cbWindow1, 0);
GUI_Delay(SPEED/3);
_hWindow2 = WM_CreateWindow(105, 125, 165, 100, WM_CF_SHOW | WM_CF_MEMDEV, _cbWindow2, 0);
GUI_Delay(SPEED);
}
گفتیم که هر پنجره باید دارای تابع Callback باشه. پنجره دستکتاپ مهمترین پنجره است و باید دارای تابع cb باشه. دستوری به نام WM_SetCallback کارش اینه که تابع cb یک پنجره رو مشخص می کنه. باید ID پنجره رو به همراه اشاره گر تابع مربوطه بهش معرفی کنید.تابع cb باید الگوی خاصی داشته باشه که بعدا خواهم گفت. دستوری که یک پنجره ایجاد میکنه اسمش WM_CreateWindow است:
کد:
_hWindow1 = WM_CreateWindow( 50, 70, 165, 100, WM_CF_SHOW | WM_CF_MEMDEV, _cbWindow1, 0);
صورت کلی اون به این صورته:
کد:
WM_HWIN WM_CreateWindow(int x0, int y0,
int width, int height,
U32 Style, WM_CALLBACK * cb
int NumExtraBytes);
ارگومانهای اول و دوم مختصات گوشه بالا سمت چپ پنجره است و دو آرگومان بعدی طول و عرض اون پنجره است. آرگومان بعد پرچمهایی است که باید ست کنید. لیست همه پرچمها رو درصفحه350 میتونید ببینید. مهمترینهاش ایناست:
WM_CF_MEMDEV: برای اینکه از حافظه مجازی استفاده شود (برای ریفرش)
WM_CF_SHOW: پنجره به محض ایجاد نمایش داده شود
WM_CF_STAYONTOP: بالاتر از سایرپنجره ها قرار گیرد
WM_CF_HIDE: در لحظه ایجاد نمایش داده نشود.
پرچمها رو با |درهم ادغام کنید.
آرگومان بعدی اشاره گری به تابع Callback این پنجره است که باید بهش معرفی کنید. و آخری هم آرگومان اختیاری است که معمولا صفر مقداردهی می شود.خروجی دستور باید در متغیری از نوع WM_HWIN قرار گیرد که بعوان ID یا Handle پنجره برای دسترسی به اون مورد استفاده قرار میگیره.
کد:
static WM_HWIN _hWindow1;
خب بریم سروقت مهمترین و آخرین چیزی که خدمتتون عرض میکنم:
CALLBACK ROUTINE
ابتدا Callback پنجره ِدستکتاب رو ببینیم. قبلا گفتم که این پنجره بلافاصله پس از initialize شدن WM ایجاد میشه و بصورت پیشفرض فاقد تابع callback است. با دستور WM_SetCallback تابع _cbBkWindow رو بعنوان Callback دستکتاپ بهش معرفی کرده ایم. این تابع رو ببینیم:
کد:
static void _cbBkWindow(WM_MESSAGE* pMsg) {
switch (pMsg->MsgId) {
case MSG_CHANGE_TEXT:
strcpy(_acInfoText, pMsg->Data.p);
case WM_PAINT:
GUI_SetBkColor(GUI_BLACK);
GUI_Clear();
GUI_SetColor(GUI_WHITE);
GUI_SetFont(&GUI_Font24_ASCII);
GUI_DispStringHCenterAt("WindowManager - Sample", 160, 5);
GUI_SetFont(&GUI_Font8x16);
GUI_DispStringAt(_acInfoText, 5, 40);
break;
default:{
WM_DefaultProc(pMsg);
break;}
}
}
الگوی توابع Callback باید بصورت زیر باشه:
کد:
static void _cbBkWindow(WM_MESSAGE* pMsg)
ساختارWM_MESSAGE پیامی است که از WM به این تابع ارسال میشه. این ساختار دارای عضوهای زیر است: Data,MsgId,hWin, hWinSrc
عضو MsgId پیام اصلی رو مشخص می کنه. مهمترین پیامی که به یک پنجره فرستاده میشه WM_PAINT است. هر وقت این پیام دریافت بشه پنجره باید خودش رو ترسیم کنه. بنابراین آنچه که میخواهید در پنجره دیده بشه رو باید اینجا بنویسید.(در case WM_PAINT)
در case WM_PAINT مراحل ترسیم رو مرور می کنیم:
دستور GUI_SetBkColor رنگ زمینه پنجره فعال (اینجا دستکتاپ) رو مشخص می کنه.
دستور GUI_Clear پنجره فعال رو پاک می کنه (بارنگی که در دستور قبل مشخص کرده ایم)
دستور GUI_SetColor رنگ قلم رو مشخص می کنه و دستورات بعدی متنهایی رو در این پنجره می نویسه. متن دوم رو ببینید...
کیس default بسیار مهمه و همیشه باید به اینصورت پر بشه:
کد:
default:{
WM_DefaultProc(pMsg);
break;}
اینکار موجب میشه که پیام WM جهت پردازش سایر پنجره ها (فرزندان) به WM برگردانده شود. این کیس رو برای همه توابع CallBack باید بنویسیم... پیامهای WM خیلی مهم هستند. لیست کامل اونها رو در صفحه335 ببینید. WM_NOTIFY_PARENT یکی از مهمترین پیامهاست و نشون میده که پیامی برای فرزند پنجره اصلی صادر شده است که محتویات آن در data قرارگرفته است.
خب اگه Callback های دیگه رو هم مطالعه کنید دستورات جالبی در اونها می بینید.
static void _cbWindow1(WM_MESSAGE* pMsg)
اجازه بدهید نحوه ساخت پنجره فرزند رو باهم ببینیم:
کد:
static void _DemoCreateWindowAsChild(void) {
/* Create windows */
_ChangeInfoText("WM_CreateWindowAsChild()");
GUI_Delay(SPEED);
_hChild = WM_CreateWindowAsChild(10, 50, 145, 40, _hWindow2, WM_CF_SHOW | WM_CF_MEMDEV, _cbChild, 0);
GUI_Delay(SPEED);
}
دستور WM_CreateWindowAsChild مثل دستور ساخت پنجره معمولیه با این تفاوت که در آرگومان پنجم باید هندل پنجره والد رو بهش معرفی کنید. ضمن اینکه مختصات هم نسبت به پنجره والد سنجیده شده است. Callback پنجره _hWindow2 باید چیز جالبی باشه. چون علاوه بر رویدادهای پنجره والد، باید رویدادهای پنجره فرزند رو هم هندل کنه. البته توی این مثال بهش پرداخته نشده ولی من در مثال دیگه ای خدمتتون خواهم گفت. خب WM بطور اتوماتیک پنجره هایی رو که حرکت یا تغییر دارند بهشون پیام WM_PAINT رو می فرسته. فرض کنید که متنی قراره در پنجره نمایش داده بشه که توی یک متغیر قرار گرفته. و ما محتوای متغیر رو خارج از تابع Callback تغییر بدهیم. حالا WM از کجا بفهمه که باید پنجره ترسیم مجدد بشه؟ با دستور WM_InvalidateWindow بهWM می فهمانیم که پنجره ای نیاز به ترسیم مجدد داره.WM میتونه کل پنجره یا قسمتی از اون که نیاز به ترسیم مجدد داره رو ریفرش کنه WM_CLIPPING رو بخونید.
اجازه بدهید برای توضیح callback پنجره های فرزند این تابع رو مطالعه کنیم (از فایلWindow_DLG.c)
کد:
static void _cbDialog(WM_MESSAGE * pMsg) {
WM_HWIN hItem;
int Id, NCode;
// USER START (Optionally insert additional variables)
// USER END
switch (pMsg->MsgId) {
case WM_INIT_DIALOG:
//
// Initialization of 'Window'
//
hItem = pMsg->hWin;
WINDOW_SetBkColor(hItem, 0x00000000);
// USER START (Optionally insert additional code for further widget initialization)
// USER END
break;
case WM_NOTIFY_PARENT:
Id = WM_GetId(pMsg->hWinSrc);
NCode = pMsg->Data.v;
switch(Id) {
case ID_BUTTON_0: // Notifications sent by 'Button'
switch(NCode) {
case WM_NOTIFICATION_CLICKED:
// USER START (Optionally insert code for reacting on notification message)
// USER END
break;
case WM_NOTIFICATION_RELEASED:
// USER START (Optionally insert code for reacting on notification message)
// USER END
break;
// USER START (Optionally insert additional code for further notification handling)
// USER END
}
break;
// USER START (Optionally insert additional code for further Ids)
// USER END
}
break;
// USER START (Optionally insert additional message handling)
// USER END
default:
WM_DefaultProc(pMsg);
break;
}
}
فرض کنید پنجره ای ساخته ایم و یک Button در آن قرار داده ایم. (توجه کنید که Button و همه widgetها از نوع پنجره فرزند هستند) و می خواهیم رویداد های اون رو پردازش کنیم. برای پردازش رویدادهای پنجره های فرزند باید از کیس WM_NOTIFY_PARTENT استفاده کنیم. برای اینکه بفهمیم رویداد مربوط به کدام فرزند است دو متغیر به نام Id و Ncode تعریف شده اند. اولی بادستورWM_GetId آی دی پنجره فرزند را استخراج می کنیم و کنترل می کنیم که چی است
کد:
switch(Id) {
case ID_BUTTON_0: // Notifications sent by 'Button'
switch(NCode) {
case WM_NOTIFICATION_CLICKED:
در این کیس می تونید آنچه را که باید درهنگام کلیک شدن Button_0 باید انجام شود را بنویسید.
WM پنجره رو می تونه بصورت Dialog هم نمایش بده. این حالتیه که فایل GUIBuilder تولید می کنه. خیلی سخت نیست. توضیح اینه که پنجره به همراه همه Widgetها همزمان تولید میشه. Widgetها دارای IDهایی هستند که در فایل GUI.h مشخص شده ولی شما می تونید اونها رو عوض هم بکنید. فقط مواظب باشید تداخلی پیش نیاد.
فکر کنم نتونستم اونطور که باید آموزش بدم .مطالب من خیلی پراکنده بوده . به هرحال تجربه اول من در نوشتن پست بوده. امیدوارم ببخشید.مطالب خیلی خیلی زیادی هم مونده .. بهتره agape یا یکی از دوستان که تسلط کافی داره و بیانش هم خوبه بحث رو ادامه بده.
موفق باشید.