تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
كيفية تنفيذ عملية في مسار آخر وإظهار النتيجة في التحكمات على النموذج
#1
كاتب الموضوع : samerselo

سألني أحد الإخوة عن مشكلة واجهته عند تنفيذ عملية معينة على مسار آخر ومحاولته إظهار النتيجة على النموذج فإذا افترضنا أنه لدينا إجراء بسيطا ينفذ عملية ما ونفذناه على مسار آخر Thread غير المسار الرئيسي الذي تنفذ عليه عمليات البرنامج وأن ذلك الإجراء يحتوي على كود يقوم بضبط قيمة الخاصية Text لصندوق نصوص على النموذج فعند تنفيذ الكود ستحصل على رسالة خطأ

كود :
Cross-thread operation not valid: Control 'TextBox1' accessed from a thread other than the thread it was created on.
وإذا أردت توليد رسالة الخطأ السابق بنفسك أنشئ مشروعا جديدا ضع عليه صندوق نصوص وزر واجعل كود النموذج مطابقا لما يلي ثم قم بتشغيل البرنامج وستحصل على رسالة الخطأ السابقة

كود :
Imports System.Threading

Public Class Form1

Private Sub Button1_Click() Handles Button1.Click

Dim th As New Thread(AddressOf DoLongOperation)
th.Start()

End Sub

Private Sub DoLongOperation()
Me.TextBox1.Text = "Something"
End Sub

End Class
الحل الذي أقوم باستخدامه عادة لحل هكذا مشكلة هو إنشاء فئة Class تقوم بتنفيذ العملية على المسار الثاني وتعيد النتيجة للنموذج من خلال إطلاق حدث يعيد القيم الناتجة عن عملية المعالجة للنموذج فربما لا تكون هذه هي الطريقة الأفضل في جميع الحالات ولكنها طريقتي على كل حال وسأقوم بشرحها ثم يمكننا النقاش وتجربة أية حلول أخرى لتجاوز هذه المشكلة وسأطرحها عبر تنفيذ عداد بسيط للوقت فربما ستستخدم أنت هذه الطريقة للبحث عن ملفات أو تنفيذ عمليات معالجة معقدة تستغرق وقتا طويلا ولكنني هنا اخترت مثالا يعيد قيمة وحيدة بحيث يكون بسيطا قدر الإمكان
الآن سأقوم بإضافة فئة Class جديد للمشروع يتم عبره تنفيذ العملية الطويلة التي نريد تنفيذها على مسار آخر وسأقوم بتسميتها MyStopWatch في الوقت الحالي وبما أننا سنتعامل مع المسارات سنحتاج للاستيراد التالي قبل تعريف الفئة


كود :
Imports System.Threading
سأقوم بتعريف فئة فرعية داخل الفئة MyStopWatch باسم ReturnValueEventArgs سأستخدمها لاحقا لإطلاق الحدث الذي سيعيد النتيجة إلى النموذج وهذه يجب أن تكون موروثة من الفئة EventArgs بما أنها فئة خاصة بإعادة قيم الحدث الذي سيتم إطلاقه وسأعرف فيها خاصية وحيدة ReturnVlaue ستكون للقراءة فقط بما أننا لن نحتاج لضبط قيمتها إلا من خلال باني الفئة تعيد القيمة وباني للفئة يمرر له قيمة نصية وحيدة تمثل القيمة المعادة وبهذا يكون كود الفئة ReturnValueEventArgs كما يلي

كود :
Public Class ReturnValueEventArgs
Inherits EventArgs

Private _ReturnValue As String

Public ReadOnly Property ReturnVlaue() As String
Get
Return _ReturnValue
End Get
End Property

Public Sub New(ByVal RetVal As String)
_ReturnValue = RetVal
End Sub

End Class
ضمن كود الفئة MyStopWtach وبعد نهاية تعريف الفئة ReturnValueEventArgs نقوم بتعريف الحدث الذي سنقوم بإطلاقه ليعيد القيمة إلى النموذج ومن أجل الالتزام بتنسيق الأحداث كما نرى في التحكمات والفئات قمنا بتعريف الفئة ReturnValueEventArgs وبهذا يكون تعريف الحدث في قسم تعريف المتغيرات العامة في الفئة MyStopWatch كما يلي


كود :
Public Event ReturnValue(ByVal sender As Object, ByVal e As ReturnValueEventArgs)
عرف متغيرا عاما على مستوى الفئة MyStopWtach باسم _MyTimer وهو من النوع Stopwatch كما يلي


كود :
Private _MyTimer As Stopwatch
حيث سنستخدمه كعداد للوقت من أجل الحصول على قيمة ليتم إعادتها ضمن إجراء المعالجة الذي سيتم تنفيذه على المسار الآخر بحيث سيكون كود إجراء المعالجة الذي سينفذ على المسار الثاني كما يلي


كود :
Private Sub DoProcessing()
Do
Dim Ret = _MyTimer.Elapsed.Hours & ":" & _
_MyTimer.Elapsed.Minutes & ":" & _
_MyTimer.Elapsed.Seconds & ":" & _
_MyTimer.Elapsed.Milliseconds

