اذهب الي المحتوي
أوفيسنا
بحث مخصص من جوجل فى أوفيسنا
Custom Search

الصوره التوضيحيه %s

عن هذا الملف

هذا الموضوع يعد مهماً وحيوياً لمصممي ومطوري النظم أكثر من المستخدم العادي

خاصة عند استخدام حدث Timer مع أي نموذج ويريد مطور النظام فتح محرر الأكواد (VBA) في نفس وقت عرض النماذج هنا تحدث المشكلة الكلاسيكية:

المطور لا يستطيع تعديل أي كود لأن التايمر النموذج يعطل التركيز داخل المحرر و يسبب أخطاء 

بفضل هذه الفكرة الثورية  هذا الأمر أصبح من الماضي السحيق

فكرة العمل باختصار

  • 1 - عند فتح النموذج يبدأ TimerInterval تلقائياً
  • 2 - إذا تم فتح محرر VBA بأى طريقة كانت يتم إيقاف التايمر فوراً
  • 3 - عند إغلاق المحرر والعودة إلى النماذج يتم تشغيل التايمر من جديد تلقائياً وفوراً
  • 4 - لا حاجة لتمرير اسم النموذج الرئيسي أو اسم عنصر الـ Subform يدوياً
  • 5 - المطلوب فقط هو تمرير اسم عنصر التحكم الذي سيتم عرض الوقت فيه (Label أو TextBox) حسب الحاجة والرغبة

 مميزات الحل

  • ذكـي : يكتشف النموذج الرئيسي والفرعي تلقائياً
  • متعدد : يعمل مع أكثر من نموذج في نفس الوقت
  • مـــرن : يدعم Label و TextBox كعناصر عرض للوقت
  • آمـــن :  إذا تم حذف الوحدة النمطية يتحول تلقائياً إلى Fallback Mode باستخدام دالة الوقت الرئيسية Now()
  • خفيف :  لا يعتمد على Timer إضافي للمراقبة
  • جاهــز : يعمل مع النماذج الرئيسية و الفرعية أو الرئيسية المستقلة أو الرئيسية والفرعية معا

-----------------------------------------------------


الوحدة النمطية العامة

الاسم المقترح: basClockManager
الكود...

Option Compare Database
Option Explicit

#If VBA7 Then
    Private Declare PtrSafe Function SetWinEventHook Lib "user32" ( _
        ByVal eventMin As Long, _
        ByVal eventMax As Long, _
        ByVal hmodWinEventProc As LongPtr, _
        ByVal pfnWinEventProc As LongPtr, _
        ByVal idProcess As Long, _
        ByVal idThread As Long, _
        ByVal dwFlags As Long) As LongPtr
        
    Private Declare PtrSafe Function UnhookWinEvent Lib "user32" ( _
        ByVal hWinEventHook As LongPtr) As Long

    Private Declare PtrSafe Function GetAncestor Lib "user32" ( _
        ByVal hwnd As LongPtr, _
        ByVal gaFlags As Long) As LongPtr
#Else
    Private Declare Function SetWinEventHook Lib "user32" ( _
        ByVal eventMin As Long, _
        ByVal eventMax As Long, _
        ByVal hmodWinEventProc As Long, _
        ByVal pfnWinEventProc As Long, _
        ByVal idProcess As Long, _
        ByVal idThread As Long, _
        ByVal dwFlags As Long) As Long

    Private Declare Function UnhookWinEvent Lib "user32" ( _
        ByVal hWinEventHook As Long) As Long

    Private Declare Function GetAncestor Lib "user32" ( _
        ByVal hwnd As Long, _
        ByVal gaFlags As Long) As Long
#End If

Private Const EVENT_SYSTEM_FOREGROUND   As Long = &H3
Private Const WINEVENT_OUTOFCONTEXT     As Long = &H0
Private Const GA_ROOT                   As Long = 2

Private gHook                           As LongPtr
Private gAccessHwnd                     As LongPtr
Private gClocks                         As Object

Private Sub EnsureClockStore()
    If gClocks Is Nothing Then
        Set gClocks = CreateObject("Scripting.Dictionary")
    End If
End Sub

Private Function MakeClockKey(frm As Access.Form) As String
    On Error GoTo ErrorHandler

    Dim vParentForm As Access.Form
    Dim vControlName As String

    Set vParentForm = frm.Parent
    vControlName = GetSubformControlName(vParentForm, frm)

    If Len(vControlName) > 0 Then
        MakeClockKey = vParentForm.Name & "|" & vControlName & "|" & frm.Name
        Exit Function
    End If

