اقوم ببناء برنامج فى الوقت الحالى و احد متطلباته هو ان يحتوى على محاكى للـ Command Prompt الخاص بالويندوز و حيث اننى لست ممن يقوموا بكتابة كل كود البرنامج داخل الـ Form لذا فقد صنعت اداة هى محاكى للـ Command Prompt.
الأداه وارثه لـ RichTextBox و شكلها فى وقت التشغيل كالتالى (طبعا بدون الفورم)
تحتوى هذه الاداه على قصور شديد (فإنا لم اقم حتى الأن بدمجها مع برنامجى) فهى لا تحتوى على اى احداث و انا لا اقصد احداث الـ RichTextBox و لكنى اقصد احداث للتعامل مع الأوامر التى يتم ادخالها من المستخدم و معالجتها من قبل مستخدم ال062F%uداه او منع تنفيذها.
بالطبع توجد لدى العديد من الأفكار الشيقه جدا لتلك الأداه و لكن و حتى لا تكون معقده جدا سأشرح لك نظام عملها و الفكره التى تقوم عليها و بعدها سأترك لك خيار تحديثها و تطويرها بما يتلائم مع عملك (إذا كنت ستستخدمها).
هذا هو شكل الأداه برمجيا (كالمعتاد بدون كود)
Imports System.Interme.InteropServices
Public Class CommandPrompt
Inherits RichTextBox
Private Shared mRestrictedCommand() As String = {"cmd", "exit", "edit"}
Private Shared mPrivateCommand() As String = {"cls"}
Private start_i= 0
s Integer = 0
Private curDir As String = Applith
.StartupPath
Private ReadOnly StartUpMessage As String = Application.ProductName & " v" & Application.ProductVersion
Protected Overrides Sub OnCreateControl()
Protected Overrides Sub OnKeyDown(ByVal e As KeyEventArgs)
Protected Overrides Sub OnKeyUp(ByVal e As System.Windows.Forms.KeyEventArgs)
Private Sub ExecuteHandler(ByVal cmdType As Commander.CommandType, ByVal value As String)
Private Sub ExecutePrivate(ByVal cmd As String)
Private Sub ExecuteRestricted(ByVal cmd As String)
Private Sub Protect()
Private Class Commander
Public Enum CommandType
Allow
[Private]
Restricted
End Enum
Public Delegate Sub Action(ByVal CmdType As CommandType, ByVal value As String)
Public Shared Function ExecuteCommand(ByVal command As String, ByVal res As Action, Optional ByVal curDir As String = vbNullString) As Boolean
End Class
End Classقبل ان نبدأ فى استعراض الكود سويا سأشرح كيف تعمل نظريا و ما الأساليب (الأسهل) التى يمكن أتباعها للحصول على نفس الغرض منها و عيوب و مزايا كل منهم.
طباعة الـ ProductName و الـ ProductVersion.
اضافة سطر فارغ.
اضافة المسار الذى يعمل البرنامج منه ثم اضافة علامة اكبر من "<".
تحديد كل ما فى مربع النص
استدعاء الخاصيه SelectionProtected و جعلها True و ذلك لمنع المستخدم من التعديل فى القيم المطلوبه.
إلغاء التحديد و وضع مؤشر الكتابه عند نهاية النص (كذلك الإحتفاظ بموقع المؤشر الحالى داخل المتغير start_index).
نأتى الأن للفكره نفسها الخاصه بالـ Command Prompt
توجد طريقتين لدمجه داخل برنامجنا هو ان نقوم بتشغيله عمل تحويل لمدخلاته و مخرجاته لبرنامجنا و إرسال المدخلات إليه و استقبال المخرجات (إما ان تكون مخرجات عاديه او رسائل خطأ) او تشغيله و جعله ينفذ امر معين ثم نحصل على المخرجات و نضعها على شاشة البرنامج.
بالنسبه للطريقه الأولى فهى لن تعمل لإن البرنامج سيتوقف عن العمل حتى تقوم بإدخال قيمه لـ Command Prompt و حيث انه مخفى و لا يظهر فسيظل البرنامج متوقف عن العمل.
بالنسبه للطريقه الثانيه فهى الطريقه ال062B%0ث ى و هى التى سنتبعها و ايضا توجد طريقتين لتنفيذها و هم:
- استخدام ادوات (فئات) الدوت نت لتطبيق الفكره الثانيه.
- استخدام دوال الـ API لتطبيق الفكره الثانيه.
1 - استخدام ادوات (فئات) الدوت نت لتطبيق الفكره الثانيه.
ليتم استدعاء اوامر الدوس سنقوم بإستخدام الصيغه
cmd.exe /k command
يمكنك فعل هذا بإستخدام الداله Shell و لكن يعيبها ان شاشة الدوس ستظهر ثم ستختفى (هذا إذا لم تحدد Style للتنفيذ) و ايضا لن نستطيع الحصول على نتيجة تنفيذ الأمر لذا فالحل هو استخدام الفئه ProcessStartInfo حيث تتيح لك تحديد معلومات عن البرنامج الذى تريد تشغيله و ايضا تحديد ما اذا كنت تريد تحويل مخرجاته لبرنامجك لذا سنقوم بملء نسخه من هذه الفئه ببعض البيانات و هى كالتالى
Dim si As New ProcessStartInfo With si .FileName = "cmd.exe" .Arguments = "/k " & command .CreateNoWindow = True .RedirectStandardError = True .RedirectStandardOutput = True .UseShellExecute = False End With
الأن و قد قمت بتحديد المعلومات الاساسيه الخاصه بتنفيذ البرنامج، الباقى هو تشغيله.
Imports System.IO Dim p As Process = Process.Start(si) Dim so As StreamReader = p.StandardOutput Dim se As StreamReader = p.StandardError
الأن كما تلاحظ سيتم تشغيل الـ cmd لينفذ الأمر command و بعد ذلك النتيجه المعاده سيتم تخزينها داخل so و se ، فما هما ؟؟
الـ so اختصار لـ standard output و se اختصار لـ standard error.
عندما يتم تنفيذ البرنامج توجد حالتين ان يتم التنفيذ بشكل سليم و يتم العوده بناتج التنفيذ ايا كان و بذلك سيتم تخزينه داخل so. اما إذا حدث خطأ ما فسيتم الرجوع برسالة الخطأ و سيتم حفظها داخل se.
كما ترى الأمور جيده و لكن المشكله ستحدث بعد قليل.
عند تنفيذ امر مثل ping يتم الرجوع بقيم كل فتره زمنيه معينه حتى ينتهى البرنامج من عمله، لذا فى حالتنا هذه سيتم الرجوع بعد ان ينتهى البرنامج من عمله بمعنى ان البرنامج سيتوقف عن العمل حتى ينتهى الأمر من التنفيذ.
النقطه الثانيه نلاحظ ان cmd يقوم بإظهار رسائل الخطأ قبل ان يظهر النتيجه (هذا إذا كانت توجد رسالة خطأ)، اما فى حالتنا هذd106ف0630%uذا حاولت ان تأخذ القيمه من se و كانت لا تحتوى على اى بيانات فسيهنج البرنامج تماما و لن يعمل إلا إذا اغلقته من الـ task manager.
اخطأ بالجمله فما الحل. حتى لا ترهق نفسك فقد بحثت كثييييييييييرا عن حل بادوات الدوت نت و لا يوجد لذا فقد استعنت بدوال الـ API لتلاشى تلك الأخطاء.
النظام مع دوال الـ API مختلف قليلا مع العلم انه يوجد Structure شبيه بالفئه ProcessStartInfo.
الدوال التى سنستخدمها هى:
Private Declare Function CreatePipe Lib "kernel32" (ByRef phReadPipe As IntPtr, ByRef phWritePipe As IntPtr, ByRef lpPipeAttributes As SECURITY_ATTRIBUTES, ByVal nSize As Integer) As Integer Private Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessA" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, ByVal lpProcessAttributes As Integer, ByVal lpThreadAttributes As Integer, ByVal bInheritHandles As Integer, ByVal dwCreationFlags As Integer, ByVal lpEnvironment As Integer, ByVal lpCurrentDriectory As String, ByRef lpStartupInfo As STARTUPINFO, ByRef lpProcessInformation As PROCESS_INFORMATION) As Integer Private Declare Function ReadFile Lib "kernel32" (ByVal hFile As IntPtr, ByVal lpBuffer() As Byte, ByVal numBytesToRead As Integer, ByRef numBytesReaded As Integer, ByVal overlapped As IntPtr) As Integer Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As IntPtr) As Integer Private Declare Function PeekNamedPipe Lib "kernel32" (ByVal hNamedPipe As IntPtr, ByRef lpBuffer As Integer, ByVal nBufferSize As Integer, ByVal lpBytesRead As Integer, ByRef lpTotalBytesAvail As Integer, ByRef lpBytesLeftThisMessage As Integer) As Integer
خطوات التنفيذ هى :
- قم بعمل Pipe للقراءه منها و الكتابه فيها (الـ pipe تشبه الـ Memory stream).
- قم بعمل نسخه من الـ STARTUPINFO و ضع فيه البيانات الخاصه بتشغيل البرنامج cmd و اسمح بعميلة تحويل البيانات للـ pipe.
- قم بإستجعاء الداله CreateProcess و مرر لها الـ STARTUPINFO من الخطوه السابقه.
- ابدء فى القراءه من الـ pipe و ما تقرءه قم بكتابته داخل خاصية الـ Text.
- استمر فى القراء من الـ pipe حتى تصبح فارغه.
خطوات بسيطه و لكن قبل تطبيقها برمجيا دعنا نتكلم قليلا عن امر ما.
ما اريد التكلم عنه هنا هو اوامر الدوس حيث توجد اوامر اذا قمت بتنفيذها سواء من الدوت نت او من الـ API ستسبب لك مشكله مثل edit او cls
بالنسبه للأمر الاول edit فهو يقوم بتشغيل محرر نصوص من داخل cmd و ينتظر من المستخدم ان يقوم بإدخال و بالتالى لن يعمل البرنامج حتى ينتهى edit من عمله و بشكل عام edit لأا داعى منه داخل windows حيث انك ستقوم بإستخدام الـ notepad.
بالنسبه للأمر cls فهو ليس امر انما هو expression خاص ببرنامج cmd و حتى اوضح الأمر اكثر جميع الأوامر التى نستخدمها من داخل cmd هى برامج مستقل%\x0020\x00ئ30اتها اما cls فهو ليس مثلهم انما فقط هو امر داخلى للـ cmd لمسح الشاشه و بالتالى ان حاولت تطبيقه ستظهر رسالة خطأ بأنه غير موجود.
اوامر الدوس قمت بتقسمها إلى 3 انواع و هم Allow و Private و Restricted.
النوع الأول هو الأوامر العاديه مثل ping او help.
النوع الثانى و هو الأوامر الداخليه مثل cls.
النوع الثالث و هو الأوامر المحظوره مثل edit او exit او cmd
من احد الأوامر المستخدمه داخل cmd هو الأمر cd ألخاص بتغيير المسار الحالى، هذا الأمر لن يعمل معنا لإننا فى كل مره نقوم بإستدعاء الـ cmd يقوم ببدء عمله من داخل المسار الموجود به البرنامج.
يمكننا تتبع المسار الموجود داخل الـ textbox حيث انه دائما اخر نتيجه عائده من الـ pipe هى مسار معين و علامة "<" لذا قد نقوم بأخذ ما قبل هذه العلامه و نحفظه داخل متغير و عند محاولة تنفيذ امر جديد نقوم باستدعاء الداله CreateProcess و تمرر لها من ضمن المعاملات المسار الذى نحتفظ به و بالتالى سيعتبر cmd ان المسار الذى تم تمرير هو المكان الذى يعمل منه حاليا.
و بذلك نكون قد نجحنا فى عمل محاكى للـ Command Prompt لنقوم بالتنفيذ الأن:
فى البدايه قمت بعمل فئه اسمها CommandPrompt و هى وارثه لـ RichTextBox و تحتوى داخلها على فئه اسمها Commander و هى التى تقوم بتنفيذ الأوامر الخاصه بالدوس
Public Class CommandPrompt Inherits RichTextBox Private Class Commander End Class End Class
سنتحدث اولا عن الفئه Commander حيث انها هى الفئه التى تقوم فعليا بتنفيذ الأوامر
Private Class Commander Private Structure PROCESS_INFORMATION Dim Dim hProcess As IntPtr Dim hThread As IntPtr Dim dwProcessId As Integer Dim dwThreadId As Integer End Structure Private Structure STARTUPINFO Dim cb As Integer Dim lpReserved As String Dim lpDesktop As String Dim lpTitle As String Dim dwX As Integer Dim dwY As Integer Dim dwXSize As Integer Dim dwYSize As Integer Dim dwXCountChars As Integer Dim dwYCountChars As Integer Dim dwFillAttribute As Integer Dim dwFlags As Integer Dim wShowWindow As Short Dim cbReserved2 As Short Dim lpReserved2 As Integer Dim hStdInput As IntPtr Dim hStdOutput As IntPtr Dim hStdError As IntPtr End Structure Private Structure SECURITY_ATTRIBUTES Dim nLength As Integer Dim lpSecurityDescriptor As Integer Dim bInheritHandle As Integer End Structure Private Declare Function CreatePipe Lib "kernel32" (ByRef phReadPipe As IntPtr, ByRef phWritePipe As IntPtr, ByRef lpPipeAttributes As SECURITY_ATTRIBUTES, ByVal nSize As Integer) As Integer Private Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessA" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, ByVal lpProcessAttributes As Integer, ByVal lpThreadAttributes As Integer, ByVal bInheritHandles As Integer, ByVal dwCreationFlags As Integer, ByVal lpEnvironment As Integer, ByVal lpCurrentDriectory As String, ByRef lpStartupInfo As STARTUPINFO, ByRef lpProcessInformation As PROCESS_INFORMATION) As Integer Private Declare Function ReadFile Lib "kernel32" (ByVal hFile As IntPtr, ByVal lpBuffer() As Byte, ByVal numBytesToRead As Integer, ByRef numBytesReaded As Integer, ByVal overlapped As IntPtr) As Integer Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As IntPtr) As Integer Private Declare Function PeekNamedPipe Lib "kernel32" (ByVal hNamedPipe As IntPtr, ByRef lpBuffer As Integer, ByVal nBufferSize As Integer, ByVal lpBytesRead As Integer, ByRef lpTotalBytesAvail As Integer, ByRef lpBytesLeftThisMessage As Integer) As Integer Public Enum CommandType Allow [Private] Restricted End Enum Public Delegate Sub Action(ByVal CmdType As CommandType, ByVal value As String) Public Shared Function ExecuteCommand(ByVal command As String, ByVal res As Action, Optional ByVal curDir As String = vbNullString) As Boolean command = command.Trim For i As Integer = 0 To mRestrictedCommand.Length - 1 If command.ToLower = mRestrictedCommand(i) Then res.Invoke(CommandType.Restricted, command) Return False End If Next For i As Integer = 0 To mPrivateCommand.Length - 1 If command.ToLower = mPrivateCommand(i) Then res.Invoke(CommandType.Private, mPrivateCommand(i)) Return False End If Next Const STARTF_USESTDHANDLES As Integer = &H100 Const STARTF_USESHOWWINDOW As Integer = &H1 Const SW_HIDE As Integer = 0 Dim BUFFER_SIZE As Integer = 1024 * 10 Dim sa As SECURITY_ATTRIBUTES sa.lpSecurityDescriptor = 0 sa.bInheritHandle = -1 sa.nLength = Marshal.SizeOf(GetType(SECURITY_ATTRIBUTES)) Dim siStart As STARTUPINFO = Nothing Dim hOutRead As IntPtr Dim hOutWrite As IntPtr If CreatePipe(hOutRead, hOutWrite, sa, 0) <> 0 Then siStart.cb = Marshal.SizeOf(GetType(STARTUPINFO)) siStart.hStdOutput = hOutWrite siStart.hStdError = hOutWrite siStart.dwFlags = STARTF_USESTDHANDLES Or STARTF_USESHOWWINDOW siStart.wShowWindow = SW_HIDE End If Dim piNewProc As PROCESS_INFORMATION = Nothing Dim bytesRead As Integer = 0 Dim bytesLeft As Integer = 0 If CreateProcess(vbNullString, "cmd.exe /k " & command, 0, 0, -1, 0, 0, curDir, siStart, piNewProc) <> 0 Then Threading.Thread.Sleep(50) While PeekNamedPipe(hOutRead, 0, 0, 0, bytesLeft, 0) <> 0 If bytesLeft <> 0 Then If bytesLeft > BUFFER_SIZE Then bytesLeft = BUFFER_SIZE Dim buf(bytesLeft) As Byte If ReadFile(hOutRead, buf, bytesLeft, 0, IntPtr.Zero) <> 0 Then CloseHandle(hOutWrite) res.Invoke(CommandType.Allow, System.Text.UTF8Encoding.Default.GetString(buf)) End If Else Application.DoEvents() End If End While End If Try Process.GetProcessById(piNewProc.dwProcessId).Close() Catch End Try CloseHandle(hOutRead) CloseHandle(hOutWrite) CloseHandle(piNewProc.hProcess) CloseHandle(piNewProc.hThread) Return True End Function
نبذه صغيره عن نظام عمل الفئه Commander
- تحتوى هذه الفئه على داله واحده اسمها ExecuteCommand تأخذ منك 3 معاملات هما
- command : و هو الأمر الذى تريد تنفيذه
- res : و هى عنوان لداله سيتم استدعائها و أرسال نوع الأمر كأول معامل و قيمه معينه كمعامل ثانى هذه القيمه تحدد بناءا على المعامل الأول فإذا كان Allow فالقيمه هى النتيجه المعاده من تنفيذ الأومر و غير ذلك يكون اسم الأمر نفسه.
- curDir : و هو معامل اختيارى القيمه الإفتراضيه له لاشئ و هو يمثل المسار الإفتراضى الذى سوف يتم تمريره لداله CreateProcess.
الداله ExecuteCommand تعود بـ True إذا كان نوع الأمر Allow و قد تم الإنتهاء من الحصول على نتيجة تنفيذه و تعود بـ False إذا كان نوع الأمر Private أو Restricted.
فى بداية الداله قمنا بتحويل الـ command إلى حروف صغيره بعدها قمنا بإختبار وجود الـ command داخل المصفوفه mRestrictedCommand فإن وجد نستدعى الداله res و نعود بـ False.
بعد ذلك نتحقق من وجود الـ command داخل المصفوفه mPrivateCommand فإن وجد نستدعى الداله res و نعود بـ False.
بعدها نقوم بإنشاء نسخه من SECURITY_ATTRIBUTES بإسم sa ثم ملئها بالقيم الخاصه بها استعدادا لتمريرها للداله CreatePipe.
بعد ذلك نقوم بإنشاء المتغير si و هو من نوع STARTUPINFO و بعدها ننشى المتغيرين hOutRead و hOutWrite و اللذان فيما بعد سيحتويان على Handles الخاصه بالقراءه و الكتابه من الـ Pipe.
بعد ذلك نقوم بإستدعاء الداله CreatePipe و نمرر لها المتغيرين السابقين فإن عادت الداله CreatePipe بقيمه لا تساوى صفرفهذا يعنى انه تم انشاء الـ Pipe و المتغيرين يحتووا الأن على مقابض القراءه و الكتابه الخاصه بها و بالتالى نقوم بإسناد قيمة مقبض القراءه hOutRead للمتغيرين hStdOutput و hStdError الموجودين داخل si.
أيضا نقوم بوضع القيمه SW_HIDE داخل المتغير wShowWindow لعدم اظهار شاشة الدوس اثناء تنفيذ الأمرايضا نقوم بوضع حجم الـ STARTUPINFO داخل المتغير cb و نضع القيمه STARTF_USESTDHANDLES Or STARTF_USESHOWWINDOW لتحديد ان المتغيرين wShowWindow و متغيرات المقابص تحتوى على بيانات سليمه ليتم استخدامهم.
بعد ذلك نقوم بإنشاء المتغير piNewProc و هو من نوع PROCESS_INFORMATION و الذى سيحتوى فيما بعد على بيانات الـ Process الذى قمنا بتشغيله.
بعدها نقوم بإستدعاء الداله CreateProcess و نمرر لها المعاملات التى تريدها فإذا كانت القيمه العائده منها لا تساوى صفر فقد تم البدء فى تنفيذ الأمر الخاص بالدوس و بعدها ندخل فى حلقة while ان القيمه العائده من الداله PeekNamedPipe لا تساوى صفر. الداله PeekNamedPipe ستعود بصفر عندما ينتهى الـ Process من عمله.
الداله PeekNamedPipe تعود بقيمه داخل المتغير bytesLeft و هى تمثل عدد البيتات المتبقيه داخل الـ pipe و التى لم يتم قرائتها بعد.
بعدها إن كان bytesLeft تحتوى على صفر فنعلم البرنامج ان يستمر فى عمله و ذلك عن طريق الأمation.ication.DoEvents اما إن كان bytesLeاوى صفتساوى صفر تات اللتحقق بان عدد البيتات المتبقيه ليست اكبر من القيمه المخزنه داخل الثابت BUFFER_SIZE و الذى يحتوى على القيمه الإفتراضيه لعدد البايت التى سيتم قرائتها و هو 5 ميجا، فإذا كانت قيمة bytesLeft اكبر من BUFFER_SIZE يتم تخزين قيمة الـ BUFFER_SIZE داخل bytesLeft.
بعدها نستخدام الداله ReadFile مع مقبض القراءه الخاص بالـ Pipe لنقراء منها و نمرر لها ايضا المتغير bytesLeft ليتم القراءه بعدد القيمه المخزنه به ايضا نقوم بإنشاء المتغير buf و هى مصفوفه من نوع byte و حجمها يساوى الموجود داخل bytesLeft و نقوم بتمرير هذا المتغير للداله ليتم نسخ البايتات المقروءه داخله.
عندما تقوم الداله ReadFile بالقراءه تقوم بتحريك مؤشر القراءه لأخر مكان تقراء منه لذا عندما تقوم الداله PeekNamedPipe بحساب عدد البياتات التى لم يتم قرائتها تبدأ من مكان مؤشر القراءه و بالتالى ستتناقص القيمه تدريجيا.
القيم الموجوده داخل المصفوفه buf هى حروف ascii، لذا نريد تحويلها إلى نص و ذلك سيتم عن طريق استخدام الفئه System.Text.ding حcoding حيث سنقوم بإستخدام النسخه الإفتراضيه منها (المتغير default الموجود بها) حيث تحتوى على الداله GetString و التى تقبل مصفوفه bytes و تحولها لـ string.
لذا كل ما سنفعله الأن هو استدعاء الداله res ثم نمرر لها نوع الأمر Allowed و ايضا نمرر لها النتيجه العائده من الداله GetString.
فى النهايه نقوم بإغلاق الـ Process إذا كان مازال يعمل و ايضا نقوم بإغلاق كل الـ Handles المفتوحه.
انتهينا من شرح مجتويات الفئه Commander و نأتى الأن للفئه CommandPrompt.
Public Class CommandPrompt
Inherits RichTextBox
Private Shared mRestrictedCommand() As String = {"cmd", "exit", "edit", "doskey"}
Private Shared mPrivateCommand() As String = {"cls"}
Private start_index As Integer = 0
Private curDir As String = Application.StartupPath
Private ReadOnly StartUpMessage As String = Application.ProductName & " v" & Application.ProductVersion
Protected Overrides Sub OnCreateControl()
MyBase.OnCreateControl()
If Not DesignMode Then
ScrollBars = RichTextBoxScrollBars.ForcedVertical
ShortcutsEnabled = False
DetectUrls = False
Text = StartUpMessage & vbNewLine & vbNewLine & curDir & ">"
Protect()
End If
End Sub
Protected Overrides Sub OnKeyDown(ByVal e As KeyEventArgs)
If e.KeyCode = Keys.Enter Then
e.Handled = True
Dim cmd As String = Text.Substring(start_index)
If Commander.ExecuteCommand(cmd, AddressOf ExecuteHandler, curDir) = True Then
curDir = Lines(Lines.Length - 1).Split(">"c)(0)
Protect()
End If
End If
MyBase.OnKeyDown(e)
End Sub
Protected Overrides Sub OnKeyUp(ByVal e As System.Windows.Forms.KeyEventArgs)
If SelectionStart >= start_index Then
SelectionProtected = False
End If
MyBase.OnKeyUp(e)
End Sub
Private Sub ExecuteHandler(ByVal cmdType As Commander.CommandType, ByVal value As String)
If cmdType = Commander.CommandType.Restricted Then
ExecuteRestricted(value)
Return
ElseIf cmdType = Commander.CommandType.Private Then
ExecutePrivate(value)
Return
End If
If Text = vbNullString Then
Text = value
Else
If value.EndsWith(vbNewLine) OrElse value.EndsWith(vbCr) OrElse value.EndsWith(vbCrLf) OrElse value.EndsWith(vbLf) Then
Text += value
Else
Text += vbLf & value
End If
End If
End Sub
Private Sub ExecutePrivate(ByVal cmd As String)
If cmd = "cls" Then
Clear()
Text = curDir & ">"
Protect()
End If
End Sub
Private Sub ExecuteRestricted(ByVal cmd As String)
Text += vbNewLine & String.Format("command ""{0}"" is restricted, you can not use it.", cmd) & vbNewLine & vbNewLine
Text += curDir & ">"
Protect()
End Sub
Private Sub Protect()
SelectAll()
SelectionProtected = True
start_index = Text.Length
SelectionStart = start_index
SelectionLength = 0
SelectionProtected = False
End Sub
End Classفى الباديه داخل الداله OnCreateControl قمنا بالتحقق من إذا كانت الأداه ليست فى ةضع التصميم نقوم بتحديد الخصائص الخاصه بها مثل الـ scrollbars او النص الإفتراضى الذى يكتب داخل خاصيه الـ Text و هو بالمناسبه مزيج من المتغير StartUpMessage و curDir.
داخل الداله OnKeyDown إذا كان الزر هو Enter نقوم بمنع طبع قيمته أى لا نضيف سطر جديد ثم نقوم بإستخلاص الأمر المدخل و تمريره للداله ExecuteCommand وايضا نمرر لها عنوان الداله ExecuteHandler و الخاصه بإستقبال القيمه العائده من تنفيذ الأمر ايضا نقوم بتمرير المسار الحالى و الموجود داخل curDir.
إذا عادت الداله ExecuteCommand بـ True نقوم بأخذ المسار الحالى من اخر سطر فى خاصية Text و حتى نصل للعلامه "<". ايضا نقوم بإستدعاء الداله Protect و التى تقوم بحماية النص المكتوب داخل الخاصيه Text من التعديل ايضا تقوم بوضع المؤشر الخاص بالكتابه بعد اخر حرف مكتوب داخل خاصية الـ Text بالإضافه إلى ذلك يتم تحديث المتغير start_index إلى عدد الحروف فى مربع النص.
اذا قمت بالتحرك داخل اى منطقه من النص و كانت هذه المنطقه غير قابله للتعديل و قمت بعدها بالرجوع إلى اول منطقه قابله للكتابه فلن يتم الكتابه فيها و ستعتبر محميه و لمنع ذلك قمت بالتحقق من موقع المؤشر الخاص بالكتابه داخل الداله OnKeyUp فإن كان موقع المؤشر اكبر من او يساوى start_index فسيتم إلغاء الحمايه لأدعك تكتب كما يحلو لك.
اثناء تنفيذ الداله ExecuteCommand يتم استدعاء الداله ExecuteHandler و التى تتحقق فى اولها من نوع الأمر فإن كان Private تقوم بإستددعاء الداله ExecutePrivate و تمرر لها اسم الأمر و التى تقوم بدورها بتنفيذ كود بناءا على هذا الامر و فى حالتنا هذه لا يوجد إلا الأمر cls اما إذا كان نوع الأمر Restricted فسيتم إستدعاء الداله ExecuteRestricted و التى تظهر رساله تفيد ان هذا الأمر غير قابل للتنفيذ.
اذا كان نوع الأمر Allowed ستقوم الداله بإختبار اخر حرف فى القيمه التى ستوضع داخل خاصية Text فإن كانت تمثل سطر جديد فيتم إضافتها للنص كما هى و غير ذلك يتم اضافة سطر جديد يليه القيمه المستقبله.
الصوره الموجوده بالأعلى هو صورة المثال الموجود بالمرفقات.
هذه الاداه ليست متكامله فلا تعتقد انها فابله للعمل كما هى حيث انه توجد بها بعض القصور و التى حتى الأن لما اعالجها بها فتذكر ان تجربها جيدا ان كنت ستستخدمها فى برامجك
هذا المرفق غير سليم للتحميل قم بالذهاب للمشركه رقم 12 من هنا و قم بتحميل اما الملف الثالث او الرابع
و الله ولى التوفيق
