فصل ۸ — خطاها و تابع Error
خطاهای زمان اجرا (Runtime Errors) میتوانند بسیار آسان و حتی بدون تقصیر برنامهنویس وارد کد شوند. ممکن است کاربر کاری خارج از محدودهای که کد برای آن طراحی شده انجام دهد و پیام خطایی ایجاد شود که تمام اجرای کد را متوقف کند. این وضعیت میتواند هنگام دسترسی به یک منبع دادهٔ خارجی مانند پایگاهداده رخ دهد. همچنین ممکن است کاربر عملی انجام دهد که برنامهنویس هرگز پیشبینی نکرده است.
هرقدر هم تلاش کنید همهٔ وضعیتها را پوشش دهید، همیشه ممکن است کاربری کاری انجام دهد که به ذهن شما نرسیده و عملاً قواعدی را که نوشتهاید بشکند. گاهی مسئله به سادگی وجود یک آپاستروف در نامی مانند O’Brien است. اگر این نام را در رشتهٔ یک Query SQL به کار ببرید، شیوهٔ پردازش آن میتواند مشکل ایجاد کند.
نمونهٔ دیگر، خواندن فایل از یک درایو است. ممکن است به کاربر اجازه دهید حرف درایو فایل را انتخاب کند و فرض کنید فایلها عمدتاً از درایوهای شبکه خوانده میشوند و گزینهٔ D: برای CD/DVD فقط امکانی اضافی است که احتمالاً استفاده نمیشود. اما اگر کاربر D: را انتخاب کند و هیچ دیسکی در درایو نباشد، خطایی رخ میدهد که اجرای برنامه را متوقف میکند. چنین تجربهای اعتماد کاربر به برنامه را بهشدت کاهش میدهد. بنابراین خطا باید Trap شود و در کد VBA واکنش مناسبی به آن نشان داده شود.
Error Handling در Common Dialog Formها نیز استفاده میشود؛ برای مثال برای تشخیص کلیک روی دکمهٔ Cancel که در فصل ۱۰ بررسی میشود. برای آزمودن Cancel ابتدا باید Property با نام CancelError را روی True قرار دهید و سپس با یک دستور On Error تعیین کنید در صورت خطا اجرای کد به کجا منتقل شود.
یک نمونهٔ سادهٔ Error Trapping
بدون قرار دادن دیسک در درایو D، کد زیر را در یک Code Module قرار دهید و با F5 اجرا کنید:
Sub Test_Error()
temp = Dir("d:\*.*")
End Sub
پیامی دریافت میکنید که میگوید درایو D آماده نیست. از دید کاربر، برنامه Crash کرده و تا رفع خطا دیگر کار نمیکند؛ نتیجهای نامطلوب.
میتوانید یک روال سادهٔ Trap کردن خطا بنویسید:
Sub Test_Error()
On Error GoTo err_handler
temp = Dir("d:\*.*")
Exit Sub
err_handler:
MsgBox "The D drive is not ready" & " " & Err.Description
End Sub
خط اول با دستور On Error مسیری را مشخص میکند که هنگام وقوع خطا باید به آن پرش شود. این مسیر به Label با نام err_handler اشاره میکند که پایینتر، پس از Exit Sub قرار دارد و وضعیت خطا را مدیریت میکند. Label بخشی از کد را نامگذاری میکند تا بتوان با GoTo به آن منتقل شد.
خط خواندن درایو D همان خط قبلی است و سپس Exit Sub قرار دارد؛ زیرا اگر همهچیز درست باشد نباید اجرا وارد روال err_handler شود. اگر پس از On Error خطایی رخ دهد، اجرا به err_handler میرود و Message Box نمایش داده میشود.
اما این پرش برای هر خطایی رخ میدهد، نه فقط «Drive not ready». حتی یک اشتباه تایپی در کد نیز میتواند اجرای این Handler را باعث شود. خوشبختانه میتوان خطا را بررسی کرد تا علت دقیق آن معلوم شود. Object با نام Err توضیح خطا را نیز در اختیار میگذارد و میتوانید Err.Description را به پیام خود اضافه کنید. همچنین Err.Number شمارهٔ خطای Runtime رخداده را برمیگرداند:
Sub Test_Error()
On Error GoTo err_handler
temp = Dir("d:\*.*")
Exit Sub
err_handler:
If Err.Number = 71 Then
MsgBox "The D drive is not ready"
Else
MsgBox "An error occurred"
End If
End Sub
در مثال نخست، شمارهٔ خطای «Drive not ready» برابر ۷۱ بود. کد مقدار Err را، که آخرین شمارهٔ خطای سیستم را نگه میدارد، بررسی میکند. اگر ۷۱ باشد پیام مخصوص آمادهنبودن درایو D و در غیر این صورت پیام عمومی خطا نمایش داده میشود.
دستور Resume
با افزودن Resume میتوان اجرای کد را به همان دستوری که خطا در آن رخ داده بازگرداند. این کار فرصتی میدهد تا کاربر مداخله کند؛ مثلاً CD را داخل درایو D بگذارد و سپس همان خط دوباره درایو را بررسی کند:
Sub Test_Error()
On Error GoTo err_handler
temp = Dir("d:\*.*")
Exit Sub
err_handler:
If Err.Number = 71 Then
MsgBox "The D drive is not ready"
Else
MsgBox "An error occurred"
End If
Resume
End Sub
میتوانید واژهٔ Next را نیز به Resume اضافه کنید. در این حالت دستور ایجادکنندهٔ خطا رد میشود و خطا عملاً نادیده گرفته میشود:
Sub Test_Error()
On Error Resume Next
temp = Dir("d:\*.*")
End Sub
اگر دیسکی در درایو D نباشد، این کد بهدلیل On Error Resume Next همچنان بدون توقف اجرا میشود؛ خط مشکلدار فقط رد میشود.
Resume Next میتواند برای عبور از خطاهای شناختهشده مفید باشد، اما اشکالزدایی را بسیار دشوار میکند. ممکن است در بخشهای بعدی برنامه، دادهٔ غلط یا اصلاً هیچ دادهای تولید نشود، چون خطای قبلی نادیده گرفته شده است. ممکن است برنامه ظاهراً اجرا شود اما در عمل بهعلت باگهای پنهان یا دادههای ناقص کاری انجام ندهد. هر بار که خطایی رخ دهد اجرا فقط از روی آن میگذرد و تصویری نادرست از وضعیت واقعی ایجاد میکند. بنابراین اگر از On Error Resume Next استفاده میکنید، ورودیها و خروجیهای کد را دقیق بررسی کنید و مطمئن شوید این دستور خطای خواندن جدول یا فایل را پنهان نمیکند؛ چنین چیزی میتواند برای برنامه بسیار مخرب باشد، درحالیکه برنامه ظاهراً کاملاً درست کار میکند.
پیامدهای Error Trapping
وقتی در یک Procedure از On Error استفاده میکنید، آن Trap تا پایان همان Procedure فعال میماند مگر اینکه آن را غیرفعال کنید. ممکن است روالی برای بررسی درایو D نوشته باشید، اما پس از عبور از نقطهای که احتمال آن خطا وجود دارد باید Handler را خاموش کنید. در غیر این صورت همهٔ خطاهای بعدی همان Procedure نیز وارد همان روال میشوند و کاربر پیامهایی کاملاً بیربط خواهد دید؛ مثلاً خطای تقسیم بر صفر رخ میدهد اما پیام «Drive D not ready» نمایش داده میشود.
اگر On Error Resume Next فعال بماند، انواع خطاها ممکن است بدون اطلاع کاربر رخ دهند. برای بازگرداندن Error Handling به حالت عادی از این دستورات استفاده کنید:
On Error Resume Next
On Error GoTo 0
دستور نخست همهٔ خطاها را نادیده میگیرد. On Error GoTo 0 هر Error Handler جاری را لغو میکند تا خطاها دوباره به شکل معمول نمایش داده شوند.
ایجاد خطای دلخواه
ممکن است این پرسش پیش بیاید که چرا باید عمداً خطا ایجاد کنیم، درحالیکه هدف کد بدون خطاست. گاهی برای آزمایش برنامه یا زمانی که میخواهید یک وضعیت خاص را معادل Runtime Error در نظر بگیرید، این کار مفید است.
Sub Test_Error()
Error 71
End Sub
این کد خطای «Drive not ready» را شبیهسازی میکند. برای همین کار میتوان از Err.Raise(71) نیز استفاده کرد. همچنین میتوانید خطای جاری را دوباره ایجاد کنید:
Error Err
هدف این است که پیام خطای فعلی، برای مثال آمادهنبودن درایو، دوباره درست مانند زمانی که واقعاً همان مشکل رخ داده است Raise شود. اگر بعداً خطای دیگری ایجاد شود، آن خطا به خطای جاری تبدیل خواهد شد.
فصل ۹ — فرمها و گزارشها (Forms and Reports)
فرمها و گزارشها ابزار اصلی ارتباط برنامه با کاربر و ارائهٔ اطلاعات موجود در پایگاهدادهاند. در یک پایگاهدادهٔ رابطهای، داده در جدولها نگهداری میشود و با دوبار کلیک روی نام جدول میتوان آن را دید، اما خود جدولها معمولاً اطلاعات را به شکلی معنادار و مناسب برای کاربر ارائه نمیکنند.
دلیل این موضوع آن است که داده در چند جدول با رابطههای مختلف ذخیره میشود. برای مثال در سفارشهای مشتری، اطلاعات مشتری در جدولی جداگانه قرار دارد و هر سفارش از طریق یک ID داخلی به رکورد مشتری مربوط میشود. اگر فقط جدول سفارشها را ببینید، برای مشتری تنها یک شمارهٔ مرجع میبینید. برای نمایش تصویر کامل اطلاعات، Query لازم است تا جدولها را Join کند.
فرمها و گزارشها از Queryها و جدولهای زیرین استفاده میکنند تا اطلاعات را به شکل قابل فهم به کاربر ارائه کنند. VBA نیز امکانات فراوانی برای توسعه و کنترل Form و Report در اختیار شما میگذارد. مثالهای این فصل از پایگاهدادهٔ نمونهٔ Northwind استفاده میکنند. هنگام اجرای Access میتوانید در بخش Available Templates روی Sample کلیک و سپس Northwind را انتخاب کنید.
فرمها
Form بر مبنای یک Table یا Query ساخته میشود و به کاربر اجازه میدهد میان رکوردها حرکت کند، فیلدهای مشخص را ببیند و از Filterها استفاده کند. بسته به Propertyهای فرم، کاربر ممکن است بتواند رکوردها را ویرایش، بهروزرسانی یا حذف کند.
ساخت یک فرم ساده
در Ribbon به تب Create بروید و در گروه Forms روی Form Design کلیک کنید تا فرم خالی ایجاد شود. با کشیدن حاشیهٔ راست یا پایین میتوانید اندازهٔ فرم را تغییر دهید. برای نامگذاری فرم، Save یا CTRL+S را بزنید و نامی مانند frmMyForm انتخاب کنید.
اکنون فرم باید به یک Table یا Query بهعنوان Data Source متصل شود. روی فرم راستکلیک کنید و Properties را برگزینید. در بالای پنجرهٔ Properties، از Selection Type گزینهٔ Form را انتخاب کنید. سپس در تب All به Property نخست یعنی Record Source بروید. فهرست کشویی آن تمام Tableها و Queryهای پایگاهداده را نشان میدهد. میتوانید یکی را انتخاب کنید یا یک رشتهٔ SQL مانند زیر بنویسید:
select * from orders
دکمهٔ سهنقطه کنار این Property نیز Query Builder را باز میکند. برای مثال این فصل، Orders را بهعنوان Record Source انتخاب کنید؛ از این پس آن را Orders Form مینامیم.
با مشخصشدن Data Source میتوانید Controlها را اضافه و به داده Bind کنید. در تب Design از گروه Controls یک Text Box روی فرم بکشید. Access بهصورت خودکار یک Label نیز کنار آن اضافه میکند تا عنوان Text Box را نمایش دهید.
شکل ۹-۱ — یک فرم جدید در Design Mode همراه با Text Box
برای حذف Label یا Text Box کافی است آن را انتخاب و DELETE را بزنید. میتوانید Text Box را جابهجا کنید؛ Label همراه آن حرکت میکند. با Handleهای دور Control نیز اندازه را تغییر دهید.
برای Bind کردن Text Box به یک فیلد، روی Control راستکلیک، Properties را باز و در تب All گزینهٔ Control Source را انتخاب کنید. فهرست کشویی فیلدهایی را نشان میدهد که بر اساس Record Source فرم در دسترساند. فیلدی را که میخواهید نمایش دهید انتخاب کنید. برای نمایش، بهتر است فیلدهای متنی مانند Ship Name یا Ship City را انتخاب کنید، نه Employee ID یا Customer ID که فیلدهای رابطهای هستند. همین کار را برای چند فیلد دیگر تکرار کنید.
برای مشاهدهٔ فرم، در تب Design روی Form View در گروه Views کلیک کنید. بسته به فیلدهای انتخابشده فرم باید مشابه شکل زیر باشد.
شکل ۹-۲ — نمایش فرم در Form View
در پایین فرم Record Selectorهایی وجود دارند که حرکت عقب و جلو میان رکوردها را ممکن میکنند و تعداد رکوردها نیز نمایش داده میشود. از تب Home و ابزارهای Sort & Filter میتوانید مرتبسازی و Filter را اعمال کنید، مشروط بر اینکه Property با نام Allow Filters روی Yes باشد. کنار Record Selectorها نیز Search Box وجود دارد.
اگر Record Source قابل Update باشد، مانند یک Table یا Query بدون رابطهٔ Many-to-Many، دکمههای Record Selector شامل آیکن رکورد خالی برای افزودن رکورد نیز خواهند بود؛ این قابلیت به Allow Additions = Yes وابسته است. اگر Allow Edits و Allow Deletes هم Yes باشند، کاربر میتواند رکورد را تغییر یا حذف کند و این موضوع خطراتی دارد. تغییر ناخواستهٔ یک فیلد بسیار آسان است و بهمحض رفتن به رکورد دیگر یا بستن فرم، تغییر در جدول زیرین ذخیره میشود، بدون آنکه هشداری دربارهٔ تغییر نشان داده شود. برای Delete، اگر هشدارها را خاموش نکرده باشید پیام تأیید نمایش داده میشود.
هنگام ایجاد فرم، همهٔ Propertyهای Allow بهطور پیشفرض Yes هستند. بهتر است اطمینان حاصل کنید فقط کاربران مجاز میتوانند رکوردها را تغییر دهند؛ VBA در اینجا میتواند کمک زیادی کند. در این مرحله یک رابط کاربری ساده برای جدول Orders در Northwind ساختهاید.
استفاده از فیلدهای رابطهای در فرم
در جدول Orders فیلدهایی مانند Employee ID و Customer ID وجود دارند. این فیلدها با یک ID داخلی یکتا رکورد را به جدولهای Employees و Customers متصل میکنند.
برای نمایش نام کارمند بهجای ID میتوان Record Source فرم را Queryای قرار داد که Orders و Employees را با Employee ID Join میکند. این روش برای مرور و مشاهده خوب است، اما هنگام Edit یا افزودن رکورد جدید مشکل ایجاد میشود: کاربر باید نام کارمند را ببیند و فقط از فهرست نامها انتخاب کند، ولی برای حفظ یکپارچگی پایگاهداده باید ID همان کارمند در Orders ذخیره شود.
برای این کار از Combo Box استفاده میشود. این Control فهرست کشویی برای انتخاب مقدار فراهم میکند و میتوان آن را طوری تنظیم کرد که فقط مقادیر موجود در فهرست مجاز باشند.
فرم را در Design Mode باز کنید و از گروه Controls یک Combo Box روی فرم بکشید. Wizard ظاهر میشود؛ چون این مثال دربارهٔ فیلد رابطهای است، Cancel را بزنید و Propertyها را دستی تنظیم کنید:
- Control Source: روی
Employee ID قرار گیرد؛ فیلدی که ID واقعی را ذخیره میکند.
- Column Count: مقدار ۲؛ ستون نخست ID و ستون دوم نام کارمند است.
- Column Widths: مقدار
0;4 که به صورت 0 cm;4 cm ظاهر میشود. ستون ID برای کاربر معنایی ندارد و عرض آن صفر میشود؛ ستون نام ۴ سانتیمتر دیده میشود.
- Row Source: Query زیر فهرست مقادیر را میسازد:
SELECT id, [first name] & " " & [last name] FROM employees;
این یک SQL Select استاندارد است. First Name و Last Name با یک فاصله به هم متصل شدهاند و چون نام فیلدها فاصله دارند داخل براکت قرار گرفتهاند. میتوانید همین Query را در Access اجرا کنید؛ خروجی باید دو ستون، ID و نام کامل کارمند، داشته باشد.
- Row Source Type: روی
Table/Query.
- Bound Column: مقدار ۱؛ یعنی ستون نخست Combo Box به جدول زیرین Bind میشود. این همان ستون مخفی ID است و هنگام تغییر کارمند یا افزودن رکورد جدید، ID به فیلد Employee ID در Orders نوشته میشود.
- Limit To List: روی Yes تا کاربر فقط بتواند کارمند موجود در پایگاهداده را انتخاب کند.
اکنون در Form View، Combo Box نام کامل کارمند را نمایش میدهد، هرچند این نام از دو فیلد جدا در Employees ساخته شده است. با بازکردن فهرست نامها را میبینید، درحالیکه ID مخفی هر کارمند نیز همراه آن وجود دارد. یک کارمند را در رکوردی تغییر دهید، به رکورد بعد بروید تا تغییر ذخیره شود و سپس بازگردید؛ نام جدید روی رکورد دیده میشود.
مزیت این روش آن است که کارمندهای جدید بهمحض اضافهشدن به Employees بهطور خودکار همراه ID خود در فهرست ظاهر میشوند. به این ترتیب یکپارچگی پایگاهداده حفظ میشود و Queryهای مبتنی بر رابطهٔ Orders و Employees همچنان نتایج درست برمیگردانند.
Subformها
تا اینجا رابط کاربری طوری طراحی شد که کاربر جدول را رکوردبهرکورد مرور کند. این روش برای Orders خوب است، اما فقط Header سفارش را میبینید و جزئیات محصولات و تعدادهای موجود در سفارش دیده نمیشود. ممکن است چندین ردیف محصول برای یک سفارش وجود داشته باشد. Subform برای نمایش چند Detail مرتبط با یک رکورد در Parent Form به کار میرود.
یک فرم خالی با نام subfrmOrderDetails ایجاد کنید. استفاده از Convention نامگذاری برای تمایز Subformها از Parent Formها ایدهٔ خوبی است. Record Source آن را روی Order Details قرار دهید. Text Boxهایی برای Quantity، Unit Price و Discount بسازید و Control Sourceهای مربوط را تنظیم کنید.
در Form View، همانند Orders، مقادیر این فیلدها دیده میشوند، اما مشکل این است که جزئیات همهٔ سفارشها بدون مشخصبودن ارتباطشان با سفارش اصلی نمایش داده میشوند.
به Orders Form برگردید، Design View را باز و یک Subform Control اضافه کنید. آیکن آن در گروه Controls با عنوان Subform/Subreport مشخص میشود. اگر Wizard ظاهر شد میتوانید از آن استفاده کنید، اما برای تنظیم دستی Cancel را بزنید. در Property با نام Source Object فرم subfrmOrderDetails را انتخاب کنید. هر دو فرم اصلی و فرعی باید از قبل به Record Source متصل باشند.
در Link Master Fields روی دکمهٔ سهنقطه کلیک کنید. Access سعی میکند رابطهٔ منطقی را پیدا کند، اما بهتر است آن را بررسی کنید. این Link بر اساس فیلدهای رابطهای Record Sourceهاست، نه فقط کنترلهای نمایشی. در این مثال Order ID در Orders Form بهعنوان Master Field به Order ID در Subform بهعنوان Child Field متصل میشود. برای Label Subform نیز عنوانی مانند Order Details بنویسید.
شکل ۹-۳ — Orders Form همراه با Order Details Subform
Subform در پنجرهٔ مستقل خود داخل فرم اصلی قرار میگیرد و Record Selector و Record Count خودش را دارد. با Selectorهای فرم اصلی میان سفارشها حرکت کنید؛ Subform محصولات و تعدادهای همان سفارش را نشان میدهد. Selectorهای Subform نیز حرکت میان محصولات همان سفارش و حتی افزودن یا حذف ردیف در Order Details را ممکن میکنند.
برای نمایش چند ردیف Detail همزمان میتوانید Subform را Datasheet کنید. ابتدا Orders Form را ببندید، چون هنگام بازبودن Subform در Parent Form نمیتوان آن را تغییر داد. Subform را در Design View باز کنید و Default View را روی Datasheet View قرار دهید. در Datasheet View میتوانید عرض ستونها را با کشیدن مرز Headings تنظیم کنید. مطمئن شوید Selection Type در Property Sheet روی Form است.
فهرست کشویی Product همچنان کار میکند. Subform را Save و ببندید، سپس Orders Form را دوباره باز کنید.
شکل ۹-۴ — Order Details Subform در حالت Datasheet داخل Orders Form
برای کاربران این نمایش معمولاً واضحتر است، زیرا تعداد ردیفهای هر سفارش بلافاصله مشخص میشود.
Datasheet View
Form View برای تعداد کمی رکورد، مانند Customer Table، مناسب است؛ اما وقتی Record Source رکوردهای زیادی دارد مرور یکبهیک خستهکننده میشود. Datasheet View رکوردها را ستونی و شبیه Table یا Spreadsheet نمایش میدهد.
شکل ۹-۵ — نمایش فرم در Datasheet View
برای نمایش فرم بهصورت Datasheet، Property با نام Default View را روی Datasheet تنظیم کنید. با Propertyهایی مانند Allow Form View = No میتوان دسترسی کاربر به سایر Viewها را نیز محدود کرد. این قالب امکان مشاهدهٔ همزمان تعداد زیادی رکورد و Scroll آسان را فراهم میکند. کاربر حتی میتواند همهٔ رکوردها را انتخاب و در برنامهای دیگر مانند Spreadsheet کپی کند.
در Datasheet نیازی به همترازکردن Text Boxها و Controlها روی فرم نیست. عنوان هر ستون از Caption مربوط به Label Control گرفته میشود و اگر Label حذف شده باشد، نام خود Control استفاده میشود. ترتیب ستونها از Property با نام Tab Index تعیین میشود. تغییر Tab Index یک Control، سایر Controlها را بهطور خودکار شمارهگذاری مجدد میکند. Property با نام Allow Datasheet View باید روی Yes باشد.
Datasheet Viewها اغلب در Subformها استفاده میشوند؛ کاربر میان رکوردهای اصلی حرکت میکند و Detail مربوط به هر رکورد را به شکل چند ردیف میبیند.
حرفهایتر کردن ظاهر فرم
برای تمیزترشدن ظاهر فرم میتوانید Record Selector Bar سمت چپ را حذف و Navigation Buttonهای پایین را نگه دارید. Property با نام Record Selector را No و Navigation Button را Yes قرار دهید. پیش از تغییر این گزینهها مطمئن شوید Selection Type روی Form است.
همتراز و هماندازهکردن Controlها با Drag کردن دستی دشوار است و ممکن است دو Text Box فقط کمی اختلاف ارتفاع داشته باشند. هرچند موضوع ظاهری است، کاربران معمولاً چنین جزئیاتی را سریع میبینند و انتظار دارند همهٔ Controlها مرتب باشند.
در Design View، Controlهای موردنظر را با کشیدن یک Selection Box انتخاب کنید. در تب Arrange و گروه Sizing & Ordering، منوی Align امکان تراز چپ، راست، بالا یا پایین را میدهد. برای مثال چند Text Box عمودی را انتخاب و Left را بزنید تا همه بر اساس چپترین Control تراز شوند.
برای یکسانکردن ارتفاع یا عرض، از Size/Space و گزینههای To Tallest، To Widest، To Shortest و To Narrowest استفاده کنید. گزینههای Spacing مانند Equal Horizontal و Equal Vertical فاصلههای میان Controlها را یکسان میکنند.
برای حرکت بسیار جزئی، Control را انتخاب کنید، CTRL را نگه دارید و کلیدهای جهت را بزنید. نویسنده این قابلیت را برای مرتبکردن فرم بسیار مفید میداند.
با Property با نام Back Color میتوانید پسزمینهٔ فرم و Controlهای منفرد را تغییر دهید. دکمهٔ سهنقطه Palette رنگ را باز میکند. Property با نام Picture نیز تصویر پسزمینهای را داخل فرم Embed میکند؛ مثلاً تصویر مرتبط یا Logo شرکت. تصاویر حافظه مصرف میکنند و تعداد زیادشان میتواند پایگاهداده را سنگین کند.
برای متنهای دیگر از Label و Caption استفاده کنید و Font، رنگ، Point Size و ویژگیهای دیگر را تنظیم کنید. با Close Button = No میتوانید دکمهٔ Close را حذف کنید، اما باید راه دیگری برای خروج فراهم کنید. Propertyهای Min Max Buttons و Control Box نیز میتوانند تغییر اندازه یا کنترل پنجره را محدود کنند.
استفاده از Ribbon سفارشی روی فرم
فرم مجبور نیست همیشه Ribbon استاندارد Access را نشان دهد. فصل ۱۱ نحوهٔ ساخت Ribbon سفارشی برای Formها را توضیح میدهد. پس از طراحی Ribbon کافی است Property با نام Ribbon Name فرم را تنظیم کنید. حتی میتوان با همین روش Ribbon را کاملاً از فرم حذف کرد.
سایر Controlها
Command Button
Command Button برای هدایت کاربر به یک عمل مشخص مفید است؛ مثلاً فرم اولیهای شامل گزینهها، بازکردن فرم دیگر یا فراهمکردن Exit سفارشی. اگر Close Button را غیرفعال کردهاید، ممکن است بخواهید خروج فقط از یک Button انجام شود تا پیش از خروج Updateهای لازم اجرا شوند.
در Design View، از تب Design آیکن Button را روی فرم بکشید. Command Button Wizard ظاهر میشود.
شکل ۹-۶ — Command Button Wizard
اگر Wizard را نمیخواهید Cancel کنید و Propertyهایی مانند Caption را دستی تنظیم کنید. بااینحال Wizard آیکنهای آمادهٔ مناسبی مانند Close Form دارد و حتی میتواند کد لازم را بدون نوشتن دستی ایجاد کند.
برای دسترسی به کد Button، روی Control راستکلیک و Build Event را انتخاب کنید، سپس Code Builder. پس از این کار Property رویداد On Click مقدار [Event Procedure] خواهد داشت. برای بازگشت به کد نیز روی مقدار On Click راستکلیک و Build را انتخاب کنید. نمونهٔ بستن فرم جاری:
Private Sub Mybutton_Click()
DoCmd.Close
End Sub
List Box
List Box مانند Combo Box عمل میکند، اما فهرست گزینهها همیشه دیده میشود. چون فضای بیشتری میگیرد محبوبیت کمتری دارد. همان Propertyهایی که برای Combo Box گفته شد را میتوان استفاده کرد، با این تفاوت که Limit To List لازم نیست، زیرا کاربر امکان ورود آزاد ندارد.
Image
Image Control اجازه میدهد تصویر در فرم قرار دهید. تصویر داخل فرم و برنامه Embed میشود و اندازهٔ فایل را افزایش میدهد. برای نمایش Logo شرکت روی فرمها و ایجاد ظاهر سازمانی میتواند مفید باشد.
Check Box
Check Box امکان انتخاب یا لغو انتخاب را به کاربر میدهد. Property با نام Value مقدار True یا False را نشان میدهد:
Private Sub Detail_Click()
If Me.MyCheckBox.Value Then
MsgBox "Box is checked"
Else
MsgBox "Box is unchecked"
End If
End Sub
Property با نام Default Value را میتوان روی ۰ یا -1 تنظیم کرد و مقدار اولیه را از VBA نیز تغییر داد.
Option Buttonها
Option Buttonها که گاهی Radio Button نامیده میشوند، در یک گروه طوری رفتار میکنند که انتخاب یکی، بقیه را از حالت انتخاب خارج میکند. نسبت به Check Box تنظیم و کدنویسی پیچیدهتری دارند اما مزیتشان این است که کاربر فقط یک گزینه را میتواند انتخاب کند.
ابتدا باید Option Group را روی فرم بگذارید. در Design View آیکن Option Group را از گروه Controls بکشید، اندازهٔ آن را متناسب با تعداد گزینهها تنظیم کنید و در Label همراهش عنوان بنویسید. بسیار مهم است که Option Group را پیش از Option Buttonها ایجاد کنید؛ Buttonای که پیش از Group ساخته شده را نمیتوان بعداً به آن Group اضافه کرد.
سپس Option Buttonها را اضافه کنید. در Form View میبینید با انتخاب هر Button، بقیه خاموش میشوند. برای تشخیص گزینهٔ فعال در VBA:
MsgBox MyOptionGroup.Value
اگر Button نخست انتخاب باشد ۱، دومی ۲ و به همین ترتیب برمیگردد. برای Default نیز Property با نام Default Value در Option Group را روی شمارهٔ Button موردنظر قرار دهید؛ مثلاً ۲ برای گزینهٔ دوم.
Tab Control
Tab Control صفحههای Tabدار در فرم ایجاد میکند و وقتی فضای فرم کم است بسیار مفید است. Controlها را میتوان روی Tabها مانند یک فرم قرار داد، اما Drag-and-Drop مستقیم مشکلساز است: در Design View ممکن است به نظر برسد Control روی Tab قرار گرفته، درحالیکه در Form View پشت Tab Control پنهان شده است.
برای قراردادن Control روی Tab، ابتدا آن را روی بخش خالی فرم بکشید و سپس با Cut/Paste به Tab Control منتقل کنید. در Design Mode با کلیک Tabها انتخاب میشوند. برای افزودن یا حذف Page روی Tab Control راستکلیک و گزینهٔ مناسب را انتخاب کنید. برای تغییر نام Tab، آن را انتخاب، Properties را باز و Caption را تغییر دهید. Controlهای داخل Tab در VBA همانند Controlهای معمولی فرم قابل استفادهاند.
استفاده از VBA روی فرمها
تا اینجا فصل بیشتر دربارهٔ ساخت فرم و Bind کردن Controlها به داده بود. این کار مستقیماً VBA نیست، اما بخش ضروری ساخت یک برنامهٔ Access است. Formها Moduleهای خودشان را دارند و هم خود Form و هم Controlهای آن ساختار رویدادمحور (Event-driven) کاملی دارند. مجموعهٔ گستردهٔ Propertyها اجازه میدهد بر اساس شرایط مختلف آنچه کاربر میبیند تغییر کند.
بازکردن فرم از VBA
فرم اولیهٔ برنامه معمولاً از طریق Property با نام Display Form در Current Database تعیین میشود: File > Access Options، سپس Current Database و در Application Options انتخاب Display Form. فرم انتخابشده هنگام بازشدن Database خودکار Load میشود.
برای بازکردن فرمهای دیگر از Method با نام OpenForm در Object با نام DoCmd استفاده کنید؛ فصل ۱۶ این Object را بیشتر توضیح میدهد:
DoCmd.OpenForm _
(FormName,View,FilterName,WhereCondition,DataMode,WindowMode,OpenArgs)
FormName اجباری و بقیه اختیاریاند:
- View: ثابت
acFormView که View فرم را مشخص میکند؛ پیشفرض acNormal.
- FilterName: نام یک Query در Database جاری.
- WhereCondition: عبارت SQL WHERE بدون خود واژهٔ
WHERE.
- DataMode: ثابت
acFormOpenDataMode برای حالت ورود داده.
- WindowMode: ثابت
acWindowMode برای نحوهٔ بازشدن پنجره.
- OpenArgs: مقدار Property با نام OpenArgs فرم.
برای بستن فرم نیز از Method با نام Close در DoCmd استفاده کنید.
استفاده از Eventهای فرم
وقتی کاربر فرم را باز میکند، میان رکوردها حرکت میکند، رکورد حذف یا ویرایش یا اضافه میکند و فرم را میبندد، Eventهای متعددی رخ میدهند که میتوان در VBA از آنها استفاده کرد. زمان بعضی Eventها ممکن است گیجکننده باشد؛ نویسنده پیشنهاد میکند برای یادگیری، موقتاً در هر Event یک Message Box ساده قرار دهید تا لحظهٔ رخدادن آن را ببینید.
در Design Mode روی فرم راستکلیک و Build Event را انتخاب کنید، سپس Code Builder. در فهرست بالای چپ Module که معمولاً Detail را نشان میدهد Form را انتخاب کنید و از فهرست بالای راست Eventها را ببینید.
Activate
وقتی Focus روی فرم قرار میگیرد Activate رخ میدهد. این Event با Open یا Load یکسان نیست؛ فرم ممکن است از قبل نمایش داده شده باشد اما Focus نداشته باشد.
After/Before Delete Confirm
این دو Event پیش و پس از Delete یک رکورد رخ میدهند. پیش از تأیید حذف، Access بهطور خودکار Confirm Message نمایش میدهد. آیکن Delete Record به شکل پیشفرض در Ribbon فرم دیده نمیشود؛ باید Toolbar را Customize کنید یا Button و روال خودتان را بسازید.
میتوانید Warningهای Access را خاموش کنید:
DoCmd.SetWarnings False
و سپس Warning سفارشی خودتان را در Event ایجاد کنید:
Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
x = MsgBox("Are you sure that you wish to delete this record?")
If x = vbNo Then Cancel = True
End Sub
Before Event را میتوان برای اعتبارسنجی Delete نیز به کار برد. نویسنده مثال میزند در یکی از برنامههایش کاربر اجازه نداشت تنها رکورد باقیمانده در یک Table را حذف کند؛ اگر فقط یک رکورد باقی بود پیام نمایش داده و عملیات Cancel میشد. همچنین پس از حذف رکورد Parent ممکن است لازم باشد رکوردهای مرتبط و Orphanشده در Tableهای دیگر حذف شوند؛ مثلاً با حذف Order، Order Details مربوط نیز پاک شوند. این کار را میتوان در After Delete Confirm انجام داد.
After/Before Insert
پیش و پس از Insert رکورد جدید رخ میدهند. برخلاف Delete پیام Warning خودکار وجود ندارد، بنابراین میتوانید در Before Insert پیام تأیید بسازید:
Private Sub Form_BeforeInsert(Cancel As Integer)
x = MsgBox("Are you sure that you wish to create a new record?")
If x = vbNo Then Cancel = True
End Sub
After Insert میتواند برای ایجاد یک Child Record مرتبط در Table دیگر استفاده شود.
After/Before Update
پیش و پس از Update یک Record رخ میدهند. برای تأیید تغییر:
Private Sub Form_BeforeUpdate(Cancel As Integer)
x = MsgBox("Are you sure that you wish to amend this record?")
If x = vbNo Then Cancel = True
End Sub
After Update میتواند Child Recordهای مرتبط را نیز Update کند.
Click
با کلیک ماوس در جایی از فرم رخ میدهد. Controlها Event مربوط به Click خودشان را نیز دارند، بنابراین محل واقعی کلیک را در نظر بگیرید.
Close
هنگام بستهشدن فرم توسط کاربر یا کد رخ میدهد. برای مثال میتوان هنگام بستن، فرم دیگری را باز کرد:
Private Sub Form_Close()
DoCmd.OpenForm ("MyForm")
End Sub
Current
وقتی کاربر به یک Record میرود و آن Record جاری میشود، یا فرم Requery/Refresh میشود، On Current رخ میدهد. نام آن ممکن است با Load اشتباه شود.
Load
هنگام بازشدن فرم و نمایش رکوردها رخ میدهد.
Open
Open با Load تفاوت دارد. نویسنده توضیح میدهد Open پس از اجرای Query فرم رخ میدهد و در این نقطه فرم دارای داده است و کد میتواند با آن تعامل کند، درحالیکه در Load هنوز Controlها Populate نشدهاند.
Timer
Timer برای اجرای Procedure در فاصلهٔ زمانی ثابت مفید است؛ مثلاً Requery فرم تا تغییرات سایر کاربران باعث کهنهشدن داده نشود:
Private Sub Form_Timer()
Me.Requery
End Sub
Object با نام Me به فرم جاری اشاره دارد و فصل ۱۵ آن را بیشتر توضیح میدهد. Property با نام Timer Interval در Property Sheet فرم تنظیم میشود. مقدار پیشفرض ۰ است، یعنی هرگز اجرا نمیشود. واحد آن میلیثانیه و حداکثر ۶۵۵۳۶، حدود یک دقیقه، است. مقدار خیلی کم، بهخصوص کمتر از ۱۰۰۰، انتخاب نکنید؛ زیرا Procedure زمانی دائماً اجرا میشود و استفاده از فرم دشوار خواهد شد.
گزارشها (Reports)
Report از برخی جهات شبیه Form است: بر اساس Table یا Query ساخته میشود و Controlهایی مانند Text Box و Label دارد. یک Row را با Controlها تعریف میکنید و Sorting، Grouping، Header و Footer را اضافه میکنید. هنگام اجرای Report، تعریف همان Row در سراسر گزارش تکرار میشود و کاربر میتواند داده را ببیند و چاپ کند. Report برخلاف Form معمولاً رابط تعاملی کاربر نیست.
ساخت یک گزارش ساده
این مثال Reportای بر اساس Orders، Order Details و Products در Northwind میسازد. نخست Query لازم را ایجاد کنید: از Create روی Query Design کلیک کنید و Tableهای Orders، Order Details و Products را اضافه کنید. Access Joinها را میان Key Fieldها خودکار میسازد؛ ممکن است لازم باشد Join میان Orders و Order Details را از Left Join به Join مستقیم تغییر دهید.
فیلدهای Order ID، Ship Name، Ship Address، Product Name و Quantity را در خروجی قرار دهید.
شکل ۹-۷ — Query Design Window برای Query گزارش
Query را اجرا و از بازگشت رکوردها مطمئن شوید، سپس Save و Close کنید. در Create، Report Wizard را باز کنید. نویسنده معمولاً طرفدار Wizardها نیست، چون ممکن است کاری غیر از انتظار انجام دهند، اما Report Wizard را استثنا میداند؛ چون میتواند بهسرعت Reportای حرفهای با Sorting و Grouping ایجاد کند که بعداً قابل Customize است.
در صفحهٔ نخست Wizard، Query جدید را انتخاب کنید. چهار فیلد Ship Name، Ship Address، Product Name و Quantity را وارد Report کنید و سپس:
- در صفحهٔ بعد، مقدار پیشفرض Orders را برای نحوهٔ View داده بپذیرید.
- در صفحهٔ بعد بر اساس Ship Name گروهبندی کنید.
- در صفحهٔ بعد Product Name را برای Sort Order انتخاب کنید.
- روی Summary Options کلیک کنید. چون Quantity تنها فیلد عددی است میتوان روی آن محاسبه انجام داد؛ در این مثال محاسبهای انتخاب نکنید.
- در صفحههای Layout و Style مقادیر پیشفرض را بپذیرید.
- Report را Preview کنید.
شکل ۹-۸ — یک گزارش ساده بر اساس سفارشها و محصولات
Wizard گزارش مرتب و حرفهای تولید میکند و معمولاً سریعتر از ساخت دستی است.
مرتب نگهداشتن Report
ممکن است بعداً کاربران درخواست اضافهشدن Fieldهای جدید یا جابهجایی Fieldهای موجود را بدهند و Report تمیز اولیه بهتدریج نامرتب شود. همان مشکل تراز و اندازهٔ Controlها که در Form وجود داشت اینجا هم دیده میشود.
در Design View Controlهای موردنظر را انتخاب کنید. در تب Arrange و گروه Control Alignment، ابزارهای تراز چپ، راست، بالا و پایین وجود دارند. گروه Size نیز گزینههای To Tallest، To Widest، To Shortest و To Narrowest را دارد. گروه Position با Equal Horizontal و Equal Vertical فاصلهها را یکنواخت میکند. برای حرکتهای بسیار ریز نیز CTRL را نگه دارید و کلیدهای جهت را بزنید.
با Back Color میتوانید رنگ پسزمینهٔ Report و Controlها را تغییر دهید. برای پسزمینهٔ Report باید Sectionهای مختلف مانند Report Header، Page Header، Section Header و Detail را جداگانه انتخاب و رنگشان را تنظیم کنید.
Property با نام Picture تصویر را داخل Report Embed میکند. برای تکرار تصویر در طول Report، Picture Tiling = Yes قرار دهید. این قابلیت برای Logo شرکت مفید است، هرچند ممکن است از تمرکز روی اطلاعات گزارش بکاهد. تصاویر زیاد نیز اندازهٔ Database را افزایش میدهند.
برای متن اضافی از Label و Caption استفاده کنید. Font، رنگ، Point Size و دیگر تنظیمات قابل تغییرند. با Close Button = No دکمهٔ Close را حذف کنید اما راه خروج دیگری بسازید. Min Max Buttons و Control Box نیز میتوانند تغییر اندازه و کنترل پنجره را محدود کنند.
استفاده از فرمولها در Report
Report میتواند Formulaهایی برای Totalها و اطلاعات تکمیلی کاربر داشته باشد. Orders Report ساختهشده را در Design Mode باز کنید. روی نوار افقی Ship Name Header راستکلیک و Sorting and Grouping را انتخاب کنید.
شکل ۹-۹ — Report Design Window با Sorting and Grouping
روی More کلیک و در Footer Section گزینهٔ with a footer section را انتخاب کنید. پس از بستن Sorting and Grouping، Section تازهای به نام Ship Name Footer خواهید داشت که هر بار Ship Name عوض شود نمایش داده میشود.
یک Text Box روی Footer بکشید و آن را با Quantity در Detail همتراز کنید. Control Source آن را به عبارت زیر تنظیم و Label را Total کنید:
=Sum([quantity])
Text Box دیگری در Header بسازید و Control Source آن را روی عبارت زیر بگذارید:
="Summary for " & "'Ship Name' = " & " " & [Ship Name] & " (" & Count(*) & " " & IIf(Count(*)=1,"detail record","detail records") & ")"
این Expression متن، نام فیلد و Functionها را به یک رشته تبدیل میکند. [Ship Name] فیلدی در Record Source است، Count(*) تعداد رکوردهای Section را میدهد و IIf میان عبارت مفرد detail record و جمع detail records انتخاب میکند. Label متناظر را حذف کنید چون لازم نیست.
شکل ۹-۱۰ — استفاده از Formulaها در Report
در Report View، Text Boxهای فرمولی جزئیات مربوط به هر Ship Name را نمایش میدهند. افزودن Section و Formula بهصورت دستی زمانبر است؛ به همین علت بهتر است Report Wizard کار اولیهٔ سنگین را انجام دهد و سپس نتیجه را Customize کنید.
Ribbon سفارشی روی Report
همانند Form، Report مجبور نیست Ribbon استاندارد Access را نشان دهد. فصل ۱۱ ساخت Custom Ribbon را توضیح میدهد. پس از طراحی Ribbon، Property با نام Ribbon Name را روی Report تنظیم کنید. حتی میتوان Ribbon را کاملاً حذف کرد.
استفاده از VBA روی Report
VBA روی Reportها کمتر از Formها استفاده میشود، زیرا Report عمدتاً برای مشاهده و چاپ داده است و پس از نمایش معمولاً تعامل کاربر محدود به چاپ میشود. بااینحال Reportها Moduleهای خودشان و ساختار Event-driven دارند و Propertyهای فراوان آنها امکان تغییر ظاهر یا رفتار را با توجه به شرایط فراهم میکند.
بازکردن Report از VBA
برای Load کردن Report از Method با نام OpenReport در Object با نام DoCmd استفاده کنید:
DoCmd.OpenReport _
(ReportName,View,FilterName,WhereCondition,WindowMode,OpenArgs)
ReportName اجباری و بقیه پارامترها اختیاریاند:
- View: ثابت
acView برای View؛ پیشفرض acNormal.
- FilterName: نام Query در Database جاری.
- WhereCondition: SQL WHERE Clause بدون واژهٔ WHERE.
- WindowMode: ثابت
acWindowMode برای نحوهٔ بازشدن پنجره.
- OpenArgs: مقدار Property با نام OpenArgs گزارش.
برای بستن Report نیز از Method با نام Close در DoCmd استفاده کنید.
Eventهای Report
با بازشدن و استفاده از Report، Eventهایی رخ میدهند که میتوان در VBA از آنها استفاده کرد. برای مشاهدهٔ آنها در Design Mode روی Report راستکلیک، Build Event و سپس Code Builder را انتخاب کنید. در فهرست بالای چپ Module که معمولاً ReportHeader یا ReportDetail را نشان میدهد، Report را انتخاب کنید؛ فهرست بالا-راست Eventها را نمایش میدهد.
Activate
هنگامی رخ میدهد که Report Focus میگیرد. با Open یا Load یکسان نیست، زیرا Report ممکن است از قبل نمایش داده شده باشد اما Focus نداشته باشد.
Click
با کلیک ماوس روی Report رخ میدهد. Controlهای روی Report نیز On Click مخصوص خود را دارند، بنابراین محل کلیک مهم است.
Close
هنگام بستهشدن Report، چه توسط کاربر و چه توسط کد، رخ میدهد. نمونهٔ کد:
Private Sub Report_Close()
DoCmd.OpenReport ("MyReport")
End Sub
Current
وقتی کاربر روی Row جدیدی در Report کلیک میکند رخ میدهد. نام آن میتواند با Load اشتباه گرفته شود.
Load
هنگام بازشدن Report و نمایش دادههایش رخ میدهد.
Open
Open پس از اجرای Query گزارش رخ میدهد؛ در این نقطه Report داده دارد و کد میتواند با آن تعامل کند، درحالیکه هنگام Load هنوز Controlها Populate نشدهاند.
Print
هنگام چاپ Report رخ میدهد و میتوان از آن برای نگهداری Log اینکه چه کسی و چه زمانی Report را چاپ کرده است استفاده کرد.
Timer
برای اجرای Procedure در فاصلههای ثابت، مثلاً Requery Report تا داده در اثر فعالیت سایر کاربران کهنه نشود:
Private Sub Report_Timer()
Me.Requery
End Sub
Timer Interval در Property Sheet گزارش تنظیم میشود. مقدار پیشفرض ۰ یعنی غیرفعال است؛ واحد میلیثانیه و حداکثر ۶۵۵۳۶، حدود یک دقیقه، است. مقدار کمتر از ۱۰۰۰ انتخاب نکنید، زیرا Procedure زمانی دائماً اجرا خواهد شد و استفاده از Report را مختل میکند.