RaiseEvent ReturnValue(Me, New ReturnValueEventArgs(Ret))
Loop Until _MyTimer.IsRunning = False
End Sub
حيث وضعنا قيمة العداد في متغير نصي Ret ثم استخدمنا الدالة RaiseEvent لإطلاق الحدث ReturnValue حيث القيمة Me التي تشير للفئة الحالية كبارمتر أول للحدث ReturnValue يكون البارامتر الثاني للحدث عبارة عن كيان Instance من الفئة ReturnValueEventArgs التي نمرر لبانيها المتغير Ret الذي يشكل القيمة المعادة من الخاصية ReturnVlaue العائدة للفئة ReturnValueEventArgs عندما سنستقبلها من النموذج
وسيكون لدينا إجراء لبدء تنفيذ المؤقت على المسار الثاني باسم StartTimer بحيث يكون كوده على الشكل


كود :
Public Sub StartTimer()
_MyTimer.Reset()
_MyTimer.Start()

Dim th As New Thread(AddressOf DoProcessing)
th.Start()
End Sub
حيث قمنا بتصفير العداد وبدئه ثم عرفنا مسارا جديدا th يقوم بتنفيذ الإجراء DoProcessing ومن أجل إيقاف العداد سنحتاج لإجراء StopTimer يكون كوده على الشكل


كود :
Public Sub StopTimer()
_MyTimer.Stop()
End Sub
وبهذا تكون قد اكتملت فئتنا التي ستقوم بعملية المعالجة على مسار ثاني وتعيد قيمة نصية سنقوم بعرضها في صندوق نصوص لاحقا ويكون بذلك الكود الكامل لهذه الفئة


كود :
Imports System.Threading

Public Class MyStopWtach

Public Class ReturnValueEventArgs
Inherits EventArgs

Private _ReturnValue As String

Public ReadOnly Property ReturnVlaue() As String
Get
Return _ReturnValue
End Get
End Property

Public Sub New(ByVal RetVal As String)
_ReturnValue = RetVal
End Sub

End Class

Public Event ReturnValue(ByVal sender As Object, ByVal e As ReturnValueEventArgs)
Private _MyTimer As New Stopwatch

Public Sub StartTimer()
_MyTimer.Reset()
_MyTimer.Start()

Dim th As New Thread(AddressOf DoProcessing)
th.Start()
End Sub

Private Sub DoProcessing()
Do
Dim Ret = _MyTimer.Elapsed.Hours & ":" & _
_MyTimer.Elapsed.Minutes & ":" & _
_MyTimer.Elapsed.Seconds & ":" & _
_MyTimer.Elapsed.Milliseconds

RaiseEvent ReturnValue(Me, New ReturnValueEventArgs(Ret))
Loop Until _MyTimer.IsRunning = False
End Sub

Public Sub StopTimer()
_MyTimer.Stop()
End Sub


End Class
}}}
تم الشكر بواسطة:
#2
نعود للنموذج الخاص بالمشروع الذي نحتاج لوجود صندوق نصوص وزرين عليه للقيام بتجربة الفئة الجديدة حيث سنقوم بتعريف متغير خاص على مستوى النموذج باسم MyTimer من نوع فئتنا MyStopWtach وباستخدام العبارة WithEvents التي ستمكننا من استقبال الأحداث التي ستطلقها فئتنا


كود :
Private WithEvents MyTimer As New MyStopWtach
وسيكون كود الزرين من أجل بدء وإيقاف المؤقت باستخدام فئتنا السابقة كما يلي


كود :
Private Sub Button1_Click() Handles Button1.Click
MyTimer.StartTimer()
End Sub

Private Sub Button2_Click() Handles Button2.Click
MyTimer.StopTimer()
End Sub
الآن أنشئ معالج للحدث ReturnVlaue العائد للمتغير MyTimer واجعله بحيث يكون الكود فيه كالتالي ثم جرب تشغيل البرنامج فستحصل على رسالة مشابهة للرسالة في بداية المقال


كود :
Private Sub MyTimer_ReturnValue(ByVal sender As Object, _
ByVal e As MyStopWtach.ReturnValueEventArgs) Handles MyTimer.ReturnValue

Me.TextBox1.Text = e.ReturnVlaue

End Sub
ولمعالجة هذه النقطة والتخلص من رسالة الخطأ سنحتاج لعمل Invoke للإجراء MyTimer_ReturnValue حتى نستطيع استخدام القيم المعادة منه في ضبط قيم خصائص التحكمات على النموذج وفي حالتنا هنا الخاصية Text لصندوق النصوص والعملية ببساطة ستتم كما يلي
في قسم المتغيرات العامة في النموذج سنقوم بتعريف إجراء مفوض Delegate يحمل نفس توقيع الإجراء MyTimer_ReturnValue وبدون جسم للإجراء كما يلي


كود :
Private Delegate Sub MyTimer_ReturnValueDelegate(ByVal sender As Object, _
ByVal e As MyStopWtach.ReturnValueEventArgs)
ويكون الكود الذي سينفذ المهمة بصورة صحيحة كما يلي


