تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
الإعدادات من وجهة نظر VB .net من 2002 حتى 2005
#1
كاتب الموضوع : samerselo



العديد من التطبيقات تحتاج لتخزين إعدادات محددة لمتابعتها بين الجلسات. ولكن كيف ستقوم بتخزين هذه الإعدادات ضمن التطبيقات المكتوبة ضمن الفريموورك.؟ ليس من السهل دوما الاجابة الصحيحة عن هذا السؤال وستجد مجالا متنوعا من الحلول لهذه المسألة من العديد من المصادر ولكن قليلا منها يمثل الطريقة المثلى لمعالجة هذه الحاجة
دعنا أولا نعرف نوعين رئيسيين لهذه الإعدادات: إعدادات خاصة بالتطبيق وإعدادات خاصة بالمستخدم فإعدادات التطبيق يتم توزيعها مع البرنامج وهي تؤثر على جميع المستخدمين ويكون المسؤول عن تغييرها في العادة مدراء الأنظمة عندما نريد تغيير التصرف العام للتطبيق. فمثلا برنامج لتنظيم الأيدي العاملة عندما يتم تطبيقه في مؤسسة ما سيتم ضبط إعداداته العامة مثل إعداد مخدم قاعدة البيانات التي سيتصل بها المستخدمون وإضافة شعار الشركة لواجهة البرنامج وهكذا. إعدادات التطبيق هذه لاعلاقة لها بالمستخدم ويمكن أن يتم إعدادها في نفس المجلد الذي سيتم تثبيت التطبيق فيه وبشكل يكون فقط مدير النظام له صلاحيات التعديل والحذف على هذه الإعدادات وفي الحالات العامة سيحتاج التطبيق لقراءة هذه الإعدادات دون الحاجة لتعديلها
وإعدادات المستخدم تكون خاصة بكل مستخدم على حدة ويجب أن تكون قابلة للتحرير من داخل الكود فمن أجل تطبيق ما يمكن أن تضم إعدادات المستخدم حجم ومكان نافذة التطبيق وحالة البدء للتطبيق وتغيير هذه المعلومات بسرعة. كل هذه المعلومات يجب استعادتها عند بدء جلسة التطبيق وتحريرها عند الضرورة أثناء تشغيل التطبيق وحفظها للقرص لتكون متوفرة في المرة القادمة التي يقوم المستخدم بتشغيل التطبيق.

ضبط واستعادة إعدادات المستخدم
إنشاء ملف الإعداد للتطبيق موثق جيدا في وثائق الفريموورك ولكن أحببت أن أقدم لك عرضا سريعا لها. أول خطوة هي إنشاء ملف إعداد داخل تطبيقك باختيار Add New Item من قائمة Project و اختيار Application Configuration File و اترك الاسم الافتراضي للملف App.Config كما هو دون تغيير حيث سيتم نقله وإعادة تسميته تلقائيا بالشكل المناسب عند قيامك ببناء التطبيق ففي الملف الجديد يمكنك تحديد إعدادات مخصصة أو إضافة إعداداتك الخاصة ضمن القسم appSettings حيث تكون عبارة عن أزواج اسم/قيمة وتخزين بعض المعلومات البسيطة قد ينتج ملفا شبيها بالتالي

كود :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="Company Name" value="Java Jitters" />
<add key="Database Server" value="BigSQLServer" />
</appSettings>
</configuration>
ومن السهل استعادة هذه القيم عند الحاجة من خلال الفئات والإجراءات الموجودة ضمن النطاق System.Configuration وحتى أنك لن تحتاج لتحميل الملف بنفسك حيث سيتم اكتشافه وتحميله تلقائيا إن كان يحمل الاسم المناسب وموضوعا في المكان المناسب

