9. مكونات الخادم ASP - 3
9.1. Introduction
نواصل عملنا على واجهة المستخدم من خلال تعميق قدرات المكونات [DataList] و [DataGrid]، لا سيما في مجال تحديث البيانات التي تعرضها
9.2. إدارة الأحداث المرتبطة ببيانات مكونات ربط البيانات
9.2.1. المثال
لنأخذ الصفحة التالية:
![]() |
تتضمن الصفحة ثلاثة مكونات مرتبطة بقائمة بيانات:
- مكون [DataList] باسم [DataList1]
- مكون [DataGrid] باسم [DataGrid1]
- مكون [Repeater] باسم [Repeater1]
قائمة البيانات المرتبطة هي المصفوفة {"صفر"، "واحد"، "اثنان"، "ثلاثة"}. ويرتبط بكل من هذه البيانات مجموعة من زرين يحملان التسميتين [Infos1] و [Infos2]. ينقر المستخدم على أحد الزرين فيظهر نص يعرض اسم الزر الذي تم النقر عليه. نسعى هنا إلى توضيح كيفية إدارة قائمة من الأزرار أو الروابط.
9.2.2. تكوين المكونات
يتم تكوين المكون [DataList1] بالطريقة التالية:
<asp:datalist id="DataList1" ... runat="server">
<SelectedItemStyle ...</SelectedItemStyle>
<HeaderTemplate>
[début]
</HeaderTemplate>
<FooterTemplate>
[fin]
</FooterTemplate>
<ItemStyle ...></ItemStyle>
<ItemTemplate>
<P><%# Container.DataItem %>
<asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
<asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
</ItemTemplate>
<FooterStyle ...></FooterStyle>
<HeaderStyle ...></HeaderStyle>
</asp:datalist>
لقد حذفنا كل ما يتعلق بمظهر [DataList] لنتركز فقط على محتواه:
- يحدد القسم <HeaderTemplate> رأس صفحة [DataList]، ويحدد القسم <FooterTemplate> تذييلها.
- القسم <ItemTemplate> هو نموذج العرض المستخدم لكل بيانات قائمة البيانات المرتبطة. ويحتوي على العناصر التالية:
- قيمة البيانات الحالية لقائمة البيانات المرتبطة بالمكون: <%# Container.DataItem %>
- زرين يحملان على التوالي [Infos1] و [Infos2]. تحتوي الفئة [Button] على سمة [CommandName] المستخدمة هنا. وستسمح لنا هذه السمة بتحديد الزر الذي تسبب في حدوث حدث في [DataList]. لإدارة النقر على الأزرار، سيكون لدينا معالج أحداث واحد فقط سيكون مرتبطًا بـ [DataList] نفسه وليس بالأزرار. سيتلقى هذا المدير معلومة تشير إلى السطر الذي حدث فيه النقر في [DataList]. سيسمح لنا السمة [CommandName] بمعرفة الزر الذي حدث فيه النقر في هذا السطر.
يتم تكوين المكون [Repeater1] بطريقة مشابهة جدًا:
<asp:repeater id="Repeater1" runat="server">
<HeaderTemplate>
[début]<hr />
</HeaderTemplate>
<FooterTemplate>
<hr />
[fin]
</FooterTemplate>
<SeparatorTemplate>
<hr />
</SeparatorTemplate>
<ItemTemplate>
<%# Container.DataItem %>
<asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
<asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
</ItemTemplate>
</asp:repeater>
تمت إضافة قسم <SeparatorTemplate> ببساطة لفصل البيانات المتتالية التي يعرضها المكون بواسطة شريط أفقي.
وأخيرًا، تم تكوين المكون [DataGrid1] على النحو التالي:
<asp:datagrid id="DataGrid1" ... runat="server" PageSize="2" AllowPaging="True">
<SelectedItemStyle ...></SelectedItemStyle>
<AlternatingItemStyle ...></AlternatingItemStyle>
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ....></FooterStyle>
<Columns>
<asp:ButtonColumn Text="Infos1" ButtonType="PushButton" CommandName="Infos1">
</asp:ButtonColumn>
<asp:ButtonColumn Text="Infos2" ButtonType="PushButton" CommandName="Infos2">
</asp:ButtonColumn>
</Columns>
<PagerStyle .... Mode="NumericPages"></PagerStyle>
</asp:datagrid>
هنا أيضًا، قمنا بحذف معلومات الأنماط (الألوان، العرض، ...). نحن في وضع الإنشاء التلقائي للأعمدة وهو الوضع الافتراضي لـ [DataGrid]. وهذا يعني أنه سيكون هناك عدد من الأعمدة يساوي عددها في مصدر البيانات. هنا، يوجد عمود واحد. أضفنا عمودين آخرين تم تمييزهما بـ <asp:ButtonColumn>. نحدد فيهما معلومات مماثلة لتلك المحددة للمكونين الآخرين بالإضافة إلى نوع الزر، وهو هنا [PushButton]. النوع الافتراضي هو [LinkButton]، c.a.d. رابط. علاوة على ذلك، سيتم تقسيم البيانات إلى صفحات [AllowPaging=true] بحجم صفحة مكون من عنصرين [PageSize=2].
9.2.3. كود عرض الصفحة
تم وضع كود عرض صفحتنا النموذجية في ملف [main.aspx]:
<%@ page codebehind="main.aspx.vb" inherits="vs.main" autoeventwireup="false" %>
<HTML>
<HEAD>
</HEAD>
<body>
<form runat="server">
<P>Gestion d'événements de composants associés à des listes de données</P>
<HR width="100%" SIZE="1">
<table cellSpacing="1" cellPadding="1" bgColor="#ffcc00" border="1">
<tr>
<td ...>DataList</td>
<td ...>DataGrid</td>
<td ...>Repeater</td>
</tr>
<tr>
<td ...>
<asp:datalist id="DataList1" ... runat="server">
<HeaderTemplate>
[début]
</HeaderTemplate>
<FooterTemplate>
[fin]
</FooterTemplate>
<ItemStyle ...></ItemStyle>
<ItemTemplate>
<P><%# Container.DataItem %>
<asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
<asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
</ItemTemplate>
<FooterStyle ...></FooterStyle>
<HeaderStyle ....></HeaderStyle>
</asp:datalist>
<P></P>
</td>
<td ...>
<asp:datagrid id="DataGrid1" ... runat="server" PageSize="2" AllowPaging="True">
<AlternatingItemStyle ...></AlternatingItemStyle>
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
<asp:ButtonColumn Text="Infos1" ButtonType="PushButton" CommandName="Infos1">
</asp:ButtonColumn>
<asp:ButtonColumn Text="Infos2" ButtonType="PushButton" CommandName="Infos2">
</asp:ButtonColumn>
</Columns>
<PagerStyle ... Mode="NumericPages"></PagerStyle>
</asp:datagrid>
</td>
<td ...>
<asp:repeater id="Repeater1" runat="server">
<HeaderTemplate>
[début]<hr />
</HeaderTemplate>
<FooterTemplate>
<hr />
[fin]
</FooterTemplate>
<SeparatorTemplate>
<hr />
</SeparatorTemplate>
<ItemTemplate>
<%# Container.DataItem %>
<asp:Button runat="server" Text="Infos1" CommandName="infos1"></asp:Button>
<asp:Button runat="server" Text="Infos2" CommandName="infos2"></asp:Button></P>
</ItemTemplate>
</asp:repeater>
</td>
</tr>
</table>
<P><asp:label id="lblInfo" runat="server"></asp:label></P>
<P></P>
</form>
</body>
</HTML>
في الكود أعلاه، قمنا بحذف كود التنسيق (الألوان، الخطوط، الأحجام، ...)
9.2.4. كود التحكم في الصفحة
تم وضع كود التحكم في التطبيق في الملف [main.aspx.vb]:
Public Class main
Inherits System.Web.UI.Page
' مكونات الصفحة
Protected WithEvents DataList1 As System.Web.UI.WebControls.DataList
Protected WithEvents lblInfo As System.Web.UI.WebControls.Label
Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
Protected WithEvents Repeater1 As System.Web.UI.WebControls.Repeater
' مصدر البيانات
Protected textes() As String = {"zéro", "un", "deux", "trois"}
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
If Not IsPostBack Then
'الروابط مع مصدر البيانات
DataList1.DataSource = textes
DataGrid1.DataSource = textes
Repeater1.DataSource = textes
Page.DataBind()
End If
End Sub
Private Sub DataList1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles DataList1.ItemCommand
' حدث خطأ في أحد أسطر [datalist]
lblInfo.Text = "Vous avez cliqué sur le bouton [" + e.CommandName + "] de l'élément [" + e.Item.ItemIndex.ToString + "] du composant [DataList]"
End Sub
Private Sub Repeater1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.RepeaterCommandEventArgs) Handles Repeater1.ItemCommand
' حدث حدث على أحد أسطر [repeater]
lblInfo.Text = "Vous avez cliqué sur le bouton [" + e.CommandName + "] de l'élément [" + e.Item.ItemIndex.ToString + "] du composant [Repeater]"
End Sub
Private Sub DataGrid1_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.ItemCommand
' حدث خطأ في أحد أسطر [datagrid]
lblInfo.Text = "Vous avez cliqué sur le bouton [" + e.CommandName + "] de l'élément [" + e.Item.ItemIndex.ToString + "] de la page [" + DataGrid1.CurrentPageIndex.ToString() + "] du composant [DataGrid]"
End Sub
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
' تغيير الصفحة
With DataGrid1
.CurrentPageIndex = e.NewPageIndex
.DataSource = textes
.DataBind()
End With
End Sub
End Class
تعليقات:
- مصدر البيانات [textes] هو مجرد جدول من سلاسل الأحرف. سيتم ربطه بالمكونات الثلاثة الموجودة على الصفحة
- يتم إجراء هذا الربط في الإجراء [Page_Load] عند الطلب الأول. بالنسبة للطلبات التالية، ستستعيد المكونات الثلاثة قيمها من خلال آلية [VIEW_STATE].
- تحتوي المكونات الثلاثة على معالج للحدث [ItemCommand]. يحدث هذا الحدث عند النقر على زر أو رابط في أحد أسطر المكون. يتلقى المعالج معلومتين:
- المصدر: مرجع الكائن (زر أو رابط) الذي تسبب في الحدث
- a: معلومات عن الحدث من النوع [DataListCommandEventArgs]، [RepeaterCommandEventArgs]، [DataGridCommandEventArgs] حسب الحالة. توفر الحجة a معلومات متنوعة. هنا، هناك معلومتان تهماننا:
- a.Item: يمثل السطر الذي وقع فيه الحدث، من النوع [DataListItem] أو [DataGridItem] أو [RepeaterItem]. بغض النظر عن النوع الدقيق، فإن العنصر [Item] له سمة [ItemIndex] تشير إلى رقم السطر [Item] في الحاوية التي ينتمي إليها. نعرض هنا رقم السطر هذا
- a.CommandName: هو السمة [CommandName] للزر (Button، LinkButton، ImageButton) الذي تسبب في الحدث. تسمح لنا هذه المعلومات، إلى جانب المعلومات السابقة، بمعرفة أي زر في الحاوية هو مصدر الحدث [ItemCommand]
9.3. التطبيق - إدارة قائمة الاشتراكات
الآن بعد أن عرفنا كيفية اعتراض الأحداث التي تحدث داخل حاوية البيانات، نقدم مثالاً يوضح كيفية إدارتها.
9.3.1. مقدمة
يحاكي التطبيق المعروض تطبيق اشتراكات في قوائم بريدية. يتم تعريف هذه القوائم بواسطة كائن [DataTable] مكون من ثلاثة أعمدة:
الاسم | النوع | الدور |
سلسلة | المفتاح الأساسي | |
سلسلة | اسم موضوع القائمة | |
سلسلة | وصف للمواضيع التي تتناولها القائمة |
حتى لا نثقل مثالنا، سيتم إنشاء الكائن [DataTable] أعلاه برمجياً بشكل تعسفي. في تطبيق حقيقي، من المحتمل أن يتم توفيره بواسطة طريقة لفئة الوصول إلى البيانات. سيتم إنشاء جدول القوائم في الإجراء [Application_Start] وسيتم وضع الجدول الناتج في التطبيق. سنسميها الجدول [dtThèmes]. سيقوم المستخدم بالاشتراك في بعض مواضيع هذا الجدول. سيتم الاحتفاظ بقائمة اشتراكاته هناك أيضًا في كائن [DataTable] يسمى [dtAbonnements] وستكون بنيته كما يلي:
الاسم | النوع | الدور |
سلسلة | المفتاح الأساسي | |
سلسلة | اسم موضوع القائمة |
التطبيق ذو الصفحة الواحدة هو التالي:
![]() |
رقم | الاسم | النوع | الخصائص | الدور |
DataGrid | قوائم البريد الإلكتروني المتاحة للاشتراك | |||
DataList | قائمة اشتراكات المستخدم في القوائم السابقة | |||
لوحة | لوحة معلومات حول الموضوع الذي اختاره المستخدم مع رابط [Plus d'informations] | |||
تسمية | جزء من [panelInfos] | اسم الموضوع | ||
العلامة | جزء من [panelInfos] | وصف الموضوع | ||
العلامة | رسالة معلومات التطبيق |
يهدف مثالنا إلى توضيح استخدام المكونات [DataGrid] و [DataList]، ولا سيما إدارة الأحداث التي تحدث على مستوى أسطر حاويات البيانات هذه. لذلك، لا يحتوي التطبيق على زر تأكيد يقوم بتخزين اختيارات المستخدم في قاعدة بيانات، على سبيل المثال. ومع ذلك، فهو واقعي. نحن قريبون من تطبيق للتجارة الإلكترونية حيث يضع المستخدم منتجات (اشتراكات) في سلة التسوق الخاصة به.
9.3.2. طريقة العمل
أول ما يراه المستخدم هو ما يلي:
![]() |
ينقر المستخدم على الروابط [Plus d'informations] للحصول على معلومات حول موضوع ما. يتم عرض هذه المعلومات في [panelInfos]:

ينقر على الروابط [S'abonner] للاشتراك في موضوع ما. يتم تسجيل الموضوعات المختارة في المكون [dlAbonnements]:

قد يرغب المستخدم في الاشتراك في قائمة هو مشترك فيها بالفعل. تظهر له رسالة تنبهه إلى ذلك:

أخيرًا، يمكنه إلغاء اشتراكه في أحد الموضوعات باستخدام الأزرار [Retirer] أعلاه. لا يُطلب أي تأكيد. وهذا غير ضروري هنا لأن المستخدم يمكنه إعادة الاشتراك بسهولة. إليك الشاشة بعد إلغاء الاشتراك في [thème1]:

9.3.3. تكوين حاويات البيانات
المكون [dgThèmes] من النوع [DataGrid] مرتبط بمصدر من النوع [DataTable]. تم تنسيقه باستخدام الرابط [Mise en forme automatique] من لوحة خصائص [DataGrid]. أما تعريف خصائصه فقد تم باستخدام الرابط [Générateur de proprités] من نفس اللوحة. والرمز الذي تم إنشاؤه هو التالي (تم حذف رمز التنسيق):
<asp:datagrid id="dgThèmes" AutoGenerateColumns="False" AllowPaging="True" PageSize="5"
runat="server">
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
<asp:BoundColumn DataField="thème" HeaderText="Thème"></asp:BoundColumn>
<asp:ButtonColumn Text="Plus d'informations" CommandName="infos"></asp:ButtonColumn>
<asp:ButtonColumn Text="S'abonner" CommandName="abonner"></asp:ButtonColumn>
</Columns>
<PagerStyle HorizontalAlign="Center" ... Mode="NumericPages"></PagerStyle>
</asp:datagrid>
تجدر الإشارة إلى النقاط التالية:
نحدد بأنفسنا الأعمدة التي سيتم عرضها في القسم <columns>...</columns> | |
لترقيم صفحات البيانات | |
يحدد العمود [thème] (HeaderText) من [DataGrid] الذي سيتم ربطه بالعمود [thème] من مصدر البيانات (DataField) | |
يحدد عمودين من الأزرار (أو الروابط). للتمييز بين الرابطين في نفس السطر، سنستخدم خاصيتهما [CommandName]. |
المكون [DataGrid] لم يتم تكوينه بالكامل. سيتم تكوينه في كود وحدة التحكم.
المكون [dlAbonnements] من النوع [DataList] مرتبط بمصدر من النوع [DataTable]. تم تنسيقه باستخدام الرابط [Mise en forme automatique] من لوحة خصائص [DataList]. أما تعريف خصائصه فقد تم مباشرةً في كود العرض. هذا الكود هو التالي (تم حذف كود التنسيق):
<asp:DataList id="dlAbonnements" ... runat="server" >
<HeaderTemplate>
<div align="center">
Vos abonnements</div>
</HeaderTemplate>
<ItemStyle ...></ItemStyle>
<ItemTemplate>
<TABLE>
<TR>
<TD><%#Container.DataItem("thème")%></TD>
<TD>
<asp:Button id="lnkRetirer" CommandName="retirer" runat="server" Text="Retirer" /></TD>
</TR>
</TABLE>
</ItemTemplate>
<HeaderStyle ...></HeaderStyle>
</asp:DataList>
يحدد نص رأس [DataList] | |
يحدد العنصر الحالي لـ [DataList] - هنا تم وضع جدول مكون من عمودين وصف واحد. ستستخدم الخلية الأولى لاحتواء اسم الموضوع الذي يرغب المستخدم في الاشتراك فيه، والأخرى زر [Retirer] الذي يسمح له بإلغاء اختياره. |
9.3.4. صفحة العرض
رمز العرض [main.aspx] هو التالي:
<%@ page src="main.aspx.vb" inherits="main" autoeventwireup="false" %>
<HTML>
<HEAD>
<title></title>
</HEAD>
<body>
<P>Indiquez les thèmes auxquels vous voulez vous abonner :</P>
<HR width="100%" SIZE="1">
<form runat="server">
<table>
<tr>
<td vAlign="top">
<asp:datagrid id="dgThèmes" ... runat="server">
...
</asp:datagrid>
</td>
<td vAlign="top">
<asp:DataList id="dlAbonnements" ... runat="server" GridLines="Horizontal" ShowFooter="False">
....
</asp:DataList>
</td>
<td vAlign="top">
<asp:Panel ID="panelInfo" Runat="server" EnableViewState="False">
<TABLE>
<TR>
<TD bgColor="#99cccc">
<asp:Label id="lblThème" runat="server"></asp:Label></TD>
</TR>
<TR>
<TD bgColor="#ffff99">
<asp:Label id="lblDescription" runat="server"></asp:Label></TD>
</TR>
</TABLE>
</asp:Panel>
</td>
</tr>
</table>
<P>
<asp:Label id="lblInfo" runat="server" EnableViewState="False"></asp:Label></P>
</form>
</body>
</HTML>
9.3.5. وحدات التحكم
يتم توزيع التحكم بين الملفين [global.asax] و [main.aspx]. الملف [global.asax] هو التالي:
يحتوي الملف المرتبط [global.asax.vb] على الكود التالي:
Imports System.Web
Imports System.Web.SessionState
Imports System.Data
Imports System
Public Class global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' يتم تهيئة مصدر البيانات
Dim thèmes As New DataTable
' الأعمدة
With thèmes.Columns
.Add("id", GetType(System.Int32))
.Add("thème", GetType(System.String))
.Add("description", GetType(System.String))
End With
' سيكون عمود id هو المفتاح الأساسي
thèmes.Constraints.Add("cléprimaire", thèmes.Columns("id"), True)
' الصفوف
Dim ligne As DataRow
For i As Integer = 0 To 10
ligne = thèmes.NewRow
ligne.Item("id") = i.ToString
ligne.Item("thème") = "thème" + i.ToString
ligne.Item("description") = "description du thème " + i.ToString
thèmes.Rows.Add(ligne)
Next
' يتم وضع مصدر البيانات في التطبيق
Application("thèmes") = thèmes
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' بداية الجلسة - يتم إنشاء جدول اشتراكات فارغ
Dim dtAbonnements As New DataTable
With dtAbonnements
' الأعمدة
.Columns.Add("id", GetType(String))
.Columns.Add("thème", GetType(String))
' المفتاح الأساسي
.PrimaryKey = New DataColumn() {.Columns("id")}
End With
' يتم وضع الجدول في الجلسة
Session.Item("abonnements") = dtAbonnements
End Sub
End Class
تقوم الإجراء [Application_Start]، الذي يتم تنفيذه عندما يتلقى التطبيق أول طلب له، بإنشاء [DataTable] للموضوعات التي يمكن الاشتراك فيها. يتم إنشاؤه بشكل عشوائي باستخدام كود. دعونا نذكر التقنية التي سبق أن تعرفنا عليها. يتم الإنشاء بالترتيب التالي:
- كائن [DataTable] فارغ، بدون بنية وبدون بيانات
- هيكل الجدول من خلال تحديد أعمدةه (اسم ونوع البيانات التي يحتوي عليها)
- أسطر الجدول التي تمثل البيانات المفيدة
لقد أضفنا هنا مفتاحًا أساسيًا. العمود "id" هو الذي يستخدم كمفتاح أساسي. هناك عدة طرق للتعبير عن ذلك. هنا، استخدمنا قيدًا. في SQL، القيد هو قاعدة يجب أن تلتزم بها بيانات السطر حتى يمكن إضافته إلى الجدول. هناك أنواع عديدة من القيود الممكنة. يفرض القيد "Primary Key" على العمود الذي يطبق عليه أن يحتوي على قيم فريدة وغير فارغة. يمكن أن يتكون المفتاح الأساسي في الواقع من تعبير يتضمن قيمًا من عدة أعمدة. [DataTable].Constraints هي مجموعة قيود جدول معين. لإضافة قيد، نستخدم الطريقة [DataTable.Constraints.Add]. لهذه الطريقة عدة توقيعات. هنا، استخدمنا الطريقة [Add(Byval nom as String, Byval colonne as DataColumn, Byval cléPrimaire as Boolean)]:
اسم القيد - يمكن أن يكون أي اسم | |
العمود الذي سيكون المفتاح الأساسي - من النوع [DataColumn] | |
يجب أن يكون [vrai] لجعل [colonne] مفتاحًا أساسيًا. إذا كان [cléPrimaire=false]، فلن يكون لدينا سوى قيد القيم الفريدة على [colonne] |
لجعل العمود المسمى "id" المفتاح الأساسي للجدول [dtAbonnements]، نكتب إذن:
الإجراء [Session_Start]، الذي يتم تنفيذه عندما يتلقى التطبيق أول طلب من عميل. ويستخدم لإنشاء كائنات خاصة بكل عميل يجب أن تستمر عبر طلباته المختلفة. يقوم الإجراء بإنشاء [DataTable] لاشتراكات العميل. يتم إنشاء هيكلها فقط لأن هذه الجدولة فارغة في البداية. وستتم ملؤها مع تراكم الطلبات. وهنا أيضًا، تُستخدم العمود "id" كمفتاح أساسي. وقد استُخدمت تقنية مختلفة لإعلان هذا القيد:
هو مصفوفة الأعمدة التي تشكل المفتاح الأساسي - هنا تم تعريف مصفوفة ذات عنصر واحد: العمود المسمى "id" |
عندما تصل طلب العميل إلى وحدة التحكم [main.aspx]، يكون الكائنان [DataTable] متاحين في التطبيق لجدول السمات وفي الجلسة لجدول الاشتراكات. وحدة التحكم [main.aspx.vb] هي كما يلي:
Imports System.Data
Public Class main
Inherits System.Web.UI.Page
Protected WithEvents dgThèmes As System.Web.UI.WebControls.DataGrid
Protected WithEvents lblThème As System.Web.UI.WebControls.Label
Protected WithEvents lblDescription As System.Web.UI.WebControls.Label
Protected WithEvents dlAbonnements As System.Web.UI.WebControls.DataList
Protected WithEvents lblInfo As System.Web.UI.WebControls.Label
Protected WithEvents panelInfo As System.Web.UI.WebControls.Panel
Protected dtThèmes As DataTable
Protected dtAbonnements As DataTable
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub liaisons()
...
End Sub
Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
...
End Sub
Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
...
End Sub
Private Sub infos(ByVal id As String)
...
End Sub
Private Sub abonner(ByVal id As String)
...
End Sub
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
...
End Sub
End Class
تتمثل المهمة الأساسية للإجراء [Page_Load] في:
- استرداد الجدولين [dtThèmes] و [dtAbonnements] الموجودين في التطبيق والجلسة على التوالي، وذلك لإتاحتهما لجميع طرق الصفحة
- ربط بيانات هذين المصدرين بحاويات كل منهما. ويتم ذلك فقط عند الطلب الأول. أما في الطلبات التالية، فلا يتم الربط بشكل منهجي، وعندما يتعين القيام به، قد يتعين في بعض الأحيان انتظار حدث لاحق لـ [Page_Load] للقيام به.
رمز [Page_Load] هو التالي:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' يتم استرداد مصادر البيانات
dtThèmes = CType(Application("thèmes"), DataTable)
dtAbonnements = CType(Session("abonnements"), DataTable)
' ربط البيانات
If Not IsPostBack Then
liaisons()
End If
' يتم إخفاء بعض المعلومات
panelInfo.Visible = False
End Sub
Private Sub liaisons()
'يتم ربط مصدر البيانات بالمكون [datagrid]
With dgThèmes
.DataSource = dtThèmes
.DataKeyField = "id"
End With
' يتم ربط مصدر البيانات بالمكون [datalist]
With dlAbonnements
.DataSource = dtAbonnements
.DataKeyField = "id"
End With
' تعيين البيانات للمكونات
Page.DataBind()
End Sub
في الإجراء [liaisons]، نستخدم الخاصية [DataKeyField] للمكونات [DataList] و [DataGrid] لتحديد عمود مصدر البيانات الذي سيُستخدم لتعريف أسطر الحاويات بشكل فريد. عادةً ما يكون هذا العمود هو المفتاح الأساسي لمصدر البيانات، ولكن هذا ليس إلزاميًا. يكفي أن يكون العمود خاليًا من التكرارات والقيم الفارغة. بالنسبة للحاوية [dgThèmes]، سيكون العمود "id" من المصدر [dtThèmes] هو المفتاح الأساسي، وبالنسبة للحاوية [dlAbonnements]، سيكون العمود "id" من المصدر [dtAbonnements]. ليس من الضروري أن يعرض الحاوية العمود الذي يستخدم كمفتاح أساسي لها. هنا، لا تعرض أي من الحاويتين عمود المفتاح الأساسي. الفائدة من أن يكون للحاوية مفتاح أساسي هي أنه يتيح العثور بسهولة في مصدر البيانات على المعلومات المرتبطة بسطر الحاوية الذي وقع فيه حدث ما. في الواقع، غالبًا ما يتعين، انطلاقًا من سطر الحاوية الذي وقع فيه الحدث، العمل على السطر المقابل في مصدر البيانات المرتبط به. وييسر المفتاح الأساسي هذه المهمة.
تتم إدارة ترقيم الصفحات في [DataGrid] بالطريقة التقليدية:
Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
' تغيير الصفحة
dgThèmes.CurrentPageIndex = e.NewPageIndex
' الربط
liaisons()
End Sub
يتم إدارة الإجراءات على الروابط [Plus d'informations] و [S'abonner] بواسطة الإجراء [dgThèmes_ItemCommand]:
Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
' حدث على سطر في [datagrid]
Dim commande As String = e.CommandName
Select Case commande
Case "infos"
infos(dgThèmes.DataKeys(e.Item.ItemIndex))
Case "abonner"
abonner(dgThèmes.DataKeys(e.Item.ItemIndex))
End Select
' الربط
liaisons()
End Sub
يتم استخدام حقيقة أن كلا الرابطين لهما سمة [CommandName] للتمييز بينهما. وفقًا لقيمة هذه السمة، يتم استدعاء الإجراء [infos] أو [abonner] مع تمرير المفتاح "id" المرتبط بعنصر [DataGrid] الذي وقع فيه الحدث في كلتا الحالتين. وباستخدام هذه المعلومات، ستعرض الإجراء [info] معلومات السمة التي اختارها المستخدم:
Private Sub infos(ByVal id As String)
' معلومات حول موضوع مفتاح id
' يتم استرداد السطر من [datatable] المطابق للمفتاح
Dim ligne As DataRow
ligne = dtThèmes.Rows.Find(id)
If Not ligne Is Nothing Then
' عرض المعلومات
lblThème.Text = CType(ligne("thème"), String)
lblDescription.Text = CType(ligne("description"), String)
panelInfo.Visible = True
End If
End Sub
نظرًا لأن الجدول [dtThèmes] يحتوي على مفتاح أساسي، فإن الطريقة [dtThèmes.Rows.Find("P")] تسمح بالعثور على السطر الذي يحتوي على المفتاح الأساسي P. إذا تم العثور عليه، نحصل على كائن [DataRow]. هنا، يجب أن نبحث عن السطر الذي يحتوي على المفتاح الأساسي [id] حيث يتم تمرير [id] كمعلمة. إذا تم العثور على السطر، يتم وضع المعلومات [thème] و [description] من هذا السطر في لوحة المعلومات التي يتم إظهارها بعد ذلك.
يجب أن تضيف الإجراء [abonner(id)] موضوع المفتاح [id] إلى قائمة الاشتراكات. ورمزها هو التالي:
Private Sub abonner(ByVal id As String)
' الاشتراك في موضوع مفتاح id
' نسترد السطر الخاص بـ [datatable] المطابق للمفتاح
Dim ligne As DataRow
ligne = dtThèmes.Rows.Find(id)
If Not ligne Is Nothing Then
' التحقق مما إذا كان المستخدم مشتركًا بالفعل
Dim abonnement As DataRow
abonnement = dtAbonnements.Rows.Find(id)
If Not abonnement Is Nothing Then
' يتم الإبلاغ عن الخطأ
lblInfo.Text = "Vous êtes déjà abonné au thème [" + ligne("thème") + "]"
Else
' يتم إضافة الموضوع إلى الاشتراكات
abonnement = dtAbonnements.NewRow
abonnement("id") = id
abonnement("thème") = ligne("thème")
dtAbonnements.Rows.Add(abonnement)
' يتم إجراء الروابط
liaisons()
End If
End If
End Sub
في قائمة السمات [dtThèmes]، نبحث أولاً عن سطر المفتاح [id]. إذا تم العثور عليه، يتم التحقق من أن هذا الموضوع غير موجود بالفعل في قائمة الاشتراكات لتجنب تسجيله مرتين. إذا كان الأمر كذلك، يتم عرض رسالة خطأ. وإلا، يتم إضافة اشتراك جديد إلى الجدول [dtAbonnements] ويتم ربط مكونات قائمة البيانات بمصادرها المعنية.
عندما ينقر المستخدم على زر [Retirer]، يجب حذف عنصر من الجدول [dtAbonnements]. ويتم ذلك من خلال الإجراء التالي:
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
' إلغاء اشتراك
Dim commande As String = e.CommandName
If commande = "retirer" Then
' إلغاء الاشتراك من [datatable]
With dtAbonnements.Rows
.Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
End With
' الروابط
liaisons()
End If
End Sub
نقوم أولاً بالتحقق من السمة [CommandName] للعنصر الذي تسبب في الحدث. وهذا في الواقع غير ضروري إلى حد كبير، لأن الزر [Retirer] هو عنصر التحكم الوحيد القادر على توليد حدث في المكون [DataList]. لذلك لا يوجد أي غموض. لحذف سطر من كائن [DataTable]، نستخدم الطريقة [DataList.Remove(DataRow)] التي تزيل من الجدول السطر من النوع [DataRow] الذي تم تمريره كمعلمة. يتم العثور على هذا الصف من خلال الطريقة [DataList.Find] التي تم تمرير المفتاح الأساسي للصف المطلوب إليها. بمجرد حذف الصف، يتم ربط البيانات بالمكونات
9.4. إدارة مكون [DataList] المقسم إلى صفحات
نستأنف المثال السابق من أجل ترقيم صفحات المكون [DataList] الذي يمثل قائمة اشتراكات المستخدم. على عكس المكون [DataGrid]، لا يوفر المكون [DataList] أي سهولة لترقيم الصفحات. سنرى أن تنفيذ ذلك أمر معقد، مما سيجعلنا نقدر الترقيم التلقائي لـ [DataGrid] حق قدره.
9.4.1. طريقة العمل
الفرق الوحيد هو ترقيم الصفحات في المكون [DataList]. ستحتوي الصفحات على اشتراكين. إذا كان لدى المستخدم خمسة اشتراكات، فسيكون هناك ثلاث صفحات. ستكون الصفحة الأولى كما يلي:

يتم الحصول على الصفحة الثانية عبر الرابط [Suivant]:

الصفحة الثالثة:

يُلاحظ أن الرابطين [Précédent] و [Suivant] لا يظهران إلا إذا كانت هناك صفحة تسبق الصفحة الحالية وأخرى تليها على التوالي.
9.4.2. كود العرض
يتم الحصول على الروابط [Précédent] و [Suivant] بإضافة علامة <FooterTemplate> إلى [DataList]:
<asp:datalist id="dlAbonnements" runat="server" ...>
....
<FooterTemplate>
<asp:LinkButton id="lnkPrecedent" runat="server" CommandName="precedent">Précédent</asp:LinkButton>
<asp:LinkButton id="lnkSuivant" runat="server" CommandName="suivant">Suivant</asp:LinkButton>
</FooterTemplate>
....
</asp:datalist>
9.4.3. رمز التحقق
يتطور الملف المرتبط [global.asax.vb] على النحو التالي:
...
Public Class global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
...
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' بداية الجلسة - إنشاء جدول اشتراكات فارغ
Dim dtAbonnements As New DataTable
With dtAbonnements
' الأعمدة
.Columns.Add("id", GetType(String))
.Columns.Add("thème", GetType(String))
' المفتاح الأساسي
.PrimaryKey = New DataColumn() {.Columns("id")}
End With
' يتم وضع الجدول في الجلسة
Session.Item("abonnements") = dtAbonnements
' الصفحة الحالية هي الصفحة 0
Session.Item("pAC") = 0
' عدد الاشتراكات في هذه الصفحة هو 0
Session.Item("nbAC") = 0
End Sub
بالإضافة إلى جدول الاشتراكات [dtAbonnements]، يتم إدراج معلومتين أخريين في الجلسة:
من النوع [Integer] - وهو رقم الصفحة الحالية المعروضة عند آخر طلب | |
من النوع [Integer] - عدد الأسطر المعروضة في الصفحة الحالية السابقة |
في بداية الجلسة، يكون رقم الصفحة الحالية وعدد الأسطر في هذه الصفحة صفرين.
يتطور عنصر التحكم [main.aspx.vb] على النحو التالي:
....
Public Class main
Inherits System.Web.UI.Page
....
' بيانات التطبيق
Protected dtThèmes As DataTable
Protected dtAbonnements As DataTable
Protected dtPA As DataTable ' la page d'abonnements affichée
Protected Const nbAP As Integer = 2 ' nbre abonnements par page
Protected pAC As Integer ' page abonnement courant
Protected nbAC As Integer ' nombre d'abonnements dans page courante
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub terminer()
...
End Sub
Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
...
End Sub
Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
...
End Sub
Private Sub infos(ByVal id As String)
...
End Sub
Private Sub abonner(ByVal id As String)
...
End Sub
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
...
End Sub
Private Sub changePAC()
...
End Sub
Private Sub setLiens(ByVal ctl As Control, ByVal blPrec As Boolean, ByVal blSuivant As Boolean)
...
End Sub
End Class
يتم فيها تعريف بيانات جديدة تتعلق بترقيم صفحات الاشتراكات:
Protected dtPA As DataTable ' la page d'abonnements affichée
Protected Const nbAP As Integer = 2 ' nbre abonnements par page
Protected pAC As Integer ' page abonnement courant
Protected nbAC As Integer ' nombre d'abonnements dans page courante
يتم تخزين بعض هذه المعلومات في الجلسة ويتم استردادها عند كل طلب في الإجراء [Page_Load]:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' يتم استرداد مصادر البيانات
dtThèmes = CType(Application("thèmes"), DataTable)
dtAbonnements = CType(Session("abonnements"), DataTable)
' ومعلومات عرض الاشتراكات
pAC = CType(Session("pAC"), Integer)
nbAC = CType(Session("nbAC"), Integer)
' ربط البيانات
If Not IsPostBack Then
' عرض قائمة اشتراكات فارغة
terminer()
End If
' يتم إخفاء بعض المعلومات
panelInfo.Visible = False
End Sub
المعلومات التي تم استردادها [pAC] و [nbAC] هي معلومات عن صفحة الاشتراكات التي تم عرضها عند الطلب السابق:
هذا هو رقم الصفحة الحالية التي تم عرضها عند الطلب السابق | |
عدد الأسطر المعروضة في هذه الصفحة الحالية |
تقوم الطريقة [terminer] بربط المكونات بمصادر البيانات الخاصة بها كما كانت تفعل الطريقة [liaisons] في التطبيق السابق. الجديد هنا هو ربط [DataList] بالجدول [dtPA] الذي يمثل صفحة الاشتراكات المراد عرضها:
Private Sub terminer()
' ربط مصدر البيانات بالمكون [datagrid]
With dgThèmes
.DataSource = dtThèmes
.DataKeyField = "id"
End With
' ربط صفحة الاشتراكات بالمكون [datalist] مع مراعاة الصفحة الحالية pAC
changePAC()
' يتم عرض الصفحة p
With dlAbonnements
.DataSource = dtPA
.DataKeyField = "id"
End With
' يتم تعيين البيانات للمكونات
Page.DataBind()
' إدارة الروابط [précédent] و [suivant] من [datalist]
Dim blprec As Boolean = pAC <> 0
Dim blsuivant As Boolean = pAC <> (dtAbonnements.Rows.Count - 1) \ nbAP
Dim nbLiensTrouvés As Integer = 0
setLiens(dlAbonnements, blprec, blsuivant, nbLiensTrouvés)
' يتم حفظ معلومات الصفحة الحالية في الجلسة
Session("pAC") = pAC
Session("nbAC") = dtPA.Rows.Count
End Sub
يجب ملاحظة النقاط التالية:
- يعتمد المصدر [dtPA] على رقم الصفحة الحالية [pAC] المراد عرضها. المتغير [pAC] هو متغير عام للفئة، يتم التعامل معه بواسطة الطرق التي تعمل على تغيير رقم الصفحة الحالية. تقوم الطريقة [changePAC] بإنشاء الجدول [dtPA] الذي سيتم ربطه بالمكون [dlAbonnements].
- تتمثل وظيفة الطريقة [setLiens] في إظهار أو إخفاء الروابط [Précédent] و [Suivant] حسب ما إذا كانت الصفحة الحالية [pAC] المعروضة مسبوقة أو متبوعة بصفحة أخرى. ولها أربعة معلمات:
- [dlAbonnements]: عنصر التحكم [DataList] الذي سنستكشف شجرة عناصر التحكم الخاصة به للعثور على الرابطين. في الواقع، على الرغم من أن هذين الرابطين موجودان في مكان محدد وهو تذييل الصفحة لـ [DataList]، لا يبدو أن هناك طريقة بسيطة للإشارة إليهما مباشرة. على أي حال، لم يتم العثور عليها هنا.
- [blPrecedent]: قيمة منطقية تُعزى إلى الخاصية [visible] للرابط [Precedent] - تكون صحيحة إذا كانت الصفحة الحالية مختلفة عن 0
- [blSuivant]: قيمة منطقية تُعيَّن للخاصية [visible] للرابط [Suivant] - تكون صحيحة إذا لم تكن الصفحة الحالية هي الصفحة الأخيرة في قائمة الاشتراكات
- [nbLiensTrouvés]: معلمة إخراج تحسب عدد الروابط التي تم العثور عليها. بمجرد أن يساوي هذا العدد اثنين، تنتهي الطريقة.
- يتم حفظ المعلومات [pAC] و [nbAC] في الجلسة للاستخدام في الطلب التالي.
تقوم الطريقة [changePAC] بإنشاء الجدول [dtPA] الذي سيتم ربطه بالمكون [dlAbonnements]. وتقوم بذلك استنادًا إلى رقم [pAC] الخاص بالصفحة الحالية المراد عرضها. يجب أن تعرض الجدولة [dtPA] بعض الصفوف من جدول الاشتراكات [dtAbonnements]. تجدر الإشارة إلى أن هذه الجدولة يتم تخزينها في الجلسة ويتم تحديثها (زيادة أو نقصان) مع كل طلب. نبدأ بتحديد الفاصل [premier,dernier] لأرقام الأسطر في الجدول [dtAbonnements] التي يجب أن يعرضها الجدول [dtPA]:
Private Sub changePAC()
' جعل الصفحة pAC، الصفحة الحالية للاشتراكات
' إدارة صفحات [datalist]
Dim nbAbonnements = dtAbonnements.Rows.Count
Dim dernièrePage = (nbAbonnements - 1) \ nbAP
' الاشتراك الأول والأخير
If pAC < 0 Then pAC = 0
If pAC > dernièrePage Then pAC = dernièrePage
Dim premier As Integer = pAC * nbAP
Dim dernier As Integer = (pAC + 1) * nbAP - 1
If dernier > nbAbonnements - 1 Then dernier = nbAbonnements - 1
بعد ذلك، يمكن إنشاء الجدول [dtPA]. نحدد أولاً هيكلها [id,thème] ثم نملؤها بنسخ الأسطر من [dtAbonnements] التي يقع رقمها في النطاق [premier,dernier] الذي تم حسابه مسبقاً.
' إنشاء جدول البيانات dtpa
dtPA = New DataTable
With dtPA
' الأعمدة
.Columns.Add("id", GetType(String))
.Columns.Add("thème", GetType(String))
' المفتاح الأساسي
.PrimaryKey = New DataColumn() {.Columns("id")}
End With
Dim abonnement As DataRow
For i As Integer = premier To dernier
abonnement = dtPA.NewRow
With abonnement
.Item("id") = dtAbonnements.Rows(i).Item("id")
.Item("thème") = dtAbonnements.Rows(i).Item("thème")
End With
dtPA.Rows.Add(abonnement)
Next
End Sub
في نهاية الأسلوب [changePAC]، تم إنشاء الجدول [dtPA] ويمكن ربطه بالمكون [DataList]. يتم ذلك في الأسلوب [terminer]. في هذه الطريقة نفسها، يتم استخدام الإجراء [setLiens] لتحديد حالة الروابط [Précédent] و [Suivant] الخاصة بـ [DataList]. رمز هذا الإجراء هو كما يلي:
Private Sub setLiens(ByVal ctl As Control, ByVal blPrec As Boolean, ByVal blSuivant As Boolean, ByRef nbLiensTrouvés As Integer)
' نبحث عن الروابط [précédent] و [suivant]
' في شجرة عناصر التحكم الخاصة بـ [datalist]
' هل تم العثور على جميع الروابط؟
If nbLiensTrouvés = 2 Then Exit Sub
' فحص عناصر التحكم الفرعية
Dim c As Control
For Each c In ctl.Controls
' نعمل أولاً بعمق - الروابط موجودة في أسفل الشجرة
setLiens(c, blPrec, blSuivant, nbLiensTrouvés)
' رابط [Précédent]؟
If c.ID = "lnkPrecedent" Then
CType(c, LinkButton).Visible = blPrec
nbLiensTrouvés += 1
End If
' رابط [Suivant]؟
If c.ID = "lnkSuivant" Then
CType(c, LinkButton).Visible = blSuivant
nbLiensTrouvés += 1
End If
Next
End Sub
الإجراء متكرر. يبحث أولاً، بين عناصر التحكم الفرعية للمكون [dlAbonnements] عن المكونات المسماة [lnkPrecedent] و [lnkSuivant] التي تمثل هويات (السمة ID) رابطي الترقيم. يبحث عنها أولاً بدءاً من أسفل شجرة عناصر التحكم لأنهما موجودان هناك. بمجرد العثور على رابط، يتم زيادة العداد [nbLiensTrouvés] ويتم ملء الخاصية [visible] للرابط بقيمة تم تمريرها كمعلمة للإجراء. بمجرد العثور على الرابطين، يتوقف استكشاف شجرة عناصر التحكم وتنتهي الإجراء التكراري.
لقد ذكرنا أن الطريقة [changePAC] التي تحدد مصدر البيانات [dtPA] للمكون [dlAbonnements] تعمل مع رقم [pAC] للصفحة الحالية المراد عرضها. هناك عدة إجراءات تقوم بتعديل هذا الرقم:
Private Sub abonner(ByVal id As String)
' اشتراك في موضوع مفتاح id
..
' إضافة الموضوع إلى الاشتراكات
..
' تحديث رقم الصفحة الحالية - أصبحت الآن الصفحة الأخيرة
pAC = (dtAbonnements.Rows.Count - 1) \ nbAP
' روابط البيانات
terminer()
End If
End Sub
بعد إضافة اشتراك، يظهر هذا الاشتراك في نهاية قائمة الاشتراكات. لذلك يتم الانتقال إلى الصفحة الأخيرة من الاشتراكات حتى يرى المستخدم الإضافة التي تمت.
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
' إلغاء اشتراك
Dim commande As String = e.CommandName
Select Case commande
Case "retirer"
' إزالة الاشتراك من [datatable]
With dtAbonnements.Rows
.Remove(.Find(dlAbonnements.DataKeys(e.Item.ItemIndex)))
End With
' هل يجب تغيير الصفحة الحالية؟
nbAC -= 1
If nbAC = 0 Then pAC -= 1
Case "precedent"
' تغيير الصفحة الحالية
pAC -= 1
Case "suivant"
' تغيير الصفحة الحالية
pAC += 1
End Select
' روابط البيانات
terminer()
End Sub
- [nbAC] هو عدد الأسطر المعروضة في الصفحة الحالية قبل إلغاء الاشتراك. إذا كان العدد الجديد لأسطر الصفحة يساوي 0، يتم تقليل رقم الصفحة الحالية [pAC] بمقدار وحدة واحدة.
- في حالة النقر على الرابط [Precedent]، يتم تقليل رقم الصفحة الحالية [pAC] بمقدار وحدة واحدة.
- في حالة النقر على الرابط [Suivant]، يتم زيادة رقم الصفحة الحالية [pAC] بمقدار وحدة واحدة.
تظل الإجراءات الأخرى كما كانت سابقًا.
9.4.4. الخلاصة
لقد أوضحنا في هذا المثال أنه يمكننا ترقيم صفحات مكون [DataList]. يعد هذا الترقيم أمرًا دقيقًا، ومن الأفضل الاعتماد على الترقيم التلقائي لمكون [DataGrid] عندما يكون ذلك ممكنًا. أظهر لنا هذا المثال أيضًا كيفية الوصول إلى المكونات الموجودة في تذييل المكون [DataList].
9.5. فئة الوصول إلى قاعدة المنتجات
نعود مرة أخرى إلى قاعدة البيانات ACCESS [produits] التي استخدمناها سابقًا. تذكر أن هذه القاعدة تحتوي على جدول واحد يسمى [liste]، وهيكلها كما يلي:
![]() | ![]() |
سنقوم بإنشاء فئة للوصول إلى الجدول [liste] والتي ستسمح بقراءته وتحديثه. سنقوم أيضًا بإنشاء عميل وحدة تحكم يستخدم الفئة السابقة لتحديث الجدول. في مرحلة لاحقة، سنقوم بإنشاء عميل ويب للقيام بنفس المهمة.
9.5.1. الفئة ExceptionProduits
تحتوي الفئة [Exception] على منشئ يقبل رسالة خطأ كمعلمة. نريد هنا أن يكون لدينا فئة استثناء تحتوي على منشئ يقبل قائمة برسائل الخطأ بدلاً من رسالة خطأ واحدة. ستكون هذه هي الفئة [ExceptionProduits] أدناه:
Public Class ExceptionProduits
Inherits Exception
' رسائل الأخطاء المتعلقة بالاستثناء
Private _erreurs As ArrayList
' المنشئ
Public Sub New(ByVal erreurs As ArrayList)
Me._erreurs = erreurs
End Sub
' الخاصية
Public ReadOnly Property erreurs() As ArrayList
Get
Return _erreurs
End Get
End Property
End Class
9.5.2. الهيكل [sProduit]
ستمثل البنية [produit] منتجًا [id, nom, prix]:
' البنية sProduit
Public Structure sProduit
' الحقول
Private _id As Integer
Private _nom As String
Private _prix As Double
' معرف الخاصية
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
_id = Value
End Set
End Property
' خاصية الاسم
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
If IsNothing(Value) OrElse Value.Trim = String.Empty Then Throw New Exception
_nom = Value
End Set
End Property
' خاصية السعر
Public Property prix() As Double
Get
Return _prix
End Get
Set(ByVal Value As Double)
If IsNothing(Value) OrElse Value < 0 Then Throw New Exception
_prix = Value
End Set
End Property
End Structure
لا تقبل البنية سوى البيانات الصالحة للحقول [nom] و [prix].
9.5.3. فئة المنتجات
الفئة [Produits] هي الفئة التي ستسمح لنا بتحديث الجدول [liste] في قاعدة بيانات المنتجات. وهي تتكون من العناصر التالية:
Public Class produits
' بيانات المثيل
Public Sub New(ByVal chaineConnexionOLEDB As String)
....
End Sub
Public Function getProduits() As DataTable
....
End Function
Public Sub ajouterProduit(ByVal produit As sProduit)
...
End Sub
Public Sub modifierProduit(ByVal produit As sProduit)
...
End Sub
Public Sub supprimerProduit(ByVal id As Integer)
...
End Sub
End Class
بيانات المثيل
البيانات المشتركة بين الطرق المختلفة للفئة هي التالية:
Private connexion As OleDbConnection
Private Const selectText As String = "select id,nom,prix from liste"
Private Const insertText As String = "insert into liste(nom,prix) values(?,?)"
Private Const updateText As String = "update liste set nom=?,prix=? where id=?"
Private Const deleteText As String = "delete from liste where id=?"
Private selectCommand As New OleDbCommand
Dim insertCommand As New OleDbCommand
Dim updateCommand As New OleDbCommand
Dim deleteCommand As New OleDbCommand
Dim adaptateur As New OleDbDataAdapter
سيتم فتح الاتصال بقاعدة البيانات لتنفيذ الأمر SQL ثم إغلاقه فورًا بعد ذلك | |
الاستعلام SQL [select] للحصول على الجدول بأكمله [liste] | |
طلب يسمح بإدراج سطر (الاسم، السعر) في الجدول [liste]. يُلاحظ أنه لم يتم تحديد الحقل [id]. في الواقع، يتم زيادة هذا الحقل تلقائيًا بواسطة SGBD، وبالتالي لا داعي لتحديده. | |
استعلام يسمح بتحديث الحقول (الاسم، السعر) في السطر من الجدول [liste] الذي يحمل المفتاح [id] | |
استعلام يسمح بحذف السطر من الجدول [liste] الذي يحمل المفتاح [id] | |
الكائن [OleDbCommand] الذي ينفذ الاستعلام [selectText] على الاتصال [connexion] | |
الكائن [OleDbCommand] الذي ينفذ الاستعلام [updateText] على الاتصال [connexion] | |
الكائن [OleDbCommand] الذي ينفذ الاستعلام [insertText] على الاتصال [connexion] | |
الكائن [OleDbCommand] الذي ينفذ الاستعلام [deleteText] على الاتصال [connexion] | |
كائن يسمح باسترداد نتيجة تنفيذ [selectCommand] في كائن [DataSet] |
المنشئ
يتلقى المنشئ معلمة واحدة [chaineConnexionOLEDB] وهي سلسلة الاتصال التي تشير إلى قاعدة البيانات المراد استغلالها. انطلاقًا من هذه السلسلة، يتم إعداد الأوامر الأربعة لاستغلال الجدول وتحديثه بالإضافة إلى المحول. هذا مجرد إعداد ولا يتم إجراء أي اتصال.
Public Sub New(ByVal chaineConnexionOLEDB As String)
' يتم إعداد الاتصال
connexion = New OleDbConnection(chaineConnexionOLEDB)
' يتم إعداد أوامر الاستعلام
Dim commandes() As OleDbCommand = {selectCommand, insertCommand, updateCommand, deleteCommand}
Dim textes() As String = {selectText, insertText, updateText, deleteText}
For i As Integer = 0 To commandes.Length - 1
With commandes(i)
.CommandText = textes(i)
.Connection = connexion
End With
Next
' يتم إعداد محول الوصول إلى البيانات
adaptateur.SelectCommand = selectCommand
End Sub
الطريقة getProduits
تسمح هذه الطريقة بالحصول على محتوى الجدول [Liste] في كائن [DataTable]. ورمزها هو التالي:
Public Function getProduits() As DataTable
' يتم وضع الجدول [liste] في [dataset]
Dim contenu As New DataSet
' يتم إنشاء كائن DataAdapter لقراءة البيانات من المصدر OLEDB
Try
With adaptateur
.FillSchema(contenu, SchemaType.Source)
.Fill(contenu)
End With
Catch e As Exception
' مشكلة
Dim erreursCommande As New ArrayList
erreursCommande.Add(String.Format("Erreur d'accès à la base de données : {0}", e.Message))
Throw New ExceptionProduits(erreursCommande)
End Try
' نقوم بإرجاع النتيجة
Return contenu.Tables(0)
End Function
يتم تنفيذ المهمة من خلال الأمرين التاليين:
With adaptateur
.FillSchema(contenu, SchemaType.Source)
.Fill(contenu)
End With
تحدد الطريقة [FillSchema] بنية (الأعمدة، والقيود، والعلاقات) لـ [DataSet] contenu انطلاقًا من بنية قاعدة البيانات المشار إليها بواسطة [adaptateur.Connexion]. وهذا يسمح لنا باسترداد بنية الجدول [liste] وبالأخص مفتاحه الأساسي. تقوم العملية [Fill] التالية بملء [Dataset] و contenu بصفوف الجدول [liste]. باستخدام هذه العملية وحدها، كنا سنحصل على البيانات والهيكل ولكن ليس المفتاح الأساسي. ومع ذلك، سيكون هذا مفيدًا لنا لتحديث الجدول [liste] في الذاكرة. هنا، كما في الطرق الأخرى، نعالج الأخطاء المحتملة باستخدام الفئة [ExceptionProduits] للحصول على قائمة (ArrayList) بالأخطاء بدلاً من خطأ واحد. تقوم الطريقة [getProduits] بإرجاع الجدول [liste] في شكل كائن [DataTable].
الطريقة ajouterProduits
تسمح هذه الطريقة بإضافة سطر (id، اسم، سعر) إلى الجدول [liste]. يتم تزويدها بهذه المعلومات في شكل بنية [sProduit] من الحقول [id, nom, prix]. رمز الطريقة هو التالي:
Public Sub ajouterProduit(ByVal produit As sProduit)
' يتم إضافة منتج [nom,prix]
' نقوم بإعداد معلمات الإضافة
With insertCommand.Parameters
.Clear()
.Add(New OleDbParameter("nom", produit.nom))
.Add(New OleDbParameter("prix", produit.prix))
End With
' تمت الإضافة
Try
' فتح الاتصال
connexion.Open()
' تنفيذ الأمر
insertCommand.ExecuteNonQuery()
Catch ex As Exception
' مشكلة
Dim erreursCommande As New ArrayList
erreursCommande.Add(String.Format("Erreur lors de l'ajout : {0}", ex.Message))
Throw New ExceptionProduits(erreursCommande)
Finally
' إغلاق الاتصال
connexion.Close()
End Try
End Sub
يتم إدخال حقول البنية [produit] في معلمات الأمر [insertCommand]. لنذكر التكوين الحالي لهذا الأمر (انظر المصنع):
Private Const insertText As String = "insert into liste(nom,prix) values(?,?)"
insertCommand.Connexion=connexion
يحتوي نص الأمر SQL [insert] على معلمات شكلية يجب استبدالها بمعلمات فعلية. ويتم ذلك باستخدام المجموعة [Parameters] من الفئة [OleDbCommand]. تحتوي هذه المجموعة على عناصر من النوع [OleDbParameter] التي تحدد المعلمات الفعلية التي يجب أن تحل محل المعلمات الشكلية؟. ونظرًا لأن هذه المعلمات غير مسماة، يتم استخدام فهرس المعلمات الفعلية لمعرفة أي معلمة شكلية تتوافق مع معلمة فعلية معينة. هنا، ستحل المعلمة الفعلية رقم i في المجموعة [Parameters] محل المعلمة الشكلية؟ رقم i. لإنشاء معلمة فعلية من النوع [OleDbParameter]، نستخدم هنا المنشئ [OleDbParameter (Byval nom as String, Byval valeur as Object)] الذي يحدد اسم وقيمة المعلمة الفعلية. يمكن أن يكون الاسم أي اسم. بالإضافة إلى ذلك، لن يتم استخدامه هنا. يتلقى المعلمتان في الأمر SQL [insert] قيم الحقول [nom, prix] من البنية [produit]. وبعد ذلك، يتم الإدراج بواسطة الأمر [insertCommand.ExecuteNonQuery].
الطريقة modifierProduits
تسمح هذه الطريقة بتعديل سطر الجدول [liste]. يتم تزويدها بالمعلومات اللازمة لها ضمن بنية [sProduit] لحقول [id, nom, prix].
Public Sub modifierProduit(ByVal produit As sProduit)
' يتم تعديل منتج [id,nom,prix]
' إعداد معلمات التحديث
With updateCommand.Parameters
.Clear()
.Add(New OleDbParameter("nom", produit.nom))
.Add(New OleDbParameter("prix", produit.prix))
.Add(New OleDbParameter("id", produit.id))
End With
' إجراء التعديل
Try
' فتح الاتصال
connexion.Open()
' تنفيذ الأمر
Dim nbLignes As Integer = updateCommand.ExecuteNonQuery()
If nbLignes = 0 Then Throw New Exception(String.Format("Le produit de clé [{0}] n'existe pas dans la table des données", produit.id))
Catch ex As Exception
' مشكلة
Dim erreursCommande As New ArrayList
erreursCommande.Add(String.Format("Erreur lors de la modification : {0}", ex.Message))
Throw New ExceptionProduits(erreursCommande)
Finally
' إغلاق الاتصال
connexion.Close()
End Try
End Sub
الرمز مطابق تقريبًا لرمز الطريقة [ajouterProduits] باستثناء أن الأمر [OleDbCommand] المعني هو [updateCommand] بدلاً من [insertCommand].
الطريقة supprimerProduits
تسمح هذه الطريقة بحذف السطر من الجدول [liste] ذي المفتاح [id] الذي تم تمريره كمعلمة. الرمز هو التالي:
Public Sub supprimerProduit(ByVal id As Integer)
' حذف ناتج المفتاح [id]
' يتم إعداد معلمات الحذف
With deleteCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", id))
End With
' يتم الحذف
Try
' فتح الاتصال
connexion.Open()
' تنفيذ الأمر
Dim nbLignes As Integer = deleteCommand.ExecuteNonQuery()
If nbLignes = 0 Then Throw New Exception(String.Format("Le produit de clé [{0}] n'existe pas dans la table des données", id))
Catch ex As Exception
' مشكلة
Dim erreursCommande As New ArrayList
erreursCommande.Add(String.Format("Erreur lors de la suppression : {0}", ex.Message))
Throw New ExceptionProduits(erreursCommande)
Finally
' إغلاق الاتصال
connexion.Close()
End Try
End Sub
نجد نفس النهج المتبع في الطرق السابقة.
9.5.4. اختبارات الفئة [produits]
قد يكون برنامج الاختبارات [testproduits.vb] من نوع وحدة التحكم كما يلي:
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections
Namespace st.istia.univangers.fr
' صفحة اختبار
Module testproduits
Dim contenu As DataTable
Sub Main(ByVal arguments() As String)
' عرض محتوى جدول المنتجات
' الجدول موجود في قاعدة بيانات ACCESS التي يحصل pg منها على اسم الملف
Const syntaxe1 As String = "pg bdACCESS"
' التحقق من معلمات البرنامج
If arguments.Length <> 1 Then
' رسالة خطأ
Console.Error.WriteLine(syntaxe1)
' النهاية
Environment.Exit(1)
End If
' يتم إعداد سلسلة الاتصال
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + arguments(0)
' إنشاء كائن منتجات
Dim objProduits As produits = New produits(chaineConnexion)
' عرض جميع المنتجات
afficheProduits(objProduits)
' إدراج منتج
Dim produit As New sProduit
With produit
.nom = "xxx"
.prix = 1
End With
Try
objProduits.ajouterProduit(produit)
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
End Try
' عرض جميع المنتجات
afficheProduits(objProduits)
' استرداد معرف المنتج المضاف
produit.id = CType(contenu.Rows(contenu.Rows.Count - 1)("id"), Integer)
' يتم تعديل المنتج المضاف
produit.prix = 200
Try
objProduits.modifierProduit(produit)
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
End Try
' عرض جميع المنتجات
afficheProduits(objProduits)
' حذف المنتج المضاف
Try
objProduits.supprimerProduit(produit.id)
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
End Try
' عرض جميع المنتجات
afficheProduits(objProduits)
End Sub
Sub afficheProduits(ByRef objProduits As produits)
' استرداد جدول المنتجات في جدول بيانات
Try
contenu = objProduits.getProduits()
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
Environment.Exit(2)
End Try
Dim lignes As DataRowCollection = contenu.Rows
For i As Integer = 0 To lignes.Count - 1
' السطر i من الجدول
Console.Out.WriteLine(lignes(i).Item("id").ToString + "," + lignes(i).Item("nom").ToString + _
"," + lignes(i).Item("prix").ToString)
Next
' إيقاف تدفق وحدة التحكم
Console.WriteLine("...")
Console.ReadLine()
End Sub
Sub afficheErreurs(ByRef erreurs As ArrayList)
' عرض الأخطاء على وحدة التحكم
If erreurs.Count <> 0 Then
Console.WriteLine("Les erreurs suivantes se sont produites :")
For i As Integer = 0 To erreurs.Count - 1
Console.WriteLine(String.Format("-- {0}", CType(erreurs(i), String)))
Next
End If
' إيقاف تدفق وحدة التحكم
Console.WriteLine("...")
Console.ReadLine()
End Sub
End Module
End Namespace
نقوم بتجميع الملفين المصدرين:
dos>vbc /t:library /r:system.dll /r:system.data.dll /r:system.xml.dll produits.vb
dos >vbc /r:produits.dll /r:system.dll /r:system.data.dll /r:system.xml.dll testproduits.vb
dos>dir
07/04/2004 08:40 7 168 produits.dll
04/04/2004 16:38 118 784 produits.mdb
07/04/2004 08:31 6 209 produits.vb
07/04/2004 08:40 5 120 testproduits.exe
03/04/2004 19:02 3 312 testproduits.vb
ثم نختبر:
dos>testproduits produits.mdb
1,produit1,10
2,produit2,20
3,produit3,30
...
1,produit1,10
2,produit2,20
3,produit3,30
8,xxx,1
...
1,produit1,10
2,produit2,20
3,produit3,30
8,xxx,200
...
1,produit1,10
2,produit2,20
3,produit3,30
...
يُطلب من القارئ مقارنة الناتج المعروض أعلاه برمز برنامج الاختبار.
9.6. تطبيق ويب لتحديث جدول المنتجات المخزنة مؤقتًا
9.6.1. مقدمة
نقوم الآن بكتابة تطبيق ويب لتحديث جدول المنتجات (إضافة، حذف، تعديل). سيبقى الجدول المحدث في الذاكرة في كائن [DataTable] وسيتم مشاركته بين جميع المستخدمين. نريد تسليط الضوء على نقطتين:
- إدارة كائن [DataTable]
- مشكلة التحديث المتزامن لجدول من قبل عدة مستخدمين.
ستكون بنية التطبيق MVC كما يلي:
![]() |
9.6.2. التشغيل والعروض
الطريقة التالية هي طريقة عرض الصفحة الرئيسية للتطبيق:

تسمح هذه الشاشة، التي تسمى [formulaire]، للمستخدم بوضع شرط تصفية على المنتجات وتحديد عدد المنتجات التي يرغب في عرضها في كل صفحة.
رقم | الاسم | النوع | الدور |
LinkButton | يعرض العرض [Formulaire] الذي يستخدم لتحديد شرط التصفية | ||
LinkButton | يعرض العرض [Produits] الذي يستخدم لعرض وتحديث جدول المنتجات (التعديل والحذف) | ||
LinkButton | يعرض عرض [Ajout] الذي يُستخدم لإضافة منتج | ||
vueFormulaire | عرض [Formulaire] | ||
TextBox | شرط التصفية | ||
TextBox | عدد المنتجات في كل صفحة | ||
RequiredFieldValidator | تحقق من وجود قيمة في [txtPages] | ||
RangeValidator | يتحقق من أن txtPages يقع في النطاق [3,10] | ||
زر [submit] الذي يعرض طريقة العرض [produits] التي تمت تصفيتها حسب الشرط (5) | |||
التسمية | نص إعلامي في حالة وجود أخطاء |
على سبيل المثال، إذا تم ملء العرض [formulaire] بالطريقة التالية:

نحصل على النتيجة التالية:
![]() |
رقم | الاسم | النوع | الدور |
لوحة | |||
RadioButton | يسمح للمستخدم بتحديد ترتيب الفرز المطلوب عند النقر على عنوان أحد الأعمدة [nom]، [prix]. الزران جزء من المجموعة [rdTri]. | ||
DataGrid | شبكة عرض لإطلالة مفلترة من جدول المنتجات. الفلتر هو الذي تم تحديده بواسطة الإطلالة [formulaire]. لدينا أيضًا .AllowPaging=true، .AllowSorting=true | ||
DataGrid | يعرض الجدول الكامل للمنتجات - يسمح بمتابعة التحديثات | ||
تسمية | نص إعلامي خاصة في حالة وجود أخطاء | ||
DataGrid | سيعرض المنتجات المحذوفة في جدول المنتجات | ||
DataGrid | سيعرض المنتجات التي تم تعديلها في جدول المنتجات | ||
DataGrid | سيعرض المنتجات المضافة في جدول المنتجات |
توجد خمسة حاويات بيانات في هذه الصفحة. تعرض جميعها نفس الجدول [dtProduits] من خلال عرض مختلف [DataView]. تمثل طريقة العرض مجموعة فرعية من صفوف جدول مصدر طريقة العرض. يتم إنشاء هذه المجموعة الفرعية من خصائص [RowFilter] و [RowStateFilter] للفئة [DataView]:
- تسمح [RowFilter] بتعيين مرشح على الصفوف، كما هو موضح أعلاه في [prix>30]. سيتم استخدام هذا النوع من التصفية بواسطة [DataGrid1].
- يسمح [RowStateFilter] بتعيين مرشح وفقًا لحالة سطر الجدول. ويشير هذا إلى حالة السطر مقارنة بالحالة الأصلية التي كان عليها عند إنشاء عرض الجدول. هنا، تأتي الجدول [dtProduits] من قاعدة بيانات. في البداية، ستكون حالة جميع صفوفه مساوية لـ [Original] للإشارة إلى أننا نتعامل مع الصفوف الأصلية للجدول. يمكن أن تتغير هذه الحالة لاحقًا وتأخذ قيمًا مختلفة، وفيما يلي بعض منها:
- [Added]: تمت إضافة السطر - لم يكن جزءًا من الجدول الأصلي
- [Deleted]: تم حذف السطر - ولا يزال موجودًا في الجدول ولكنه "مُعلم" على أنه "يجب حذفه"
- [Modified]: تم تعديل السطر
[RowStateFilter] يسمح بعرض الأسطر في الجدول التي لها حالة معينة:
- (تابع)
- [DataViewRowState.Added]: يتم عرض الأسطر المضافة فقط. ويتم عرضها بقيمها الحالية.
- [DataViewRowState.ModifiedOriginal]: يتم عرض الأسطر المعدلة فقط. يتم عرضها بقيمها الأصلية.
- [DataViewRowState.ModifiedCurrent]: يتم عرض الأسطر المعدلة فقط. ويتم عرضها بقيمها الحالية.
- [DataViewRowState.Deleted]: يتم عرض الأسطر المحذوفة فقط. ويتم عرضها بقيمها الأصلية.
- [DataViewRowState.CurrentRows]: يتم عرض الأسطر غير المحذوفة. يتم عرضها بقيمها الحالية.
وبالتالي، سيكون للمرشح [RowStateFilter] القيم التالية:
لا يوجد مرشح لحالة الخطوط | ||
DataViewRowState.CurrentRows | يعرض الحالة الحالية لجدول المنتجات | |
DataViewRowState.Deleted | يعرض الأسطر المحذوفة من جدول المنتجات | |
DataViewRowState.ModifiedOriginal | يعرض الأسطر المعدلة في جدول المنتجات مع قيمها الأولية | |
DataViewRowState.Added | يعرض الأسطر المضافة إلى جدول المنتجات الأصلي |
ستسمح لنا الحاويات [DataGrid1-5] بمتابعة تحديث الجدول [dtProduits]. يسمح المكون [DataGrid1] بتعديل وحذف منتج. سنرى كيف تسمح تكوينات المكون بهذا التحديث. كما أنه مقسم إلى صفحات ومصنف. وقد تمت دراسة هذين الأمرين بالفعل في مثال سابق.
يتيح الرابط [Ajout] الوصول إلى نموذج لإضافة منتج:
![]() |
رقم | الاسم | النوع | الدور |
لوحة | |||
TextBox | اسم المنتج | ||
RequiredFieldValidator | تحقق من وجود قيمة في [txtNom] | ||
TextBox | سعر المنتج | ||
RequiredFieldValidator | تحقق من وجود قيمة في [txtPrix] | ||
CompareValidator | يتحقق من أن السعر >= 0 | ||
زر | زر [submit] لإضافة المنتج | ||
تسمية | نص إعلامي عن نتيجة عملية الإضافة |
قد لا تكون قاعدة البيانات [produits] متاحة عند بدء تشغيل التطبيق. في هذه الحالة، يتم عرض العرض [erreurs] للمستخدم:
![]() |
رقم | الاسم | النوع | الدور |
لوحة | |||
مكرر | قائمة الأخطاء |
9.6.3. تكوين حاويات البيانات
تم تكوين الحاويات الخمس [DataGrid] تحت [WebMatrix]. تم تنسيقها (الألوان والحدود) باستخدام الرابط [Configuration automatique] من لوحة خصائصها. تم تكوين الحاوية [DataGrid1] باستخدام الرابط [Générateur de propriétés] في نفس اللوحة. تم السماح بالفرز (علامة التبويب [Général]):

لا يتم إنشاء الأعمدة تلقائيًا على عكس الحاويات الأربع الأخرى. تم تعريفها يدويًا باستخدام المعالج:

تم أولاً إنشاء عمودين من النوع [Colonne connexe] يسميان [nom] و [prix]. وقد تم ربطهما على التوالي بالحقول [nom] و [prix] من مصدر البيانات الذي ستعرضه الحاويات. وفيما يلي، على سبيل المثال، تكوين العمود [nom]:

تعبير الفرز هو التعبير الذي يجب وضعه خلف جملة [order by] في الأمر SQL [select] الذي سيتم تنفيذه عندمايضغط المستخدم على اسم عمود العنوان [nom] المرتبط بالحقل [nom] في [DataGrid]. هنا قمنا بوضع [nom] بحيث تكون جملة الفرز هي [order by nom]. سنرى أننا سنقوم بتعديل هذا الشرط ليصبح [order by nom asc] أو [order by nom desc] وفقًا لترتيب الفرز الذي يختاره المستخدم.
كما أنشأنا عمودين من الأزرار:

سيسمح لنا العمود [Modifier, Mettre à jour, Annuler] بتعديل منتج، بينما يسمح لنا العمود [Supprimer] بحذفه. يمكن تكوين كل من هذه الأعمدة. يوفر العمود [Modifier, Mettre à jour, Annuler] التكوين التالي:

نلاحظ أنه يمكن تعديل نصوص الأزرار. فيما يتعلق بالأزرار، يمكن الاختيار بين الروابط والأزرار (القائمة المنسدلة أعلاه). وقد تم اختيار الروابط هنا. إعداد العمود [Supprimer] مشابه. بالإضافة إلى مساعد التكوين هذا، استخدمنا نافذة خصائص [DataGrid] مباشرةً لملء الخاصية [DataKeyField] التي تحدد الحقل في مصدر البيانات الذي يتم فهرسة أسطر [DataGrid] بناءً عليه. هنا يتم استخدام المفتاح الأساسي لجدول المنتجات:

وفي النهاية، يُنتج هذا التكوين كود العرض التالي:
<asp:DataGrid id="DataGrid1" runat="server" AllowSorting="True" PageSize="4" AllowPaging="True" AutoGenerateColumns="False" DataKeyField="id">
<SelectedItemStyle ...></SelectedItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
<asp:BoundColumn DataField="nom" SortExpression="nom" HeaderText="nom"></asp:BoundColumn>
<asp:BoundColumn DataField="prix" SortExpression="prix" HeaderText="prix"></asp:BoundColumn>
<asp:EditCommandColumn ButtonType="LinkButton" UpdateText="Mettre à jour" CancelText="Annuler" EditText="Modifier"></asp:EditCommandColumn>
<asp:ButtonColumn Text="Supprimer" CommandName="Delete"></asp:ButtonColumn>
</Columns>
<PagerStyle NextPageText="Suivant" PrevPageText="Précédent" ...></PagerStyle>
</asp:DataGrid>
كما هو الحال دائمًا، بمجرد اكتساب بعض الخبرة، يمكن كتابة كل أو جزء من الكود أعلاه مباشرةً.
تتمتع الحاويات الأخرى [DataGrid] بالتكوين الافتراضي الذي يتم الحصول عليه من خلال التوليد التلقائي لأعمدة [DataGrid] انطلاقًا من أعمدة مصدر البيانات المرتبط بها.
يُستخدم الحاوية [Repeater] لعرض قائمة بالأخطاء. يتم تكوينها مباشرةً في كود العرض:
<asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
<HeaderTemplate>
Les erreurs suivantes se sont produites :
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
يعرض كل سطر من المكون القيمة [Container.DataItem]، c.a.d. القيمة المقابلة من قائمة البيانات. وستكون هذه من النوع [ArrayList] وستمثل قائمة بالأخطاء.
9.6.4. رمز عرض التطبيق
يتم وضعه في الملف [main.aspx]:
<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
<HEAD>
</HEAD>
<body>
<form id="Form1" runat="server">
<P>
<table>
<tr>
<td><FONT size="6">Options :</FONT></td>
<td>
<asp:linkbutton id="lnkFiltre" runat="server" CausesValidation="False">
Filtrage
</asp:linkbutton>
</td>
<td>
<asp:linkbutton id="lnkMisajour" runat="server" CausesValidation="False">
Mise à jour
</asp:linkbutton>
</td>
<td>
<asp:linkbutton id="lnkAjout" runat="server" CausesValidation="False">
Ajout
</asp:linkbutton>
</td>
</tr>
</table>
</P>
<HR width="100%" SIZE="1">
<table>
<tr>
<td>
<asp:panel id="vueFormulaire" runat="server">
<P>Condition de filtrage sur la table LISTE. Exemple : prix<100 and
prix>50</P>
<P>
<asp:TextBox id="txtFiltre" runat="server" Columns="60"></asp:TextBox></P>
<P>Nombre de lignes par page :
<asp:TextBox id="txtPages" runat="server" Columns="3">5</asp:TextBox>
<asp:RequiredFieldValidator id="rfvLignes" runat="server" Display="Dynamic"
ControlToValidate="txtPages" ErrorMessage="Indiquez le nombre de lignes par page"
EnableClientScript="False">
</asp:RequiredFieldValidator></P>
<P>
<asp:RangeValidator id="rvLignes" runat="server" Display="Dynamic"
ControlToValidate="txtPages" ErrorMessage="Vous devez indiquer un nombre entre 3 et 10"
EnableClientScript="False" MaximumValue="10" MinimumValue="3" Type="Integer"
EnableViewState="False">
</asp:RangeValidator></P>
<P>
<asp:Label id="lblinfo1" runat="server"></asp:Label></P>
<P>
<asp:Button id="btnExécuter" runat="server" CausesValidation="False"
EnableViewState="False" Text="Exécuter">
</asp:Button></P>
</asp:panel>
<asp:panel id="vueProduits" runat="server">
<TABLE>
<TR>
<TD align="center" bgColor="#ff9966">
<P>Tri
<asp:RadioButton id="rdCroissant" runat="server" Text="croissant"
GroupName="rdTri" Checked="True">
</asp:RadioButton>
<asp:RadioButton id="rdDécroissant" runat="server" Text="décroissant"
GroupName="rdTri">
</asp:RadioButton></P>
</TD>
<TD align="center" bgColor="#ff9966">Tous les produits
</TD>
<TD align="center" bgColor="#ff9966">Suppressions
</TD>
<TD align="center" bgColor="#ff9966">Modifications
</TD>
<TD align="center" bgColor="#ff9966">Ajouts
</TD>
<TR>
<TD vAlign="top">
<P>
<asp:DataGrid id="DataGrid1" runat="server" AllowSorting="True" PageSize="4"
AllowPaging="True" .... AutoGenerateColumns="False" DataKeyField="id">
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<Columns>
<asp:BoundColumn DataField="nom" SortExpression="nom" HeaderText="nom">
</asp:BoundColumn>
<asp:BoundColumn DataField="prix" SortExpression="prix" HeaderText="prix">
</asp:BoundColumn>
<asp:EditCommandColumn ButtonType="LinkButton" UpdateText="Mettre à jour"
CancelText="Annuler" EditText="Modifier">
</asp:EditCommandColumn>
<asp:ButtonColumn Text="Supprimer" CommandName="Delete">
</asp:ButtonColumn>
</Columns>
<PagerStyle NextPageText="Suivant" PrevPageText="Précédent" ....>
</PagerStyle>
</asp:DataGrid>
</P>
</TD>
<TD vAlign="top">
<asp:DataGrid id="DataGrid2" runat="server" ...>
<AlternatingItemStyle ...></AlternatingItemStyle>
<ItemStyle ...></ItemStyle>
<HeaderStyle ....></HeaderStyle>
<FooterStyle ...></FooterStyle>
<PagerStyle ... Mode="NumericPages"></PagerStyle>
</asp:DataGrid>
</TD>
<TD vAlign="top">
<asp:DataGrid id="DataGrid3" runat="server" ...>
<ItemStyle ...></ItemStyle>
<HeaderStyle ...></HeaderStyle>
<FooterStyle ...></FooterStyle>
<PagerStyle ...></PagerStyle>
</asp:DataGrid>
</TD>
<TD vAlign="top">
<asp:DataGrid id="DataGrid4" runat="server" ...>
<ItemStyle ....></ItemStyle>
<HeaderStyle ....></HeaderStyle>
<FooterStyle ...></FooterStyle>
<PagerStyle .... Mode="NumericPages"></PagerStyle>
</asp:DataGrid>
</TD>
<TD vAlign="top">
<asp:DataGrid id="DataGrid5" runat="server....>
<AlternatingItemStyle ...></AlternatingItemStyle>
<HeaderStyle ..></HeaderStyle>
<FooterStyle ...></FooterStyle>
<PagerStyle ....></PagerStyle>
</asp:DataGrid>
</TD>
</TR>
</TABLE>
<P></P>
<P>
<asp:Label id="lblInfo2" runat="server"></asp:Label></P>
</asp:panel>
<asp:panel id="vueErreurs" runat="server">
<asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
<HeaderTemplate>
Les erreurs suivantes se sont produites :
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</asp:panel>
<asp:panel id="vueAjout" EnableViewState="False" Runat="server">
<P>Ajout d'un produit</P>
<P>
<TABLE ... border="1">
<TR>
<TD>nom</TD>
<TD>
<asp:TextBox id="txtNom" runat="server" Columns="30"></asp:TextBox>
<asp:RequiredFieldValidator id="rfvNom" runat="server" ControlToValidate="txtNom"
ErrorMessage="Vous devez indiquer un nom">
</asp:RequiredFieldValidator>
</TD>
</TR>
<TR>
<TD>prix</TD>
<TD>
<asp:TextBox id="txtPrix" runat="server" Columns="10"></asp:TextBox>
<asp:RequiredFieldValidator id="rfvPrix" runat="server" ControlToValidate="txtPrix"
ErrorMessage="Vous devez indiquer un prix">
</asp:RequiredFieldValidator>
<asp:CompareValidator id="cvPrix" runat="server" ControlToValidate="txtPrix"
ErrorMessage="CompareValidator" Type="Double" Operator="GreaterThanEqual"
ValueToCompare="0">
</asp:CompareValidator>
</TD>
</TR>
</TABLE>
</P>
<P>
<asp:Button id="btnAjouter" runat="server" CausesValidation="False"
Text="Ajouter">
</asp:Button></P>
<P>
<asp:Label id="lblInfo3" runat="server"></asp:Label></P>
</asp:panel>
</td>
</tr>
</table>
</form>
</body>
</HTML>
تجدر الإشارة إلى النقاط التالية:
- تتكون الصفحة من أربعة حاويات (لوحات) [vueFormulaire, vueProduits, vueAjout, vueErreurs] التي ستشكل العروض الأربعة للتطبيق.
- الأزرار أو الروابط من النوع [submit] لها الخاصية [CausesValidation=false]. تؤدي الخاصية [causesValidation=true] إلى تنفيذ جميع عناصر التحقق من صحة الصفحة. ولكن هنا، لا يجب إجراء جميع عناصر التحقق من الصحة في نفس الوقت. عند إجراء إضافة على سبيل المثال، لا نريد تنفيذ عمليات التحقق المتعلقة بعدد الأسطر في كل صفحة. لذلك سنحدد بأنفسنا عمليات التحقق من الصحة التي يجب إجراؤها.
9.6.5. رمز التحكم [global.asax]
وحدة التحكم [global.asax] هي كما يلي:
الرمز المرتبط [global.asax.vb]:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' يتم استرداد معلومات التكوين
Dim chaînedeConnexion As String = ConfigurationSettings.AppSettings("OLEDBStringConnection")
Dim defaultProduitsPage As String = ConfigurationSettings.AppSettings("defaultProduitsPage")
Dim erreurs As New ArrayList
If IsNothing(chaînedeConnexion) Then erreurs.Add("Le paramètre [OLEDBStringConnection] n'a pas été initialisé")
If IsNothing(defaultProduitsPage) Then
erreurs.Add("Le paramètre [defaultProduitsPage] n'a pas été initialisé")
Else
Try
Dim defProduitsPage As Integer = CType(defaultProduitsPage, Integer)
If defProduitsPage <= 0 Then Throw New Exception
Catch ex As Exception
erreurs.Add("Le paramètre [defaultProduitsPage] a une valeur incorrecte")
End Try
End If
' هل توجد أخطاء في التكوين؟
If erreurs.Count <> 0 Then
' تدوين الأخطاء
Application("erreurs") = erreurs
' نخرج
Exit Sub
End If
' لا توجد أخطاء في التكوين هنا
' يتم إنشاء كائن منتجات
Dim dtProduits As DataTable
Try
dtProduits = New produits(chaînedeConnexion).getProduits
Catch ex As ExceptionProduits
'حدث خطأ في الوصول إلى المنتجات، يتم تسجيله في التطبيق
Application("erreurs") = ex.erreurs
Exit Sub
Catch ex As Exception
' خطأ غير معالج
erreurs.Add(ex.Message)
Application("erreurs") = erreurs
' خروج من الدالة الفرعية
End Try
' لا توجد أخطاء في التهيئة هنا
' يتم حفظ عدد المنتجات في كل صفحة
Application("defaultProduitsPage") = defaultProduitsPage
' يتم حفظ جدول المنتجات
Application("dtProduits") = dtProduits
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' تهيئة متغيرات الجلسة
If IsNothing(Application("erreurs")) Then
' عرض جدول المنتجات
Session("dvProduits") = CType(Application("dtProduits"), DataTable).DefaultView
' عدد المنتجات في كل صفحة
Session("nbProduitsPage") = Application("defaultProduitsPage")
' الصفحة الحالية المعروضة
Session("pageCourante") = 0
End If
End Sub
End Class
في [Application_Start]، نبدأ باسترداد معلومتين من ملف التكوين [web.config] الخاص بالتطبيق:
- OLEDBStringConnection: سلسلة OLEDB للاتصال بقاعدة بيانات المنتجات
- defaultProduitsPage: العدد الافتراضي للمنتجات في كل صفحة معروضة
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="OLEDBStringConnection" value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\devel\aspnet\poly\webforms3\vs\majproduits1\produits.mdb" />
<add key="defaultProduitsPage" value="5" />
</appSettings>
</configuration>
إذا كانت إحدى هاتين المعلومتين غير موجودة، يتم إنشاء قائمة بالأخطاء وإدراجها في التطبيق. وينطبق الأمر نفسه إذا كان المعامل [defaultProduitsPage] موجودًا ولكنه غير صحيح. إذا كان المعلمتان المتوقعان موجودتين وصحيحتين، يتم إنشاء جدول [dtProduits] وإدراجه في التطبيق. هذا الجدول هو الذي سيتم استخدامه وتحديثه من قبل العملاء المختلفين. أما قاعدة البيانات، فستبقى دون تغيير. سنهتم بتحديثها في تطبيق قادم. يتم إنشاء هذه الجدولة من مثيل لفئة [produits] التي تمت دراستها سابقًا ومن طريقة [getProduits] الخاصة بها. قد يفشل الحصول على الجدولة [dtProduits]. في هذه الحالة، نعلم أن الفئة [produits] تطلق استثناءً من النوع [ExceptionProduits]. يتم اعتراضه هنا ويتم وضع قائمة الأخطاء المرتبطة به في التطبيق المرتبط بالمفتاح [erreurs]. سيتم اختبار وجود هذا المفتاح في المعلومات المسجلة في التطبيق عند كل معالجة لطلب. إذا تم العثور عليه، سيتم إرسال العرض [erreurs] إلى العميل.
إذا كانت الجدولة [dtProduits] مشتركة بين جميع عملاء الويب، فسيكون لكل منهم مع ذلك عرض [dvProduits] خاص به عليها. في الواقع، يمكن لكل عميل ويب تعيين مرشح على الجدول [dtProduits] بالإضافة إلى ترتيب الفرز. يتم تخزين هذه المعلومات الخاصة بكل عميل ويب في عرض العميل. ولذلك يتم إنشاؤها في [Session_Start] لوضعها في الجلسة الخاصة بكل عميل. يتم استخدام السمة [DefaultView] من الجدول [dtProduits] للحصول على عرض افتراضي للجدول. في البداية، لا يوجد مرشح ولا ترتيب فرز. علاوة على ذلك، يتم أيضًا وضع معلومتين في الجلسة:
- عدد المنتجات في كل صفحة للمفتاح [nbProduitsPage]. عند بدء الجلسة، يكون هذا العدد مساوياً للعدد الافتراضي المحدد في ملف التكوين.
- رقم الصفحة الحالية للمنتجات. في البداية، تكون هذه هي الصفحة الأولى.
9.6.6. رمز التحكم [main.aspx.vb]
هيكل وحدة التحكم هو كما يلي:
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Data
Imports st.istia.univangers.fr
Imports System
Imports System.Xml
Imports System.Web.UI.WebControls
Public Class main
Inherits System.Web.UI.Page
' مكونات الصفحة
Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
Protected WithEvents btnExécuter As System.Web.UI.WebControls.Button
Protected WithEvents vueFormulaire As System.Web.UI.WebControls.Panel
Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
Protected WithEvents lnkErreurs As System.Web.UI.WebControls.LinkButton
Protected WithEvents vueErreurs As System.Web.UI.WebControls.Panel
Protected WithEvents rdCroissant As System.Web.UI.WebControls.RadioButton
Protected WithEvents rdDécroissant As System.Web.UI.WebControls.RadioButton
Protected WithEvents txtFiltre As System.Web.UI.WebControls.TextBox
Protected WithEvents lblInfo2 As System.Web.UI.WebControls.Label
Protected WithEvents lblinfo1 As System.Web.UI.WebControls.Label
Protected WithEvents lblErreurs As System.Web.UI.WebControls.Label
Protected WithEvents DataGrid2 As System.Web.UI.WebControls.DataGrid
Protected WithEvents vueProduits As System.Web.UI.WebControls.Panel
Protected WithEvents vueAjout As System.Web.UI.WebControls.Panel
Protected WithEvents lnkMisajour As System.Web.UI.WebControls.LinkButton
Protected WithEvents lnkFiltre As System.Web.UI.WebControls.LinkButton
Protected WithEvents txtNom As System.Web.UI.WebControls.TextBox
Protected WithEvents txtPrix As System.Web.UI.WebControls.TextBox
Protected WithEvents btnAjouter As System.Web.UI.WebControls.Button
Protected WithEvents lblInfo3 As System.Web.UI.WebControls.Label
Protected WithEvents rfvLignes As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents rvLignes As System.Web.UI.WebControls.RangeValidator
Protected WithEvents rfvNom As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents rfvPrix As System.Web.UI.WebControls.RequiredFieldValidator
Protected WithEvents cvPrix As System.Web.UI.WebControls.CompareValidator
Protected WithEvents lnkAjout As System.Web.UI.WebControls.LinkButton
Protected WithEvents DataGrid3 As System.Web.UI.WebControls.DataGrid
Protected WithEvents DataGrid4 As System.Web.UI.WebControls.DataGrid
Protected WithEvents DataGrid5 As System.Web.UI.WebControls.DataGrid
' بيانات الصفحة
Protected dtProduits As DataTable
Protected dvProduits As DataView
Protected defaultProduitsPage As Integer
Protected erreur As Boolean = False
' تحميل الصفحة
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub afficheErreurs()
...
End Sub
Private Sub afficheFormulaire()
...
End Sub
Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
....
End Sub
Private Sub afficheProduits(ByVal page As Integer, ByVal taillePage As Integer)
...
End Sub
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
...
End Sub
Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
....
End Sub
Private Sub DataGrid1_DeleteCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.DeleteCommand
....
End Sub
Private Sub DataGrid1_EditCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.EditCommand
...
End Sub
Private Sub DataGrid1_CancelCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.CancelCommand
...
End Sub
Private Sub DataGrid1_UpdateCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.UpdateCommand
...
End Sub
Private Sub supprimerProduit(ByVal idProduit As Integer)
...
End Sub
Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
....
End Sub
Private Sub lnkMisajour_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkMisajour.Click
...
End Sub
Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
...
End Sub
Private Sub afficheAjout()
...
End Sub
Private Sub lnkFiltre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkFiltre.Click
....
End Sub
Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
....
End Sub
End Class
9.6.7. بيانات المثيل
تستخدم الفئة [main] بيانات المثيل التالية:
Public Class main
Inherits System.Web.UI.Page
' مكونات الصفحة
Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
...
' بيانات الصفحة
Protected dtProduits As DataTable
Protected dvProduits As DataView
Protected defaultProduitsPage As Integer
جدول المنتجات [DataTable] - مشترك بين جميع العملاء | |
عرض [DataView] للمنتجات - خاص بكل عميل | |
عدد المنتجات لكل صفحة المقترح افتراضيًا |
9.6.8. إجراء [Page_Load] لتحميل الصفحة
رمز الإجراء [page_Load] هو التالي:
' تحميل الصفحة
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' التحقق من وجود خطأ في التطبيق
If Not IsNothing(Application("erreurs")) Then
' لم يتم تهيئة التطبيق بشكل صحيح
afficheErreurs(CType(Application("erreurs"), ArrayList))
Exit Sub
End If
' استرداد مرجع من جدول المنتجات
dtProduits = CType(Application("dtProduits"), DataTable)
' استرداد عرض المنتجات
dvProduits = CType(Session("dvProduits"), DataView)
' يتم استرداد عدد المنتجات في كل صفحة
defaultProduitsPage = CType(Application("defaultProduitsPage"), Integer)
' إلغاء أي تحديث جاري
DataGrid1.EditItemIndex = -1
'الطلب الأول
If Not IsPostBack Then
' يتم عرض النموذج الأولي
txtPages.Text = defaultProduitsPage.ToString
afficheFormulaire()
End If
End Sub
- نبدأ أولاً بالتحقق مما إذا كان قد تم تحميل جدول المنتجات عند بدء تشغيل التطبيق. إذا لم يكن الأمر كذلك، يتم عرض العرض [erreurs] مع رسائل الخطأ المناسبة ثم يتم الخروج من الإجراء بعد تعيين القيمة المنطقية [erreur] على "صحيح". سيتم اختبار هذا المؤشر من خلال إجراءات معينة.
- يتم استرداد جدول المنتجات [dtProduits] في التطبيق ووضعه في متغير المثيل [dtProduits] حتى يتم مشاركته من قبل جميع طرق الصفحة.
- ونفعل الشيء نفسه مع العرض [dvProduits] الذي تم استرداده من الجلسة وعدد المنتجات الافتراضي لكل صفحة الذي تم استرداده من التطبيق.
- إذا كان هذا هو الطلب الأول للعميل، نعرض له نموذج تحديد شروط التصفية والترقيم مع ترقيم افتراضي لـ [defaultProduitsPage] منتجًا في كل صفحة.
- يتم إلغاء وضع التحرير للمكون [dataGrid1]. يحتوي هذا المكون على سمة [EditItemIndex] وهي مؤشر العنصر قيد التعديل. إذا كان [EditItemIndex]=-1، فهذا يعني أنه لا يوجد أي عنصر قيد التعديل. إذا كان [EditItemIndex]=i، فإن العنصر رقم i من المكون [DataGrid] قيد التحرير. وعندئذ يتم عرضه بشكل مختلف عن العناصر الأخرى في [dataGrid]:

فيما سبق، العنصر [Produit8, 80] في وضع [édition]. نرى أعلاه أن المستخدم يمكنه تأكيد تحديثه عبر الرابط [Mettre à jour]، وإلغائه عبر الرابط [Annuler]. كما يمكنه استخدام روابط أخرى لا علاقة لها بالعنصر قيد التحرير. بوضع [DataGrid1.EditItemIndex=-1] عند كل تحميل للصفحة، نضمن إلغاء وضع التحرير لـ [DataGrid1] بشكل منهجي. لن يتم تعيين قيمة مختلفة له إلا إذا تم النقر على رابط [Modifier]. سيتم ذلك في الإجراء الذي يعالج هذا الحدث. ستكون لدينا الحالات التالية:
- تم النقر على الرابط [Modifier] الخاص بالعنصر رقم 8 من [DataGrid1]. يقوم [Page_Load] أولاً بوضع -1 في [DataGrid1.EditItemIndex]. ثم ستقوم الإجراء الذي يعالج الحدث [Modifier] بوضع 8 في [DataGrid1.EditItemIndex]. في النهاية، عندما يتم إرجاع الصفحة إلى العميل، سيكون العنصر رقم 8 بالفعل في الوضع [édition] وسيظهر هذا كما هو موضح أعلاه.
- يقوم المستخدم بتعديل المنتج الموجود في الوضع [édition] ويؤكد تعديله عبر الرابط [Mettre à jour]. يقوم [Page_Load] بوضع -1 في [DataGrid1.EditItemIndex]. ثم سيتم تنفيذ الإجراء الذي يعالج الحدث [Mettre à jour] وسيتم تحديث العنصر رقم 8 في [dtProduits]. عندما يتم إرجاع الصفحة إلى العميل، لن يكون أي عنصر من [DataGrid1] في وضع التحرير (DataGrid.EditItemIndex=-1).
- إذا دخل المستخدم في عملية تحديث منتج، فمن المنطقي أن يتخلى عن هذا التحديث بواسطة [Annuler]. ومع ذلك، لا شيء يمنعه من النقر على رابط آخر. لذلك، يجب أن نأخذ هذه الحالة في الاعتبار. في هذه الحالة، كما في الحالات السابقة، يبدأ [Page_Load] بوضع -1 في [DataGrid1.EditItemIndex]، ثم يتم تنفيذ الإجراء الذي يعالج الحدث الذي وقع. ولن تقوم بتعديل الخاصية [DataGrid1.EditItemIndex] التي ستبقى بالتالي بقيمة -1. وعندما يتم إرجاع الصفحة إلى العميل، لن يكون أي عنصر من [DataGrid1] في وضع التحرير.
نلاحظ أنه من خلال تعيين [EditItemIndex] على -1 عند تحميل الصفحة، نتجنب الحاجة إلى القلق بشأن ما إذا كان المستخدم في وضع التحديث أم لا عندما نقر على رابط.
9.6.9. عرض طرق العرض [erreurs] و [formulaire] و [ajout]
يُطلب عرض العرض [erreurs] عند تحميل الصفحة [Page_Load] إذا تم اكتشاف أن التطبيق لم يتم تهيئته بشكل صحيح. يتم هذا العرض من خلال ربط مكون البيانات [rptErreurs] بقائمة الأخطاء التي تم تمريرها في المعلمات وإظهار الحاوية المناسبة. وأخيرًا، يتم إخفاء الروابط الثلاثة [Filtre, Mise à jour, Ajout] لأنه في حالة حدوث خطأ، لا يمكن استخدام التطبيق.
Private Sub afficheErreurs(ByVal erreurs As ArrayList)
' ربط قائمة الأخطاء بالمكرر rptErreurs
With rptErreurs
.DataSource = erreurs
.DataBind()
End With
'يتم تعطيل الخيارات
lnkAjout.Visible = False
lnkMisajour.Visible = False
lnkFiltre.Visible = False
' يتم عرض العرض [erreurs]
vueErreurs.Visible = True
vueFormulaire.Visible = False
vueProduits.Visible = False
vueAjout.Visible = False
End Sub
يتم طلب العروض الأخرى من خلال الخيارات المتاحة للمستخدم:

يتم طلب عرض العرض [Ajout] عندما ينقر المستخدم على الرابط [Ajout] في الصفحة:
Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
' يتم عرض العرض [ajout]
afficheAjout()
End Sub
Private Sub afficheAjout()
' عرض طريقة عرض الإضافة
vueAjout.Visible = True
vueFormulaire.Visible = False
vueProduits.Visible = False
vueErreurs.Visible = False
End Sub
يُطلب عرض طريقة العرض [Formulaire] عندما ينقر المستخدم على الرابط [Filtrage] في الصفحة. يجب عندئذ عرض طريقة العرض التي تسمح بتحديد
- تصفية جدول المنتجات
- عدد المنتجات المعروضة في كل صفحة ويب
يتم عرض نموذج يتضمن القيم المخزنة في الجلسة :
Private Sub lnkFiltre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkFiltre.Click
' تحديد شروط التصفية
txtFiltre.Text = dvProduits.RowFilter
' تحديد ترقيم الصفحات
txtPages.Text = CType(Session("nbProduitsPage"), String)
' يتم عرض نموذج التصفية
afficheFormulaire()
End Sub
يتم تعريف شرط تصفية العرض في سمة [RowFilter] الخاصة به. تجدر الإشارة إلى أن العرض الذي تمت تصفيته وتقسيمه إلى صفحات يسمى [dvProduits] وتم استرداده في الجلسة عند تحميل الصفحة [Page_Load]. وبالتالي، يتم استرداد شرط التصفية في [dvProduits.RowFilter]. كما يتم استرداد عدد المنتجات في كل صفحة من الجلسة. إذا لم يقم المستخدم بتحديد هذه المعلومة مطلقًا، فإن هذا العدد يساوي العدد الافتراضي للمنتجات في كل صفحة. بعد ذلك، يتم عرض النموذج الذي يسمح بتحديد هاتين المعلومتين:
Private Sub afficheFormulaire()
' عرض طريقة العرض [formulaire]
vueFormulaire.Visible = True
vueErreurs.Visible = False
vueProduits.Visible = False
vueAjout.Visible = False
End Sub

9.6.10. التحقق من صحة العرض [formulaire]
يتم معالجة النقر على الزر [Exécuter] أعلاه من خلال الإجراء التالي:
Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
' هل الصفحة صالحة؟
rfvLignes.Validate()
rvLignes.Validate()
If Not rfvLignes.IsValid Or Not rvLignes.IsValid Then
afficheFormulaire()
Exit Sub
End If
' إرفاق البيانات المفلترة بالجدول
Try
dvProduits.RowFilter = txtFiltre.Text.Trim
Catch ex As Exception
lblinfo1.Text = "Erreur de filtrage (" + ex.Message + ")"
afficheFormulaire()
Exit Sub
End Try
' يتم حفظ عدد المنتجات في كل صفحة
Session("nbProduitsPage") = txtPages.Text
'والصفحة الحالية
Session("pageCourante") = 0
' كل شيء على ما يرام - يتم عرض البيانات
afficheProduits(0, CType(txtPages.Text, Integer))
End Sub
دعونا نذكر أولاً أن الصفحة تحتوي على مكونين للتحقق من الصحة:
رقم | الاسم | النوع | الدور |
RequiredFieldValidator | يتحقق من وجود قيمة في [txtPages] | ||
RangeValidator | يتحقق من أن txtPages يقع في النطاق [3,10] |
تبدأ العملية بتنفيذ رمز التحقق للمكونين أعلاه باستخدام طريقتهم [Validate] ثم تتحقق من قيمة سمة [IsValid] الخاصة بهم. وتكون هذه القيمة هي [true] فقط إذا تم العثور على البيانات التي تم التحقق منها صالحة. إذا فشلت اختبارات صحة أحد المكونين، يتم إعادة عرض العرض [formulaire] حتى يقوم المستخدم بتصحيح أخطائه. يتم تطبيق شرط التصفية على السمة [dvProduits.RowFilter]. هنا قد تحدث استثناء إذا قام المستخدم بوضع معيار تصفية غير صحيح كما هو موضح أدناه:

في هذه الحالة، يتم إعادة عرض العرض [formulaire]. إذا كانت المعلومات المدخلة صحيحة، يتم وضع بيانين في الجلسة:
- عدد المنتجات في كل صفحة الذي اختاره المستخدم
- رقم الصفحة التي سيتم عرضها - في البداية الصفحة 0
يتم استخدام هاتين المعلومتين في كل مرة يتم فيها عرض [produits].
9.6.11. عرض العرض [produits]
الطريقة [produits] هي كما يلي:

لدينا 5 مكونات [DataGrid] يعرض كل منها عرضًا معينًا لجدول المنتجات [dtProduits]. بدءًا من اليسار:
الاسم | الدور |
شبكة عرض لمنظر مفلتر من جدول المنتجات. الفلتر هو الذي تم تحديده بواسطة المنظر [formulaire]. لدينا أيضًا .AllowPaging=true، .AllowSorting=true | |
يعرض الجدول الكامل للمنتجات - يتيح متابعة التحديثات | |
سيعرض المنتجات المحذوفة في جدول المنتجات | |
سيعرض المنتجات التي تم تعديلها في جدول المنتجات | |
سيعرض المنتجات المضافة في جدول المنتجات |
الإجراء [afficheProduits] مسؤول عن عرض العرض السابق:
Private Sub afficheProduits(ByVal page As Integer, ByVal taillePage As Integer)
' إرفاق البيانات بالمكونين [DataGrid]
Application.Lock()
With DataGrid1
.DataSource = dvProduits
.PageSize = taillePage
.CurrentPageIndex = page
.DataBind()
End With
Dim dvCurrent As New DataView(dtProduits)
dvCurrent.RowStateFilter = DataViewRowState.CurrentRows
With DataGrid2
.DataSource = dvCurrent
.DataBind()
End With
Dim dvSupp As DataView = New DataView(dtProduits)
dvSupp.RowStateFilter = DataViewRowState.Deleted
With DataGrid3
.DataSource = dvSupp
.DataBind()
End With
Dim dvModif As DataView = New DataView(dtProduits)
dvModif.RowStateFilter = DataViewRowState.ModifiedOriginal
With DataGrid4
.DataSource = dvModif
.DataBind()
End With
Dim dvAjout As DataView = New DataView(dtProduits)
dvAjout.RowStateFilter = DataViewRowState.Added
With DataGrid5
.DataSource = dvAjout
.DataBind()
End With
Application.UnLock()
' يتم عرض العرض [produits]
vueProduits.Visible = True
vueFormulaire.Visible = False
vueErreurs.Visible = False
vueAjout.Visible = False
' يتم حفظ الصفحة الحالية
Session("pageCourante") = page
End Sub
تقبل الإجراء معلمتين:
- رقم الصفحة [page] المراد عرضها في [DataGrid1]
- عدد المنتجات [taillePage] في كل صفحة
يتم ربط [DataGrid] بجدول المنتجات من خلال 5 طرق عرض مختلفة.
- ترتبط [DataGrid1] بالعرض المقسم إلى صفحات والمصنف [dvProduits].
With DataGrid1
.DataSource = dvProduits
.PageSize = taillePage
.CurrentPageIndex = page
.DataBind()
End With
عند ربط [DataGrid1] بمصدر البيانات الخاص به، نحتاج إلى المعلومات التي تم تمريرها كمعلمات إلى الإجراء.
- يرتبط [DataGrid2] بعرض يعرض جميع العناصر الحالية في الجدول [dtProduits]. وهو يعرض، مثل [DataGrid1]، عرضًا محدثًا للجدول [dtProduits] ولكن بدون ترقيم صفحات أو فرز.
Dim dvCurrent As New DataView(dtProduits)
dvCurrent.RowStateFilter = DataViewRowState.CurrentRows
With DataGrid2
.DataSource = dvCurrent
.DataBind()
End With
نستخدم هنا السمة [RowStateFilter] من الفئة [DataView]. يحتوي كل سطر في جدول أو في عرض هنا على سمة [RowState] تشير إلى حالة السطر. فيما يلي بعض منها:
- (تابع)
- [Added]: تمت إضافة السطر
- [Modified]: تم تعديل السطر
- [Deleted]: تم حذف السطر
- [Unchanged]: لم يتغير السطر
عندما تم إنشاء الجدول [dtProduits] في البداية في [Application_Start]، كانت جميع أسطره في الحالة [Unchanged]. ستتغير هذه الحالة مع تحديثات عملاء الويب:
- (تابع)
- عندما ينشئ عميل منتجًا جديدًا، تتم إضافة سطر إلى الجدول [dtProduits]. وسيكون في الحالة [Added].
- عندما يتم تعديل منتج، تتغير حالته من [Unchanged] إلى [Modified].
- عند حذف منتج، لا يتم حذف السطر فعليًا. بل يتم وضع علامة عليه على أنه مراد حذفه ويتغير حالته إلى [Deleted]. يمكن إلغاء هذا الحذف.
يسمح السمة [RowStateFilter] للفئة [DataView] بتصفية عرض وفقًا لحالة [RowState] لخطوطه. قيمها الممكنة هي تلك الموجودة في التعداد [DataRowViewState]:
- (تابع)
- DataRowViewState.CurrentRows: يتم عرض الأسطر غير المحذوفة بقيمها الحالية
- DataRowViewState.Added: يتم عرض الأسطر المضافة بقيمها الحالية
- DataRowViewState.Deleted: يتم عرض الأسطر المحذوفة بقيمها الأصلية
- DataRowViewState.ModifiedOriginal: يتم عرض الأسطر التي تم تعديلها مع قيمها الأصلية
- DataRowViewState.ModifiedCurrent: يتم عرض الأسطر المعدلة مع قيمها الحالية
يحتوي السطر المعدل على قيمتين: القيمة الأصلية التي كان السطر يحتوي عليها قبل التعديل الأول، والقيمة الحالية التي تم الحصول عليها بعد تعديل واحد أو أكثر. يتم الاحتفاظ بهاتين القيمتين في آن واحد.
تعرض الأسطر من 2 إلى 5 عرضًا للجدول [dtProduits] تمت تصفيته بواسطة السمة [RowStateFilter]
DataGrid | تصفية |
RowStateFilter= DataRowViewState.CurrentRows | |
RowStateFilter= DataRowViewState.Deleted | |
RowStateFilter= DataRowViewState.ModifiedOriginal | |
RowStateFilter= DataRowViewState.Added |
يتم تحديث الجدول [dtPoduits] في وقت واحد من قبل عملاء ويب مختلفين، مما قد يؤدي إلى تعارضات في الوصول إلى الجدول. عندما يعرض عميل ما طرق عرضه للجدول [dtProduits]، نريد تجنب قيامه بذلك بينما الجدول في حالة غير مستقرة لأنه قيد التعديل من قبل عميل ويب آخر. كما أنه في كل مرة يحتاج فيها عميل إلى الجدول [dtProduits] للقراءة كما هو مذكور أعلاه أو للكتابة عند إضافة منتجات أو تعديلها أو حذفها، فإنه سيتزامن مع العملاء الآخرين باستخدام التسلسل التالي:
قد توجد عدة أقسام حرجة في كود التطبيق:
وتتمثل آلية العمل في ما يلي:
- يصل عميل واحد أو أكثر إلى التعليمات [Application.Lock] في القسم الحرج 1. وهو موزع رمز دخول فريد. يحصل عميل واحد على هذا الرمز. لنسمه C1.
- ما لم يقم العميل C1 بإرجاع الرمز عبر [Application.Unlock]، لا يُسمح لأي عميل آخر بالدخول إلى قسم حرج يتحكم فيه [Application.Lock]. في المثال أعلاه، لا يمكن لأي عميل الدخول إلى القسمين الحرجين 1 و 2.
- يقوم العميل C1 بتنفيذ [Application.Unlock] وبالتالي يعيد رمز الدخول. يمكن بعد ذلك منح هذا الرمز لعميل آخر. يتم تكرار الخطوات من 1 إلى 3.
باستخدام هذه الآلية، سنضمن أن عميلاً واحداً فقط يمكنه الوصول إلى الجدول [dtProduits] سواء للقراءة أو الكتابة.
يعرض الرابط [Mise à jour] أيضًا العرض [Produits]:

الإجراء المرتبط هو التالي:
Private Sub lnkMisajour_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkMisajour.Click
' عرض عرض المنتجات
afficheProduits(CType(Session("pageCourante"), Integer), CType(Session("nbProduitsPage"), Integer))
End Sub
يجب عرض العرض [produits]. يتم ذلك باستخدام الإجراء [afficheProduits] الذي يقبل معلمتين: رقم الصفحة الحالية المراد عرضها وعدد المنتجات المراد عرضها في كل صفحة. توجد هاتان المعلومتان في الجلسة، مع قيمهما الأولية في حالة عدم قيام المستخدم بتحديدهما بنفسه.
9.6.12. ترقيم الصفحات والفرز في [DataGrid1]
لقد تعرفنا بالفعل على الإجراءات التي تدير ترقيم الصفحات والفرز في [DataGrid] في مثال آخر. في حالة تغيير الصفحة، يتم عرض عرض [produits] عن طريق تمرير رقم الصفحة الجديدة كمعلمة إلى الإجراء [afficheProduits].
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
' تغيير الصفحة
afficheProduits(e.NewPageIndex, DataGrid1.PageSize)
End Sub
يتم تنفيذ الإجراء [DataGrid1_SortCommand] عندما ينقر المستخدم على رأس أحد أعمدة [DataGrid1]. يجب عندئذٍ تعيين تعبير الفرز إلى السمة [Sort] في العرض [dvProduits] الذي يعرضه [DataGrid1]. وهذا التعبير مكافئ من حيث الصياغة لتعبير الفرز الموجود خلف جملة [order by] في الأمر SQL SELECT. يحتوي الوسيط [e] للإجراء على سمة [SortExpression] التي تعطينا تعبير الفرز المرتبط بالعمود الذي تم النقر على عنوانه. عند إنشاء المكون [DataGrid1]، تم تعريف تعبير الفرز هذا. فيما يلي، التعبير المحدد للعمود [nom] من [DataGrid1]:

إذا لم يتم تعريف تعبير الفرز عند تصميم [DataGrid]، فسيتم استخدام اسم حقل البيانات المرتبط بعمود [DataGrid]. يتم تحديد اتجاه الفرز (تصاعدي، تنازلي) هنا من قبل المستخدم باستخدام أزرار الاختيار [rdCroissant, rd Décroissant]:

إجراء الفرز هو كما يلي:
Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
' يتم فرز عرض البيانات
With dvProduits
.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
End With
' عرضها
afficheProduits(0, DataGrid1.PageSize)
End Sub
9.6.13. حذف منتج
لحذف منتج، يستخدم المستخدم الرابط [Supprimer] الموجود في سطر المنتج:

ثم يتم تنفيذ الإجراء [DataGrid1_DeleteCommand]:
Private Sub DataGrid1_DeleteCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.DeleteCommand
' حذف منتج
' مفتاح المنتج المراد حذفه
Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
' حذف المنتج
Dim erreur As Boolean = False
Try
supprimerProduit(idProduit)
Catch ex As Exception
' مشكلة
lblInfo2.Text = ex.Message
erreur = True
End Try
' تغيير الصفحة؟
Dim page As Integer = DataGrid1.CurrentPageIndex
If Not erreur AndAlso DataGrid1.Items.Count = 1 Then
page = DataGrid1.CurrentPageIndex - 1
If page < 0 Then page = 0
End If
' عرض المنتجات
afficheProduits(page, DataGrid1.PageSize)
End Sub
سيتم تحديث المنتجات عبر مفتاح المنتج [id]. في الواقع، العمود [id] في الجدول [dtProduits] هو مفتاح أساسي، وبفضله يمكننا العثور في الجدول [dtProduits] على سطر المنتج الذي تم تحديثه في المكون [DataGrid1]. لذلك تبدأ العملية باسترداد مفتاح المنتج الذي تم النقر على الرابط [Supprimer] الخاص به:
' مفتاح المنتج المراد حذفه
Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
نحن نعلم أن [e.Item] هو العنصر في [DataGrid1] الذي تسبب في الحدث. بشكل عام، هذا العنصر هو سطر من [DataGrid]. [e.Item.ItemIndex] هو رقم السطر الذي تسبب في الحدث. هذا الفهرس مرتبط بالصفحة الحالية المعروضة. وبالتالي، فإن السطر الأول من الصفحة المعروضة له الخاصية [ItemIndex=0] حتى لو كان رقمه 17 في جدول المنتجات. [DataGrid1.DataKeys] هي قائمة مفاتيح [DataGrid]. ولأننا كتبنا، عند التصميم، [DataGrid1.DataKey=id]، فإن مفاتيح [DataGrid1] تتكون من قيم العمود [id] في الجدول [dtProduits] الذي هو في الوقت نفسه مفتاح أساسي. وبالتالي، فإن [DataGrid1.DataKeys(e.Item.ItemIndex)] هو المفتاح [id] للمنتج المراد حذفه. وبمجرد الحصول على ذلك، نطلب حذفه باستخدام الإجراء [supprimerProduit]:
' حذف المنتج
Dim erreur As Boolean = False
Try
supprimerProduit(idProduit)
Catch ex As Exception
' مشكلة
lblInfo2.Text = ex.Message
erreur = True
End Try
قد تفشل عملية الحذف هذه في بعض الحالات. سنرى السبب. عندئذ يتم إصدار استثناء. ويتم التعامل معه هنا. في حالة وجود خطأ، لا يتعين تغيير [DataGrid1]. يتم فقط إضافة رسالة خطأ إلى العرض [produits]. إذا لم يكن هناك خطأ وإذا كان المستخدم قد حذف للتو المنتج الوحيد من الصفحة الحالية، يتم عرض الصفحة السابقة.
' تغيير الصفحة؟
Dim page As Integer = DataGrid1.CurrentPageIndex
If Not erreur AndAlso DataGrid1.Items.Count = 1 Then
page = DataGrid1.CurrentPageIndex - 1
If page < 0 Then page = 0
End If
في جميع الحالات، يتم إعادة عرض العرض [produits] باستخدام الإجراء [afficheProduits]:
الإجراء [supprimerProduit] هو كما يلي:
Private Sub supprimerProduit(ByVal idProduit As Integer)
Dim erreur As String
Try
' المزامنة
Application.Lock()
' البحث عن السطر المراد حذفه
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' حذف السطر
ligne.Delete()
End If
Catch ex As Exception
erreur = String.Format("Erreur de suppression : {0}", ex.Message)
Finally
' نهاية المزامنة
Application.UnLock()
End Try
' إطلاق استثناء في حالة حدوث خطأ
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
تستقبل الإجراء كمعلمة مفتاح المنتج المراد حذفه. إذا كان هناك عميل واحد فقط يقوم بالتحديثات، فيمكن إجراء هذا الحذف على النحو التالي:
مع وجود عدة عملاء يقومون بالتحديثات في نفس الوقت، يصبح الأمر أكثر تعقيدًا. في الواقع، لنفكر في التسلسل الزمني التالي:
![]() |
الوقت | الإجراء |
T1 | يقوم العميل A بقراءة الجدول [dtProduits] - يوجد منتج بمفتاح 20 |
T2 | يقوم العميل B بقراءة الجدول [dtProduits] - يوجد منتج للمفتاح 20 |
T3 | يقوم العميل A بحذف المنتج ذي المفتاح 20 |
T4 | يقوم العميل B بحذف المنتج ذي المفتاح 20 |
عندما يقرأ العميلان A و B جدول المنتجات ويعرضان محتواه في صفحة ويب، يحتوي الجدول على المنتج ذي المفتاح 20. لذلك قد يرغبان في القيام بعمل ما على هذا المنتج، مثل حذفه. لا بد أن يقوم أحدهما بذلك أولاً. ومن سيأتي بعده سيرغب في حذف منتج لم يعد موجوداً. يتم التعامل مع هذه الحالة من خلال الإجراء [supprimerProduits] بعدة طرق:
- أولاً، هناك مزامنة مع [Application.Lock]. وهذا يعني أنه عندما يتجاوز أحد العملاء هذا الحاجز، لا يوجد شخص آخر يقوم بتعديل أو قراءة جدول المنتجات. في الواقع، تتم مزامنة جميع هذه العمليات بهذه الطريقة. وقد رأينا ذلك بالفعل بالنسبة للقراءة.
- ثم يتم التحقق مما إذا كان المنتج الذي نريد حذفه موجودًا. إذا كان موجودًا، يتم حذفه، وإلا يتم إعداد رسالة خطأ.
' البحث عن السطر المراد حذفه
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' حذف السطر
ligne.Delete()
End If
- يتم الخروج من التسلسل الحرج بواسطة [Application.Unlock] للسماح للعملاء الآخرين بإجراء تحديثاتهم.
- إذا تعذر حذف السطر، فإن الإجراء يطلق استثناءً مرتبطًا برسالة خطأ.
لنلقِ نظرة على مثال. نطلق عميل [Mozilla] ونختار على الفور الخيار [Mise à jour] (عرض جزئي):

ونفعل الشيء نفسه مع عميل [Internet Explorer] (عرض جزئي):

مع العميل [Mozilla]، نحذف المنتج [produit2]. نحصل على الصفحة الجديدة التالية:

تمت عملية الحذف بنجاح. يظهر المنتج المحذوف في [DataGrid] ضمن المنتجات المحذوفة ولم يعد يظهر في قائمة منتجات المكونات [DataGrid1] و [DataGrid2] التي تعرض المنتجات الموجودة حاليًا في الجدول. لنفعل الشيء نفسه مع [Interbet Explorer]. نقوم بحذف العنصر [produit2]:

والنتيجة التي نحصل عليها هي التالية:

تشير رسالة خطأ إلى المستخدم بأن المنتج ذو المفتاح [2] غير موجود. ترسل الإجابة إلى العميل عرضًا جديدًا يعكس الحالة الحالية للجدول. نرى أن المنتج ذو المفتاح [2] موجود بالفعل في قائمة المنتجات المحذوفة. وبالتالي، تعكس طريقة العرض [produits] تحديثات جميع العملاء وليس عميل واحد فقط.
9.6.14. إضافة منتج
لإضافة منتج، يستخدم المستخدم الرابط [Ajout] من الخيارات. ويكتفي هذا الرابط بعرض العرض [Ajout]:


الإجراءان المتضمنان في هذه العملية هما:
Private Sub lnkAjout_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkAjout.Click
' يتم عرض العرض [ajout]
afficheAjout()
End Sub
Private Sub afficheAjout()
' عرض طريقة العرض "إضافة"
vueAjout.Visible = True
vueFormulaire.Visible = False
vueProduits.Visible = False
vueErreurs.Visible = False
End Sub
تجدر الإشارة إلى أن العرض [Ajout] يحتوي على مكونات التحقق من الصحة:
الاسم | النوع | الدور |
RequiredFieldValidator | يتحقق من وجود قيمة في [txtNom] | |
RequiredFieldValidator | يتحقق من وجود قيمة في [txtPrix] | |
CompareValidator | يتحقق من أن السعر >= 0 |
إجراء إدارة النقر على الزر [Ajouter] هو كما يلي:
Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
' إضافة عنصر جديد إلى جدول المنتجات
' يجب أولاً أن تكون البيانات صالحة
rfvNom.Validate()
rfvPrix.Validate()
cvPrix.Validate()
If Not rfvNom.IsValid Or Not rfvPrix.IsValid Or Not cvPrix.IsValid Then
' مرة أخرى نموذج الإدخال
afficheAjout()
Exit Sub
End If
' يتم إنشاء سطر
Dim produit As DataRow = dtProduits.NewRow
produit("nom") = txtNom.Text.Trim
produit("prix") = txtPrix.Text.Trim
' إضافة السطر إلى الجدول
Application.Lock()
Try
dtProduits.Rows.Add(produit)
lblInfo3.Text = "Ajout réussi"
' التنظيف
txtNom.Text = ""
txtPrix.Text = ""
Catch ex As Exception
lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
End Try
Application.UnLock()
End Sub
أولاً، يتم تنفيذ عمليات التحقق من صحة المكونات الثلاثة [rfvNom, rfvPrix, cvPrix]. إذا فشلت إحدى عمليات التحقق، يتم إعادة عرض العرض [Ajout] مع رسائل الأخطاء الخاصة بمكونات التحقق من الصحة. فيما يلي مثال على ذلك:

إذا كانت البيانات صالحة، يتم إعداد السطر المراد إدراجه في الجدول [dtProduits].
' يتم إنشاء سطر
Dim produit As DataRow = dtProduits.NewRow
produit("nom") = txtNom.Text.Trim
produit("prix") = txtPrix.Text.Trim
يتم أولاً إنشاء صف جديد في الجدول [dtProduits] باستخدام الطريقة [DataTable.NewRow]. وسيحتوي هذا الصف على الأعمدة الثلاثة [id, nom, prix] الموجودة في الجدول [dtProduits]. يتم ملء الأعمدة [nom, prix] بالقيم التي تم إدخالها في نموذج الإضافة. أما العمود [id] فلا يتم ملؤه. هذا العمود هو من النوع [AutoIncrement]، c.a.d. حيث سيُعطى SGBD كمفتاح لمنتج جديد، وهو المفتاح الأقصى الموجود مضافًا إليه 1. السطر [produit] الذي تم إنشاؤه هنا منفصل عن الجدول [dtProduits]. علينا الآن إدراجه في هذا الجدول. ولأننا سنقوم بتحديث الجدول [dtProduits]، فإننا نقوم بتنشيط المزامنة بين العملاء:
بعد ذلك، نحاول إضافة السطر إلى الجدول [dtProduits]. إذا نجحنا، نعرض رسالة نجاح، وإلا نعرض رسالة خطأ.
Try
dtProduits.Rows.Add(produit)
lblInfo3.Text = "Ajout réussi"
' تنظيف
txtNom.Text = ""
txtPrix.Text = ""
Catch ex As Exception
lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
End Try
لا توجد عادة أي استثناءات ممكنة. ومع ذلك، تم وضع نظام لإدارة الاستثناءات كإجراء احترازي. بعد الإضافة، نغادر القسم الحرج بواسطة [Application.Unlock].
9.6.15. تعديل منتج
لتعديل منتج، يستخدم المستخدم الرابط [Modifier] الموجود في سطر المنتج:

ثم ننتقل إلى وضع التحرير:

بفضل المكون [DataGrid]، يتم الحصول على هذه النتيجة باستخدام القليل جدًا من التعليمات البرمجية:
Private Sub DataGrid1_EditCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.EditCommand
' وضع العنصر الحالي في وضع التحرير
DataGrid1.EditItemIndex = e.Item.ItemIndex
' إعادة عرض المنتجات
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
End Sub
يؤدي النقر على الرابط [Modifier] إلى تنفيذ الإجراء [DataGrid1_EditCommand] على جانب الخادم. يحتوي المكون [DataGrid1] على حقل [EditItemIndex]. يتم وضع السطر الذي يحمل قيمة [EditItemIndex] في وضع التحرير. يمكن تعديل كل قيمة في السطر الذي تم تحديثه بهذه الطريقة في مربع إدخال كما هو موضح في لقطة الشاشة أعلاه. لذلك، نسترد مؤشر المنتج الذي تم النقر عليه في الرابط [Modifier] ونعيّنه للسمة [EditItemIndex] للمكون [DataGrid1]. لم يتبق لنا سوى إعادة عرض طريقة العرض [produits]. ستظهر كما كانت من قبل ولكن مع سطر في وضع التحرير.
يتيح الرابط [Annuler] للمنتج قيد التحرير للمستخدم إلغاء التحديث. الإجراء المرتبط بهذا الرابط هو كما يلي:
Private Sub DataGrid1_CancelCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.CancelCommand
' إعادة عرض المنتجات
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
End Sub
وهي تكتفي بإعادة عرض العرض [produits]. قد نميل إلى إدخال -1 في [DataGrid1.EditItemIndex] لإلغاء وضع التحديث. في الواقع، نعلم أن الإجراء [Page_Load] يقوم بذلك بشكل منهجي. لذا، لا داعي لإعادة القيام بذلك.
يتم التحقق من صحة التعديل من خلال الرابط [Mettre à jour] في السطر قيد التحرير. ثم يتم تنفيذ الإجراء التالي:
Private Sub DataGrid1_UpdateCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles DataGrid1.UpdateCommand
' مفتاح المنتج المراد تعديله
Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
' العناصر التي تم تعديلها
Dim nom As String = CType(e.Item.Cells(0).Controls(0), TextBox).Text.Trim
Dim prix As String = CType(e.Item.Cells(1).Controls(0), TextBox).Text.Trim
' هل التعديلات صالحة؟
lblInfo2.Text = ""
If nom = String.Empty Then lblInfo2.Text += "[Indiquez un nom]"
If prix = String.Empty Then
lblInfo2.Text += "[Indiquez un prix]"
Else
Dim nouveauPrix As Double
Try
nouveauPrix = CType(prix, Double)
If nouveauPrix < 0 Then Throw New Exception
Catch ex As Exception
lblInfo2.Text += "[prix invalide]"
End Try
End If
' في حالة وجود خطأ، يتم إعادة عرض صفحة التحديث
If lblInfo2.Text <> String.Empty Then
' إعادة السطر إلى وضع التحديث
DataGrid1.EditItemIndex = e.Item.ItemIndex
' عرض المنتجات
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
' النهاية
Exit Sub
End If
' في حالة عدم وجود خطأ - يتم تعديل الجدول
Try
modifierProduit(idProduit, e.Item)
Catch ex As Exception
' مشكلة
lblInfo2.Text = ex.Message
End Try
' عرض المنتجات
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
End Sub
كما هو الحال بالنسبة للحذف، يتعين علينا استرداد مفتاح المنتج المراد تعديله من أجل العثور على السطر الخاص به في جدول المنتجات [dtProduits]:
' مفتاح المنتج المراد تعديله
Dim idProduit As Integer = CType(DataGrid1.DataKeys(e.Item.ItemIndex), Integer)
بعد ذلك، علينا استرداد القيم الجديدة التي سيتم تعيينها للسطر. وتوجد هذه القيم في المكون [DataGrid1]. وتوجد الحجة [e] الخاصة بالإجراء لمساعدتنا في ذلك. يمثل [e.Item] السطر من [DataGrid1] مصدر الحدث. وبالتالي، فهو السطر الذي يجري تحديثه لأن الرابط [Mettre à jour] موجود فقط في هذا السطر. يحتوي هذا السطر على أعمدة محددة بواسطة مجموعة [Cells] للسطر. وبالتالي، يمثل [e.Item.Cells(0)] العمود 0 من السطر الذي يتم تحديثه. ونعلم أن القيم الجديدة موجودة في حقول الإدخال. تمثل المجموعة [e.Item.Cells(i).Controls] مجموعة عناصر التحكم في العمود i من السطر [e.Item]. تسترد التعليمتان التاليتان قيم مربعات الإدخال للسطر الذي تم تحديثه في [DataGrid1]:
' العناصر المعدلة
Dim nom As String = CType(e.Item.Cells(0).Controls(0), TextBox).Text.Trim
Dim prix As String = CType(e.Item.Cells(1).Controls(0), TextBox).Text.Trim
وبذلك نحصل على القيم الجديدة للسطر المعدل في شكل سلاسل أحرف. ثم نتحقق من صحة هذه البيانات. يجب ألا يكون الاسم فارغًا ويجب أن يكون السعر عددًا موجبًا أو صفرًا:
' هل التعديلات صالحة؟
lblInfo2.Text = ""
If nom = String.Empty Then lblInfo2.Text += "[Indiquez un nom]"
If prix = String.Empty Then
lblInfo2.Text += "[Indiquez un prix]"
Else
Dim nouveauPrix As Double
Try
nouveauPrix = CType(prix, Double)
If nouveauPrix < 0 Then Throw New Exception
Catch ex As Exception
lblInfo2.Text += "[prix invalide]"
End Try
End If
في حالة حدوث خطأ، ستعرض العلامة [lblInfo2] رسالة خطأ ونكتفي بإعادة عرض نفس الصفحة:
' في حالة وجود خطأ، يتم إعادة عرض صفحة التحديث
If lblInfo2.Text <> String.Empty Then
' إعادة السطر إلى وضع التحديث
DataGrid1.EditItemIndex = e.Item.ItemIndex
' عرض المنتجات
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
' النهاية
Exit Sub
End If
ما لا يظهره الرمز أعلاه هو أن القيم التي تم إدخالها قد ضاعت. في الواقع، يرتبط [DataGrid1] ببيانات الجدول [dtProduits] الذي يحتوي على القيم الأصلية للسطر الذي تم تعديله. إليك مثال على ذلك.

الرد الذي تم الحصول عليه هو التالي:

نلاحظ أننا فقدنا القيم التي تم إدخالها في البداية. في تطبيق احترافي، قد لا يكون هذا مقبولاً على الأرجح. نصل هنا إلى بعض حدود الوضع القياسي لتحديث المكون [DataGrid]. سيكون من الأفضل أن يكون لدينا عرض [Modification] مشابه لعرض [Ajout].
إذا كانت البيانات صالحة، يتم استخدامها لتحديث الجدول [dtProduits]:
' في حالة عدم وجود خطأ - يتم تعديل الجدول
Try
modifierProduit(idProduit, e.Item)
Catch ex As Exception
' مشكلة
lblInfo2.Text = ex.Message
End Try
' عرض المنتجات
afficheProduits(DataGrid1.CurrentPageIndex, DataGrid1.PageSize)
للسبب نفسه الذي تم ذكره عند حذف منتج، قد تفشل عملية التعديل. في الواقع، بين اللحظة التي قرأ فيها العميل الجدول [dtProduits] واللحظة التي سيقوم فيها بتعديل أحد منتجاته، قد يكون هذا المنتج قد تم حذفه من قبل عميل آخر. تتعامل الإجراء [modifierProduit] مع هذه الحالة عن طريق إصدار استثناء. يتم التعامل مع هذا الاستثناء هنا. بعد نجاح أو فشل التحديث، تعيد التطبيق عرض [produits] إلى العميل. يبقى لنا أن نرى كيف تقوم الإجراء [modifierProduit] بإجراء التحديث:
Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
Dim erreur As String
Try
' المزامنة
Application.Lock()
' البحث عن السطر المراد تعديله
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' يتم تعديل السطر
With ligne
.Item("nom") = CType(item.Cells(0).Controls(0), TextBox).Text.Trim
.Item("prix") = CType(item.Cells(1).Controls(0), TextBox).Text.Trim
End With
End If
Catch ex As Exception
erreur = String.Format("Erreur lors de la modification : {0}", ex.Message)
Finally
' نهاية المزامنة
Application.UnLock()
End Try
' إطلاق استثناء في حالة حدوث خطأ
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
لن ندخل في تفاصيل هذا الإجراء الذي يشبه في رمزه الإجراء [supprimerProduit] الذي تم شرحه بالتفصيل. لنلقِ نظرة على مثالين فقط. أولاً، نقوم بتعديل سعر المنتج [produit1]:

يؤدي استخدام الرابط [Mettre à jour] أعلاه إلى الرد التالي:

التعديل موجود بالفعل في المكونات [DataGrid] 1 و 2 التي تعكس الحالة الحالية للجدول [dtProduits]. كما أنها موجودة في المكون [DataGrid4] الذي يمثل عرضًا للصفوف المعدلة، حيث يتم عرضها بقيمها الأصلية. والآن لنلقِ نظرة على حالة تعارض الوصول. كما هو الحال في مثال الحذف، سنستخدم عميلين ويب مختلفين. يقوم عميل [Mozilla] بقراءة الجدول [dtProduits] ويبدأ في تعديل المنتج [produit1]:

يستعد العميل [Internet Explorer] لحذف المنتج [produit1]:

يقوم العميل [Internet Explorer] بحذف [produit1]:

يُلاحظ أن [produit1] لم يعد موجودًا في جدول الأسطر المعدلة بل في جدول الأسطر المحذوفة. يقوم العميل [Mozilla] بالتحقق من صحة تحديثه:

يتلقى العميل [Mozilla] الرد التالي على تحديثه:

يمكنه ملاحظة أن [produit1] قد تم حذفه لأنه موجود في قائمة المنتجات المحذوفة.
9.7. تطبيق ويب لتحديث الجدول الفعلي للمنتجات
9.7.1. الحلول المقترحة
كان التطبيق السابق بمثابة مثال نظري يهدف إلى توضيح إدارة كائن [DataTable] في ذاكرة التخزين المؤقت أكثر منه حالة شائعة. فمن الضروري، في وقت ما، تحديث مصدر البيانات الفعلي. يمكن اختيار استراتيجيتين مختلفتين:
- استخدام ذاكرة التخزين المؤقت [dtProduits] في الذاكرة لتحديث مصدر البيانات. يمكن توفير صفحة موجودة في شجرة الويب للتطبيق السابق من أجل الوصول إلى ذاكرة التخزين المؤقت [dtProduits] الخاصة به. ستتيح هذه الصفحة للمسؤول إمكانية نقل التعديلات الموجودة في ذاكرة التخزين المؤقت [dtProduits] إلى مصدر البيانات الفعلي. ولتحقيق ذلك، يمكن إضافة طريقة جديدة إلى فئة الوصول [produits] التي تقبل ذاكرة التخزين المؤقت [dtProduits] كمعلمة وتقوم باستخدام هذه الذاكرة بتحديث مصدر البيانات الفعلي.
- يتم تحديث مصدر البيانات الفعلي في نفس الوقت الذي يتم فيه تحديث ذاكرة التخزين المؤقت.
تسمح الاستراتيجية رقم 1 بفتح اتصال واحد فقط مع مصدر البيانات الفعلي. تتطلب الاستراتيجية رقم 2 اتصالاً عند كل تحديث. ويمكن تفضيل إحدى الاستراتيجيتين حسب توفر الاتصالات. ونظرًا لأن لدينا الأدوات اللازمة لتنفيذها (الفئة [produits])، فإننا نختار الاستراتيجية رقم 2.
9.7.2. الحل 1
نختار الاستراتيجية التالية حرصًا على الاستمرارية مع التطبيق السابق:
- يتم تحديث المصدر الفعلي في نفس الوقت الذي يتم فيه تحديث ذاكرة التخزين المؤقت
- لا يتم إنشاء ذاكرة التخزين المؤقت إلا مرة واحدة عند تهيئة التطبيق في [global.asax.vb]. وهذا يعني أنه إذا تم تحديث مصدر البيانات الفعلي بواسطة عملاء آخرين غير عملاء الويب، فإن هؤلاء لا يرون هذه التعديلات. إنهم يرون فقط التعديلات التي يضيفونها هم أنفسهم إلى الجدول في ذاكرة التخزين المؤقت.
لتحديث المصدر الفعلي للبيانات، نحتاج إلى مثيل لفئة الوصول إلى المنتجات. يمكن أن يكون لكل عميل مثيله الخاص. يمكننا أيضًا مشاركة مثيل واحد يتم إنشاؤه بواسطة التطبيق عند بدء تشغيله. هذا هو الحل الذي نختاره هنا. يتم تعديل كود التحكم [global.asax.vb] على النحو التالي:
Imports System
Imports System.Web
...
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' استرداد معلومات التكوين
Dim chaînedeConnexion As String = ConfigurationSettings.AppSettings("OLEDBStringConnection")
Dim defaultProduitsPage As String = ConfigurationSettings.AppSettings("defaultProduitsPage")
Dim erreurs As New ArrayList
...
' لا توجد أخطاء في التكوين هنا
' يتم إنشاء كائن منتجات
Dim objProduits As New produits(chaînedeConnexion)
Dim dtProduits As DataTable
Try
dtProduits = objProduits.getProduits
Catch ex As ExceptionProduits
'حدث خطأ في الوصول إلى المنتجات، يتم تسجيله في التطبيق
Application("erreurs") = ex.erreurs
Exit Sub
Catch ex As Exception
' خطأ غير معالج
erreurs.Add(ex.Message)
Application("erreurs") = erreurs
' خروج من الدالة الفرعية
End Try
' لا توجد أخطاء في التهيئة هنا
...
' يتم حفظ مثيل الوصول إلى البيانات
Application("objProduits") = objProduits
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
...
End Sub
End Class
تم تخزين مثيل لفئة الوصول إلى البيانات في التطبيق، مرتبط بالمفتاح [objProduits]. سيستخدم كل عميل هذا المثيل للوصول إلى مصدر البيانات الفعلي. سيتم استرداده في الإجراء [Page_Load] من [main.aspx.vb]:
Protected objProduits As produits
....
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
' استرداد مثيل الوصول إلى المنتجات
objProduits = CType(Application("objProduits"), produits)
...
End Sub
يمكن الوصول إلى مثيل الوصول إلى البيانات [objProduits] من جميع طرق الصفحة. وسيتم استخدامه في عمليات التحديث الثلاث: الإضافة والحذف والتعديل.
يتم تعديل إجراء الإضافة على النحو التالي:
Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
...
' يتم إضافة السطر
Application.Lock()
Try
' يتم إضافة السطر إلى الجدول المخزن مؤقتًا
dtProduits.Rows.Add(produit)
' يتم إضافة السطر إلى المصدر الفعلي
Dim nouveauProduit As sProduit
With nouveauProduit
.nom = CType(produit("nom"), String)
.prix = CType(produit("prix"), Double)
End With
objProduits.ajouterProduit(nouveauProduit)
' المتابعة
lblInfo3.Text = "Ajout réussi"
' التنظيف
txtNom.Text = ""
txtPrix.Text = ""
Catch ex As Exception
' خطأ
lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
End Try
Application.UnLock()
End Sub
يتم تعديل إجراء التعديل على النحو التالي:
Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
Dim erreur As String
Try
' المزامنة
Application.Lock()
' البحث عن السطر المراد تعديله
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' يتم تعديل السطر في ذاكرة التخزين المؤقت
With ligne
.Item("nom") = CType(item.Cells(0).Controls(0), TextBox).Text.Trim
.Item("prix") = CType(item.Cells(1).Controls(0), TextBox).Text.Trim
End With
' يتم تعديل السطر في المصدر الفعلي
Dim nouveauProduit As sProduit
With nouveauProduit
.id = idProduit
.nom = CType(ligne.Item("nom"), String)
.prix = CType(ligne.Item("prix"), Double)
End With
objProduits.modifierProduit(nouveauProduit)
End If
Catch ex As Exception
erreur = String.Format("Erreur lors de la modification : {0}", ex.Message)
Finally
' نهاية المزامنة
Application.UnLock()
End Try
' إطلاق استثناء في حالة حدوث خطأ
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
تم تعديل إجراء الحذف على النحو التالي:
Private Sub supprimerProduit(ByVal idProduit As Integer)
Dim erreur As String
Try
' المزامنة
Application.Lock()
' البحث عن السطر المراد حذفه
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' حذف السطر من ذاكرة التخزين المؤقت
ligne.Delete()
' حذف السطر من المصدر الفعلي
objProduits.supprimerProduit(idProduit)
End If
Catch ex As Exception
erreur = String.Format("Erreur de suppression : {0}", ex.Message)
Finally
' نهاية المزامنة
Application.UnLock()
End Try
' إطلاق استثناء في حالة حدوث خطأ
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
9.7.3. الاختبارات
نبدأ من جدول البيانات التالي في ملف ACCESS:

يتم تشغيل عميل ويب:

نضيف منتجًا:

نقوم بحذف [produit1]:

نقوم بتعديل سعر [produit2]:

بعد هذه التعديلات، ننظر إلى محتوى الجدول [liste] في قاعدة البيانات ACCESS:

تم أخذ التعديلات الثلاثة في الاعتبار بشكل صحيح في الجدول الفعلي. لنفحص الآن حالة تعارض. نحذف، مباشرةً تحت ACCESS، السطر الخاص بـ [produit2]:

نعود إلى عميل الويب لدينا. لا يرى هذا العميل عملية الحذف التي تمت، ويريد بدوره حذف [produit2]:

ويحصل على الرد التالي:

تم حذف السطر [produit2] من ذاكرة التخزين المؤقتة كما هو موضح في قائمة المنتجات المحذوفة. أما حذف [produit2] من المصدر الفعلي فقد فشل كما هو موضح في رسالة الخطأ.
9.7.4. الحل 2
في الحل السابق، يقوم عملاء الويب بتحديث مصدر البيانات الفعلي في وقت واحد، لكنهم لا يرون التغييرات التي أجراها العملاء الآخرون. إنهم يرون تغييراتهم فقط. نريد الآن أن يتمكن العميل من رؤية مصدر البيانات الفعلي كما هو حالياً وليس كما كان عند تشغيل التطبيق. لهذا، سنقدم للعميل خياراً جديداً:

باستخدام الخيار [Rafraîchir]، يفرض العميل إعادة قراءة مصدر البيانات الفعلي. لكي لا يؤثر ذلك على العملاء الآخرين، يجب أن تنتمي الجدولة الناتجة عن هذه القراءة إلى العميل الذي يقوم بالتحديث وألا يتم مشاركتها مع العملاء الآخرين. هذا هو الاختلاف الأول عن التطبيق السابق. سيتم إنشاء ذاكرة التخزين المؤقت [dtProduits] لمصدر البيانات بواسطة كل عميل وليس بواسطة التطبيق نفسه. يتم إجراء التعديل في [global.asax.vb]:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Data
Imports Microsoft.VisualBasic
Imports System.Collections
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' استرداد معلومات التكوين
...
' لا توجد أخطاء في التكوين هنا
' يتم إنشاء كائن منتجات
Dim objProduits As New produits(chaînedeConnexion)
' يتم حفظ عدد المنتجات في كل صفحة
Application("defaultProduitsPage") = defaultProduitsPage
' يتم حفظ مثيل الوصول إلى البيانات
Application("objProduits") = objProduits
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' تتم تهيئة متغيرات الجلسة
If IsNothing(Application("erreurs")) Then
' يتم تخزين مصدر البيانات في ذاكرة التخزين المؤقت
Dim dtProduits As DataTable
Try
Application.Lock()
dtProduits = CType(Application("objProduits"), produits).getProduits
Catch ex As ExceptionProduits
'حدث خطأ في الوصول إلى المنتجات، يتم تسجيله في الجلسة
Session("erreurs") = ex.erreurs
Exit Sub
Finally
Application.UnLock()
End Try
' يتم وضع ذاكرة التخزين المؤقتة [dtProduits] في الجلسة
Session("dtProduits") = dtProduits
'عرض جدول المنتجات
Session("dvProduits") = dtProduits.DefaultView
' عدد المنتجات في كل صفحة
Session("nbProduitsPage") = Application("defaultProduitsPage")
' الصفحة الحالية المعروضة
Session("pageCourante") = 0
End If
End Sub
End Class
يتم استرداد المعلومات التي تم إدخالها في الجلسة عند كل طلب في الإجراء [Page_Load]:
' بيانات الصفحة
Protected dtProduits As DataTable
Protected dvProduits As DataView
Protected objProduits As produits
Protected nbProduitsPage As Integer
Protected pageCourante As Integer
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' نتحقق مما إذا كان التطبيق يعاني من خطأ
If Not IsNothing(Application("erreurs")) Then
' لم يتم تهيئة التطبيق بشكل صحيح
afficheErreurs(CType(Application("erreurs"), ArrayList))
Exit Sub
End If
' التحقق من وجود خطأ في الجلسة
If Not IsNothing(Session("erreurs")) Then
' لم يتم تهيئة الجلسة بشكل صحيح
afficheErreurs(CType(Session("erreurs"), ArrayList))
Exit Sub
End If
' يتم استرداد مرجع من جدول المنتجات
dtProduits = CType(Session("dtProduits"), DataTable)
' يتم استرداد عرض المنتجات
dvProduits = CType(Session("dvProduits"), DataView)
' يتم استرداد عدد المنتجات في كل صفحة
nbProduitsPage = CType(Session("nbProduitsPage"), Integer)
' يتم استرداد الصفحة الحالية
pageCourante = CType(Session("pageCourante"), Integer)
' يتم استرداد مثيل الوصول إلى المنتجات
objProduits = CType(Application("objProduits"), produits)
' إلغاء أي تحديث جاري
DataGrid1.EditItemIndex = -1
'الطلب الأول
If Not IsPostBack Then
' يتم عرض النموذج الأولي
txtPages.Text = nbProduitsPage.ToString
afficheFormulaire()
End If
End Sub
سيتم حفظ المعلومات التي تم استردادها خلال الجلسة في نهاية هذه الجلسة نفسها عند انتهاء كل طلب:
Private Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.PreRender
' يتم حفظ بعض المعلومات في الجلسة
Session("dtProduits") = dtProduits
Session("dvProduits") = dvProduits
Session("nbProduitsPage") = nbProduitsPage
Session("pageCourante") = pageCourante
End Sub
الحدث [PreRender] هو الذي يشير إلى أن الرد سيتم إرساله إلى العميل. ونستغل هذه الفرصة لحفظ جميع البيانات التي يجب الاحتفاظ بها في الجلسة. وهذا أمر مفرط إلى حد ما، حيث إن قيم بعض هذه البيانات فقط هي التي تتغير في كثير من الأحيان. وتتميز عملية الحفظ هذه بأنها تخلصنا من عبء إدارة الجلسة في الطرق الأخرى للصفحة.
يتم إجراء عملية تحديث ذاكرة التخزين المؤقت من خلال الإجراء التالي:
Private Sub lnkRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkRefresh.Click
' يجب تحديث ذاكرة التخزين المؤقت [dtProduits] باستخدام مصدر البيانات الفعلي
' بدء المزامنة
Application.Lock()
Try
' جدول المنتجات
dtProduits = CType(Application("objProduits"), produits).getProduits
' يتم حفظ الفلتر الحالي
Dim filtre As String = dvProduits.RowFilter
' يتم إنشاء العرض الجديد المصفى
dvProduits = New DataView(dtProduits)
' إعادة تعيين الفلتر
dvProduits.RowFilter = filtre
Catch ex As ExceptionProduits
'حدث خطأ في الوصول إلى المنتجات، يتم تسجيله في الجلسة
Session("erreurs") = ex.erreurs
' يتم عرض العرض [erreurs]
afficheErreurs(ex.erreurs)
' انتهى
Exit Sub
Finally
' انتهت المزامنة
Application.UnLock()
End Try
' سار الأمر على ما يرام - يتم عرض المنتجات بدءًا من الصفحة الأولى
afficheProduits(0, nbProduitsPage)
End Sub
تقوم الإجراء بإعادة إنشاء قيم جديدة لذاكرة التخزين المؤقت [dtProduits] وطريقة العرض [dvProduits]. سيتم وضع هذه القيم في الجلسة بواسطة الإجراء [Page_PreRender] الموصوف أعلاه. بمجرد إعادة بناء ذاكرة التخزين المؤقت [dtProduits]، يتم عرض المنتجات بدءًا من الصفحة الأولى.
فيما يلي مثال على التنفيذ. يتم تشغيل عميل [mozilla] ويعرض المنتجات:

ويفعل عميل [Internet Explorer] الشيء نفسه:

يقوم العميل [Mozilla] بحذف [produit1] وتعديل [produit2] وإضافة منتج جديد. ويحصل على الصفحة الجديدة التالية:

يريد العميل [Internet Explorer] حذف [produit1].

ويحصل على الرد التالي:

تم إخطاره بأن [produit1] لم يعد موجودًا. ثم قرر المستخدم تحديث ذاكرة التخزين المؤقتة الخاصة به باستخدام الرابط [Rafraîchir] أعلاه. وحصل على الرد التالي:

لديه الآن نفس مصدر البيانات مثل العميل [Mozilla].
9.8. Conclusion
في هذا الفصل، قضينا وقتًا طويلاً في مناقشة حاويات البيانات وربطها بمصادر البيانات. واختتمنا الفصل بشرح كيفية تحديث مصدر البيانات باستخدام مكون [DataGrid]. وقد اتخذنا جدول قاعدة البيانات كمصدر للبيانات. وعلى الرغم من أن المكون [DataGrid] يسهل الأمور قليلاً فيما يتعلق بعرض البيانات، فإن الصعوبة الحقيقية لا تكمن في جزء العرض بل في إدارة تحديثات مصدر البيانات التي يقوم بها العملاء المختلفون. قد تنشأ تعارضات في الوصول ويجب معالجتها. هنا، قمنا بمعالجتها في وحدة التحكم باستخدام [Application.Lock]. ربما يكون من الأفضل مزامنة الوصول إلى مصدر البيانات في فئة الوصول إليه حتى لا تضطر وحدة التحكم إلى الاهتمام بمثل هذه التفاصيل التي لا تدخل في نطاق اختصاصها.
في الممارسة العملية، ترتبط جداول قاعدة البيانات ببعضها البعض من خلال علاقات ويجب أن تأخذ عملية تحديثها هذه العلاقات في الاعتبار. يؤثر هذا أولاً على فئة الوصول إلى البيانات التي تصبح أكثر تعقيدًا مما هو ضروري لجداول مستقلة. كما يؤثر هذا عمومًا على جزء العرض في تطبيق الويب لأنه غالبًا ما يتعين عرض ليس جدولًا واحدًا بل جداول مرتبطة ببعضها البعض من خلال علاقات.
وقد سمح هذا الفصل، من ناحية أخرى، بعرض هياكل بيانات مختلفة مثل [DataTable, DataView].









