بسم الله الرحمن الرحيم نبدأ
أضع هذا الموضوع وهو إجتهاد شخصي وهو كيف نعمل مؤقت أوعداد زمني سنحتاجه عندما نريد أن يعمل بشكل صحيح دون تغيير في زمنه ،
حيث كنت سأضعه أيام عيد الأضحى المبارك ولكنني إنشغلت ولم أكمله إلى هذا اليوم.
وسأقوم في البدء بشرح بعض الأمور التي ستساعد على فهم المقصود ،حيث يمكن أن يستفيد من الموضوع المبرمج المبتدىء والمتوسط ولا أعرف إذا كان سيفيد منه من كان بمستوى الخبير.
من فترة شكى إليّ صديق كنت قد علمته على برنامج الفوجول بيسك وكيفية صنع البرامج ، حيث قام بعمل مؤقت زمني بسيط لإطفاء الجهاز ، وبالفعل كان أداء البرنامج من أحسن مايكون .
ولكن عندما قام بتطوير البرنامج وإضافة بعض المؤقتات الأخرى وبعض الحركات على الفورم تبين أن الوقت الذي حدده لإطفاء الجهاز قد زاد عن موعده المحدد ،مع أن جميع أكواده صحيحة ، لماذا تغير توقيت وما الذي حصل ؟ هذا ما شأشرحه هنا بإذن الله.
هذا الكود البرمجي معروف للكل لعمل عداد زمني بالزيادة حيث يصل العداد للنقطة المطلوبة ويقوم بعمل ماتريد كإيقاف تشغيل الجهاز أو ..
Dim T As Integer Private Sub Form_Load() Timer1.Interval = 1000 End Sub Private Sub Timer1_Timer() T = T + 1 If T = 60 Then MsgBox "إكتمل الوقت المطلوب": End End Sub
شرح بسيط للكود :
حيث صرحنا في الأول عن متغير T في قسم التصريحات العامة ، وهنا عند تشغيل البرنامج ستكون قيمة المتحول T هي الصفر كونه عددي (Integer)
نقطة هامة يجب ذكرها هنا وهي أن التصريح عن المتحولات العددية من نوع (Integer) سيتعامل مع كمية محدودة من الأعداد ولا يمكن أن يزيد عنها ، مثلاً لوأعطينا قيمة ما كبيرة للمتحول (T) السابق ولتكن 50000 وقمنا بتشغيل البرنامج فإن رسالة الطفحان (OverFlow) ستظهر ، مثال:
Dim T As Integer Private Sub Command1_Click() T = 50000 End Sub
لذلك عند التعامل مع أعداد كبيرة سنستعمل النوع (Long) في التصريح عن المتحول
وإذا كنا سنتعامل مع أعداد قليلة سيكون النوع (Byte) هو المناسب ويتعامل مع الأعداد من الصفر وحتى 255 فقط
وسبب التعامل مع الأنواع السابقة هو بحسب إستخدامنا للمتحولات في برنامجنا ،فلو كان برنامجنا يتعامل مع كم متحول فإستخدامنا للنوع (Long)للأعداد الطويلة أو(Integer) للأعداد القليلة سيكون مناسباً ، بينما لو تعاملنا مع عشرات المتحولات وربما تصل للمئات فإن دراسة نوع المتحولات سيكون له تأثير جيد في عمل البرنامج .
فنحن نعرف أن النوع (Long) يحجز مكان له في الذاكرة وهو 4 بايت وهذا يؤدي عند كثرته في البرنامج لتقليل الذاكرة وهذا يعني تقليل أداء جهازك وسرعته
والنوع (Integer) سيستحل 2 بايت في الذاكرة وهو نصف المتحول السابق
والنوع (Byte) سيأخذ 1 بايت من الذاكرة وهو أقل المتحولات العددية وكشكل عام
فأي نوع ستصرح عنه في قسم التصريحات العامة أو متحولات الخواص العامة مثل (Tag) سيبقى في الذاكرة إلى حين إنتهاء البرنامج ، وكلما زادت المتحولات وكثرت البرامج المشغلة لديك فسيؤدي ذلك لنقص ذاكرة جهازك مما يؤدي لبطىء سرعة الجهاز ولبطىء تنفيد الأكواد وخاصة إذا كان معالجك قليل السرعة والذاكرة قليلة السعة وحتى لو كان جهازك من أفضل الاجهزة فهنالك أمور ستجدها تبطىء من تنفيد أكوادك ، وهذا هو محور حديثنا . .
أي أننا يمكن جعل الكود السابق هكذا:
Dim T As Byte Private Sub Form_Load() Timer1.Interval = 1000 End Sub Private Sub Timer1_Timer() T = T + 1 If T = 60 Then MsgBox "إكتمل الوقت المطلوب": End End Sub
فجعلنا هنا T من نوع (Byte) وهذا سبب أنه أعطينا T = 60 والعدد 60 هو من ضمن مجال النوع (Byte) كما ذكرت من 0 وحتى 255
حيث يمكن أن نستخدم المتحول في مثالنا السابق لأربع دقائق وثلث تقريباً (255 ثانية ÷ 60 = 4.25 دقيقة )
ملاحظة عند إستعمالك للنوع (Byte) من إجراءات متعددة في برنامجك فيجب عليك تذكر أن هذا النوع محدود ولا يمكن أن نساوي معه نوع آخر وقيم أكبر منه.
نعود لبدء الحديث بشرح الكود حيث ذكرت أن عند تشغيل البرنامج ستكون قيمة المتحول T مساوية الصفر كونه عددي (الشرح معروف للجميع ولكن وضعته للمبتدىء والذي لا يعرف ذلك)
ثم قمنا في إجراء بدء التحميل بإعطاء القيمة 1000 للتايمر1 أي أن التايمر سينفذ نفسه كل 1000 ميلي ثانية أي 1 ثانية (طبعاً يمكن إضافة القيمة لخاصية Interval للتايمر فوراً أو أي قيمة نريد ولكن كتبتها في بدء التحميل لمعرفة أننا نتعامل في هذا المثال مع الثواني).
في الواقع لو أعطينا التايمر القيمة 1ميلي ثانية ونفذنا تعليمة ما بداخله لوجدنا أنه لا يمكن تنفيد التعليمة بهذه السرعة الكبيرة ، ويمكنك الإطلاع على هذا الموضوع المساعد : محتار بالتايمر, سوال خاص عن التايمر
ثم في الخطوة التالية من الكود أضفنا للمتحول T في إجراء تنفيد التايمر القيمة 1 (T = T + 1) أي أن المتحول ستزداد قيمته كل ثانية ب 1 وهكذا حتى نصل للزمن المحدد لتنفيد أمر محدد وفي مثالنا هو 60 ثانية وهذا هو مبدأ أي عداد زمني.
كما يمكن عمل العداد تراجعي كأن نعطي للمتحول قيمة ما ويقوم التايمر بإنقاصها إلى حين نقطة معينة وكمثال :
Dim T As Byte Private Sub Form_Load() Timer1.Interval = 1000 T = 60 End Sub Private Sub Timer1_Timer() T = T - 1 If T = 0 Then MsgBox "OK": End End Sub
********************************************************************
هنا نأتي لمستوى المتوسطين
طيب ماذا عن أسطر تعليمات برنامجك وكثرتها وخاصة الأسطر التي تستخدم التعليمة For .. To أو ماشابها من Loop هذا كله يستهلك من قدرة المعالج ومن الذاكرة ،ويجب دراسته جيداً لزيادة السرعة وتقليل الذاكرة.
فعند تصريح متحول في ال For .. To مثلاً ستجد أن الفوجول بيسك سيتعامل مع المتحول فوراً بأنه من النوع (Integer) ولو قمنا بكتابة العبارة Option Explicit في أول التصريحات العامة لوجدنا أن معظم المبرمجين يصرح عن المتحول ب Dim i As Integer وقد يكون المتحول أصلاً من 0 إلى 20 مثلاً ، أليس من الأفضل التصريح عن المتغير بنوع بايت(Byte) في ذلك السطر ليكون ملائم لكوده الصغير وخاصة عندما يكون برنامجك كبير.
Dim i As Byte For i = 0 To 20 '... Next
فإذا كنت مبرمج جيد وتستطيع إختصار تعليمات برنامجك بالشكل المناسب وتقليل المتحولات العامة والخاصة لما يناسب برنامجك سيكون له تأثير كبير على برنامجك .
فالمتحول المعروف من نوع (Boolean) هو لإعطاء قيمة صح أو خطأ أي قيمتين فقط ويفيد كثيراً في البرنامج ويحجز 2 بايت في الذاكرة فتأمل لوكان لديك عدد لا بأس به من هذا النوع في برنامجك ، أفلا يمكنك إستبداله بالمتحول من نوع (Byte) بإعطائك له الصفر أو الواحد وهو بنصف الحجم وسيعطي نفس الوظيفة لكودك ، ويمكن أن يخدمك بطريقة أخرى بإعطاءه أعداد منوعة ضمن مجاله.
وأعلم أيضاً أنه يمكنك إختصار المتحولات لمتحول واحد ولكن هذا سيؤدي لزيادة المعالجة وسيؤدي للإبطاء في تنفيد البرنامج والإبطاء في سرعة معالج جهازك (ستلاحظ ذلك في أكوادك الكثيرة وبرامجك قيد التشغيل والعمل) ،والعكس صحيح يمكنك الإكثار من المتحولات في الذاكرة على حساب توفير من سرعة المعالج ،مثال بسيط جداً للتوضيح وسنتعامل مع الأعداد في هذا المثال من 0 إلى 9 فقط :
Dim A As Byte, B As Byte Private Sub Form_Load() A = 1 B = 5 End Sub Private Sub Command1_Click() MsgBox A + B End Sub
فقمنا بالتصريح عن متحولين من نوع بايت في الذاكرة فهذا يعني أنه سيتم حجز 1 + 1 = 2 بايت في الذاكرة حتى إغلاق البرنامج
ثم أعطينا قيمة لهذين المتحولين 1 و 5 ، وعند تنفيد عملية الجمع سيقوم المعالج بأخذ قيمة أول متحول من الذاكرة ويجمعها مع القيمة الثانية ويظهر النتيجة ، فهنا كان إستخدامنا للذاكرة 2 بايت ، فهل يمكن أن نحجز 1 بايت فقط في الذاكرة ،الجواب يمكن كما يلي:
Dim C As Byte Private Sub Form_Load() C = 1 C = C & 5 End Sub Private Sub Command1_Click() MsgBox Val(Left(C, 1)) + Val(Right(C, 1)) End Sub
النتيجة هي نفسها (6) حيث قمنا هنا بالتصريح عن متحول واحد فقط © ،وأستطعنا إعطاء المتحول رقمين بجانب بعضهم بإستعمال الدالة (&)، ولكن كانت عملية تحليل هذا المتحول طويلة أي أنه قللنا الذاكرة على حساب المعالج الذي قام بإستدعاء دالة Val مرتين ودالة أخذ أول حرف من اليسار ودالة اليمين ودالة الجمع ، وأيضاً من أجل ماحدا يزعل (دالة المسج).
سيقول لي البعض لماذا تذكر النوع بايت (Byte) كثيراً فسأقول لأنني أحببت التعامل معه وهو أصغر المتحولات في الذاكرة والأمر عائد لك.
طبعاً كان المثال السابق للتوضيح ولا يمكن تطبيقه على كل شيىء فهذا كله كما ذكرت عائد لك وما تمليه عليك متطلبات برنامجك وجهازك
*************************************************************
بعد أن شرحت بعض أسباب البطء في جهازك والبطء في تنفيد أوامر برنامجك الكثيرة وخاصة مع كثرة تشغيل البرامج على الجهاز ستلاحظ أنه إذا كنت مستخدم تايمر ببرنامجك أو كنت تستخدم إجراء فيه تأخير زمني بلوب Loop أو For فإن العداد لن ينفذ في الوقت المحدد وستجد أن زمن العداد المطلوب قد تغير .
فما هو الحل ؟ وكيف سنحل هذه المشكلة ؟ فستقول لي نعمل العداد من الساعة الموجود بالجهاز كما في هذا المثال :
Dim A As Long Private Sub Command1_Click() A = Timer Timer1.Interval = 1000 End Sub Private Sub Timer1_Timer() Dim B As Long: B = Timer 'Label1 = B - A If B - A >= 60 Then MsgBox "إكتمل الوقت المطلوب": End End Sub
الدالة Timer تقوم بإعطاءك قيمة الوقت الحالي بالثانية مضافاً إليها فاصلة ثم ثلاث أرقام هم بالملي ثانية ، مثال: 46800.999
وهذا الرقم كمثال هو عدد الثواني من منتصف الليل الساعة 12 ليلاً إلى الساعة 1 ظهراً ، فعند بدء اليوم الجديد أي اللحظة التي بعد 12 ليلاً ستعود الدالة بإعطاءك الرقم صفر من جديد وتبدأ بالعد من جديد.
قمنا في المثال السابق بالتصريح عن المتحول A بنوع (Long) وذلك لحذف الفاصلة من الرقم وجعله رقم صحيح
وإذا قمنا بالتصريح عن المتحول A بالنوع (Integer) لن يفيد لأن القيمة المسندة للمتحول ستكون خارج نطاق هذا النوع وسيظهر لك عند بدء تشغيل البرنامج رسالة الطفحان كما أن هذا النوع أو غيره سيتعرف على الفاصلة العشرية.
ففي المثال السابق عند الضغط على الزر (Command1) سيقوم الإجراء بإعطاء قيمة الوقت الحالية بالثانية للمتحول A ( وسيحتفظ المتحول A بهذه القيمة لنهاية برنامجنا) وبعد ذلك نجد أمر تشغيل التايمر وتنفيد كوده كل 1 ثانية (ملاحظة عندما تكون قيمة أي تايمر مساوية الصفر وكانت خاصية القدرة Enabled = True فإن إدراج أي قيمة للتايمر ستؤدي لتشغيل التايمر)
وأيضاً قمنا بإعطاء المتحول B - A > 60 بإشارة أكبر من 60 وذلك لضمان شرط تنفيد الأمر المطلوب إذا ظهر العد أكثر من 60
الآن عندما سيعمل التايمر فبكل بساطة سيقوم بطرح قيمة الزمن الحالي من الزمن الثابت(A) وستكون النتيجة عداد يعتمد على ساعة الجهاز بالثواني.
ضع الكود السابق في برنامجك وشغل البرنامج وستجد أن العداد قد نفذ رسالة المسج بوكس بالتحديد في الوقت المحدد دون أي تأخير رغم كل ماسيجد من معوقات .
الطريقة السابقة جيدة لعمل عداد زمني ولكن ستجد أن لها مساوىء ولن ترضيك مثلاً ماذا لو كان الوقت قبل منتصف الليل وكان برنامجك يعمل فعندها بعد دخول اليوم التالي سيصبح العداد يتعامل مع بداية الوقت وسيخرج عن السيطرة ، لذلك يمكن تعديل الكود لما يناسبه ولكن لن أتطرق لذلك لما سيأتي من شرح لطريقة أفضل ، ومن مساوىء هذه الطريقة كون يمكن لأي مستخدم أن يضبط ويغير في ساعة النظام وسيختلف توقيت عدادك كلياً
ستقول هنا أستطيع بأن أخفي خيار ظهور الساعة على سطح المكتب لأمنع المستخدم من ضبط الوقت كتنفيذ أمر بالرجستري هكذا :
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer] "HideClock"=dword:00000001
أو أمر عدم الدخول للوحة التحكم كهذا :
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer] "nocontrolpanel"=dword:00000001
مهما كثرت الطرق للإلغاء التحكم بالساعة من الوندوز فهذا الأمر في النهاية لن يرضيك لأنك ستعطي برنامج لشخص لا يستطيع التحكم بوقت جهازه وبرنامجك يعمل ،ناهيك عن أنه قد يمكن ضبط الوقت من أي برنامج خارجي أو كتفعيل ملف تنفيذي نوع Bat تعطيه هذين السطرين لتغيير الوقت والتاريخ لنظامك:
time 11:30:30 date 20-12-2007
إذا الطريقة السابقة لم تنفع فدعونا نبحث عن طريقة أو وقت ما في النظام لا يمكن تغييره ولا يمكن لأحد العبث به وطبعاً سيكون يعتمد على تردد كرستالة البيوس ، فسنجد الدالة GetTickCount التابعة للمكتبة الشهيرة (kernel32) التي ستبدأ العد من الصفر عند بدء تشغيل الوندوز حتى إنهاء تشغيل الوندور .
فلنصرح عن الأداة في قسم التصريحات العامة :
Private Declare Function GetTickCount Lib "kernel32" () As Long Dim A As Long
وسنحصل على الوقت الذي مضى على تشغيل الجهاز بالميلي ثانية كما يلي ، وفي مثالنا سنحوله للثواني ليصبح :
A = GetTickCount \ 1000
وهنا إستخدامنا إشارة التقسيم بدون الباقي .
وننتقل لكتابة باقي أسطر الكود كما فعلنا سابقاً :
Private Sub Timer1_Timer() Dim B As Long: B = GetTickCount \ 1000 If B - A >= 60 Then MsgBox "إكتمل الوقت المطلوب": End End Sub
ولا ننسى ضبط قيمة التايمر في مثالنا على 1000 ملي ثانية وشغل البرنامج وجرب الكود وستجد هذا المؤقت سيعطيك الوقت المطلوب بالزمن المحدد
صحيح أنه سيقوم بمعالجة أكواد إضافية ولكنك ضمنت أنك حصلت على تأخير زمني أو عداد مثالي سيفيدك إن شاء الله.
في المرفق مثال للطريقة السابقة ، جربها مع الضغط على المعالج مع الطريقة العادية فإذا لم تقتنع وكنت تملك جهاز حديث وسريع فإنه ليس الكل لديه مثل جهازك ،فهناك أجهزة تعمل إلى الآن بمعالج 300 ميغا أو500 وبذاكرة 128 ميغا هرتز
ويمكنك فتح برامج كثيرة ثقيلة على جهازك وشغل الأكواد السابقة وشوف وجرب ، أو يمكنك سحب كبل الداتا بسرعة للسواقة وستجد أن نظامك بدأ بالتوقف والإستمرار.
إن كان الموضوع السابق فيه شيىء من الخطأ - فمن نفسي - فأطلب من خبراء البيسك أن يصلحوا لي مابدا من أخطاء وإن كان لا - فمن الله تعالى - وإدعوا لي ولوالدي بالخير والصحة
تم بعون الله تعالى .. والله المستعان