كود :
Dim companyName As String _
= ConfigurationSettings.AppSettings("Company Name")
لماذا لاتستخدم App.Config؟
كثيرا ما نرى السؤال كيف أعمل ملف App.Config الخاص بي بعد أن استخدموا ملف التعريف ورأو عملية البرمجة السهلة المتعلقة به لماذا لايمكنهم استخدام نفس الملف لتخزين إعدادات المستخدجم الخاصة بهم لماذا يوجد طريقتين مختلفتين للإعدادات وذلك بعد اكتشافهم أن المجال System.Configuration لايقدم طريقة لكتابة الملف app.config والتي تعتبر بمثابة تحذير ولكنها لن تقف عائقا أمام المبرمج المصمم وبعد البحث في العادة يقومون باستخدام ملف عادي أو XmlDocument وهذه تعتبر فكرة غير جيدة ولايجب على تطبيقك كتابة هذه الملفات لعدة أسباب منها الصلاحيات، عدم عزل المستخدمين وكون الملف app.config جزءا من ملفات إعداد التطبيق لهذه الأسباب وأسابا أخرى لايجب عليك استخدام الملف app.config لغرض تخزين إعدادات المستخدم ولكن لاتقلق فتخزين إعدادات المستخدم ليس بتلك الصعوبة

]إنشاء فئة إعدادات وتخزينها على القرص
المفتاح لإنشاء إعداات المستخدم الخاصة بك هو إنشاء فئة Class تمثل جميع البيانات حيث سيكون واجهتك لإعدادات المستخدم الخاصة بك وهي أول شئ يجب عليك بناؤه عندما تريد تخزين أية معلومات كالفئة التالية مثلا

كود :
Imports System.Drawing
Imports System.IO
Imports System.IO.IsolatedStorage
Imports System.Environment
Imports System.Collections.Specialized

Public Class Settings

Private m_BackgroundColor As String
Private m_RecentFiles As New StringCollection
Private m_LastWindowBounds As Rectangle _
= New Rectangle(0, 0, 200, 200)
Private m_LastWindowState As Windows.Forms.FormWindowState _
= FormWindowState.Normal

<Xml.Serialization.XmlIgnore()> _
Public Property LastWindowBounds() As Rectangle
Get
Return m_LastWindowBounds
End Get
Set(ByVal Value As Rectangle)
m_LastWindowBounds = Value
End Set
End Property

Public Property LastWindowPos() As Point
Get
Return m_LastWindowBounds.Location
End Get
Set(ByVal Value As Point)
m_LastWindowBounds.Location = Value
End Set
End Property

Public Property LastWindowSize() As Size
Get
Return m_LastWindowBounds.Size
End Get
Set(ByVal Value As Size)
m_LastWindowBounds.Size = Value
End Set
End Property

Public Property LastWindowState() As Windows.Forms.FormWindowState
Get
Return m_LastWindowState
End Get
Set(ByVal Value As Windows.Forms.FormWindowState)
m_LastWindowState = Value
End Set
End Property

Public Property RecentFiles() As StringCollection
Get
If m_RecentFiles Is Nothing Then
m_RecentFiles = New StringCollection
End If
Return m_RecentFiles
End Get
Set(ByVal Value As StringCollection)
If Value Is Nothing Then
m_RecentFiles = New StringCollection
Else
M_RecentFiles = Value
End If
End Set
End Property

Public Property BackgroundColor() As String
Get
Dim colorToReturn As String

If m_BackgroundColor Is Nothing OrElse _
m_BackgroundColor = String.Empty Then
colorToReturn = ColorTranslator.ToHtml( _
Color.FromKnownColor(KnownColor.Control))
Else
colorToReturn = m_BackgroundColor
End If
Return colorToReturn
End Get
Set(ByVal Value As String)
If Not ColorTranslator.FromHtml(Value).IsEmpty Then
m_BackgroundColor = Value
Else
m_BackgroundColor = String.Empty
End If

End Set
End Property

End Class
بعد انتهاؤك من كتابة الفئة المناسبة لإعداداتك المطلوبة ستكون الخطوة التالية هي كتابتها وقرائتها من ملف على القرص وإحدى الطرق المناسبة هي الفئتينSystem.Xml.XmlReader و System.Xml.XmlWriter ولكنه يكون من الأسهل استخدام تسلسل إكس إم إل XML Serialization حيث يكفل جميع الضروريات لإنشاء تواجد للفئة في ملف XML و استعادتها منه وهو قوي بحيث يستطيع التعامل مع المعلومات المنقوصة وحتى الخصائص الإضافية التي يمكن أن يتم إضافتها مستقبلا وبهذا يتضح أنه خيار جيد ويمكنك تخزين تواجد للفئة على القرص باستخدام الفئة System.Xml.Serialization.XmlSerializer كما يمكنك استخدام نفس الفئة للاستعادة لاحقا انظر المثال