ErrorHandler:
    MakeClockKey = frm.Name
End Function

Private Function CreateClockState(frm As Access.Form, _
                                  ByVal pControlName As String, _
                                  ByVal pIntervalMS As Long) As Object

    Dim vState As Object
    Set vState = CreateObject("Scripting.Dictionary")

    Dim vIsSubform As Boolean
    Dim vParentName As String
    Dim vSubCtlName As String

    On Error Resume Next
    vParentName = frm.Parent.Name
    vSubCtlName = GetSubformControlName(frm.Parent, frm)
    vIsSubform = (Len(vSubCtlName) > 0)
    On Error GoTo 0

    vState.Add "Key", MakeClockKey(frm)
    vState.Add "HostFormName", frm.Name
    vState.Add "ParentFormName", vParentName
    vState.Add "SubformControlName", vSubCtlName
    vState.Add "IsSubform", vIsSubform
    vState.Add "ControlName", pControlName
    vState.Add "WasStoppedByVBE", False
    vState.Add "IntervalMS", pIntervalMS

    Set CreateClockState = vState
End Function

Public Sub StartSmartClock(frm As Access.Form, _
                           ByVal pControlName As String, _
                           Optional ByVal pIntervalMS As Long = 1000)
    On Error GoTo ErrorHandler

    EnsureClockStore

    Dim vKey As String
    Dim vState As Object

    vKey = MakeClockKey(frm)

    If gClocks.Exists(vKey) Then gClocks.Remove vKey

    Set vState = CreateClockState(frm, pControlName, pIntervalMS)
    gClocks.Add vKey, vState

    frm.TimerInterval = pIntervalMS

    If gAccessHwnd = 0 Then gAccessHwnd = Application.hWndAccessApp

    If gHook = 0 Then
        gHook = SetWinEventHook( _
                    EVENT_SYSTEM_FOREGROUND, _
                    EVENT_SYSTEM_FOREGROUND, _
                    0, _
                    AddressOf ForegroundChangedProc, _
                    0, 0, _
                    WINEVENT_OUTOFCONTEXT)
    End If

    Exit Sub
ErrorHandler:
End Sub

Public Sub StopSmartClock(frm As Access.Form)
    On Error Resume Next

    EnsureClockStore

    Dim vKey As String
    vKey = MakeClockKey(frm)

    frm.TimerInterval = 0

    If gClocks.Exists(vKey) Then
        gClocks.Remove vKey
    End If

    If gClocks.Count = 0 Then
        If gHook <> 0 Then
            UnhookWinEvent gHook
            gHook = 0
        End If
        Set gClocks = Nothing
    End If
End Sub

Public Sub SmartClockTimer(frm As Access.Form)
    On Error Resume Next

    EnsureClockStore

    Dim vKey As String
    Dim vState As Object

    vKey = MakeClockKey(frm)
    If Not gClocks.Exists(vKey) Then Exit Sub

    Set vState = gClocks(vKey)

    If IsVbeOpen() Then
        vState("WasStoppedByVBE") = True
        frm.TimerInterval = 0
        Exit Sub
    End If

    Dim vTimeText As String
    vTimeText = Format$(Now(), "dd/mm/yyyy hh:nn:ss AM/PM")

    If Len(vState("ControlName")) > 0 Then
        UpdateControlDisplay frm, vState("ControlName"), vTimeText
    End If
End Sub

Private Function IsVbeOpen() As Boolean
    On Error Resume Next
    IsVbeOpen = Application.VBE.MainWindow.Visible
    If Err.Number <> 0 Then IsVbeOpen = False
    On Error GoTo 0
End Function

Private Sub UpdateControlDisplay(frm As Access.Form, ByVal pControlName As String, ByVal pDisplayText As String)
    On Error Resume Next

    Dim vControl As Access.Control
    Set vControl = frm.Controls(pControlName)
    If vControl Is Nothing Then Exit Sub

    Select Case vControl.ControlType
        Case acTextBox
            If vControl.Value <> pDisplayText Then
                vControl.Value = pDisplayText
            End If
        Case acLabel
            If vControl.Caption <> pDisplayText Then
                vControl.Caption = pDisplayText
            End If
    End Select
End Sub

