Skip to content

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]. النوع الافتراضي هو [LinkButtonc.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] مكون من ثلاثة أعمدة:

الاسم
النوع
الدور
id
سلسلة
المفتاح الأساسي
thème
سلسلة
اسم موضوع القائمة
description
سلسلة
وصف للمواضيع التي تتناولها القائمة

حتى لا نثقل مثالنا، سيتم إنشاء الكائن [DataTable] أعلاه برمجياً بشكل تعسفي. في تطبيق حقيقي، من المحتمل أن يتم توفيره بواسطة طريقة لفئة الوصول إلى البيانات. سيتم إنشاء جدول القوائم في الإجراء [Application_Start] وسيتم وضع الجدول الناتج في التطبيق. سنسميها الجدول [dtThèmes]. سيقوم المستخدم بالاشتراك في بعض مواضيع هذا الجدول. سيتم الاحتفاظ بقائمة اشتراكاته هناك أيضًا في كائن [DataTable] يسمى [dtAbonnements] وستكون بنيته كما يلي:

الاسم
النوع
الدور
id
سلسلة
المفتاح الأساسي
thème
سلسلة
اسم موضوع القائمة

التطبيق ذو الصفحة الواحدة هو التالي:

رقم
الاسم
النوع
الخصائص
الدور
1
dgThèmes
DataGrid
 
قوائم البريد الإلكتروني المتاحة للاشتراك
2
dlAbonnements
DataList
 
قائمة اشتراكات المستخدم في القوائم السابقة
3
panelInfos
لوحة
 