كود :
Public Sub SaveSettings()
Dim xs As New XmlSerializer(GetType(Settings))

Dim sw As New IO.StreamWriter("C:\settings.xml")
xs.Serialize(sw, currentSettings)
sw.Close()

Dim sr As New IO.StreamReader("C:\settings.xml")
currentSettings = CType(xs.Deserialize(sr), Settings)
sr.Close()

End Sub
في هذا المثال البسيط نريد وضع كود فعلي في فئة الإعدادات الخاصة بنا كدمج بين العديد من الإجراءات والطرق المختلفة وقد زودت بعض الخيارات المختلفة ليتم تقديمها ضمن التدفق stream حيث سيهتم بحفظها على القرص وعملية التسلسل/الغاء التسلسل serializing or deserializing تتطلب أولا انشاء تواجد للفئة XmlSerializer

كود :
Dim xs As New XmlSerializer(GetType(Settings))
وستحتاج أيضا إلى تواجد لـ TextWriter, or XmlWriter أو TextReader, or XmlReader لتمثل عملية الحفظ والتحميل

كود :
Dim sw As New IO.StreamWriter("C:\settings.xml")
ومكان تخزين الملف على القرص يعتبر من النقاشات الهامة وأقترح مجلد بيانات التطبيق للمستخدم أو أي مكان معزول آخر ويعتبر الخيار الأول هو الأفضل حيث يمكنك الحصول على مساره الكامل باستخدام Application.UserAppDataPath أو System.Environment.GetFolderPath حيث يمكن الحصول على المسار المطلوب بالعديد من الطرق ولكني أعتقد أن هذه الطريقة تعمل بشكل جيد كفاية

كود :
Dim filePath As String
filePath = Path.Combine(GetFolderPath( _
SpecialFolder.ApplicationData), Application.CompanyName)
filePath = Path.Combine(filePath, Application.ProductName)
filePath = Path.Combine(filePath, FILENAME)
Dim settingsFile As New FileStream(filePath, FileMode.Create)
[color#009900]التخزين المنفصل[/color]
و المكان المثالي الآخر هو التخزين المنفصل حيث يكون له مكان خاص ضمن مجلد بيانات التطبيق للمستخدم الذي يوفر ملفات فريدا مربوط بالمستخدم وببعض المعلومات حول التطبيق حيث يمكنك استخدام الفئات الموجودة ضمن System.IO.IsolatedStorage لإنشاء ملف أو فتح آخر موجود في تلك المنطقة

كود :
Dim ifs As IsolatedStorageFile
ifs = IsolatedStorageFile.GetUserStoreForAssembly()
Dim settingsFile As New IsolatedStorageFileStream( _
FILENAME, FileMode.Create, ifs)
Return settingsFile
الحفظ والتحميل من داخل التطبيق
في النموذج الرئيسي لتطبيقك ستحتاج لتحميل ملف الإعدادات من القرص عند بدء تطبيقك وحفظها عند نهايته وكل الإجرائيات التي ستحتاجها موجودة في فئتك وسيتطلب ذلك العمل مع تلك الاجرائيات في الفئة لتحرير الإعدادات التي تريدها ففي تطبيق بسيط بنافذة واحدة سيكون من السهل انجاز هذا ببضعة سطور من الكود ومن أجل تحميل الاعدادات عند بدء التطبيق يمكنك النظر إلى المثال

كود :
Dim currentSettings As Settings

Public Sub LoadSettings()
Try
Me.currentSettings = Settings.Load()
With Me.currentSettings
Me.Bounds = .LastWindowBounds
Me.BackColor = ColorTranslator.FromHtml(.BackgroundColor)
Me.WindowState = .LastWindowState
End With
UpdateRecentFileMenu()
Catch ex As Exception
Me.currentSettings = New Settings
End Try
End Sub

Public Sub New()
MyBase.New()

'This call is required by the Windows Form Designer.
InitializeComponent()

'Add any initialization after the InitializeComponent() call
LoadSettings()

End Sub
ولتحميلها يمكنك النظر للمثال

كود :
Protected Overrides Sub OnClosing(ByVal e As _
System.ComponentModel.CancelEventArgs)
SaveSettings()
End Sub

Private Sub SaveSettings()
With Me.currentSettings
.LastWindowBounds = Me.Bounds
.BackgroundColor = ColorTranslator.ToHtml(Me.BackColor)
.LastWindowState = Me.WindowState
.PersistMe()
End With
End Sub
ماذا عن فيجول بايزيك 2005
ناقشنا في هذا الموضوع كيف وأين تقوم بتخزين إعداداتك ولكن في VB2005 كثير من هذه المسائل تمت معالجتها من أجلك حيث تمت إضافة محرر للإعدادات في خصائص التطبيق يمكنك استخدامها بسهولة لضبط كلا من إعدادات المستخدم و إعدادات التطبيق بكل سهولة وبعد قيامك بضبطها بالشكل المناسب يمكنك قراءتها بكل سهولة

كود :
Me.Text = My.Settings.CompanyName
If My.Settings.StartMaximized Then
Me.WindowState = FormWindowState.Maximized
End If
}}}
تم الشكر بواسطة:
#2
في الفريموورك 2 ستلاحظ سهولة انشاء الاعدادت حيث لم يعد من الضروري فتح ملف الإعداد وإدخال إعداداتك الخاصة بدلا عن ذلك يمكنك التوجه إلى مصمم الإعدادات مباشرة وإضافة إعداداتك مهما يكن مجالها تطبيق أم مستخدم. حيث تكون الإعدادات على مستوى التطبيق للقراءة فقط وهي مشتركة بين جميع المستخدمين في التطبيق وتكون مخزنة في الملف app.config في القسم <applicationSettings> والذي يختلف عن القسم <appSettings> الموجود في الفريموورك 1 وفي وقت التشغيل سيكون الملف app.config في المجلد Bin وسوف يتم إعادة تسميته وفقا لاسم البرنامج وكمثال على إعدادات على مستوى التطبيق تعريف وصلة قاعدة البيانات أو عنوان خدمة ويب أو عنوان ملقم ... الخ