كود :
Private Sub MyTimer_ReturnValue(ByVal sender As Object, _
ByVal e As MyStopWtach.ReturnValueEventArgs) Handles MyTimer.ReturnValue

If Me.TextBox1.InvokeRequired = True Then
Dim d As New MyTimer_ReturnValueDelegate(AddressOf MyTimer_ReturnValue)
Me.Invoke(d, New Object() {sender, e})
Else
Me.TextBox1.Text = e.ReturnVlaue
End If
End Sub
حيث فحصنا قيمة الخاصية القابلة للقراءة فقط InvokeRequired لصندوق النصوص فإن كان False نقوم بضبط قيمة الخاصية Text باستخدام القيمة المعادة من الحدث مباشرة بدون أي مشاكل وإن كانت True عندها لن نستطيع ضبط القيمة مباشرة كي لا نحصل على الخطأ الوارد في بداية المقال عندها سنعرف متغير d من نوع الإجراء المفوض MyTimer_ReturnValueDelegate ونمرر له عنوان الإجراء MyTimer_ReturnValue ثم استخدمنا الطريقة Invoke العائدة للنموذج لتنفيذ نسخة آمنة من الحدث MyTimer.ReturnValue تمكننا من ضبط القيم المعادة إلى التحكمات وذلك بتمرير المتغير d كمحدد أول للخاصية Invoke ويكون المحدد الثاني للخاصية Invoke هو مصفوفة من النوع Object يتم تمرير محددات الإجراء MyTimer_ReturnValue كعناصر لها
وفيما يلي الكود الكامل للنموذج


كود :
Public Class Form1

Private Delegate Sub MyTimer_ReturnValueDelegate(ByVal sender As Object, _
ByVal e As MyStopWtach.ReturnValueEventArgs)

Private WithEvents MyTimer As New MyStopWtach

Private Sub Button1_Click() Handles Button1.Click
MyTimer.StartTimer()
End Sub

Private Sub Button2_Click() Handles Button2.Click
MyTimer.StopTimer()
End Sub

Private Sub MyTimer_ReturnValue(ByVal sender As Object, _
ByVal e As MyStopWtach.ReturnValueEventArgs) Handles MyTimer.ReturnValue

If Me.TextBox1.InvokeRequired = True Then
Dim d As New MyTimer_ReturnValueDelegate(AddressOf MyTimer_ReturnValue)
Me.Invoke(d, New Object() {sender, e})
Else
Me.TextBox1.Text = e.ReturnVlaue
End If
End Sub

End Class
}}}
تم الشكر بواسطة:
#3
مرفق كود المشروع


الملفات المرفقة
.zip   TestStopWatch00.zip (الحجم : 15.25 ك ب / التحميلات : 47)
}}}
تم الشكر بواسطة:


المواضيع المحتمل أن تكون متشابهة .
الموضوع : الكاتب الردود : المشاهدات : آخر رد
Video [درس فيديو] حل مشكلة تغيير مسار قاعدة البيانات مع تقارير كريستال ريبورت رمضان272 0 1,681 23-04-22, 05:56 AM
آخر رد: رمضان272
Video [درس فيديو] تقارير الكريستال ريبورت وتغيير مسار الصور أثناء التشغيل رمضان272 0 1,607 28-03-22, 03:18 AM
آخر رد: رمضان272
  [سلسلة تعليمية] شرح تفصيلي وكامل عن كيفية الاتصال بسيكوال وعمل اتاش برمجي وجلب اسماء السيرفرات ابو انس 3 3,621 25-02-22, 12:44 AM
آخر رد: atefkhalf2004
  تنفيذ كود vb.net من خلال مربع نص @@أبورائد@@ 20 14,607 06-10-21, 05:05 PM
آخر رد: الماجيك مسعد
  تنفيذ سكربت القاعدة مع انطلاق البرنامج + تحديث التعديلات على القاعدة ابو ليلى 2 5,063 02-07-21, 09:05 PM
آخر رد: naserflaha71
Photo كيفية اطهار جدول داخل الأخر alsouf 3 4,470 21-11-20, 09:15 AM
آخر رد: Anas Mahmoud
  لفهم كيفية الربط الذي يتم بين الجداول viv 4 4,993 03-10-20, 05:34 PM
آخر رد: Arfat007
  [VB.NET] كيفية استخدام اداوات ديف اكسبريس devexpressلادخال السجلات الى قاعدة بيانات نوع اكسس 13adam123 0 2,579 29-03-20, 12:50 PM
آخر رد: 13adam123
Star [مقال] كيفية تحويل اسعار العملات بإستخدام Yahoo Exchange Rates Web Service Programmation 9 8,956 22-02-20, 12:58 PM
آخر رد: دمعة المقهور
Exclamation كيفية تجاوز ظهور الخطا في Global.WindowsApplication1.My.Resources الرائد 0 2,549 13-08-19, 11:40 PM
آخر رد: الرائد

التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 1 ) ضيف كريم