Private Function GetSubformControlName(vParentForm As Access.Form, vChildForm As Access.Form) As String
    On Error Resume Next

    Dim vControl As Access.Control

    For Each vControl In vParentForm.Controls
        If vControl.ControlType = acSubform Then
            If vControl.Form Is vChildForm Then
                GetSubformControlName = vControl.Name
                Exit Function
            End If
        End If
    Next vControl
End Function

Private Function GetStateTargetForm(vState As Object, ByRef vForm As Access.Form) As Boolean
    On Error GoTo ErrorHandler

    If vState("IsSubform") Then
        If (SysCmd(acSysCmdGetObjectState, acForm, vState("ParentFormName")) And acObjStateOpen) = 0 Then Exit Function
        Set vForm = Forms(vState("ParentFormName")).Controls(vState("SubformControlName")).Form
    Else
        If (SysCmd(acSysCmdGetObjectState, acForm, vState("HostFormName")) And acObjStateOpen) = 0 Then Exit Function
        Set vForm = Forms(vState("HostFormName"))
    End If

    GetStateTargetForm = Not (vForm Is Nothing)
    Exit Function
ErrorHandler:
    Set vForm = Nothing
End Function

#If VBA7 Then
Public Sub ForegroundChangedProc( _
    ByVal hWinEventHook As LongPtr, _
    ByVal eventId As Long, _
    ByVal hwnd As LongPtr, _
    ByVal idObject As Long, _
    ByVal idChild As Long, _
    ByVal dwEventThread As Long, _
    ByVal dwmsEventTime As Long)
#Else
Public Sub ForegroundChangedProc( _
    ByVal hWinEventHook As Long, _
    ByVal eventId As Long, _
    ByVal hwnd As Long, _
    ByVal idObject As Long, _
    ByVal idChild As Long, _
    ByVal dwEventThread As Long, _
    ByVal dwmsEventTime As Long)
#End If
    On Error Resume Next

    If gClocks Is Nothing Then Exit Sub
    If gClocks.Count = 0 Then Exit Sub
    If GetAncestor(hwnd, GA_ROOT) <> gAccessHwnd Then Exit Sub

    Dim vKey As Variant
    Dim vState As Object
    Dim vForm As Access.Form

    For Each vKey In gClocks.Keys
        Set vState = gClocks(vKey)

        If vState("WasStoppedByVBE") Then
            If GetStateTargetForm(vState, vForm) Then
                vForm.TimerInterval = vState("IntervalMS")

                Dim vTimeText As String
                vTimeText = Format$(Now(), "dd/mm/yyyy hh:nn:ss AM/PM")

                If Len(vState("ControlName")) > 0 Then
                    UpdateControlDisplay vForm, vState("ControlName"), vTimeText
                End If

                vState("WasStoppedByVBE") = False
            End If
        End If
    Next vKey
End Sub

-----------------------------------------------------

طريقة الاستدعاء في النماذج

نموذج يعرض الوقت في Label
الكود...

Private Sub Form_Load()
    Application.Run "StartSmartClock", Me, "lblTime", 1000
End Sub

Private Sub Form_Timer()
    Application.Run "SmartClockTimer", Me
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Application.Run "StopSmartClock", Me
End Sub

-----------------------------------------------------

نموذج يعرض الوقت في TextBox 
الكود...

Private Sub Form_Load()
    Application.Run "StartSmartClock", Me, "txtTime", 1000
End Sub

Private Sub Form_Timer()
    Application.Run "SmartClockTimer", Me
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Application.Run "StopSmartClock", Me
End Sub

-----------------------------------------------------

نموذج بدون عرض نصي للوقت
الكود...

Private Sub Form_Load()
    Application.Run "StartSmartClock", Me, "", 1000
End Sub

Private Sub Form_Timer()
    Application.Run "SmartClockTimer", Me
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Application.Run "StopSmartClock", Me
End Sub

-----------------------------------------------------
 

كما تلاحظون الاستدعاء سهل وموحد سواء أردنا عرض الوقت في TextBox أو Label

Application.Run "StartSmartClock", Me, "", 1000


ملاحظة هامة حول معامل : TimerInterval وهو المعامل الثالث فى الاستدعاء  (Interval) 

الرقم:  1000 هو قيمة TimerInterval يمثل الفاصل الزمني بالميلي ثانية

القيمة الافتراضية 1000 (ثانية واحدة) تم ضبطها مسبقاً داخل الوحدة النمطية