كود :
<applicationSettings>
<MySettingsDemo.MySettings>
<setting name="SMTPServerIP" serializeAs="String">
<value>127.0.0.1</value>
</setting>
<setting name="MyServicesURL" serializeAs="String">
<value>http://localhost/myservices.asmx</value>
</setting>
</MySettingsDemo.MySettings>
</applicationSettings>
الاعدادات على مستوى المستخدم تكون مخصصة لكل مستخدم ويمكن قراءتها وإعادة ضبطها بأمان من خلال كود التطبيق وتكون مخزنة في ملف user.config ولكي نكون دقيقين هناك ملفان user.config لكل مستخدم لكل تطبيق واحد للمشاركة وواحد بدون مشاركة ومع ذلك تنص وثائق VB2005 على أن الملف user.config سيتم تسميته تبعا لاسم المستخدم ولكن هذا ليس دقيقا حيث سيتم تخزينه في المسار

كود :
<c:\Documents and Settings>\<username>\[Local Settings\]Application

Data\<companyname>\<appdomainname>_<eid>_<hash>\<verison>
حيث

كود :
<c:\Documents and Settings> مجلد بيانات المستخدم
<username> اسم المستخدم
<companyname> اسم الشركة
<appdomainname> Application Domain
<eid> معرف المستخدم
<hash> hash
<version> رقم نسخة التطبيق
وكمثال

كود :
C:\Documents and Settings\Emad.BROKENOAK\Local Settings\Application
Data\MySettingsDemo\MySettingsDemo_9cfe5ef1\1.0.0.0
حيث يتم إنشاء الملف user.config في أول مرة يتم فيها تشغيل التطبيق بواسطة مستخدم جديد والقيم الغير افتراضية سوف يتم وضعها في ملف على مستوى المستخدم بينما القيم الموجودة في app.config في القسم <userSettings> هي القيم الافتراضية لهذه الاعدادات وهي محددة أيضا في الملف app.config وفيمايلي كيف يمكن ان يبدو الملف app.config

