الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

كيفية تنفيذ عملية في مسار آخر وإظهار النتيجة في التحكمات على النموذج

بدأه محمد سامر أبو سلو في 10 أكتوبر 2008 · 2 رد · 792 مشاهدة · في Microsoft Visual Basic.NET
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

سألني أحد الإخوة عن مشكلة واجهته عند تنفيذ عملية معينة على مسار آخر ومحاولته إظهار النتيجة على النموذج فإذا افترضنا أنه لدينا إجراء بسيطا ينفذ عملية ما ونفذناه على مسار آخر 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

نعود للنموذج الخاص بالمشروع الذي نحتاج لوجود صندوق نصوص وزرين عليه للقيام بتجربة الفئة الجديدة حيث سنقوم بتعريف متغير خاص على مستوى النموذج باسم 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

مرفق كود المشروع

TestStopWatch00.zip

عن عائشة رضي الله عنها أن النبي صلى الله عليه وسلم قال: إن الله يحب إذا عمل أحدكم عملا أن يتقنه

نعيب زماننا والعيب فينا ... وما لزماننا عيب سوانا

ونهجو ذا الزمان بغير ذنب ... ولو نطق الزمان لنا هجانا

محمد سامر أبو سلو

#2

وهذا مشروع StopWatch يستخدم نفس الأسلوب السابق ولكن مع خيارات أكثر وبالتالي تعقيد أكبر للكود

StopWatchTest.zip

عن عائشة رضي الله عنها أن النبي صلى الله عليه وسلم قال: إن الله يحب إذا عمل أحدكم عملا أن يتقنه

نعيب زماننا والعيب فينا ... وما لزماننا عيب سوانا

ونهجو ذا الزمان بغير ذنب ... ولو نطق الزمان لنا هجانا

محمد سامر أبو سلو

مواضيع مشابهة

عدد الزوار حالياً

المتواجدون خلال آخر دقيقتين · يتحدّث كل ٣٠ ثانية

—الإجمالي—أعضاء مسجّلون—زوار بدون تسجيل

جارٍ التحقق من المتواجدين…