هل هو إجباري؟
لايمكن حذفه والاعتماد على القيمة الافتراضية
طرق الاستدعاء المختلفة مع المعامل :

' الطريقة الأولى: تمرير قيمة مخصصة (مثلاً نصف ثانية)
Application.Run "StartSmartClock", Me, "lblTime", 500

' الطريقة الثانية: حذف المعامل (يتم استخدام 1000 تلقائياً)
Application.Run "StartSmartClock", Me, "lblTime"

في بعض الحالات  قد يرغب المصمم في عمل التالى :

  • ساعة عادية لذلك سوف تكون قيمة : TimerInterval =1000 (ثانية واحدة)
  • ساعة دقيقة (Stopwatch) تكون قيمة  TimerInterval = 100 (جزء من الثانية)
  • ساعة بطيئة (تحديث نادر) تكون قيمة TimerInterval = 5000 (5 ثوانٍ)

لهذا السبب تم جعل هذا المعامل اختيارياً مع  استخدام القيمة الإفتراضية المنطقية مع إتاحة المجال للمصمم لتغييره حسب احتياجاته
-----------------------------------------------------

 نموذج مع Fallback ( منتهى الأمان )
الكود...

Option Compare Database
Option Explicit

Private mIsSmartClockActive As Boolean

Private Sub Form_Load()
    AttemptStartSmartClock
End Sub

Private Sub Form_Timer()
    Dim vTimeText As String
    vTimeText = Format$(Now(), "dd/mm/yyyy hh:nn:ss AM/PM")
    
    If mIsSmartClockActive Then
        Application.Run "SmartClockTimer", Me
        Me.txtDClock = vTimeText
    Else
        ' Fallback: النموذج يحدث نفسه بنفسه
        Me.txtDClock = vTimeText
        Me.lblDClock.Caption = vTimeText
    End If
End Sub

Private Sub Form_Unload(Cancel As Integer)
    On Error Resume Next
    If mIsSmartClockActive Then
        Application.Run "StopSmartClock", Me
    End If
    Me.TimerInterval = 0
    On Error GoTo 0
End Sub

Private Sub AttemptStartSmartClock()
    On Error GoTo FallbackMode
    Application.Run "StartSmartClock", Me, "lblDClock", 1000
    mIsSmartClockActive = True
    Exit Sub
    
FallbackMode:
    Err.Clear
    mIsSmartClockActive = False
    Me.TimerInterval = 1000
End Sub

-----------------------------------------------------


الخلاصة النهائية :الاستدعاء المناسب

  • عرض الوقت في : Label
LabelStartSmartClock Me, "lblTime"
  • عرض الوقت في : TextBox
StartSmartClock Me, "txtTime"
  • بدون عرض نصى للوقت مثلا ساعة عقارب (بدون عرض نصي)
StartSmartClock Me, ""

نموذج فرعي (Subform) نفس الكود - يتم اكتشافه تلقائياً

نماذج متعددة في نفس الوقت مدعوم بالكامل


ملاحظات مهمة

  • إعدادات الأمان     : يجب تفعيل Trust access to the VBA project object model في إعدادات Trust Center
  • إصدار Access      :  يعمل مع Access 2007 والإصدارات الأحدث
  • مراجع إضافية      :  لا يحتاج إلى إضافة أي مراجع 
  • Fallback Mode   :  إذا تم حذف الوحدة النمطية، يتحول النموذج تلقائياً إلى استخدام Now()

 

مع هذا الحل الفريد أصبح :

  • التحكم في TimerInterval أثناء فتح محرر VBA أمراً تلقائياً بالكامل، دون أي تدخل يدوي أو تعقيدات
  • سهولة فتح محرر VBA و إضافة أو تعديل أى أكواد في نفس وقت عرض النماذج وبدون أن تحدث المشكلة الكلاسيكية بسبب : TimerInterval

استمتع بتجربة تطوير سلسة وخالية من المشاكل

 

 

 

 


اراء المستخدمين

Recommended Comments

لاتوجد تعليقات لعرضها .

انشئ حساب جديد او قم بتسجيل دخولك لتتمكن من اضافه تعليق جديد

يجب ان تكون عضوا لدينا لتتمكن من التعليق

انشئ حساب جديد

سجل حسابك الجديد لدينا في الموقع بمنتهي السهوله .

سجل حساب جديد

تسجيل دخول

هل تمتلك حساب بالفعل ؟ سجل دخولك من هنا.

سجل دخولك الان
×
×
  • اضف...

Important Information