كود :
<userSettings>
<MySettingsDemo.MySettings>
<setting name="LastSearchedItem" serializeAs="String">
<value />
</setting>
<setting name="FormSize" serializeAs="String">
<value>400, 400</value>
</setting>
<setting name="FormLocation" serializeAs="String">
<value>0, 0</value>
</setting>
</MySettingsDemo.MySettings>
</userSettings>
الإعدادات على مستوى المستخدم عظيمة لتخزين إعدادات التطبيق حيث عادة ما تختلف من مستخدم لآخر وكمثال على ذلك إعدادات العرض كحجم الخط وموقع النافذة أو لائحة الملفات المفتوحة أخيرا وهكذا. وهذه البنية مرنة جدا لأنها تمكن تطبيقك من تخزين الاعدادات لتطبيقك حتى لو كنت تعمل في بيئة محدودة الصلاحيات.

إنشاء إعداد
لنفترض أنه لدينا صندوق بحث في تطبيقك وأنت تريد تخزين آخر قيم تم البحث عنها
1 - في مستكشف الحل انقر نقرا مزدوجا على المشروع لعرض خصائص المشروع
2 - انقر صفحة Settings لعرض مصمم الإعدادات
3 - أدخل الاسم Name والنوع Type والمجال Scope للإعداد المطلوب
4 - في حقل القيمة Value أدهل القيمة الافتراضية لذلك الإعداد

وراء المشهد
عندما تضيف إعدادا جديدا في مصمم الإعدادات تجري عدة أشياء في الخلفية أولا يتم إضافة مدخل جديد في الملف app.config لتحديد هذا الاعداد

كود :
<configSections>
<sectionGroup
name="applicationSettings"type="System.Configuration.ApplicationSettingsGroup, System,
Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="MySettingsDemo.MySettings"
type="System.Configuration.ClientSettingsSection, System,
Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</sectionGroup>
</configSections>
ثانيا تعريف الإعداد ذات نفسه يتم إضافته للملف app.config بالاعتماد على نوع Type الاعداد الذي تحدده حيث يمكنك أن ترى قسم applicationSettings أو userSettings أو connectionStrings أو أكثر من واحد منها اعتمادا على الإعدادات التي تحددها كمثال:

كود :
<userSettings>
<MySettingsDemo.MySettings>
<setting name="LastSearchedItem" serializeAs="String">
<value />
</setting>
<setting name="FormSize" serializeAs="String">
<value>400, 400</value>
</setting>
<setting name="FormLocation" serializeAs="String">
<value>0, 0</value>
</setting>
</MySettingsDemo.MySettings>
</userSettings>
<connectionStrings>
<add name="MySettingsDemo.MySettings.ConnectionString"
connectionString="Server=TABLET; User ID=sa; Password=1234;
Database=Northwind; Persist Security Info=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
<applicationSettings>
<MySettingsDemo.MySettings>
<setting name="SMTPServerIP" serializeAs="String">
<value>192.168.2.11</value>
</setting>
<setting name="MyServicesURL" serializeAs="String">
<value>http://localhost/MyServices/Service1.asmx</value>
</setting>
</MySettingsDemo.MySettings>
</applicationSettings>
[color]الوصول للإعدادات[/color]
بعد إضافة الإعداد سنحتاج لقراءته وتحديثه حيث يمكنك الوصول إليه عبر My.Settings كمثال

كود :
My.Settings.LastSearchedItem
وكطريقة أخرى للوصول لهذه الاعدادات هي ربطها بإحدى خصائص التحكم حيث من صندوق الخصائص انتقل إلى قسم Data وفيه (ApplicationSettings) ومنها (PropertyBinding) ومن الصندوق الذي يظهر لك انتقل للخاصية التير تريد ربطها مثلا Location وقم باختيار الضبط المناسب لها مثلا FormLocation أو حتى يمكنك إنشاء إعداد جديد من هناك بالنقر على New وكنتيجة لهذا الربط سيقوم المصمم بإنشاء كود في الخلف كالتالي