لوحة معلومات حول الموضوع الذي اختاره المستخدم مع رابط [Plus d'informations]
4
lblThème
تسمية
جزء من [panelInfos]
اسم الموضوع
5
lblDescription
العلامة
جزء من [panelInfos]
وصف الموضوع
6
lblInfo
العلامة
 
رسالة معلومات التطبيق

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

9.3.2. طريقة العمل

أول ما يراه المستخدم هو ما يلي:

ينقر المستخدم على الروابط [Plus d'informations] للحصول على معلومات حول موضوع ما. يتم عرض هذه المعلومات في [panelInfos]:

Image

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

Image

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

Image

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

Image

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&#232;me" HeaderText="Th&#232;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>

تجدر الإشارة إلى النقاط التالية:

AutoGenerateColumns=false
نحدد بأنفسنا الأعمدة التي سيتم عرضها في القسم <columns>...</columns>
AllowPaging=true
PageSize=5
لترقيم صفحات البيانات
<asp:BoundColumn>
يحدد العمود [thème] (HeaderText) من [DataGrid] الذي سيتم ربطه بالعمود [thème] من مصدر البيانات (DataField)
<asp:ButtonColumn>
يحدد عمودين من الأزرار (أو الروابط). للتمييز بين الرابطين في نفس السطر، سنستخدم خاصيتهما [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>
<HeaderTemplate>
يحدد نص رأس [DataList]
<ItemTemplate>
يحدد العنصر الحالي لـ [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] هو التالي:

<%@ Application src="global.asax.vb" inherits="global" %>

يحتوي الملف المرتبط [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)]:

nom
اسم القيد - يمكن أن يكون أي اسم
colonne
العمود الذي سيكون المفتاح الأساسي - من النوع [DataColumn]
cléPrimaire
يجب أن يكون [vrai] لجعل [colonne] مفتاحًا أساسيًا. إذا كان [cléPrimaire=false]، فلن يكون لدينا سوى قيد القيم الفريدة على [colonne]

لجعل العمود المسمى "id" المفتاح الأساسي للجدول [dtAbonnements]، نكتب إذن:

dtAbonnements.Constraints.Add("xxx",dtAbonnements.Columns("id"),true)

الإجراء [Session_Start]، الذي يتم تنفيذه عندما يتلقى التطبيق أول طلب من عميل. ويستخدم لإنشاء كائنات خاصة بكل عميل يجب أن تستمر عبر طلباته المختلفة. يقوم الإجراء بإنشاء [DataTable] لاشتراكات العميل. يتم إنشاء هيكلها فقط لأن هذه الجدولة فارغة في البداية. وستتم ملؤها مع تراكم الطلبات. وهنا أيضًا، تُستخدم العمود "id" كمفتاح أساسي. وقد استُخدمت تقنية مختلفة لإعلان هذا القيد:

[DataTable].PrimaryKey
هو مصفوفة الأعمدة التي تشكل المفتاح الأساسي - هنا تم تعريف مصفوفة ذات عنصر واحد: العمود المسمى "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]. ستحتوي الصفحات على اشتراكين. إذا كان لدى المستخدم خمسة اشتراكات، فسيكون هناك ثلاث صفحات. ستكون الصفحة الأولى كما يلي:

Image

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

Image

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

Image

يُلاحظ أن الرابطين [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]، يتم إدراج معلومتين أخريين في الجلسة:

pAC
من النوع [Integer] - وهو رقم الصفحة الحالية المعروضة عند آخر طلب
nbAC
من النوع [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] هي معلومات عن صفحة الاشتراكات التي تم عرضها عند الطلب السابق:

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
connexion
سيتم فتح الاتصال بقاعدة البيانات لتنفيذ الأمر SQL ثم إغلاقه فورًا بعد ذلك
selectText
الاستعلام SQL [select] للحصول على الجدول بأكمله [liste]
insertText
طلب يسمح بإدراج سطر (الاسم، السعر) في الجدول [liste]. يُلاحظ أنه لم يتم تحديد الحقل [id]. في الواقع، يتم زيادة هذا الحقل تلقائيًا بواسطة SGBD، وبالتالي لا داعي لتحديده.
updateText
استعلام يسمح بتحديث الحقول (الاسم، السعر) في السطر من الجدول [liste] الذي يحمل المفتاح [id]
deleteText
استعلام يسمح بحذف السطر من الجدول [liste] الذي يحمل المفتاح [id]
selectCommand
الكائن [OleDbCommand] الذي ينفذ الاستعلام [selectText] على الاتصال [connexion]
updateCommand
الكائن [OleDbCommand] الذي ينفذ الاستعلام [updateText] على الاتصال [connexion]
insertCommand
الكائن [OleDbCommand] الذي ينفذ الاستعلام [insertText] على الاتصال [connexion]
deleteCommand
الكائن [OleDbCommand] الذي ينفذ الاستعلام [deleteText] على الاتصال [connexion]
adaptateur
كائن يسمح باسترداد نتيجة تنفيذ [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. التشغيل والعروض

الطريقة التالية هي طريقة عرض الصفحة الرئيسية للتطبيق:

Image

 

تسمح هذه الشاشة، التي تسمى [formulaire]، للمستخدم بوضع شرط تصفية على المنتجات وتحديد عدد المنتجات التي يرغب في عرضها في كل صفحة.

رقم
الاسم
النوع
الدور
1
lnkFiltre
LinkButton
يعرض العرض [Formulaire] الذي يستخدم لتحديد شرط التصفية
2
lnkMisaJour
LinkButton
يعرض العرض [Produits] الذي يستخدم لعرض وتحديث جدول المنتجات (التعديل والحذف)
3
lnkAjout
LinkButton
يعرض عرض [Ajout] الذي يُستخدم لإضافة منتج
4
panel
vueFormulaire
عرض [Formulaire]
5
txtFiltre
TextBox
شرط التصفية
6
txtPages
TextBox
عدد المنتجات في كل صفحة
7
rfvLignes
RequiredFieldValidator
تحقق من وجود قيمة في [txtPages]
8
rvLignes
RangeValidator
يتحقق من أن txtPages يقع في النطاق [3,10]
9
btnExécuter
 
زر [submit] الذي يعرض طريقة العرض [produits] التي تمت تصفيتها حسب الشرط (5)
10
lblInfo1
التسمية
نص إعلامي في حالة وجود أخطاء

على سبيل المثال، إذا تم ملء العرض [formulaire] بالطريقة التالية:

Image

نحصل على النتيجة التالية:

رقم
الاسم
النوع
الدور
1
vueProduits
لوحة
 
2
rdCroissant
rdDécroissant
RadioButton
يسمح للمستخدم بتحديد ترتيب الفرز المطلوب عند النقر على عنوان أحد الأعمدة [nom]، [prix]. الزران جزء من المجموعة [rdTri].
3
DataGrid1
DataGrid
شبكة عرض لإطلالة مفلترة من جدول المنتجات. الفلتر هو الذي تم تحديده بواسطة الإطلالة [formulaire]. لدينا أيضًا .AllowPaging=true، .AllowSorting=true
4
DataGrid2
DataGrid
يعرض الجدول الكامل للمنتجات - يسمح بمتابعة التحديثات
5
LblInfo2
تسمية
نص إعلامي خاصة في حالة وجود أخطاء
6
DataGrid3
DataGrid
سيعرض المنتجات المحذوفة في جدول المنتجات
7
DataGrid4
DataGrid
سيعرض المنتجات التي تم تعديلها في جدول المنتجات
8
DataGrid5
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] القيم التالية:

DataGrid1
لا يوجد مرشح لحالة الخطوط
 
DataGrid2
DataViewRowState.CurrentRows
يعرض الحالة الحالية لجدول المنتجات
DataGrid3
DataViewRowState.Deleted
يعرض الأسطر المحذوفة من جدول المنتجات
DataGrid4
DataViewRowState.ModifiedOriginal
يعرض الأسطر المعدلة في جدول المنتجات مع قيمها الأولية
DataGrid5
DataViewRowState.Added
يعرض الأسطر المضافة إلى جدول المنتجات الأصلي

ستسمح لنا الحاويات [DataGrid1-5] بمتابعة تحديث الجدول [dtProduits]. يسمح المكون [DataGrid1] بتعديل وحذف منتج. سنرى كيف تسمح تكوينات المكون بهذا التحديث. كما أنه مقسم إلى صفحات ومصنف. وقد تمت دراسة هذين الأمرين بالفعل في مثال سابق.

يتيح الرابط [Ajout] الوصول إلى نموذج لإضافة منتج:

رقم
الاسم
النوع
الدور
1
vueAjout
لوحة
 
2
txtNom
TextBox
اسم المنتج
3
rfvNom
RequiredFieldValidator
تحقق من وجود قيمة في [txtNom]
4
txtPrix
TextBox
سعر المنتج
5
rfvPrix
RequiredFieldValidator
تحقق من وجود قيمة في [txtPrix]
6
cvPrix
CompareValidator
يتحقق من أن السعر >= 0
7
btnAjouter
زر
زر [submit] لإضافة المنتج
8
lblInfo3
تسمية
نص إعلامي عن نتيجة عملية الإضافة

قد لا تكون قاعدة البيانات [produits] متاحة عند بدء تشغيل التطبيق. في هذه الحالة، يتم عرض العرض [erreurs] للمستخدم:

رقم
الاسم
النوع
الدور
1
vueErreurs
لوحة
 
2
rptErreurs
مكرر
قائمة الأخطاء

9.6.3. تكوين حاويات البيانات

تم تكوين الحاويات الخمس [DataGrid] تحت [WebMatrix]. تم تنسيقها (الألوان والحدود) باستخدام الرابط [Configuration automatique] من لوحة خصائصها. تم تكوين الحاوية [DataGrid1] باستخدام الرابط [Générateur de propriétés] في نفس اللوحة. تم السماح بالفرز (علامة التبويب [Général]):

Image

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

Image

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

Image

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

كما أنشأنا عمودين من الأزرار:

Image

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

Image

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

Image

وفي النهاية، يُنتج هذا التكوين كود العرض التالي:


<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 &#224; jour" CancelText="Annuler" EditText="Modifier"></asp:EditCommandColumn>
          <asp:ButtonColumn Text="Supprimer" CommandName="Delete"></asp:ButtonColumn>
  </Columns>
<PagerStyle NextPageText="Suivant" PrevPageText="Pr&#233;c&#233;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.DataItemc.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&nbsp;la table LISTE.&nbsp;Exemple : prix&lt;100 and 
                                prix&gt;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 &#224; jour"
                                                         CancelText="Annuler" EditText="Modifier">
                                       </asp:EditCommandColumn>
                                                    <asp:ButtonColumn Text="Supprimer" CommandName="Delete">
                                     </asp:ButtonColumn>
                                                </Columns>
                                                <PagerStyle NextPageText="Suivant" PrevPageText="Pr&#233;c&#233;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] هي كما يلي:

<%@ Application src="global.asax.vb" inherits="Global" %>

الرمز المرتبط [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
dtProduits
جدول المنتجات [DataTable] - مشترك بين جميع العملاء
dvProduits
عرض [DataView] للمنتجات - خاص بكل عميل
defaultProduitsPage
عدد المنتجات لكل صفحة المقترح افتراضيًا

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]:

Image

فيما سبق، العنصر [Produit8, 80] في وضع [édition]. نرى أعلاه أن المستخدم يمكنه تأكيد تحديثه عبر الرابط [Mettre à jour]، وإلغائه عبر الرابط [Annuler]. كما يمكنه استخدام روابط أخرى لا علاقة لها بالعنصر قيد التحرير. بوضع [DataGrid1.EditItemIndex=-1] عند كل تحميل للصفحة، نضمن إلغاء وضع التحرير لـ [DataGrid1] بشكل منهجي. لن يتم تعيين قيمة مختلفة له إلا إذا تم النقر على رابط [Modifier]. سيتم ذلك في الإجراء الذي يعالج هذا الحدث. ستكون لدينا الحالات التالية:

  1. تم النقر على الرابط [Modifier] الخاص بالعنصر رقم 8 من [DataGrid1]. يقوم [Page_Load] أولاً بوضع -1 في [DataGrid1.EditItemIndex]. ثم ستقوم الإجراء الذي يعالج الحدث [Modifier] بوضع 8 في [DataGrid1.EditItemIndex]. في النهاية، عندما يتم إرجاع الصفحة إلى العميل، سيكون العنصر رقم 8 بالفعل في الوضع [édition] وسيظهر هذا كما هو موضح أعلاه.
  2. يقوم المستخدم بتعديل المنتج الموجود في الوضع [édition] ويؤكد تعديله عبر الرابط [Mettre à jour]. يقوم [Page_Load] بوضع -1 في [DataGrid1.EditItemIndex]. ثم سيتم تنفيذ الإجراء الذي يعالج الحدث [Mettre à jour] وسيتم تحديث العنصر رقم 8 في [dtProduits]. عندما يتم إرجاع الصفحة إلى العميل، لن يكون أي عنصر من [DataGrid1] في وضع التحرير (DataGrid.EditItemIndex=-1).
  3. إذا دخل المستخدم في عملية تحديث منتج، فمن المنطقي أن يتخلى عن هذا التحديث بواسطة [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

يتم طلب العروض الأخرى من خلال الخيارات المتاحة للمستخدم:

Image

يتم طلب عرض العرض [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

Image

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

دعونا نذكر أولاً أن الصفحة تحتوي على مكونين للتحقق من الصحة:

رقم
الاسم
النوع
الدور
7
rfvLignes
RequiredFieldValidator
يتحقق من وجود قيمة في [txtPages]
8
rvLignes
RangeValidator
يتحقق من أن txtPages يقع في النطاق [3,10]

تبدأ العملية بتنفيذ رمز التحقق للمكونين أعلاه باستخدام طريقتهم [Validate] ثم تتحقق من قيمة سمة [IsValid] الخاصة بهم. وتكون هذه القيمة هي [true] فقط إذا تم العثور على البيانات التي تم التحقق منها صالحة. إذا فشلت اختبارات صحة أحد المكونين، يتم إعادة عرض العرض [formulaire] حتى يقوم المستخدم بتصحيح أخطائه. يتم تطبيق شرط التصفية على السمة [dvProduits.RowFilter]. هنا قد تحدث استثناء إذا قام المستخدم بوضع معيار تصفية غير صحيح كما هو موضح أدناه:

Image

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

  • عدد المنتجات في كل صفحة الذي اختاره المستخدم
  • رقم الصفحة التي سيتم عرضها - في البداية الصفحة 0

يتم استخدام هاتين المعلومتين في كل مرة يتم فيها عرض [produits].

9.6.11. عرض العرض [produits]

الطريقة [produits] هي كما يلي:

Image

لدينا 5 مكونات [DataGrid] يعرض كل منها عرضًا معينًا لجدول المنتجات [dtProduits]. بدءًا من اليسار:

الاسم
الدور
DataGrid1
شبكة عرض لمنظر مفلتر من جدول المنتجات. الفلتر هو الذي تم تحديده بواسطة المنظر [formulaire]. لدينا أيضًا .AllowPaging=true، .AllowSorting=true
DataGrid2
يعرض الجدول الكامل للمنتجات - يتيح متابعة التحديثات
DataGrid3
سيعرض المنتجات المحذوفة في جدول المنتجات
DataGrid4
سيعرض المنتجات التي تم تعديلها في جدول المنتجات
DataGrid5
سيعرض المنتجات المضافة في جدول المنتجات

الإجراء [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
تصفية
DataGrid2
RowStateFilter= DataRowViewState.CurrentRows
DataGrid3
RowStateFilter= DataRowViewState.Deleted
DataGrid4
RowStateFilter= DataRowViewState.ModifiedOriginal
DataGrid5
RowStateFilter= DataRowViewState.Added

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

Application.Lock
... section critique 1
Application.Unlock

قد توجد عدة أقسام حرجة في كود التطبيق:

Application.Lock
... section critique 2
Application.Unlock

وتتمثل آلية العمل في ما يلي:

  1. يصل عميل واحد أو أكثر إلى التعليمات [Application.Lock] في القسم الحرج 1. وهو موزع رمز دخول فريد. يحصل عميل واحد على هذا الرمز. لنسمه C1.
  2. ما لم يقم العميل C1 بإرجاع الرمز عبر [Application.Unlock]، لا يُسمح لأي عميل آخر بالدخول إلى قسم حرج يتحكم فيه [Application.Lock]. في المثال أعلاه، لا يمكن لأي عميل الدخول إلى القسمين الحرجين 1 و 2.
  3. يقوم العميل C1 بتنفيذ [Application.Unlock] وبالتالي يعيد رمز الدخول. يمكن بعد ذلك منح هذا الرمز لعميل آخر. يتم تكرار الخطوات من 1 إلى 3.

باستخدام هذه الآلية، سنضمن أن عميلاً واحداً فقط يمكنه الوصول إلى الجدول [dtProduits] سواء للقراءة أو الكتابة.

يعرض الرابط [Mise à jour] أيضًا العرض [Produits]:

Image

الإجراء المرتبط هو التالي:


    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]:

Image

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

Image

إجراء الفرز هو كما يلي:


    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] الموجود في سطر المنتج:

Image

ثم يتم تنفيذ الإجراء [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]:

         ' عرض المنتجات
        afficheProduits(page, DataGrid1.PageSize)

الإجراء [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

تستقبل الإجراء كمعلمة مفتاح المنتج المراد حذفه. إذا كان هناك عميل واحد فقط يقوم بالتحديثات، فيمكن إجراء هذا الحذف على النحو التالي:

             ' حذف السطر [idProduit]
            dtProduits.Rows.Find(idProduit).Delete()

مع وجود عدة عملاء يقومون بالتحديثات في نفس الوقت، يصبح الأمر أكثر تعقيدًا. في الواقع، لنفكر في التسلسل الزمني التالي:

الوقت
الإجراء
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] (عرض جزئي):

Image

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

Image

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

Image

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

Image

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

Image

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

9.6.14. إضافة منتج

لإضافة منتج، يستخدم المستخدم الرابط [Ajout] من الخيارات. ويكتفي هذا الرابط بعرض العرض [Ajout]:

Image

Image

الإجراءان المتضمنان في هذه العملية هما:


    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] يحتوي على مكونات التحقق من الصحة:

الاسم
النوع
الدور
rfvNom
RequiredFieldValidator
يتحقق من وجود قيمة في [txtNom]
rfvPrix
RequiredFieldValidator
يتحقق من وجود قيمة في [txtPrix]
cvPrix
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] مع رسائل الأخطاء الخاصة بمكونات التحقق من الصحة. فيما يلي مثال على ذلك:

Image

إذا كانت البيانات صالحة، يتم إعداد السطر المراد إدراجه في الجدول [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] فلا يتم ملؤه. هذا العمود هو من النوع [AutoIncrementc.a.d. حيث سيُعطى SGBD كمفتاح لمنتج جديد، وهو المفتاح الأقصى الموجود مضافًا إليه 1. السطر [produit] الذي تم إنشاؤه هنا منفصل عن الجدول [dtProduits]. علينا الآن إدراجه في هذا الجدول. ولأننا سنقوم بتحديث الجدول [dtProduits]، فإننا نقوم بتنشيط المزامنة بين العملاء:

        Application.Lock()

بعد ذلك، نحاول إضافة السطر إلى الجدول [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] الموجود في سطر المنتج:

Image

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

Image

بفضل المكون [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] الذي يحتوي على القيم الأصلية للسطر الذي تم تعديله. إليك مثال على ذلك.

Image

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

Image

نلاحظ أننا فقدنا القيم التي تم إدخالها في البداية. في تطبيق احترافي، قد لا يكون هذا مقبولاً على الأرجح. نصل هنا إلى بعض حدود الوضع القياسي لتحديث المكون [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]:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

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

Image

يمكنه ملاحظة أن [produit1] قد تم حذفه لأنه موجود في قائمة المنتجات المحذوفة.

9.7. تطبيق ويب لتحديث الجدول الفعلي للمنتجات

9.7.1. الحلول المقترحة

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

  1. استخدام ذاكرة التخزين المؤقت [dtProduits] في الذاكرة لتحديث مصدر البيانات. يمكن توفير صفحة موجودة في شجرة الويب للتطبيق السابق من أجل الوصول إلى ذاكرة التخزين المؤقت [dtProduits] الخاصة به. ستتيح هذه الصفحة للمسؤول إمكانية نقل التعديلات الموجودة في ذاكرة التخزين المؤقت [dtProduits] إلى مصدر البيانات الفعلي. ولتحقيق ذلك، يمكن إضافة طريقة جديدة إلى فئة الوصول [produits] التي تقبل ذاكرة التخزين المؤقت [dtProduits] كمعلمة وتقوم باستخدام هذه الذاكرة بتحديث مصدر البيانات الفعلي.
  2. يتم تحديث مصدر البيانات الفعلي في نفس الوقت الذي يتم فيه تحديث ذاكرة التخزين المؤقت.

تسمح الاستراتيجية رقم 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:

Image

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

Image

نضيف منتجًا:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

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

Image

تم حذف السطر [produit2] من ذاكرة التخزين المؤقتة كما هو موضح في قائمة المنتجات المحذوفة. أما حذف [produit2] من المصدر الفعلي فقد فشل كما هو موضح في رسالة الخطأ.

9.7.4. الحل 2

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

Image

باستخدام الخيار [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] ويعرض المنتجات:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

لديه الآن نفس مصدر البيانات مثل العميل [Mozilla].

9.8. Conclusion

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

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

وقد سمح هذا الفصل، من ناحية أخرى، بعرض هياكل بيانات مختلفة مثل [DataTable, DataView].