المشاركات : 662
المواضيع 39
الإنتساب : Feb 2014
السمعة :
195
الشكر: 1474
تم شكره 1740 مرات في 622 مشاركات
تم الشكر بواسطة: Sajad , Amir_Alzubidy , محمد كريّم , محمد كريّم , khodor1985 , khodor1985 , الوادي , الشاكي لله , الشاكي لله , عدنان الشمري , عدنان الشمري , اباذر , اباذر , Hzar
المشاركات : 662
المواضيع 39
الإنتساب : Feb 2014
السمعة :
195
الشكر: 1474
تم شكره 1740 مرات في 622 مشاركات
19-06-16, 04:16 AM
(آخر تعديل لهذه المشاركة : 20-06-16, 03:22 AM {2} بواسطة ابو ليلى.)
بسم الله الرحمن الرحيم
نكمل ما بدأناه سابقا
سنحاول ان نفصل اكواد الاتصال و الحفظ في كلاس منفصل
سيكون لدي Module اسمه Connect
و نبدء باستيراد مجالات الاسماء و تعريف المتغيرات اللازمة لاجراء عملية الاتصال و كذلك الكائنات اللازمة لنقل البيانات و حفظها من و الى القاعدة
في اعلى Module المسمى Connect
كود :
Imports System.Data
Imports System.Data.OleDb
استوردنا المجالات اللازمة للتعامل مع قاعدة البيانات من النوع Access
الان داخل Module نعرف الكائنات التالية
كود :
Module Connect
Public ConnectionString As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\Coder.accdb"
Public Con As New OleDbConnection(ConnectionString)
Dim KindDa, CodeDa, InfoDa, PostDa, AttachDa, LoginDa As New OleDb.OleDbDataAdapter
Dim ComBulder1, ComBulder2, ComBulder3, ComBulder4, ComBulder5, ComBulder6 As New OleDbCommandBuilder
Public MyDataset As New DataSet
Private LoginDataSet As New DataSet
- السطر الاول هو مسار قاعدة البيانات داخل البرنامج و اسمها Coder
- السطر الثاني كائن اتصال ليؤمن لنا عملية فتح و اغلاق الاتصال مع قاعدة البيانات
- السطر الثالث مجموعة من كائنات DataAdapter سوف نستخدمها لنقل البيانات من و الى القاعدة كل واحد منها لجدول و يتضح ذلك عبر اسمائها
- السطر الرابع مجموعة من كائنات CommanBuilder سنستخدمها لتقوم بعمليات توليد جمل الادخال و التعديل و الحذف لكل جدول بدل من كتابة و تكرار جمل
الادخال , هذا الكائن سحري في التعامل مع قواعد البيانات .
- السطر الخامس حاوية البيانات (قاعدة بيانات مؤقتة) سنستخدمها لنضيف اليها الجداول السابقة.
- السطر السادس حاوية بيانات اخرى فقط لجدول LOgins السابق ذكره.
ساقوم بعدها بانشاء بعض Functions التي تسهل علينا العمل
كود :
'جدول الانواع
Private Function LoadKindTable() As DataTable
Dim SelectField As String = "Select Id,Kind,Not1 From Kind"
KindDa = New OleDbDataAdapter(SelectField, ConnectionString)
KindDa.MissingSchemaAction = MissingSchemaAction.AddWithKey
KindDa.MissingMappingAction = MissingMappingAction.Passthrough
KindDa.Fill(MyDataset, "kind")
Return MyDataset.Tables("Kind")
End Function
كما تلاحظ دالة بسيطة تعيد لي جدول بيانات الانواع , تحتوي على جملة تحديد مجموعة سجلات من الجدول
ثم يقوم ناقل البيانات KindDa وفق الاتصال الممنوح له بجلب البيانات ووضعها في جدول في حاوية البيانات
لاحظ اننا فرضنا شروطا على هذا الناقل بان يقوم يقوم بتوريث مخطط الجدول الى حاوية البيانات و كذلك
فرض مفتاح اساسي في حال وجد في بنية الجدول المنقول.
اذ ستحتوي حاوية البيانات Mydataset على نفس اسم الجدول و كذلك على نفس اسماء الاعمدة في الجدول الاصل
بنفس التخطيط هذا السطر هو المسؤول عن العملية
كود :
KindDa.MissingMappingAction = MissingMappingAction.Passthrough
اما السطر الاخر فهو سيفرض مفتاح رئيسي في الجدول على على الحقل ذو القيد PrimaryKey و هو هنا Id
كود :
KindDa.MissingSchemaAction = MissingSchemaAction.AddWithKey
سنكرر العملية لكل جدول نريد التعامل معه كما يلي
جدول الاكواد
كود :
'جدول الاكواد
Private Function LoadCodeTable() As DataTable
Dim SelectField As String = "Select Id2,Not1,Not2,Code1,Code2,Kind_id From coder"
CodeDa = New OleDbDataAdapter(SelectField, ConnectionString)
CodeDa.MissingSchemaAction = MissingSchemaAction.AddWithKey
CodeDa.MissingMappingAction = MissingMappingAction.Passthrough
CodeDa.Fill(MyDataset, "Coder")
Return MyDataset.Tables("Coder")
End Function
جدول المواضيع او المقالات
كود :
'جدول المقالات
Private Function LoadInfoTable() As DataTable
Dim SelectField As String = "Select IdInfo,Infoname,Content,UrlInfo,Id_Kind From Info"
InfoDa = New OleDbDataAdapter(SelectField, ConnectionString)
InfoDa.MissingSchemaAction = MissingSchemaAction.AddWithKey
InfoDa.MissingMappingAction = MissingMappingAction.Passthrough
InfoDa.Fill(MyDataset, "Info")
Return MyDataset.Tables("Info")
End Function
جدول الفروع
كود :
'جدول الفروع
Private Function LoadposterTable() As DataTable
Dim SelectField As String = "Select id_post,poname,id_k From poster"
PostDa = New OleDbDataAdapter(SelectField, ConnectionString)
PostDa.MissingSchemaAction = MissingSchemaAction.AddWithKey
PostDa.MissingMappingAction = MissingMappingAction.Passthrough
PostDa.Fill(MyDataset, "post")
Return MyDataset.Tables("post")
End Function
جدول المرفقات
كود :
'جدول المرفقات
Private Function LoadAttachTable() As DataTable
Dim SelectField As String = "Select Attach_Id,AttachName,AttachPath,Id_inf From Attaches "
AttachDa = New OleDbDataAdapter(SelectField, ConnectionString)
AttachDa.MissingSchemaAction = MissingSchemaAction.AddWithKey
AttachDa.MissingMappingAction = MissingMappingAction.Passthrough
AttachDa.Fill(MyDataset, "Attach")
Return MyDataset.Tables("Attach")
End Function
جدول المواقع
كود :
'جدول المواقع
Private Function LoadLoginTable() As DataTable
Dim SelectField As String = "Select Login_Id,SiteName,LoginUrl,usernamee,Passworde,ActionStr,formParams,Inputpass,Inputuser,InputAction From LoginsTb"
LoginDa = New OleDbDataAdapter(SelectField, ConnectionString)
LoginDa.MissingSchemaAction = MissingSchemaAction.AddWithKey
LoginDa.MissingMappingAction = MissingMappingAction.Passthrough
LoginDa.Fill(LoginDataSet, "LoginsTb")
Return LoginDataSet.Tables("LoginsTb")
End Function
بعد ذلك سنقوم بعمل اجراء خاص لفرض العلاقات بين الجداول و نقوم باضافته الى حاوية البيانات MyDataset
تابع الكود التالي
كود :
'فرض العلاقات
Private Sub LoadRelation()
If MyDataset.Tables.Contains("Kind") And MyDataset.Tables.Contains("Coder") And MyDataset.Tables.Contains("Info") And MyDataset.Tables.Contains("post") Then
'علاقة1
Dim KIndColumn As DataColumn = MyDataset.Tables("Kind").Columns("Id")
'علاقة2
Dim CoderColumn As DataColumn = MyDataset.Tables("Coder").Columns("Kind_id")
Dim postidColumn As DataColumn = MyDataset.Tables("post").Columns("id_post")
'علاقة3
Dim postColumn As DataColumn = MyDataset.Tables("post").Columns("id_k")
Dim InfoColumn As DataColumn = MyDataset.Tables("Info").Columns("Id_Kind")
'علاقة4
Dim InfoIdColumn As DataColumn = MyDataset.Tables("Info").Columns("IdInfo")
Dim AttachColumn As DataColumn = MyDataset.Tables("Attach").Columns("Id_inf")
'مسح العلاقات ان وجدت
If MyDataset.Relations.Contains("Kind_Coder") Then
MyDataset.Relations.Remove("Kind_Coder")
End If
'
If MyDataset.Relations.Contains("Kind_post") Then
MyDataset.Relations.Remove("Kind_post")
End If
'
If MyDataset.Relations.Contains("Kind_Info") Then
MyDataset.Relations.Remove("Kind_Info")
End If
'
If MyDataset.Relations.Contains("Info_Attach") Then
MyDataset.Relations.Remove("Info_Attach")
End If
'علاقة الانواع بالاسئلة و الاكواد
Dim Relation1 As New DataRelation("Kind_Coder", KIndColumn, CoderColumn)
MyDataset.Relations.Add(Relation1)
MyDataset.Relations("Kind_Coder").ChildKeyConstraint.UpdateRule = Rule.Cascade
MyDataset.Relations("Kind_Coder").ChildKeyConstraint.DeleteRule = Rule.Cascade
'علاقة الانواع بالفروع
Dim Relation2 As New DataRelation("Kind_post", KIndColumn, postColumn)
MyDataset.Relations.Add(Relation2)
MyDataset.Relations("Kind_post").ChildKeyConstraint.UpdateRule = Rule.Cascade
MyDataset.Relations("Kind_post").ChildKeyConstraint.DeleteRule = Rule.Cascade
'علاقة الفروع بالمقالات
Dim Relation3 As New DataRelation("Kind_Info", postidColumn, InfoColumn)
MyDataset.Relations.Add(Relation3)
MyDataset.Relations("Kind_Info").ChildKeyConstraint.UpdateRule = Rule.Cascade
MyDataset.Relations("Kind_Info").ChildKeyConstraint.DeleteRule = Rule.Cascade
'علاقة المقالات بالمرفقات
Dim Relation4 As New DataRelation("Info_Attach", InfoIdColumn, AttachColumn)
MyDataset.Relations.Add(Relation4)
MyDataset.Relations("Info_Attach").ChildKeyConstraint.UpdateRule = Rule.Cascade
MyDataset.Relations("Info_Attach").ChildKeyConstraint.DeleteRule = Rule.Cascade
End If
End Sub
الكود بسط جدا ان شاء الله و ساشرحه هنا
- في البداية انا افحص تواجد الجداول داخل Dataset .
- بعدها اقوم بتعريف الاعمدة التي ستقوم بعمليات الربط (المفتاح الرئيسي و المفتاح الثانوي).
- ثم نقوم بمسح العلاقات من Dataset ان وجدت.
- بعدها نقوم بتعريف كائن العلاقة بين جدولين و نسندله المفتاحين في الاب و الابن و نعطي اسم للعلاقة.
- نضيف العلاقة لكائن Dataset .
- ثم نقوم بفرض شرط على العلاقة ان تقوم بعمليات التحديث و الحذف بشكل متزامن بين الجدول الاب و الجدول الابن.
هذا كل ما في الامر بالنسبة لفرض العلاقة.
بعد ذلك سنقوم بعمل دالة Function تعيد لنا كائن Dataset معبئ بالجداول و العلاقات كما نحب و نشتهي
كود :
Public Function LoadDataSet() As DataSet
LoadKindTable()
LoadCodeTable()
LoadposterTable()
LoadInfoTable()
LoadAttachTable()
LoadRelation()
'
Return MyDataset
End Function
اضن الكود سهل الهضم و لا يحتاج شرح فهو تجميع للدوال السابقة.
الان ناتي الى اجراء الحفظ الخاص بكل ما جرى على الجداول من حذف و اضافة و تعديل
السيناريو الذي ستعمل معه هو تفحص تغير سجلات القاعدة المؤقته (MyDataset)
و نؤخذ نسخة من كل السجلات التي تعرضت للاضافة او التعديل او الحذف و نرحلها الى قاعدة مؤقته جديدة ساسميها SaveDataset
تحتوي فقط على السجلات المحدثة(مضاف-معدل-محذوف)
ثم سنقوم بارسال التحديثات الى القاعدة الاصل (Access Database)
و بعد الحصول على التحديثات من القاعدة سنقوم بدمج Mydataset مع SaveDataset
وقبول التغيرات الحاصلة .
طريقة سهلة و واضحة و قوية و هي لب الاتصال المنفصل
لنرى ذلك عبر الكود اللذيذ مع الشرح
كود :
Public Sub SaveData()
Try
Dim SaveDataset As New DataSet
SaveDataset = MyDataset.Clone
If MyDataset.HasChanges = True Then
SaveDataset = MyDataset.GetChanges(DataRowState.Added Or DataRowState.Deleted Or DataRowState.Modified)
ComBulder1.DataAdapter = KindDa
ComBulder2.DataAdapter = CodeDa
ComBulder3.DataAdapter = InfoDa
ComBulder4.DataAdapter = PostDa
ComBulder5.DataAdapter = AttachDa
If (Not (SaveDataset) Is Nothing) Then
KindDa.InsertCommand = ComBulder1.GetInsertCommand
KindDa.DeleteCommand = ComBulder1.GetDeleteCommand
KindDa.UpdateCommand = ComBulder1.GetUpdateCommand
'
CodeDa.InsertCommand = ComBulder2.GetInsertCommand
CodeDa.DeleteCommand = ComBulder2.GetDeleteCommand
CodeDa.UpdateCommand = ComBulder2.GetUpdateCommand
'
PostDa.InsertCommand = ComBulder4.GetInsertCommand
PostDa.DeleteCommand = ComBulder4.GetDeleteCommand
PostDa.UpdateCommand = ComBulder4.GetUpdateCommand
'
InfoDa.InsertCommand = ComBulder3.GetInsertCommand
InfoDa.DeleteCommand = ComBulder3.GetDeleteCommand
InfoDa.UpdateCommand = ComBulder3.GetUpdateCommand
'
AttachDa.InsertCommand = ComBulder5.GetInsertCommand
AttachDa.DeleteCommand = ComBulder5.GetDeleteCommand
AttachDa.UpdateCommand = ComBulder5.GetUpdateCommand
End If
KindDa.Update(SaveDataset, "Kind")
CodeDa.Update(SaveDataset, "Coder")
PostDa.Update(SaveDataset, "post")
InfoDa.Update(SaveDataset, "Info")
AttachDa.Update(SaveDataset, "Attach")
MyDataset.Merge(SaveDataset, True, MissingSchemaAction.AddWithKey)
MyDataset.AcceptChanges()
MsgBox("تم الحفظ", MsgBoxStyle.Information + MsgBoxStyle.MsgBoxRight, "حفظ")
End If
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
الشرح
- تعريف الحاوية المؤقته SaveDataset
- نسخ تخطيط الحاوية MyDataset الى الحاوية SaveDataset فقط نسخ تخطيط الجداول و العلاقات دون نسخ البيانات بمعنى اخر ستتواجد كل الجداول في الحاوية الجديدة مع العلاقات و القيود و التخطيط و كل شيئ باستثاء البيانات.
- الان نفحص وجود تغيرات في الحاوية MyDataset فاذا احتوت تغيرات جديدة منذ اخر استدعاء للطريقة (Acceptchanges) ? تابع شرحها بالاسفل
- اذا كان هناك تغيرات سنقوم باخذ نسخة من هذه التغيرات (الصفوف المضافة و المعدلة و المحذوفة) الى الحاوية SaveDataset.
- بعدها نقول له اذا كانت الحاوية SaveDataset ليست فارغة قم بمايلي:
- سنقوم بتحصيل جمل الادخال و التعديل و الحذف من الكائن CommandBilder بعد اسناده لناقل البيانات الخاص به DataAdapter ? سبق شرحه في الاعلى
- KindDa.Update(SaveDataset, "Kind") هذه الجملة و مابعدها ستقوم بتحديث القاعدة الاصلية (Access Database) كل جدول على حدا
- بعدها سنقوم بدمج التعديلات التي تمت بعد عملية تحديث القاعدة الاصلية وفق الطريقة Merge مع اضافة قيمة المفتاح الاساسي الراجع من القاعدة
- استدعاء الطريقة AcceptChanges و هي التي تعيد حالة MydataSet.HasChanges الى القيمة False بمعنى انها لا تحتوي على تغيرات جديدة يعد اخر عملية حفظ قمنا بها
يبدو انني نسيت ان اعيد حاوية جدول المواقع و هي وفق الكود التالي اذ اننا سنستخدمها في البرنامج
كود :
Public Function LoadLoginDataSet() As DataSet
LoadLoginTable()
Return LoginDataSet
End Function
و كذلك اجراء الحفظ الخاص به و هو كسابقه الخاص بحفظ التغيرات على الجداول السابقة
كود :
Public Sub SaveLogins()
Try
Dim SaveLoginsDataSet As New DataSet
SaveLoginsDataSet = LoginDataSet.Clone()
If LoginDataSet.HasChanges = True Then
SaveLoginsDataSet = LoginDataSet.GetChanges(DataRowState.Added Or DataRowState.Deleted Or DataRowState.Modified)
'
MsgBox("Hase new Ghange")
ComBulder6.DataAdapter = LoginDa
If (Not (SaveLoginsDataSet) Is Nothing) Then
LoginDa.InsertCommand = ComBulder6.GetInsertCommand
LoginDa.UpdateCommand = ComBulder6.GetUpdateCommand
LoginDa.DeleteCommand = ComBulder6.GetDeleteCommand
'
End If
LoginDa.Update(SaveLoginsDataSet, "LoginsTb")
LoginDataSet.Merge(SaveLoginsDataSet, True, MissingSchemaAction.AddWithKey)
LoginDataSet.AcceptChanges()
MsgBox("تم الحفظ", MsgBoxStyle.Information + MsgBoxStyle.MsgBoxRight, "حفظ")
End If
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
نفس شرح الكود السابق ؟؟
هكذا نكون قد انتهينا من كلاس الاتصال و الحفظ الخاص بنا
يتبع......
اللهم لك الحمد كما ينبغي لجلال وجهك و عظيم سلطانك
في حل و ترحال
المشاركات : 662
المواضيع 39
الإنتساب : Feb 2014
السمعة :
195
الشكر: 1474
تم شكره 1740 مرات في 622 مشاركات
الان ندخل الان الى الجانب العملي جانب التصميم و الربط مع النماذج
النموذج الاول لدينا هو نموذج ادخال الاكواد Form1
هذا النموذج سيعالج الادخالات في جدولين جدول الاصناف Kind و جدول الاكواد Coder
حيث سيكون لدينا اصناف مثل الفجوال بيزك - السي شارب - الجافا - SQL - WPF و غيرها حسب ما تحب
و تحت كل صنف يمكنك ادخال الاكواد التي تريدها
الفورم حسب التصميم الذي في الصورة اعلاه يحتوي على مجموعة من العناصر
عناصر الربط مع الجدول Kind
- ComboBox1 : ساستخدمه لعرض اسماء الاصناف المخزنة في الجدول Kind
- KindTextBox : لاضافة اسم التصنيف
- Not1TextBox:لاضافة ملاحظة على التصنيف
- KindBindingSource:ساستخدمه هنا للربط مع الجدول Kind و استفيد من مزاياه الخاصة بالتنقل و الاضافة
- BindingNavigator : ساستخدم قائمة تحتوي على ازرار الاضافة و الحذف و التعديل و كذلك التنقل و هي مرتبة بشكل جميل سنقوم بربطها بعنصر KindBindingSource مع تجاوز الطريقة الافتراضية لها في الحذف و الاضافة ساشرحها لاحقا
عناصر الربط مع جدول Coder
- ComboBox2:ساستخدمه للوصول السريع الى عمل الكود او اسمه في الجدول Coder
- Not1TextBox1:سنسجل فيه اسم الكود او عمله
- Not2TextBox:وصف الكود المطلوب
- Code2TextBox:تعقيب على الاجابة او شرح لعمل الكود
- Code1TextBox:الاجابة او الكود و هو ليس مربع نص عادي TextBox بل هو مربع نص خاص يعرف بالاسم ColoredTextBox يحتوي على خصائص مميزة لعرض الكود بالالوان المطلوبة كما محرر الكود في لغة البرمجة و هو مكتبة خاصة مضمنة مع البرنامج عثرت عليه في ارجاء المنتدى على ما اعتقد و هو موجود على شبكة النت يمكنك من عرض النص وفق الفئة المختارة SQL-PHP-VB-#C-HTML
- CoderBindingSource:للربط مع جدول Coder
- BindingNavigator1:سنربطها مع CoderBindingSource مع تجاوز طرقها كما سبق ذكره
هذه كل العناصر المطلوبة في النموذج الاول Form1
ناتي الان الى محرر الكود لنكتب اكوادنا الخاصة بكل العمليات اللازمة لاتمام المهمة
بداية ساقوم بتعريف هذه المتغيرات و ستعرف الحاجة منها مع كتابة الاكواد
كود :
Dim A, B As Integer
حدث تحميل النموذج
كود :
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Try
'
Me.KindBindingSource.DataSource = LoadDataSet()
Me.KindBindingSource.DataMember = "Kind"
'
Me.CoderBindingSource.DataSource = KindBindingSource
Me.CoderBindingSource.DataMember = "Kind_Coder"
'
BindingFileds()
Code1TextBox.PreferredLineWidth = Code1TextBox.Width
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
الكود سهل على ما اعتقد و الشرح
- ربط KindBindingSource بحاوية البيانات MyDataset عبر استدعاء الدالة loadDataSet التي سبق و انشأناها في الكلاس الخاص بالاتصال نربط مع الجدول Kind
- الان نربط الكائن CoderBindingSource بالعلاقة المحضونة ضمنا في الكائن السابق KindBindingSource العلاقة Kind_Coder التي تؤمن الترابط بين الجدولين.
- نستدعي الاجراء BindingFileds و هو اجراء خاص لربط العناصر بحقول قاعدة البيانات سيرد لاحقا.
- السطر الاخير هو كود خاص لجعل امتداد سطر الكود المعروض بعرض مربع النص اثناء تحجيم البرنامج.
الاجراء الخاص بربط عناصر النموذج مع حقول قاعدة البيانات BindingFileds
كود :
Private Sub BindingFileds()
Try
Me.KindTextBox.DataBindings.Add(New Binding("Text", KindBindingSource, "Kind"))
Me.Not1TextBox.DataBindings.Add(New Binding("Text", KindBindingSource, "Not1"))
'
Me.Not1TextBox1.DataBindings.Add(New Binding("Text", CoderBindingSource, "Not1"))
Me.Not2TextBox.DataBindings.Add(New Binding("Text", CoderBindingSource, "Not2"))
Me.Code1TextBox.DataBindings.Add(New Binding("Text", CoderBindingSource, "Code1"))
Me.Code2TextBox.DataBindings.Add(New Binding("Text", CoderBindingSource, "Code2"))
'
ComboBox1.DataSource = KindBindingSource
ComboBox1.DisplayMember = "kind"
ComboBox1.ValueMember = "Id"
'
ComboBox2.DataSource = CoderBindingSource
ComboBox2.DisplayMember = "Not1"
ComboBox2.ValueMember = "Id2"
'
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
الكود سهل الفهم و هو مكرر مئات المرات في صفحات المنتدى
بعض الاكواد البسيطة تابع
كود :
Private Sub CoderBindingSource_AddingNew(sender As Object, e As System.ComponentModel.AddingNewEventArgs) Handles CoderBindingSource.AddingNew
Try
Me.KindBindingSource.EndEdit()
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
و هو يستخدم عند اضافة كود جديد
نستخدمه للحصول على رقم المفتاح الرئيسي من الجدول Kind حتى اذا اضفنا سجل جديد في الجدول Coder يتم تعبئة المفتاح الثانوي بشكل صحيح
زر الاضافة الخاص باضافة الاصناف
كود :
Private Sub BindingNavigatorAddNewItem_Click(sender As System.Object, e As System.EventArgs) Handles BindingNavigatorAddNewItem.Click
Try
Me.KindTextBox.Focus()
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
زر الحذف العام (حذف التصنيف)
كود :
'زر الحذف العام
Private Sub BindingNavigatorDeleteItem_Click(sender As System.Object, e As System.EventArgs) Handles BindingNavigatorDeleteItem.Click
Try
If (Me.Validate() And Not (KindBindingSource Is Nothing)) Then
Dim DeletStud As DialogResult = MessageBox.Show("سيتم حذف القسم الحالي", "انتبه", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2, MessageBoxOptions.RightAlign)
If DeletStud = Windows.Forms.DialogResult.Yes Then
KindBindingSource.EndEdit()
CoderBindingSource.EndEdit()
'
KindBindingSource.RemoveCurrent()
End If
End If
Catch ex As Exception
MessageBox.Show(ex.Message, "زر الحذف العام", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, MessageBoxOptions.RightAlign)
End Try
End Sub
زر حذف كود
كود :
Private Sub BindingNavigatorDeleteItem1_Click(sender As System.Object, e As System.EventArgs) Handles BindingNavigatorDeleteItem1.Click
Try
If (Me.Validate() And Not (CoderBindingSource Is Nothing)) Then
Dim DeletStud As DialogResult = MessageBox.Show("سيتم حذف الكود الحالي", "انتبه", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2, MessageBoxOptions.RightAlign)
If DeletStud = Windows.Forms.DialogResult.Yes Then
CoderBindingSource.EndEdit()
'
CoderBindingSource.RemoveCurrent()
End If
End If
Catch ex As Exception
MessageBox.Show(ex.Message, "زر الحذف العام", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, MessageBoxOptions.RightAlign)
End Try
End Sub
زر اضافة كود
كود :
Private Sub BindingNavigatorAddNewItem1_Click(sender As Object, e As EventArgs) Handles BindingNavigatorAddNewItem1.Click
CoderBindingSource.AddNew()
Not1TextBox1.Focus()
End Sub
اجراء خاص بصندوق النص ColoredTextBox
كود :
Private Sub ComboBox2_SelectedIndexChanged(sender As System.Object, e As System.EventArgs) Handles ComboBox2.SelectedIndexChanged
If ComboBox2.Items.Count > 0 And Code1TextBox.Text.Length > 0 Then
Code1TextBox.GoHome()
End If
End Sub
وهو للذهاب الى السطر الاول من الكود المعروض في مربع النص عند تغير اسم الكود
كود :
Private Sub CoderBindingSource_CurrentChanged(sender As System.Object, e As System.EventArgs) Handles CoderBindingSource.CurrentChanged, CoderBindingSource.PositionChanged
If ComboBox2.Items.Count > 0 And Code1TextBox.Text.Length > 0 Then
Code1TextBox.GoHome()
End If
End Sub
End Class
نفس الكود السابق و لكن عند التنقل بين سجلات الاكواد عن طريق BindingNavigator
قمت ايضا باضافة مجموعة من الازار عند الضغط بزر الماوس الايمن فوق مربع نص الكود و هي مجموعة خدمية صغيرة يمكنك معرفة عملها وفق الكود التالي
كود :
#Region "ContextMenuStrip1"
Private Sub Con_Copy_Click(sender As System.Object, e As System.EventArgs) Handles Con_Copy.Click
Code1TextBox.Copy()
End Sub
Private Sub Con_Paste_Click(sender As System.Object, e As System.EventArgs) Handles Con_Paste.Click
Code1TextBox.Paste()
End Sub
Private Sub Con_Clear_Click(sender As System.Object, e As System.EventArgs) Handles Con_Clear.Click
Code1TextBox.Clear()
End Sub
Private Sub Con_Language_SelectedIndexChanged(sender As Object, e As System.EventArgs) Handles Con_Language.SelectedIndexChanged
With Con_Language.Text
Code1TextBox.SelectAll()
Select Case Con_Language.Text
Case Is = "VB"
Code1TextBox.Language = FastColoredTextBoxNS.Language.VB
Case Is = "CSharp"
Code1TextBox.Language = FastColoredTextBoxNS.Language.CSharp
Case Is = "HTML"
Code1TextBox.Language = FastColoredTextBoxNS.Language.HTML
Case Is = "PHP"
Code1TextBox.Language = FastColoredTextBoxNS.Language.PHP
Case Is = "SQL"
Code1TextBox.Language = FastColoredTextBoxNS.Language.SQL
Case Else
Code1TextBox.Language = FastColoredTextBoxNS.Language.Custom
End Select
Code1TextBox.UpperLowerCase()
Code1TextBox.GoHome()
End With
End Sub
Private Sub Con_SelAll_Click(sender As System.Object, e As System.EventArgs) Handles Con_SelAll.Click
Code1TextBox.SelectAll()
End Sub
#End Region
كما ترى وضائف بسيطة نسخ و لصق و مسح و تغير لغة العرض في مربع النص
الان ناتي الى اهم كود وهو زر التخزين العام و هو كما يلي
كود :
'زر التخزين العام
Private Sub BtnSave_Click(sender As System.Object, e As System.EventArgs) Handles BtnSave.Click
Try
A = KindBindingSource.Position
B = CoderBindingSource.Position
'
saveCode()
Me.Validate()
'
KindBindingSource.EndEdit()
CoderBindingSource.EndEdit()
'
SaveData()
'
KindBindingSource.Position = A
CoderBindingSource.Position = B
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
الشرح
- في البداية نحتفظ بالموقع الحالي لكل من الاصناف و الكود
- استدعي اجراء خاص SaveCode ساشرح لما استخدمته
- Me.Validate و ما بعدها سوف تتسبب في انهاء اي وضع تحرير للبيانات و بالتالي القيم المدخلة هي التي سيتم اعتمادها
- SaveData هو اجراء الحفظ العام الخاص بنا و الذي قمنا بانشائه سابقا في كلاس الاتصال
- بعد عملية الحفظ نعود الى مواقع السجلات التي خزناها سابقا
الاجراء SaveCode قمت باستخدامه بعدما لاحظت ان الاداة ColoredTextBox و لسبب ما لا تحدث قيمة النص المعروض بداخلها الى القاعدة كما يجري مع مربع النص العادي
الكود الخاص بالاجراء SaveCode
كود :
Private Sub saveCode()
If Not String.IsNullOrEmpty(Code1TextBox.Text) Then
Dim RowCode As DataRowView
RowCode = CoderBindingSource.Current
RowCode("Code1") = Code1TextBox.Text
End If
End Sub
الكود بسيط وظيفته التقاط النص الى صف البيانات الحالي و تخزينه في المكان الموافق له (الحقل المطابق له في صف البيانات)
هذا كل ما لدينا للنموذج الاول Form1
يتبع....
اللهم لك الحمد كما ينبغي لجلال وجهك و عظيم سلطانك
في حل و ترحال
المشاركات : 531
المواضيع 4
الإنتساب : May 2016
السمعة :
55
الشكر: 694
تم شكره 535 مرات في 367 مشاركات
يا ابو ليلى،
ممتاز،
لكن تعديل لمعلومة بسيطة بالرغم أنني لم أدقق في كل الموضوع، ال SqlCleint يدعم CommandBuilder أيضاً.
المشاركات : 662
المواضيع 39
الإنتساب : Feb 2014
السمعة :
195
الشكر: 1474
تم شكره 1740 مرات في 622 مشاركات
كلامك صحصح اخي الوادي ساصحح المعلومة
تتوفر نسخة منه مع المزود SqlClient لكني لا افضل الحديث عنها الان لمحاذير ربما اذكرها لاحقا
اللهم لك الحمد كما ينبغي لجلال وجهك و عظيم سلطانك
في حل و ترحال
المشاركات : 662
المواضيع 39
الإنتساب : Feb 2014
السمعة :
195
الشكر: 1474
تم شكره 1740 مرات في 622 مشاركات
20-06-16, 06:26 AM
(آخر تعديل لهذه المشاركة : 20-06-16, 06:30 AM {2} بواسطة ابو ليلى.)
الان ناتي الى النموذج الثاني Form2 و هو قلب برنامجنا الذي سيقوم بحفظ صفحات الموقع لدينا
ساعرض مجموعة من الصور هنا مع شرحها المرفق بالتمسيات للعناصر الموجودة عليها و سندخل لاحقا بالشرح
الصور مقسمة على مجموعات كما تتضح في النموذج اعلاه
بعد التوكل على الله نبدأ
سنقوم باستيراد بعض مجالات الاسماء للتعامل معها خلال الكود
كود :
Imports System.Data.OleDb
Imports System.Net
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Text
Imports System.Runtime
Imports System.Runtime.InteropServices
و بعض المتغيرات في بداية النموذج سنعرف الحاجة منها من خلال الشرح
كود :
Public Class Form2
Dim A, B, C, D As Integer
Dim sourceString As String
Dim cookies As New CookieContainer()
Dim Dt As New DataTable
Dim PathStr As String
Dim result As String = String.Empty
حدث تحميل النموذج
كود :
Private Sub Form2_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Try
Me.KindBindingSource.DataSource = LoadDataSet()
Me.KindBindingSource.DataMember = "Kind"
'
Me.PostBindingSource.DataSource = KindBindingSource
Me.PostBindingSource.DataMember = "Kind_post"
'
Me.InfoBindingSource.DataSource = PostBindingSource
Me.InfoBindingSource.DataMember = "Kind_Info"
'
Me.AttachBindingSource.DataSource = InfoBindingSource
Me.AttachBindingSource.DataMember = "Info_Attach"
'
BindingFileds()
'
CreaeDt()
BindingNavigatorAddNewItem3.Enabled = False
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
تم شرحه سابقا عند التعامل مع النموذج Form1 مع زيادة طفيفة تتعلق بالجزئية CreaeDt
و هو اجراء بسيط لانشاء جدول سنحتاجه خلال العمل كما يلي
كود :
''' <summary>
''' اجراء بناء جدول خاص لتحميل المرفقات
''' </summary>
''' <remarks></remarks>
Private Sub CreaeDt()
Dt = New DataTable
Dt.Clear()
Dim Urlcol As New DataColumn("Urlcol", System.Type.GetType("System.String"))
Dim Namecol As New DataColumn("Namecol", System.Type.GetType("System.String"))
Dt.Columns.Add(Urlcol)
Dt.Columns.Add(Namecol)
End Sub
اجراء ربط الكائنات BindingFileds
كود :
'ربط الكائنات
Private Sub BindingFileds()
Try
Me.KindTextBox.DataBindings.Add(New Binding("Text", KindBindingSource, "Kind"))
Me.Not1TextBox.DataBindings.Add(New Binding("Text", KindBindingSource, "Not1"))
'
Me.ponameTextBox.DataBindings.Add(New Binding("Text", PostBindingSource, "poname"))
'
Me.InfonameTextBox.DataBindings.Add(New Binding("Text", InfoBindingSource, "Infoname"))
Me.UrlInfoTextBox.DataBindings.Add(New Binding("Text", InfoBindingSource, "UrlInfo"))
'
ComboBox1.DataSource = KindBindingSource
ComboBox1.DisplayMember = "kind"
ComboBox1.ValueMember = "Id"
'
ComboBox2.DataSource = PostBindingSource
ComboBox2.DisplayMember = "poname"
ComboBox2.ValueMember = "id_k"
'
ComboBox3.DataSource = InfoBindingSource
ComboBox3.DisplayMember = "Infoname"
ComboBox3.ValueMember = "IdInfo"
'
ListBox2.DataSource = AttachBindingSource
ListBox2.DisplayMember = "AttachName"
ListBox2.ValueMember = "AttachPath"
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
السيناريو المتبع في العمل سيكون كالتالي
بعد تحميل النموذج سنكون امام عمليات اضافة للاصناف و الفروع المشتقة منها و كذلك المقالات المشتقة من كل فرع
وهي عملية روتينية ناقشناها في البند السابق عند التعامل مع Form1
الجديد في الموضوع اننا سنكون امام صفحات HTML سنقوم بتخزينها في قاعدة البيانات من صفحة المستعرض مباشرة
و سنقوم ايضا بتخزين المرفقات المرتبطة بالمقال المعروض في المتصفح
طبعا التخزين ليس ضمن القاعدة بل في مجلد ضمن مسار البرنامج و اسمه AttachMents
بينما سيتم تخزين اسم الملف و مساره فقط في جدول في القاعدة
سيتم الدخول تلقائيا للموقع وفق اعدادات مسبقة انت تقوم بادخالها في نموذج خاص سنناقشه لاحقا
على اي حال لن اسهب كثيرا في الشرح هنا لانني ساقوم بالشرح مع كل خطوة تستلزم الشرح
ساضع الاكواد الخاصة بالادوات (الحذف و الحفظ و التعديل) بشكل عادي دون شرحها لاننا اصبحنا نعرف شرحها كما سبق
كود :
'زر اضافة نوع
Private Sub BindingNavigatorAddNewItem_Click(sender As System.Object, e As System.EventArgs) Handles BindingNavigatorAddNewItem.Click
Try
KindBindingSource.CancelEdit()
KindBindingSource.AddNew()
A = KindBindingSource.Position
KindTextBox.Focus()
Catch ex As OleDb.OleDbException
MsgBox(ex.ToString)
End Try
End Sub
كود :
'عند اضافة فرع
Private Sub InfoBindingSource_AddingNew(sender As Object, e As System.ComponentModel.AddingNewEventArgs) Handles InfoBindingSource.AddingNew
Try
KindBindingSource.EndEdit()
Catch ex As OleDb.OleDbException
MsgBox(ex.ToString)
End Try
End Sub
كود :
'زر اضافة فرع
Private Sub BindingNavigatorAddNewItem1_Click(sender As System.Object, e As System.EventArgs) Handles BindingNavigatorAddNewItem1.Click
Try
PostBindingSource.CancelEdit()
PostBindingSource.AddNew()
B = PostBindingSource.Position
ponameTextBox.Focus()
Catch ex As OleDb.OleDbException
MsgBox(ex.ToString)
End Try
End Sub
كود :
'عند اضافة مقال
Private Sub PostBindingSource_AddingNew(sender As Object, e As System.ComponentModel.AddingNewEventArgs) Handles PostBindingSource.AddingNew
Try
Me.KindBindingSource.EndEdit()
ponameTextBox.Focus()
Catch ex As OleDb.OleDbException
MsgBox(ex.ToString)
End Try
End Sub
كود :
'زر اضافة مقال
Private Sub BindingNavigatorAddNewItem3_Click(sender As System.Object, e As System.EventArgs) Handles BindingNavigatorAddNewItem3.Click
Try
If sourceString = String.Empty Then
MsgBox("لا يوجد مقالات محملة")
Exit Sub
End If
InfoBindingSource.CancelEdit()
InfoBindingSource.AddNew()
InfonameTextBox.Text = WebBrowser1.DocumentTitle
UrlInfoTextBox.Text = WebBrowser1.Url.AbsoluteUri
'
BindingNavigatorAddNewItem3.Enabled = False
'===================
Dim w1 As Integer = 0
Using Con
Dim sw1 As String = "Select Count(IdInfo) AS IdCount From Info Where UrlInfo Like '" & UrlInfoTextBox.Text & "' "
Dim MatchCommand As New OleDbCommand(sw1, Con)
If Con.ConnectionString = Nothing Then Con.ConnectionString = ConnectionString
Con.Open()
'
w1 = MatchCommand.ExecuteScalar
'
End Using
'
If w1 >= 1 Then
MsgBox(" هذه المقالة موجودة و متوفرة في القاعدة لن يتم حفظها ", MsgBoxStyle.Exclamation + MsgBoxStyle.MsgBoxRight + MsgBoxStyle.OkOnly, "تخزين المقال ")
InfoBindingSource.CancelEdit()
Exit Sub
End If
'==================
C = InfoBindingSource.Position
Catch ex As OleDb.OleDbException
MsgBox(ex.ToString)
End Try
End Sub
هذا الكود يحتاج القليل من الشرح وهو كالتالي
عمليا عند اضافة مقال انا اقوم باضافته من المتصفح
لذلك ساختبر اذا كان المتصفح فعلا يحتوي على معلومات للصفحة المعروضة ؟ ساشرحه في الاسفل مع اجراء تحميل الصفحة
اذا كان المتصفح يحوي فعلا على معلومات ساقوم عندها باضافة اسم الموضوع و رابطه ايضا الى جدول المقالات
من خلال الصفحة المعروضة في المتصفح
عملية الاضافة لن تتم هكذا و الا اصبح لدي كم كبير من الصفحات المتشابهة
لذا جعلت عملية الاضافة تتم بعد التاكد ان الصفحة الحالية ليست مخزنه لدي في القاعدة وفق رابط الصفحة
سانتقل الان الى حدث اكتمال تحميل الصفحة في المستعرض لارتباطه بالكود السابق
عند اكتمال تحميل الصفحة انا قمت بوضع كود يقوم بنسخ محتويات الصفحة الى المتغير النصي sourceString
وهو مشمول في الاجراء التالي
كود :
'اكتمال تحميل الصفحة
Private Sub WebBrowser1_DocumentCompleted(ByVal sender As System.Object, ByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) Handles WebBrowser1.DocumentCompleted
Try
sourceString = WebBrowser1.DocumentText
'
TxtUrl.Text = e.Url.ToString
ToolStripStatusLabel2.Text = " / " & WebBrowser1.DocumentTitle
Me.Text = WebBrowser1.DocumentTitle
CollectAttachments(ListBox1)
If sourceString = String.Empty Or sourceString.Length <= 14 Then
BindingNavigatorAddNewItem3.Enabled = False
Else
BindingNavigatorAddNewItem3.Enabled = True
Exit Sub
End If
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
اعرف انني سادخل في متاهة شرح لاننا سنتفرع من اجراء الى اخر و كلها ترتبط ببعضها لذا وجب عليك ان تركز معي و ان تعذرني ان فاتني شرح جزئية معينة.
كما تلاحظ انا قمت بنسخ محتويات الصفحة الى المتغير sourceString
مع بعض الاسطر التي تخص المستعرض مثل عرض اسم الموضوع الحالي في اسفل البرنامج و اعلاه
ثم اختبرت طول المتغير النصي لاقوم بتمكين عملية الاضافة من عدمها
للمعلومات ان طول النص الافتراضي للمستعرض هو 13 على ما اعتقد لانه يحتوي على Tag
<\HTML><HTML> فارغ بدون محتويات و يبدأ بالتوسع مع وجود معلومات فعلية داخله
الكود ايضا يحتوي على دالة اخرى CollectAttachments باختصار مهمتها جلب المرفقات عند اكتمال تحميل الصفحة ساشرحه عند وروده
نكمل باقي عمليات الاضافة
كود :
'عند اضافة مرفق
Private Sub AttachBindingSource_AddingNew(sender As Object, e As System.ComponentModel.AddingNewEventArgs) Handles AttachBindingSource.AddingNew
Try
InfoBindingSource.EndEdit()
Catch ex As OleDb.OleDbException
MsgBox(ex.ToString)
End Try
End Sub
اما عملية اضافة معلومات المرفقات الى جدول المرفقات في القاعدة فهي عملية متزامنه مع تحميل المرفق الى مجلد التخزين سناتي اليها لاحقا
عمليات الحذف سنؤجل الحديث عنها الان لانها عمليه طويله تجري باتجاهين القاعدة من جهة و الملفات المخزنة من جهة اخرى
نكمل مع باقي الاكواد
كود :
'تصفح
Private Sub BtnBrowse_Click(sender As System.Object, e As System.EventArgs) Handles BtnBrowse.Click
Try
If Not Regex.IsMatch(TxtUrl.Text, "^((ht|f)tp(s?)\:\/\/|~/|/)?([\w]+:\w+@)?([a-zA-Z]{1}([\w\-]+\.)+([\w]{2,5}))(:[\d]{1,5})?((/?\w+/)+|/?)(\w+\.[\w]{3,4})?((\?\w+=\w+)?(&\w+=\w+)*)?") Then
MessageBox.Show("موقع غير صحيح")
Exit Sub
Else
SiteLogin(TxtUrl.Text.Trim)
End If
Catch ex As Exception
Return
End Try
End Sub
الكود السابق للدخول الى صفحة المطلوبة وهو يختبر اولا ان الرابط المدخل هو تنسيق صحيح لروابط المواقع
ثم بعدها ينتقل الى دالة خاصة SiteLogin و هي لب العمل في البرنامج وهي تقوم بتسجيل الدخول الى الموقع ثم الانتقال الى الصفحة المطلوبة
كود :
Private Sub SiteLogin(ByVal Url As String)
'رابط الدخول من الاعدادت
Dim LoginUrl As String = My.Settings.LoginUrl
'حاوية الكوكيز
Dim cookieContainer As New CookieContainer()
'انشاء طلب للدخول
Dim LoginRequest As HttpWebRequest = DirectCast(WebRequest.Create(LoginUrl), HttpWebRequest)
LoginRequest.CookieContainer = New CookieContainer
LoginRequest.ContentType = "application/x-www-form-urlencoded"
LoginRequest.Method = "POST"
LoginRequest.KeepAlive = True
LoginRequest.AllowAutoRedirect = True
LoginRequest.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1"
'البارمترات من الاعدادت
Dim formParams As String = My.Settings.Parmms
Dim bytes As Byte() = Encoding.UTF8.GetBytes(formParams)
'كتابة البيانات
LoginRequest.ContentLength = bytes.Length
Using os As Stream = LoginRequest.GetRequestStream()
os.Write(bytes, 0, bytes.Length)
End Using
'تحصيل الكوكيز من الموقع و تخزينه في الحاوية
cookieContainer = GetUriCookieContainer(New Uri(LoginUrl))
'جعل حاوية الطلب تحتضن الكوكيز
LoginRequest.CookieContainer = cookieContainer
'حاوية عامة للكوكيز على مستوى كل الطلبات
cookies = New CookieContainer
cookies = cookieContainer
'استقبال الرد و تخزين الكوكيز
Dim cookdisc As String
Using httpWebResponse = DirectCast(LoginRequest.GetResponse(), HttpWebResponse)
cookies.Add(httpWebResponse.Cookies)
For Each cook As Cookie In httpWebResponse.Cookies
result += cook.Name + "=" + cook.Value & ";"
cookdisc = cook.Name + "=" + cook.Value & ";"
InternetSetCookie(Url, cookdisc, cook.Expires)
Next
End Using
WebBrowser1.Navigate(Url)
End Sub
الشرح
- متغير نصي يحمل رابط صفحة الدخول من الاعدادات
- انشاء حاوية كوكيز تقوم باحتضان الكوكيز اثناء عملية الطلب و الرد
- انشاء طلب مع اعطائه رابط صفحة الدخول
- تفريغ حاوية الكوكيز الخاصة بالطلب
- بعض المعلومات المطلوب ارسالها مع الطلب و هي تعلميات قياسية خاصة بصفحات الويب يمكنك ايجاد هذه المعلومات عند فتح مصدر اي صفحة ويب و هي كثيرة ليس المكان لشرحها
- طريقة الطلب هي ادخال معلومات Post
- المحافظة على الطلب حي يرزق مع تفعيل الارتداد و اعادة التوجيه وفق رد الملقم
- معلومات قياسية اخرى تتعلق بالمتصفحات يمكنك الاستغناء عنها في بعض المواقع UserAgent
- FormsParms متغير نصي يحمل معلومات الادخال الضرورية في الموقع و ناخذها من الاعدادات
- مصفوفة من النوع Byte ستحمل بيانات الدخول وفق الترميز Utf8 الى الموقع
- بدأ عملية كتابة بيانات المصفوفة في الموقع
- GetUriCookieContainer هي دالة خاصة ستقوم بجلب الكوكيز من الموقع بعد استقبال رد المصادقة على الدخول و هي تستخدم مكاتب API .
- ملئ حاوية الكوكيز الخاصة بالطلب بالكوكيز التي حصلنا عليها وفق الدالة السابقة و كذلك ملئ الحاوية العامة التي على مستوى البرنامج بالكوكيز اللازمة cookies
- الاكواد اللاحقة يمكنك الاستغناء عنها اذ انني احببت ابقائها لتعرف طريقة الحصول على كل قيم الكوكيز و تستفيد منها كأن تخزنها او تعرضها؟
- اخيرا التوجه الى الصفحة المطلوبة بعد ان تمت المصادقة على دخولنا
طبعا هناك سطر مسؤول عن توريث الكوكيز للمستعرض و هو السطر التالي
كود :
InternetSetCookie(Url, cookdisc, cook.Expires)
كود :
''' <summary>
''' اجراء استخدمه عند الحاجة و هو للحصول على الكوكيز من الرابط
''' </summary>
''' <param name="uri">الرابط</param>
''' <returns>يعود بحاوية الكوكيز</returns>
''' <remarks>APi الدالة تستخدم مكاتب</remarks>
Public Shared Function GetUriCookieContainer(uri As Uri) As CookieContainer
Dim cookies As CookieContainer = Nothing
Dim datasize As Integer = 8192 * 16
Dim cookieData As New StringBuilder(datasize)
'
If Not InternetGetCookieEx(uri.ToString(), Nothing, cookieData, datasize, InternetCookieHttponly, IntPtr.Zero) Then
If datasize < 0 Then
Return Nothing
End If
cookieData = New StringBuilder(datasize)
If Not InternetGetCookieEx(uri.ToString(), Nothing, cookieData, datasize, InternetCookieHttponly, IntPtr.Zero) Then
Return Nothing
End If
End If
If cookieData.Length > 0 Then
cookies = New CookieContainer()
cookies.SetCookies(uri, cookieData.ToString().Replace(";"c, ","c))
End If
Return cookies
الكود السابق يسيتخدم مكاتب API ساضعها هنا
كود :
#Region "Get Set Cookies By APi"
<DllImport("wininet.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function InternetSetCookie(lpszUrlName As String, lpszCookieName As String, lpszCookieData As String) As Boolean
End Function
Private Const InternetCookieHttponly As Int32 = &H2000
<DllImport("wininet.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Private Shared Function InternetGetCookieEx(pchURL As String, pchCookieName As String, pchCookieData As StringBuilder, ByRef pcchCookieData As UInteger, dwFlags As Integer, lpReserved As IntPtr) As Boolean
End Function
#End Region
بعد دخولنا و عرض الصفحة اصبحنا الان امام صفحة معروضه في المتصفح
سنقوم الان بالتقاط المرفقات وفق الاجراء التالي
كود :
''' <summary>
''' اجراء تحصيل المرفقات من الصفحة و تجميعها في اللست بوكس
''' </summary>
''' <param name="list"></param>
''' <remarks></remarks>
Private Sub CollectAttachments(ByVal list As ListBox)
Try
Dt.Rows.Clear()
ProgressBar1.Value = 0
If (WebBrowser1.Document IsNot Nothing) Then
Dim Elems As HtmlElementCollection
Dim WebOC As WebBrowser = WebBrowser1
'
'
Elems = WebOC.Document.GetElementsByTagName("a")
For Each elem As HtmlElement In Elems
Dim NameStr As String = elem.GetAttribute("href")
If ((NameStr IsNot Nothing) And (NameStr.Length <> 0)) Then
'(.zip/.rar/.pdf) This Only For CodeProject Now
If NameStr.ToLower().Contains("attachment.php?") Or
NameStr.ToLower().Contains(".zip") Or
NameStr.ToLower().Contains(".rar") Or
NameStr.ToLower().Contains(".doc") Or
NameStr.ToLower().Contains(".docx") Or
NameStr.ToLower().Contains(".pdf") Then
Dim siteUri As New Uri(NameStr)
Dim ContentStr As String = elem.InnerText
'===========
If siteUri.AbsoluteUri.LastIndexOf("/") And Not (NameStr.ToLower().Contains("attachment.php?")) Then
ContentStr = siteUri.AbsoluteUri.Substring(siteUri.AbsoluteUri.LastIndexOf("/") + 1)
End If
Dim row As DataRow = Dt.NewRow
row("Urlcol") = siteUri
row("Namecol") = ContentStr
Dt.Rows.Add(row)
'
list.DataSource = Dt
list.DisplayMember = "Namecol"
list.ValueMember = "Urlcol"
End If
End If
Next
End If
Catch ex As WebException
MsgBox(ex.ToString)
End Try
End Sub
و هو اجراء عادي يستخدم المكتبة RegularExpressions للبحث في مستند HTML عن العناصر التي تحتوي على المرفقات و من ثم الحصول على اسم المرفق ان امكن ؟؟؟؟ و كذلك الرابط
و بعدها ندخل اسم المرفق و رابطه في جدول خاص لنستخدمه لاحقا.
اسم المرفق؟؟؟؟ العملية كثيرا شاقة مع اختلاف بنية المواقع و كثرة استخدام السكربتات من كل شكل و لون و الكثير الكثير جعلني اعيد صياغة الكود عشرات المرات هنا و مازالت المفاجئات تتكرر
بعد تعبئة الجدول سنقوم باضافة عناصره الى ListBox1 ليصبح لدينا شكل سهل و بسيط للتعامل مع الملفات و تحميلها فقط من خلال تحديدها من ListBox1 و من ثم زر Download .
ماذا بعد ذلك
الان سندخل الى كود اضافة المرفقات في القاعدة و تحميل الملف و تخزينه في مجلد التخزين
يتبع.....
اللهم لك الحمد كما ينبغي لجلال وجهك و عظيم سلطانك
في حل و ترحال
المشاركات : 662
المواضيع 39
الإنتساب : Feb 2014
السمعة :
195
الشكر: 1474
تم شكره 1740 مرات في 622 مشاركات
21-06-16, 02:49 AM
(آخر تعديل لهذه المشاركة : 21-06-16, 03:41 AM {2} بواسطة ابو ليلى.)
السلام عليكم و رحمة الله و بركاته
كنا قد توقفنا عند الحصول على المرفقات و خزنا اسماء المرفقات و روابطها في جدول خاص ثم قمنا بعرض اسم المرفق
في ListBox1 تمهيدا لتحميلها حسب الرغبة
نحن الان نمتلك رابط المرفق و اسمه , كما نملك ايضا حاوية الكوكيز و هي معبئة بالكوكيز لارسالها مع كل طلب دون ان نتعب
انفسنا بالحصول عليه مرة اخرى
كل ما تبقى هو انشاء طلب وفق رابط الملف و ارسال الكوكيز معه ثم استقبال رد الملقم و تخزين طول الملف (حجمه) في متغير رقمي
و من ثم انشاء كائن MemoryStream للتعامل مع البيانات القادمة و بعدها كتابة هذه البيانات الى القرص الصلب و تخزينها بالاسم الذي لدينا
اقصد هنا اسم الملف المعروض في ListBox1
قبل ان ابدأ بعملية تخزين الملف ساقوم بالبحث عن الملف في مكانين
المكان الاول : هو مجلد التخزين فاذا توفر الملف فلن نحمله مرة اخرى.
المكان الثاني:قاعدة البيانات و تحديدا جدول المرفقات فكما تعلم نحن نضيف اسم الملف و مساره الى القاعدة مع كل عملية تحميل
البحث في جدول المرفقات سيتم وفق اسم الملف و رقم المقالة.
اي اننا سنبحث عن الملف المرتبط بالمقالة الحالية
كما اننا سنبحث في جدول المقالات ايضا عن المقالة الحالية المعروضة في المستعرض وفق رابط المقال
سنجري مجموعة من المقارنات لكي لا نحمل اسم مرفق لا يرتبط بالمقالة الحالية
اذ اننا سنحاول قدر الامكان تحميل المرفق المرتبط مع المقالة فقط
بعد تحقق كل الشروط سنبدأ بتحميل الملف الى مجلد التخزين و اضافة رابط له في جدول المرفقات
في حدث زر التحميل DownloadAttachements_Click
كود :
'زر تحميل المرفقات
Private Sub DownloadAttachements_Click(sender As System.Object, e As System.EventArgs) Handles DownloadAttachements.Click
Try
If ListBox1.Items.Count > 0 Then
'البحث عن الملف في مجلد التخزين
Dim os As String() = System.IO.Directory.GetFiles(System.Windows.Forms.Application.StartupPath & "\AttachMents", ListBox1.Text)
'البحث عن الملف في القاعدة
Dim w1, w2 As Integer
'
Using Con
Dim rowinf As DataRowView
rowinf = InfoBindingSource.Current
Dim sw1 As String = "Select Count(AttachName) AS CountCn From Attaches Where (AttachName Like '" & ListBox1.Text & "') And (Id_inf Like '" & rowinf("IdInfo") & "') "
Dim sw2 As String = "Select Count(Infoname) As Cnt From Info Where UrlInfo Like '" & WebBrowser1.Url.AbsoluteUri & "' "
Dim MatchCommand As New OleDbCommand(sw1, Con)
Dim SelCommand As New OleDbCommand(sw2, Con)
If Con.ConnectionString = Nothing Then Con.ConnectionString = ConnectionString
Con.Open()
'
w1 = MatchCommand.ExecuteScalar
w2 = SelCommand.ExecuteScalar
'
End Using
'عمليات فحص و مطابقة
If w2 >= 1 And InfonameTextBox.Text <> WebBrowser1.DocumentTitle Then
Dim MsgResult As DialogResult = MessageBox.Show("المرفقات لا تتبع هذه المقالة , هناك مقالة لديك بهذا الاسم" & vbCrLf & "هل ما زلت تريد ربط المرفقات مع هذه المقالة", "انتبه", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2, MessageBoxOptions.RightAlign)
If MsgResult = Windows.Forms.DialogResult.Cancel Then Exit Sub
ElseIf w2 < 1 Then
MessageBox.Show("لا يوجد مقالة لديك بهذا الاسم" & vbCrLf & "قم بخزن المقالة لديك قبل تحميل المرفقات", "انتبه", MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1, MessageBoxOptions.RightAlign)
Exit Sub
End If
'
If w1 >= 1 Then
If os.Length >= 1 Then
MsgBox("المرفق المحدد موجود و مربوط مع المقال")
Exit Sub
Else
MsgBox("المسار متوفر في القاعدة بينماالمرفق غير متوفر في مجلد التخزين" & vbCrLf & "سيتم تحميل المرفق الى مجلد التخزين")
'هنا سنقوم بعملية تحديث تقتضي تحميل الملف فقط الى مجلد التخزين
If DownloadUrl(WebBrowser1.Url.AbsoluteUri) = False Then Exit Sub
End If
Else
If os.Length >= 1 Then
MsgBox("الملف متوفر فقط في مجلد التخزين " & vbCrLf & "دون وجود رابط له في القاعدة" & vbCrLf & "سيتم اضافة رابط له في القاعدة")
'هنا سنقوم بحذف المرفق من المجلد لعدم وجود ترابط مع القاعدة
'Dim filePaths() As String = Directory.GetFiles(System.Windows.Forms.Application.StartupPath & "\AttachMents", ListBox1.Text)
'For Each filePath As String In filePaths
' File.Delete(filePath)
'Next
Dim FILE_NAME As String = Application.StartupPath.ToString & "\AttachMents\" & ListBox1.Text
Me.Validate()
C = InfoBindingSource.Position
'
AttachBindingSource.AddNew()
'
D = AttachBindingSource.Position
'
Dim AttachRowview As DataRowView
AttachRowview = AttachBindingSource.Current
AttachRowview("AttachName") = ListBox1.Text
AttachRowview("AttachPath") = FILE_NAME
'
AttachBindingSource.EndEdit()
'
SaveData()
'
InfoBindingSource.Position = C
AttachBindingSource.Position = D
Else
'هنا نستخدم العملية كاملة لاضافة الملف الى القاعدة و مجلد التخزين
If DownloadUrl(WebBrowser1.Url.AbsoluteUri) = False Then Exit Sub
'
Me.Validate()
C = InfoBindingSource.Position
'
AttachBindingSource.AddNew()
'
D = AttachBindingSource.Position
'
Dim AttachRowview As DataRowView
AttachRowview = AttachBindingSource.Current
AttachRowview("AttachName") = ListBox1.Text
AttachRowview("AttachPath") = PathStr
'
AttachBindingSource.EndEdit()
'
SaveData()
'
InfoBindingSource.Position = C
AttachBindingSource.Position = D
End If
End If
'
Else
MsgBox("لا يوجد مرفقات او لم يتم الحصول على المرفق")
Exit Sub
End If
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
عمليات فحص و مطابقة تستخدم اكواد بسيطة الاغلب يعرفها
جزء منها يهتم بالتعامل مع الملفات و الجزء الاخر جمل استعلام عادية للبحث في قاعدة البيانات
كنت افضل استخدام Linq To Dataset كون البيانات متوفرة لدينا في Dataset لكنني لم استخدم استعلامات Ling
منذ بداية الموضوع لذا سنبقى على جمل Sql .
الجزء المسؤول عن التحميل هو الدالة DownloadUrl
وهي دالة عادية تلخص الحديث السابق المختص بارسال الطلب و استقبال الرد و هي كما يلي
كود :
''' <summary>
''' اجراء تحميل المرفقات
''' </summary>
''' <param name="Url">الرابط</param>
''' <returns></returns>
''' <remarks></remarks>
Private Function DownloadUrl(ByVal Url As String) As Boolean
Try
Url = ListBox1.SelectedValue.ToString
If Url.Contains("youtube") Then
MsgBox("لا يمكن التحميل من هذا الموقع")
Return False
Exit Function
End If
Dim DownloadRequest As HttpWebRequest = DirectCast(WebRequest.Create(Url), HttpWebRequest)
DownloadRequest.CookieContainer = New CookieContainer()
DownloadRequest.CookieContainer = cookies
DownloadRequest.KeepAlive = True
'
Dim DownloadResponse As WebResponse = DownloadRequest.GetResponse()
'
Dim intLen As Integer = CInt(DownloadResponse.ContentLength)
ProgressBar1.Maximum = intLen
Label1.Text = "0 / " & GetFileSize2(intLen)
System.Windows.Forms.Application.DoEvents()
'
If intLen < 0 Then
MsgBox("اما ان الملف مفقود او يوجد مشكلة في الاتصال")
DownloadResponse.Close()
Return False
Exit Function
End If
'
Dim memStream As MemoryStream
Using stmResponse As IO.Stream = DownloadResponse.GetResponseStream()
Dim buffer = New Byte(intLen) {}
Dim bytesRead As Integer
'
Do
bytesRead += stmResponse.Read(buffer, bytesRead, intLen - bytesRead)
ProgressBar1.Value = bytesRead
Label1.Text = GetFileSize2(CInt(ProgressBar1.Value.ToString())) + " / " + GetFileSize2(intLen)
ProgressBar1.Refresh()
System.Windows.Forms.Application.DoEvents()
Loop Until bytesRead = intLen
memStream = New MemoryStream(buffer, False)
'
PathStr = System.Windows.Forms.Application.StartupPath & "\AttachMents\" & ListBox1.Text
'
My.Computer.FileSystem.WriteAllBytes(PathStr, buffer, False)
'
ProgressBar1.Value = 0
Label1.Text = "0/0"
DownloadResponse.Close()
memStream.Close()
End Using
Return True
'====================
'هذه الطريقة تستخدم عندما يكون رابط الملف طبيعي اي لا يحتوي على اشاراة استفهام
' Dim FileName As String
'If Url.EndsWith("/") Then
' Url = Url.Substring(0, Url.Length - 1)
'End If
'FileName = Url.Substring(Url.LastIndexOf("/"c) + 1)
'MsgBox(FileName)
'' Exit Function
'PathStr = System.Windows.Forms.Application.StartupPath & "\AttachMents\" & FileName
''========================
'DownloadRequest.Abort()
'DownloadResponse.Close()
'
Catch ex As WebException
If WebExceptionStatus.Timeout = WebExceptionStatus.Timeout Then
MsgBox("لا يوجد رد من الملقم قد يكون الملف مفقود")
Return False
Exit Function
Else
MsgBox(ex.ToString)
End If
End Try
Return True
End Function
ارجع للشرح في الاعلى لتعرف سيناريو العمل
تبقى لدينا عدة مشاكل ليس المكان لعدها فقط ما واجهني منها
بعض الروابط في الموقع لا تعمل اما ان الملفات مفقودة او ان رابط التحميل لا يشير صراحة الى الملف المطلوب
فمثلا يوجد بعض الملفات في الموقع و خصوصا ملفات الرار (rar) يشير رابطها الى موقع YouTube و يقودني الى فلم اطفال (صديقتي...؟؟)
على اي حال متى ما كان رابط التحميل صحيح و الملف موجود ستكون قادر على التحميل
يحصل احيانا ان الملقم لا يرسل لك طول الملف بشكل حقيقي و هذا يؤدي الى اجهاض عملية التحميل اذ ان معرفة طول الملف الحقيقي
اساسا غير مدعومة اثناء رد الملقم
بعد تحميل المرفق الى مجلد التخزين سيتم اضافة رابط له في جدول المرفقات في القاعدة كما قلت سابقا
هناك مشكلة بسيطة قد تحدث عند نقل البرنامج من كان الى مكان و هي ان مسار الملف المخزن في القاعدة سيكون غير صحيح
لذا اعتمدت طريقة اخرى لفتح الملف تعتمد على البحث عن الملف في مجلد التخزين ستراها لاحقا عند الحديث عنها
بعد ان حملت الملف و تم ربطه مع القاعدة سيتم عرض كل المرفقات المرتبطة مع المقالة المخزنة في عنصر ListBox2 تحت المقالة
و بامكانك النقر مرتين على اي عنصر داخلها ليتم فتح المرفق و فق المسا المخزن في القاعدة او عبر اسمه
كود الحدث كما في الاسفل
كود :
'فتح المرفقات من الجهاز
Private Sub ListBox2_MouseDoubleClick(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles ListBox2.MouseDoubleClick
Try
If ListBox2.Items.Count > 0 Then
'البحث عن الملف في مجلد التخزين
Dim os As String() = System.IO.Directory.GetFiles(System.Windows.Forms.Application.StartupPath & "\AttachMents\", ListBox2.Text)
If os.Length >= 1 Then
Dim filePath As String = String.Empty
For Each filePath In os
If filePath <> ListBox2.SelectedValue.ToString Then
MsgBox("المسار المحفوظ في القاعدة مختلف عن مسار الملف" & vbCrLf & "ربما تم تغير مسار مجلد التخزين")
'فتح الملف وفق مسار البحث و المطابقة
Process.Start(filePath)
Exit Sub
End If
Next
'فتح الملف وفق مسار القاعدة
Process.Start(ListBox2.SelectedValue.ToString)
Else
'هنا سنقوم بحذف الادخال من القاعدة القاعدة
Dim DeleteFile As DialogResult = MessageBox.Show("رابط الملف متوفر فقط في القاعدة,دون وجود الملف في مجلد التخزين" & vbCrLf & "هل تريد حذف الادخال من القاعدة", "انتبه", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2, MessageBoxOptions.RightAlign)
If DeleteFile = Windows.Forms.DialogResult.Yes Then
C = InfoBindingSource.Position
AttachBindingSource.RemoveCurrent()
SaveData()
InfoBindingSource.Position = C
End If
End If
End If
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
اذا دققت في الكود السابق ستجد فيه اسطر اضافية باختصار عملها
اذا لم نجد الملف متوفر في مجلد التخزين عندها سنقوم بحذف الارتباط الموجود له في القاعدة لكي لا يشير هذا السطر الى ملف غير موجود
كذلك انا قمت بعمل دالة بسيطة تقوم باعطاء الحجم لكل الملف وفق الطريقة القياسية (KB,MB,GB,TR) اذ ليس معقولا ان تقوم بعرض حجم الملف
كارقام بل كحجوم و هو افضل
في الحقية يوجد دالتين الاولى للملفات المخزنة لدينا في القرص
و الثانية للملفات التي نريد تحميلها
الدالة الاولى
كود :
'هذا من اجل الملفات المخزنة في مجلد التخزين
Public Function GetFileSize(ByVal TheFile As String) As String
If TheFile.Length = 0 Then Return ""
If Not System.IO.File.Exists(TheFile) Then Return ""
'---
Dim TheSize As ULong = My.Computer.FileSystem.GetFileInfo(TheFile).Length
Dim SizeType As String = ""
'---
Try
Select Case TheSize
Case Is >= 1099511627776
DoubleBytes = CDbl(TheSize / 1099511627776) 'TB
Return FormatNumber(DoubleBytes, 2) & " TB"
Case 1073741824 To 1099511627775
DoubleBytes = CDbl(TheSize / 1073741824) 'GB
Return FormatNumber(DoubleBytes, 2) & " GB"
Case 1048576 To 1073741823
DoubleBytes = CDbl(TheSize / 1048576) 'MB
Return FormatNumber(DoubleBytes, 2) & " MB"
Case 1024 To 1048575
DoubleBytes = CDbl(TheSize / 1024) 'KB
Return FormatNumber(DoubleBytes, 2) & " KB"
Case 0 To 1023
DoubleBytes = TheSize ' bytes
Return FormatNumber(DoubleBytes, 2) & " bytes"
Case Else
Return ""
End Select
Catch
Return ""
End Try
End Function
الدالة الثانية
كود :
'هذا من اجل الملفات التي يتم تحميلها
Public Function GetFileSize2(ByVal Flength As Integer) As String
If Flength = 0 Then Return ""
'---
Dim TheSize As ULong = Flength
Dim SizeType As String = ""
'---
Try
Select Case TheSize
Case Is >= 1099511627776
DoubleBytes = CDbl(TheSize / 1099511627776) 'TB
Return FormatNumber(DoubleBytes, 2) & " TB"
Case 1073741824 To 1099511627775
DoubleBytes = CDbl(TheSize / 1073741824) 'GB
Return FormatNumber(DoubleBytes, 2) & " GB"
Case 1048576 To 1073741823
DoubleBytes = CDbl(TheSize / 1048576) 'MB
Return FormatNumber(DoubleBytes, 2) & " MB"
Case 1024 To 1048575
DoubleBytes = CDbl(TheSize / 1024) 'KB
Return FormatNumber(DoubleBytes, 2) & " KB"
Case 0 To 1023
DoubleBytes = TheSize ' bytes
Return FormatNumber(DoubleBytes, 2) & " bytes"
Case Else
Return ""
End Select
Catch
Return ""
End Try
End Function
الدالتين متشابهتين فقط في الاولى يكون البارميتر المرسل هو مسار الملف بينما في الثانية يكون طول الملف هذا كل شيئ
وهذا في حدث تحديد الملف ليتم عرض الحجم وفق التحديد
كود :
Private Sub ListBox2_SelectedIndexChanged(sender As System.Object, e As System.EventArgs) Handles ListBox2.SelectedIndexChanged
Try
If ListBox2.Items.Count > 0 Then
Dim FilePath As String = System.Windows.Forms.Application.StartupPath & "\AttachMents\" & ListBox2.Text
If Not IsNothing(FilePath) Then
Label3.Text = (GetFileSize(FilePath))
End If
Else
Label3.Text = 0
End If
Catch ex As Exception
End Try
End Sub
طبعا انا وضعت Timer بسيط لتعطيل زر تحميل المرفقات عند عدم وجود ملفات للتحميل
كود :
'تعطيل زر التحميل لعدم وجود مرفقات
Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) Handles Timer1.Tick
If ListBox1.Items.Count > 0 Then
DownloadAttachements.Enabled = True
Else
DownloadAttachements.Enabled = False
End If
End Sub
الان و بعد ان فهمنا طبيعة العمل نعود الى الازرار القياسية و تحديدا زر حفظ المقال اي حفظ صفحة HTML داخل جدول المقالات
العملية مقسومة الى شقين الاول حفظ للحقول العادية في الجدول و الثاني حفظ لبنية HTML داخل الحقل Content
كما في السابق انا لن اخزن المقال مرتين في الجدول لذا ساقوم بالبحث عن اسم المقال بدليل رابط المقال فاذا وجد لدينا سنجهض عملية حفظ
محتوى الصفحة فقط و ليس كل اجراء الحفظ بمعنى اخر ربما لسبب ما انت تريد تسمية المقال باسم اخر غير الاسم الافتراضي لذا ستكون قادرا على ذلك بينما عملية تحديث محتويات الصفحة فلن تكون قادرا على ذلك اذ ان الصفحة محفوظة لديك في القاعدة
الشق الاول عملية الحفظ (حدث زر الحفظ العام) اذ ان الحفظ يتم على مستوى كل الجداول (فقط الاجزاء التي تحتوي على تحديثات) هي فقط من سيتم تحديثها
كود :
'زر الحفظ
Private Sub BtnSave_Click(sender As System.Object, e As System.EventArgs) Handles BtnSave.Click
Try
A = KindBindingSource.Position
B = PostBindingSource.Position
C = InfoBindingSource.Position
'
Me.Validate()
'
KindBindingSource.EndEdit()
PostBindingSource.EndEdit()
InfoBindingSource.EndEdit()
'
SaveData()
InfoBindingSource.Position = C
'
SaveHtml(sourceString)
sourceString = ""
'
KindBindingSource.Position = A
PostBindingSource.Position = B
BindingNavigatorAddNewItem3.Enabled = False
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
هل تذكر الاجراء SaveData في الكلاس Connect هو من سيقوم بعملية الحفظ
لاحظ وجود الاجراء SaveHtml و هو الشق الثاني من عملية الحفظ
كود :
''' <summary>
''' اجراء حفظ المقال
''' </summary>
''' <param name="HtmlText">"محتويات الصفحة"</param>
''' <remarks></remarks>
Private Sub SaveHtml(ByVal HtmlText As String)
Dim w2 As Integer
Using Con
Dim sw2 As String = "Select IdInfo From Info Where UrlInfo Like '" & UrlInfoTextBox.Text & "' "
Dim SelCommand As New OleDbCommand(sw2, Con)
If Con.ConnectionString = Nothing Then Con.ConnectionString = ConnectionString
Con.Open()
'
w2 = SelCommand.ExecuteScalar
'
End Using
'
If w2 > 0 Then
Dim UpdateText As String = "Update Info Set Content = @Content Where IdInfo Like '" & w2 & "' "
Try
If UrlInfoTextBox.Text <> WebBrowser1.Url.AbsoluteUri Then
MsgBox(" الرابط الجديد مختلف عن الرابط المخزن ,قد تكون اخترت مقالة مختلفة " & vbCrLf & "لن يتم تحديث محتويات المقالة", MsgBoxStyle.Exclamation + MsgBoxStyle.MsgBoxRight + MsgBoxStyle.OkOnly, "تخزين المقال ")
Exit Sub
End If
Using Con
If Con.ConnectionString = Nothing Then Con.ConnectionString = ConnectionString
If HtmlText <> "" And HtmlText.Length > 13 Then
Dim UpdateCommand1 As New OleDbCommand(UpdateText, Con)
If Con.State = ConnectionState.Open Then Con.Close()
Con.Open()
'
Dim CommandParameter1 As New OleDbParameter("@Content", OleDb.OleDbType.LongVarChar)
UpdateCommand1.Parameters.Add(CommandParameter1)
CommandParameter1.Value = HtmlText
Dim S As Integer = UpdateCommand1.ExecuteNonQuery
If S > 0 Then
' MsgBox("نجحت عمليت تحديث المقالة")
Else
MsgBox("فشلت عملية التحديث")
End If
Else
MsgBox("لم يتم الحصول على محتويات الصفحة بعد")
End If
End Using
Catch ex As Exception
MsgBox(ex.ToString)
Finally
Con.Close()
End Try
Else
MsgBox("لم يتم حفظ عنوان المقال حاول حفظه ثانية")
End If
End Sub
اذا دققت في الكود السابق ككل ستجد انني اقوم اولا بتخزين حقول المقالة بدون ارسال محتويات الصفحة و بعدها انا اقوم بتخزين محتويات الصفحة بعد التاكد من من وجودها بدليل رابط المقال
طبعا اذا حاولت تخزين محتويات الصفحة الحالية المعروضة في المستعرض فانه سيقوم بعملية مطابقة رابط الصفحة في المتصفح مع الرابط المعروض حاليا في حقل المقالات فاذا وجد اختلاف سيعطيك رسالة مفادها ان المقالة الحالية التي تريد تخزينها او تحديث محتوياتها مختلفة عن المقالة المعروضة
هذه السلسلة من اجراءات الامان لكي لا نخزن محتويات صفحة لا تمت بصلة لعنوان المقالة
انتهينا من عمليات الحفظ
الان بعض الاجرائيات البسيط مثل عرض شريط حالة التحميل الخاص بالمستعرض كما يلي
كود :
'حالة تحميل الصفحة
Private Sub WebBrowser1_ProgressChanged(sender As Object, e As System.Windows.Forms.WebBrowserProgressChangedEventArgs) Handles WebBrowser1.ProgressChanged
Try
Dim CurProg As Double
Dim MaxProg As Double
CurProg = e.CurrentProgress
MaxProg = e.MaximumProgress
ToolStripProgressBar1.Value = (CurProg / MaxProg) * 100
Catch ex As Exception
'
End Try
End Sub
كود :
'معلومات حالة التحميل
Private Sub WebBrowser1_StatusTextChanged(sender As Object, e As System.EventArgs) Handles WebBrowser1.StatusTextChanged
ToolStripStatusLabel1.Text = WebBrowser1.StatusText
End Sub
اجراء الفلترة الخاص بزر الفلترة و البحث بحسب اسم المقال
كود :
'فلترة
Private Sub BtnSerch_Click(sender As System.Object, e As System.EventArgs) Handles BtnSerch.Click
If TxtSerch.Text.Trim <> "" Then
InfoBindingSource.Filter = "[Infoname] LIKE '%" & TxtSerch.Text & "%'"
End If
End Sub
زر مسح الفلتر
كود :
'مسح الفلتر
Private Sub ToolStripButton3_Click(sender As System.Object, e As System.EventArgs) Handles BtnRemoveFilter.Click
InfoBindingSource.RemoveFilter()
End Sub
زر الرجوع و الرئيسية و التحديث الخاصة بالمتصفح
كود :
'رجوع
Private Sub BtnBack_Click(sender As System.Object, e As System.EventArgs) Handles BtnBack.Click
WebBrowser1.GoBack()
End Sub
'الرئيسية
Private Sub BtnHome_Click(sender As System.Object, e As System.EventArgs) Handles BtnHome.Click
WebBrowser1.GoHome()
End Sub
'تحديث
Private Sub BtnRefresh_Click(sender As System.Object, e As System.EventArgs) Handles BtnRefresh.Click
If Not WebBrowser1.Url.Equals("about:blank") Then
WebBrowser1.Refresh()
End If
End Sub
صحيح هناك زر اضفته لاحقا للتراجع عن اضافة مقال
كود :
'زر تراجع عن
Private Sub BtnUndo_Click(sender As System.Object, e As System.EventArgs) Handles BtnUndo.Click
InfoBindingSource.CancelEdit()
End Sub
بعد ان قمنا بتخزين محتويات الصفحة في القاعدة اصبحنا قادرين الان على عرض المحتويات المخزنة في المستعرض
زر عرض المحتويات للمقالة الحالية
كود :
''' <summary>
''' تحميل الصفحة من القاعدة
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub BtnLoadContents_Click(sender As System.Object, e As System.EventArgs) Handles BtnLoadContents.Click
Dim RowView As DataRowView
RowView = InfoBindingSource.Current
Dim Sel1 As String = "Select Content From Info Where IdInfo Like '" & RowView("IdInfo") & "' "
Try
Using Con
If Con.ConnectionString = Nothing Then Con.ConnectionString = ConnectionString
Dim Command1 As New OleDbCommand(Sel1, Con)
If Con.State = ConnectionState.Open Then Con.Close()
Con.Open()
Dim Red1 As OleDbDataReader = Command1.ExecuteReader
While Red1.Read
If Red1.HasRows = True And Not Red1.IsDBNull(0) Then
sourceString = Red1("Content")
Else
MsgBox("لا يوجد محتويات محفوظة لهذه المقالة")
Exit Sub
End If
End While
If sourceString <> "" Then
WebBrowser1.DocumentText = sourceString
End If
End Using
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
بقيت لدينا ازرار الحذف التي اجلت طرحها للاخر اذ ان عملية الحذف متزامنة بين القاعدة و الملفات المخزنة (المرفقات)
نبدا اولا مع حذف مقال مع مرفقاته
كود :
'حذف مقال
Private Sub BindingNavigatorDeleteItem3_Click(sender As System.Object, e As System.EventArgs) Handles BindingNavigatorDeleteItem3.Click
Try
If (Me.Validate() And Not (InfoBindingSource Is Nothing)) Then
If InfoBindingSource.Count > 0 Then
Dim DeletStud As DialogResult = MessageBox.Show("سيتم حذف المقال الحالي", "انتبه", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2, MessageBoxOptions.RightAlign)
If DeletStud = Windows.Forms.DialogResult.Yes Then
'==========حذف الملفات المرفقة=======================
'البحث عن الملف في مجلد التخزين
If ListBox2.Items.Count > 0 Then
For i As Integer = 0 To ListBox2.Items.Count - 1
Dim RowV As DataRowView = ListBox2.Items(i)
Dim os As String() = System.IO.Directory.GetFiles(System.Windows.Forms.Application.StartupPath & "\AttachMents", RowV("AttachName"))
If os.Length >= 1 Then
Dim filePaths() As String = Directory.GetFiles(System.Windows.Forms.Application.StartupPath & "\AttachMents", RowV("AttachName"))
For Each filePath As String In filePaths
File.Delete(filePath)
Next
End If
AttachBindingSource.RemoveAt(i)
Next
End If
'حذف المقال
InfoBindingSource.EndEdit()
InfoBindingSource.RemoveCurrent()
End If
End If
End If
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
و من ثم حذف فرع كامل مع كل مقالاته و الملفات المخزنه مع كل مقال
انا جعلت عملية الحذف محمية بكلمة مرور لكي لا نتورط و نحذف كل شيئ الا بعد التأكد من ذلك
كود :
'حذف فرع
Private Sub BindingNavigatorDeleteItem1_Click_1(sender As System.Object, e As System.EventArgs) Handles BindingNavigatorDeleteItem1.Click
Try
If (Me.Validate() And Not (PostBindingSource Is Nothing)) Then
If PostBindingSource.Count > 0 Then
Dim DeletStud1 As DialogResult = MessageBox.Show("سيتم حذف الفرع بما يحتويه من مقالات" & vbCrLf & "عملية الحذف محمية", "انتبه", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2, MessageBoxOptions.RightAlign)
If DeletStud1 = Windows.Forms.DialogResult.Yes Then
Dim Message, Title, DefaultValue As String
Message = "ادخل كلمة المرور"
Title = "نموذج حماية"
DefaultValue = "ادخل كلمة المرور"
Dim ReturnValue As String = InputBox(Message, Title, DefaultValue)
If ReturnValue = "admin" Then
'====**********======
Dim DeletStud As DialogResult = MessageBox.Show("سيتم حذف الفرع بما يحتويه من مقالات", "انتبه", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2, MessageBoxOptions.RightAlign)
If DeletStud = Windows.Forms.DialogResult.Yes Then
'==========حذف الملفات المرفقة=======================
'البحث عن الملف في مجلد التخزين
If InfoBindingSource.Count > 0 Then
For p As Integer = 0 To InfoBindingSource.Count - 1
'
If AttachBindingSource.Count > 0 Then
For i As Integer = 0 To AttachBindingSource.Count - 1
Dim RowV As DataRowView = AttachBindingSource.Current
Dim os As String() = System.IO.Directory.GetFiles(System.Windows.Forms.Application.StartupPath & "\AttachMents", RowV("AttachName"))
If os.Length >= 1 Then
Dim filePaths() As String = Directory.GetFiles(System.Windows.Forms.Application.StartupPath & "\AttachMents", RowV("AttachName"))
For Each filePath As String In filePaths
File.Delete(filePath)
Next
End If
AttachBindingSource.RemoveAt(i)
Next
End If
'==============
' InfoBindingSource.RemoveAt(p)
Next
End If
'==========================
PostBindingSource.EndEdit()
InfoBindingSource.EndEdit()
'
PostBindingSource.RemoveCurrent()
End If
End If
Else
Exit Sub
End If
End If
End If
Catch ex As Exception
' MsgBox(ex.ToString)
End Try
End Sub
حذف تصنيف باكمله مع كل الفروع و المقالات و المرفقات المرتبطه به كذلك محمي بكلمة مرور
كود :
'حذف التصنيف
Private Sub BindingNavigatorDeleteItem_Click(sender As System.Object, e As System.EventArgs) Handles BindingNavigatorDeleteItem.Click
Try
If (Me.Validate() And Not (KindBindingSource Is Nothing)) Then
If KindBindingSource.Count > 0 Then
Dim DeletStud1 As DialogResult = MessageBox.Show("سيتم حذف التصنيف الحالي بكل ما يحتويه" & vbCrLf & "عملية الحذف محمية", "انتبه", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2, MessageBoxOptions.RightAlign)
If DeletStud1 = Windows.Forms.DialogResult.Yes Then
Dim Message, Title, DefaultValue As String
Message = "ادخل كلمة المرور"
Title = "نموذج حماية"
DefaultValue = "ادخل كلمة المرور"
Dim ReturnValue As String = InputBox(Message, Title, DefaultValue)
If ReturnValue = "admin" Then
'
Dim DeletStud As DialogResult = MessageBox.Show("سيتم حذف التصنيف الحالي بكل ما يحتويه", "انتبه", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2, MessageBoxOptions.RightAlign)
If DeletStud = Windows.Forms.DialogResult.Yes Then
KindBindingSource.EndEdit()
PostBindingSource.EndEdit()
InfoBindingSource.EndEdit()
'حذف كل الفروع
For i As Integer = 0 To PostBindingSource.Count - 1
DelAllPOsters()
Next
'حذف التصنيف
KindBindingSource.RemoveCurrent()
End If
End If
Else
Exit Sub
End If
End If
End If
Catch ex As Exception
MessageBox.Show(ex.Message, "زر الحذف العام", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, MessageBoxOptions.RightAlign)
End Try
End Sub
انا استخدمت اجراء لحذف كل الفروع بداخل التصنيف DelAllPOsters
كود :
'حذف فرع بشكل مباشر
Private Sub DelAllPOsters()
Try
If (Me.Validate() And Not (PostBindingSource Is Nothing)) Then
If PostBindingSource.Count > 0 Then
'==========حذف الملفات المرفقة=======================
'البحث عن الملف في مجلد التخزين
If InfoBindingSource.Count > 0 Then
For p As Integer = 0 To InfoBindingSource.Count - 1
'
If AttachBindingSource.Count > 0 Then
For i As Integer = 0 To AttachBindingSource.Count - 1
Dim RowV As DataRowView = AttachBindingSource.Current
Dim os As String() = System.IO.Directory.GetFiles(System.Windows.Forms.Application.StartupPath & "\AttachMents", RowV("AttachName"))
If os.Length >= 1 Then
Dim filePaths() As String = Directory.GetFiles(System.Windows.Forms.Application.StartupPath & "\AttachMents", RowV("AttachName"))
For Each filePath As String In filePaths
File.Delete(filePath)
Next
End If
AttachBindingSource.RemoveAt(i)
Next
End If
Next
End If
'==========================
PostBindingSource.EndEdit()
InfoBindingSource.EndEdit()
'
PostBindingSource.RemoveCurrent()
End If
End If
Catch ex As Exception
End Try
End Sub
و هو يشبه اجراء حذف الفروع السابق لكنه بدون حماية
لم يتبقى لدينا شيئ في هذه الواجهة على ما اعتقد
اكتملت كل الخيوط و الكمال لله وحده
بقي لدينا نموذج الاعدادات الخاص بالدخول للمواقع و كيفية التعامل معه مع شرح بسيط عن مصادر الصفحات الخاصة بالمواقع ان امكنني ذلك
ساعرض بعض الصور الاولية لنموذج الاعدادات SettingForm مع اختلاف المواقع التي نريد الدخول اليها
اريدك فقط ان تركز في جزئية بارمترات الدخول لتعرف بعدها ماذا تعني كل هذه الحقول التي في النموذج
عمليا كل موقع لديه صفحة للدخول اليه و في هذه الصفحة يكون هناك مربعات نصوص لادخال اسم المستخدم و كلمة المرور
وزر الدخول ايضا , ما نقوم به اثناء انشاء طلب و ارسال البارمترات معه هو عملية ادخال في هذه الحقول و من ثم ضغط زر الدخول
فاذا تمت المصادقة نكون قد حصلنا على مبتغانا
الازرار في اعلى النموذج هي اسماء الحقول في الموقع
اما مجموعة بيانات الدخول فهي المعلومات التي سندخلها
اسم المستخدم الخاص بك و كلمة المرور ورابط صفحة الدخول للموقع مع اجراء الدخول
انا قمت بتزويد النموذج ببعض الاجراءات و اسماء الحقول الخاصة بالدخول لتجدها جاهزة
فمثلا موقعنا هنا يستخدم username لاسم المستخدم,password لكلمة المرور
و يعتمد الطريقة action=do_login
بينما في مواقع اخرى قد تختلف الطريقة وفق تصميم الموقع و اسماء الحقول
هذا النموذج مرتبط مع جدول LoginsTb في قاعدة البيانات حتى تكون قادرا على تخزين مواقعك و طريقة دخولك لها
و هو ايضا مرتبط باعدادات البرنامج حتى تحفظ اعدادتك للموقع الافتراضي الذي تريد الابحار في صفحاته
فقط اختر موقعك ثم (تخزين كاعداد افتراضي) و اترك الباقي على البرنامج فهو سيتكفل بارسال المعلومات من الاعدادت
اما في جانب الكود فليس هناك الكثير للتحدث عنه فهو كما سابق الاكواد التي شرحناها
كود :
Public Class SettingForm
Dim C As Integer
حدث تحميل النموذج
كود :
Private Sub SettingForm_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
LoadLoginDataSet()
Me.LoginBindingSource.DataSource = LoadLoginDataSet()
Me.LoginBindingSource.DataMember = "LoginsTb"
BindingFileds()
TextBox1.Text = My.Settings.sitname
End Sub
زر تخزين الاعدادت الافتراضية
كود :
Private Sub BtnSaveSetting_Click(sender As System.Object, e As System.EventArgs) Handles BtnSaveSetting.Click
Try
My.Settings.LoginUrl = TxtLoginUrl.Text
My.Settings.User = TxtUser.Text
My.Settings.Password = TxtPassword.Text
My.Settings.ActionStr = TxtAction.Text
My.Settings.Parmms = TxtformParams.Text
My.Settings.sitname = TxtSiteName.Text
'
My.Settings.Inputuser = Txus.Text
My.Settings.Inputpass = Txps.Text
My.Settings.InputAction = Txact.Text
'
My.Settings.Save()
TextBox1.Text = My.Settings.sitname
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
زر تخزين لحفظ التعديلات و الاضافات الى الجدول LoginsTb
كود :
Private Sub BtnSave_Click(sender As System.Object, e As System.EventArgs) Handles BtnSave.Click
Try
C = LoginBindingSource.Position
'
If TxtUser.TextLength > 0 And TxtPassword.MaxLength > 0 Then
Dim usinput, passinput, actinput As String
usinput = Txus.SelectedItem
passinput = Txps.SelectedItem
actinput = Txact.SelectedItem
Dim formParams As String = String.Format(usinput & "={0}&" & passinput & "={1}&" & actinput & "={2}", TxtUser.Text, TxtPassword.Text, TxtAction.Text)
TxtformParams.Text = formParams
End If
Me.Validate()
LoginBindingSource.EndEdit()
'
SaveLogins()
LoginBindingSource.Position = C
'
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
اجراء ربط الحقول
كود :
Private Sub BindingFileds()
Me.TxtSiteName.DataBindings.Add(New Binding("Text", LoginBindingSource, "SiteName"))
Me.TxtLoginUrl.DataBindings.Add(New Binding("Text", LoginBindingSource, "LoginUrl"))
Me.TxtUser.DataBindings.Add(New Binding("Text", LoginBindingSource, "usernamee"))
Me.TxtPassword.DataBindings.Add(New Binding("Text", LoginBindingSource, "Passworde"))
Me.TxtAction.DataBindings.Add(New Binding("Text", LoginBindingSource, "ActionStr"))
Me.TxtformParams.DataBindings.Add(New Binding("Text", LoginBindingSource, "formParams"))
'
Me.Txus.DataBindings.Add(New Binding("Text", LoginBindingSource, "Inputuser"))
Me.Txps.DataBindings.Add(New Binding("Text", LoginBindingSource, "Inputpass"))
Me.Txact.DataBindings.Add(New Binding("Text", LoginBindingSource, "InputAction"))
End Sub
زر الاضافة
كود :
Private Sub BindingNavigatorAddNewItem_Click(sender As System.Object, e As System.EventArgs) Handles BindingNavigatorAddNewItem.Click
LoginBindingSource.AddNew()
C = LoginBindingSource.Position
End Sub
حدث تغير التحديد لتخزين نوع الاجراء في الحقل الهدف
كود :
Private Sub ComboBox1_SelectedIndexChanged(sender As System.Object, e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
If ComboBox1.SelectedItem <> String.Empty Then
TxtAction.Text = ComboBox1.SelectedItem
End If
End Sub
تم بحمد الله و فضله , ارجو ان تعذروني عن كل خلل او تقصير حاصل ضمن المقال
و لاتنسونا من دعوة صالحة في ظهر الغيب في هذا الشهر الفضيل , اخوكم ابو ليلى
بالنسبة للبرنامج ساضعه لاحقا حتى تكتمل لدي بعض الافكار و يكون جاهزا للعمل دمتم بخير
اللهم لك الحمد كما ينبغي لجلال وجهك و عظيم سلطانك
في حل و ترحال
المشاركات : 2,468
المواضيع 2
الإنتساب : Nov 2014
السمعة :
602
الشكر: 17073
تم شكره 15267 مرات في 2998 مشاركات
السلام عليكم
اخي ابو ليلى الذي منحك وسام العضو المميز
الله يقدر له الخير اينما كان وجزاه الله خير
ما شاء الله عليك يا ابو ليلى
هذا شغل عظيم واسأل الله ان يعطيك اجر كل حرف كتبته و يجعله في ميزان اعمالك الصالحة
الله يعطيك العافية والف الف الف شكرا جزيلا
المشاركات : 662
المواضيع 39
الإنتساب : Feb 2014
السمعة :
195
الشكر: 1474
تم شكره 1740 مرات في 622 مشاركات
اشكرك اخي امير و اتمنى لك انت ايضا الاجر فيما تقدمه لهذا المنتدى فانت ايضا نبع من العطاء يستحق كل ثناء
و اشكر ايضا كل الاخوة الاعضاء الساهرين على حاجات اخوانهم لا تجد عندهم الفتور و الكسل يبغون اجرا في كل حرف يكتبوه
اعطاهم الله و كفاهم و جزاهم على نياتهم
و اشكر ايضا الحاضر الغائب الذي يسعى دائما لتقدم الموقع و المحافظة على تعب كل شخص فيه .
اللهم لك الحمد كما ينبغي لجلال وجهك و عظيم سلطانك
في حل و ترحال
المشاركات : 662
المواضيع 39
الإنتساب : Feb 2014
السمعة :
195
الشكر: 1474
تم شكره 1740 مرات في 622 مشاركات
عرض لمعرفة اسماء الحقول و الاجراء في صفحة الدخول
اللهم لك الحمد كما ينبغي لجلال وجهك و عظيم سلطانك
في حل و ترحال
|