كود :
Me.DataBindings.Add(New System.Windows.Forms.Binding("Location",
MySettingsDemo.MySettings.Value, "FormLocation", True,
MySettingsDemo.MySettings.Value.LastSearchedItem, Nothing,
System.Windows.Forms.BindingUpdateMode.OnPropertyChanged))
عندما تربط الإعدادات بهذه الطريقة لن تضطر للقلق حول قراءة وكتابة هذه الإعدادات حيث سيتم ضبط الخاصية عند الحاجة وسوف يتم تخزين قيمتها تلقائيا إذا تم تغييرها ويمكنك ملاحظة أن هذا كله يتم دون اضطرارك لكتابة أي سطر من الكود كما يجدر ملاحظة أن هذه الاعدادات يتم تخزينها تلقائيا في بعض أنواع المشاريع مثل Visual Basic Windows Forms ويجب أن تستدعي الاجراء Save من My.Settings في بعضها الآخر مثل مشاريع Class Library وإذا كانت الخاصية غير مدرجة ضمن نافذة الربط يمكنك تحقيق هذا الربط باستخدام الكود حيث لبعض الأسباب الخاصية Size غير متوفرة كخاصية يمكن ربطها في النسخة التجريبية وهي متوفرة في النسخة النهائية ويمكن تحقيق الربط بإضافة Binding جديد لمجموعة DataBindings collection ضمن الفورم حيث تستخدم الفئة Binding لتحديد اسم الخاصية واسم الإعداد وطريقة التحديث كمثال يمكننا ربط الخاصية Size بالإعداد FormSize يدويا كالمثال

كود :
Me.DataBindings.Add(New System.Windows.Forms.Binding("Size", _
MySettingsDemo.MySettings.Value, _
"FormSize"))
الإعدادات المتقدمة
إذا أردت تحكما إضافيا على إعداداتك يمكنك تحديد الفئة Class الخاصة بك والتي يجب أن تشتقها من الفئة ApplicationSettingsBase التي تشكل الفئة الأب لجميع فئات الاعدادات حيث تحدد كل إعداد كخاصية لهذه الفئة و تسبقها بالخاصية ApplicationScopedSettingAttribute إذا كانت على مكستوى التطبيق أو بالخاصية UserScopedSettingAttribute إذا كانت على مستوى المستخدم وهذا ما يفعله مصمم الإعدادات بالضبط وهذا مثال من كود مولد بواسطة المصمم

كود :
Partial NotInheritable Class MySettings
Inherits System.Configuration.ApplicationSettingsBase

<System.Diagnostics.DebuggerNonUserCode(), _
System.Configuration.UserScopedSettingAttribute(), _
System.Configuration.DefaultSettingValueAttribute("400, 400")> _
Public Property FormSize() As System.Drawing.Size
Get
Return CType(Me("FormSize"),System.Drawing.Size)
End Get
Set
Me("FormSize") = value
End Set
End Property

<System.Diagnostics.DebuggerNonUserCode(), _
System.Configuration.SpecialSetting(System.Configuration.SpecialSetting.ConnectionString), _
System.Configuration.ApplicationScopedSettingAttribute(), _
System.Configuration.DefaultSettingValueAttribute("Server=TABLET;
User ID=sa; Password=1234; Database=Northwind; Persist Security In"& _
"fo=True")> _
Public ReadOnly Property ConnectionString() As String
Get
Return CType(Me("ConnectionString"),String)
End Get
End Property

<System.Diagnostics.DebuggerNonUserCode(), _
System.Configuration.ApplicationScopedSettingAttribute(), _
System.Configuration.DefaultSettingValueAttribute("192.168.2.11")> _
Public ReadOnly Property SMTPServerIP() As String
Get
Return CType(Me("SMTPServerIP"),String)
End Get
End Property
...
ويمكنك ملاحظة ان الـ ConnectionString محاط بالخاصية SpecialSetting التي يكون لها قسم خاص في ملف الإعدادات كما تجدر ملاحظة Partial class في بداية التعريف مما يعني أنه يمكنك إنشاء ملف جديد في مشروعك وتضمينه جزء آخر من أحد الفئات Class في مشروعك وبهذه التقنية يمكنك تعديل إعداداتك دون تعديل الكود المنشأ بواسطة المصمم ويمكنك إضافة معالجة للأحداث في هذه الفئة والتي تمنحك دقة أكبر في التحكم بهذه الاعدادات وهذه الأحداث هي SettingChanging و SettingsSaving و PropertyChanging
}}}
تم الشكر بواسطة:



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


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