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

آنها برای اشتراک در یک موضوع روی لینکهای [S'abonner] کلیک میکنند. موضوعات انتخابشده به کامپوننت [dlAbonnements] اضافه میشوند:

کاربر ممکن است بخواهد به فهرستی که قبلاً در آن مشترک شده است، دوباره مشترک شود. پیامی این موضوع را به او اطلاع میدهد:

در نهایت، آنها میتوانند با استفاده از دکمههای [Retirer] در بالا، از هر یک از موضوعات لغو اشتراک کنند. نیازی به تأیید نیست. این کار در اینجا ضروری نیست زیرا کاربر میتواند به راحتی دوباره اشتراک را فعال کند. در اینجا نما پس از لغو اشتراک از [thème1] آمده است:

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

به صفحه دوم از طریق لینک [Suivant] دسترسی پیدا میشود:

صفحه سوم:

توجه داشته باشید که لینکهای [Précédent] و [Suivant] تنها در صورتی قابل مشاهده هستند که به ترتیب یک صفحه قبلی و یک صفحه بعدی پس از صفحه جاری وجود داشته باشد.
9.4.2. کد ارائه
لینکهای [Précédent] و [Suivant] با افزودن یک تگ <FooterTemplate> به [DataList] ایجاد میشوند:
<asp:datalist id="dlAbonnements" runat="server" ...>
....
<FooterTemplate>
<asp:LinkButton id="lnkPrecedent" runat="server" CommandName="precedent">Précédent</asp:LinkButton>
<asp:LinkButton id="lnkSuivant" runat="server" CommandName="suivant">Suivant</asp:LinkButton>
</FooterTemplate>
....
</asp:datalist>
9.4.3. رقم کنترل
فایل مرتبط [global.asax.vb] به شرح زیر تغییر میکند:
...
Public Class global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
...
End Sub
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' آغاز جلسه – ایجاد یک جدول اشتراک خالی
Dim dtAbonnements As New DataTable
With dtAbonnements
'ستونها
.Columns.Add("id", GetType(String))
.Columns.Add("thème", GetType(String))
'کلید اصلی
.PrimaryKey = New DataColumn() {.Columns("id")}
End With
'جدول به جلسه اضافه میشود
Session.Item("abonnements") = dtAbonnements
' صفحهٔ جاری صفحهٔ 0 است
Session.Item("pAC") = 0
' تعداد اشتراکها در این صفحه 0 است
Session.Item("nbAC") = 0
End Sub
علاوه بر جدول اشتراک [dtAbonnements]، دو اطلاعات دیگر در جلسه گنجانده شدهاند:
از نوع [Integer] – این شماره صفحهٔ فعلی نمایشدادهشده در طول درخواست اخیر است | |
از نوع [Integer] – تعداد خطوط نمایش داده شده در صفحه جاری قبلی |
در ابتدای یک جلسه، هم شماره صفحه فعلی و هم تعداد خطوط آن صفحه صفر هستند.
کنترلکننده [main.aspx.vb] به شرح زیر تغییر میکند:
....
Public Class main
Inherits System.Web.UI.Page
....
' دادههای برنامه
Protected dtThèmes As DataTable
Protected dtAbonnements As DataTable
Protected dtPA As DataTable ' la page d'abonnements affichée
Protected Const nbAP As Integer = 2 ' nbre abonnements par page
Protected pAC As Integer ' page abonnement courant
Protected nbAC As Integer ' nombre d'abonnements dans page courante
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
End Sub
Private Sub terminer()
...
End Sub
Private Sub dgThèmes_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles dgThèmes.PageIndexChanged
...
End Sub
Private Sub dgThèmes_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgThèmes.ItemCommand
...
End Sub
Private Sub infos(ByVal id As String)
...
End Sub
Private Sub abonner(ByVal id As String)
...
End Sub
Private Sub dlAbonnements_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataListCommandEventArgs) Handles dlAbonnements.ItemCommand
...
End Sub
Private Sub changePAC()
...
End Sub
Private Sub setLiens(ByVal ctl As Control, ByVal blPrec As Boolean, ByVal blSuivant As Boolean)
...
End Sub
End Class
دادههای جدید مربوط به صفحهبندی اشتراک در اینجا تعریف شده است:
Protected dtPA As DataTable ' la page d'abonnements affichée
Protected Const nbAP As Integer = 2 ' nbre abonnements par page
Protected pAC As Integer ' page abonnement courant
Protected nbAC As Integer ' nombre d'abonnements dans page courante
بخشی از این اطلاعات در جلسه ذخیره شده و در هر درخواست در رویه [Page_Load] بازیابی میشود:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'بازیابی منابع داده
dtThèmes = CType(Application("thèmes"), DataTable)
dtAbonnements = CType(Session("abonnements"), DataTable)
' و اطلاعات نمایش اشتراک
pAC = CType(Session("pAC"), Integer)
nbAC = CType(Session("nbAC"), Integer)
' پیوند داده
If Not IsPostBack Then
'نمایش لیست خالی اشتراکها
terminer()
End If
' اطلاعات خاصی پنهان شده است
panelInfo.Visible = False
End Sub
اطلاعات استخراجشده از [pAC] و [nbAC] مربوط به صفحهٔ اشتراکها است که در درخواست قبلی نمایش داده شد:
این شماره صفحهٔ جاری است که در درخواست قبلی نمایش داده شده است | |
تعداد سطرهای نمایشدادهشده در این صفحهٔ جاری |
متد [terminer]، اجزا را به منابع دادهی آنها متصل میکند، درست همانطور که متد [liaisons] در اپلیکیشن قبلی این کار را انجام میداد. ویژگی جدید در اینجا پیوند بین [DataList] و جدول [dtPA] است که صفحه اشتراکنامهها برای نمایش میباشد:
Private Sub terminer()
'پیوند دادن منبع داده به کامپوننت [datagrid]
With dgThèmes
.DataSource = dtThèmes
.DataKeyField = "id"
End With
' لینک صفحهٔ اشتراکها به کامپوننت [datalist]، با در نظر گرفتن صفحهٔ فعلی pAC
changePAC()
'صفحه p نمایش داده میشود
With dlAbonnements
.DataSource = dtPA
.DataKeyField = "id"
End With
' دادهها را به کامپوننتها اختصاص میدهد
Page.DataBind()
'مدیریت لینکهای [précédent] و [suivant] برای [datalist]
Dim blprec As Boolean = pAC <> 0
Dim blsuivant As Boolean = pAC <> (dtAbonnements.Rows.Count - 1) \ nbAP
Dim nbLiensTrouvés As Integer = 0
setLiens(dlAbonnements, blprec, blsuivant, nbLiensTrouvés)
'اطلاعات صفحهٔ جاری را در جلسه ذخیره میکند
Session("pAC") = pAC
Session("nbAC") = dtPA.Rows.Count
End Sub
لطفاً به نکات زیر توجه کنید:
- منبع [dtPA] به شماره صفحه فعلی [pAC] که باید نمایش داده شود، وابسته است. متغیر [pAC] یک متغیر سراسری در کلاس است که توسط متدهای مسئول بهروزرسانی شماره صفحه فعلی مدیریت میشود. این متد [changePAC] است که جدول [dtPA] را میسازد، که به کامپوننت [dlAbonnements] لینک خواهد شد.
- نقش متد [setLiens] این است که لینکهای [Précédent] و [Suivant] را بسته به اینکه آیا صفحه جاری [pAC] که نمایش داده میشود، قبل و بعد از آن صفحه دیگری وجود دارد یا خیر، نشان دهد یا پنهان کند. این متد چهار پارامتر دارد:
- [dlAbonnements]: کنترل [DataList] که درخت کنترل آن را برای یافتن دو لینک بررسی خواهیم کرد. اگرچه این دو لینک در مکانی مشخص – یعنی در پاورقی [DataList] – قرار دارند، به نظر نمیرسد راه سادهای برای ارجاع مستقیم به آنها وجود داشته باشد. در هر صورت، هیچ روش مشابهی در اینجا یافت نشده است.
- [blPrecedent]: یک مقدار بولی که باید به ویژگی [visible] لینک [Precedent] اختصاص یابد – اگر صفحهٔ جاری 0 نباشد، روی true تنظیم میشود
- [blSuivant]: یک مقدار بولی که باید به خصوصیت [visible] لینک [Suivant] اختصاص داده شود – زمانی که صفحهٔ جاری آخرین صفحه در لیست اشتراکها نباشد، مقدار true را میگیرد
- [nbLiensTrouvés]: یک پارامتر خروجی که تعداد لینکهای یافتشده را شمارش میکند. به محض اینکه این شمارش به دو برسد، متد پایان مییابد.
- اطلاعات [pAC] و [nbAC] برای درخواست بعدی در جلسه (session) ذخیره میشود.
روش [changePAC] جدول [dtPA] را ایجاد میکند که به کامپوننت [dlAbonnements] متصل خواهد شد. این کار بر اساس شماره [pAC] صفحهٔ جاری برای نمایش انجام میشود. جدول [dtPA] باید ردیفهای مشخصی را از جدول اشتراکها [dtAbonnements] نمایش دهد. شایان ذکر است که این جدول در جلسه (session) ذخیره شده و با هر پرسوجو بهروزرسانی (افزایش یا کاهش) میشود. ما با تعیین محدوده [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)
' با استفاده از شناسه کلید به تم مشترک شوید
..
' قالب به اشتراکها اضافه شد
..
' بهروزرسانی شماره صفحه فعلی – اکنون این آخرین صفحه است
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
' fields
Private _id As Integer
Private _nom As String
Private _prix As Double
' خاصیت شناسه
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
_id = Value
End Set
End Property
' نام ویژگی
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
If IsNothing(Value) OrElse Value.Trim = String.Empty Then Throw New Exception
_nom = Value
End Set
End Property
' ویژگی قیمت
Public Property prix() As Double
Get
Return _prix
End Get
Set(ByVal Value As Double)
If IsNothing(Value) OrElse Value < 0 Then Throw New Exception
_prix = Value
End Set
End Property
End Structure
این ساختار تنها دادههای معتبر را برای فیلدهای [nom] و [prix] میپذیرد.
9.5.3. کلاس محصولات
کلاس [Produits] کلاسی است که به ما امکان میدهد جدول [liste] را در پایگاه داده محصول بهروزرسانی کنیم. ساختار آن به شرح زیر است:
Public Class produits
'دادههای نمونه
Public Sub New(ByVal chaineConnexionOLEDB As String)
....
End Sub
Public Function getProduits() As DataTable
....
End Function
Public Sub ajouterProduit(ByVal produit As sProduit)
...
End Sub
Public Sub modifierProduit(ByVal produit As sProduit)
...
End Sub
Public Sub supprimerProduit(ByVal id As Integer)
...
End Sub
End Class
دادههای نمونه
دادههای به اشتراک گذاشته شده توسط متدهای مختلف کلاس به شرح زیر است:
Private connexion As OleDbConnection
Private Const selectText As String = "select id,nom,prix from liste"
Private Const insertText As String = "insert into liste(nom,prix) values(?,?)"
Private Const updateText As String = "update liste set nom=?,prix=? where id=?"
Private Const deleteText As String = "delete from liste where id=?"
Private selectCommand As New OleDbCommand
Dim insertCommand As New OleDbCommand
Dim updateCommand As New OleDbCommand
Dim deleteCommand As New OleDbCommand
Dim adaptateur As New OleDbDataAdapter
اتصال به پایگاه داده – برای اجرای دستور SQL باز شده و بلافاصله پس از آن بسته خواهد شد | |
پرسوجوی SQL [select] کل جدول [liste] را بازیابی میکند | |
پرسوجوی اجازهٔ درج یک سطر (نام، قیمت) در جدول [liste]. توجه داشته باشید که فیلد [id] مشخص نشده است. این به این دلیل است که این فیلد توسط SGBD بهصورت خودکار افزایش مییابد، بنابراین نیازی به مشخص کردن آن نداریم. | |
پرسوجو برای بهروزرسانی فیلدهای (name, price) در سطر جدول [liste] با کلید [id] | |
پرسوجو برای حذف سطر از جدول [liste] با کلید [id] | |
شیء [OleDbCommand] در حال اجرای پرسوجوی [selectText] بر روی اتصال [connexion] | |
شیء [OleDbCommand] در حال اجرای پرسوجوی [updateText] بر روی اتصال [connexion] | |
شیء [OleDbCommand] در حال اجرای پرسوجوی [insertText] بر روی اتصال [connexion] | |
شیء [OleDbCommand] در حال اجرای پرسوجوی [deleteText] بر روی اتصال [connexion] | |
ابجکتی که برای بازیابی نتیجه اجرای [selectCommand] در یک شیء [DataSet] استفاده میشود |
سازنده
سازنده یک پارامتر واحد، [chaineConnexionOLEDB]، را میپذیرد که رشته اتصال مشخصکننده پایگاه داده مورد پرسوجو است. با استفاده از این، چهار دستور برای پرسوجو و بهروزرسانی جدول، و همچنین آداپتور، آماده میشوند. این صرفاً یک مرحله آمادهسازی است؛ در واقع هیچ اتصالی برقرار نمیشود.
Public Sub New(ByVal chaineConnexionOLEDB As String)
' آمادهسازی اتصال
connexion = New OleDbConnection(chaineConnexionOLEDB)
' آمادهسازی دستورات پرسوجو
Dim commandes() As OleDbCommand = {selectCommand, insertCommand, updateCommand, deleteCommand}
Dim textes() As String = {selectText, insertText, updateText, deleteText}
For i As Integer = 0 To commandes.Length - 1
With commandes(i)
.CommandText = textes(i)
.Connection = connexion
End With
Next
'آمادهسازی آداپتور دسترسی به دادهها
adaptateur.SelectCommand = selectCommand
End Sub
روش getProduits
این روش محتویات جدول [Liste] را در یک شیء [DataTable] بازیابی میکند. کد آن به شرح زیر است:
Public Function getProduits() As DataTable
'قرار دادن جدول [liste] در [dataset]
Dim contenu As New DataSet
'ایجاد یک شیء DataAdapter برای خواندن دادهها از منبع OLEDB
Try
With adaptateur
.FillSchema(contenu, SchemaType.Source)
.Fill(contenu)
End With
Catch e As Exception
' pb
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, name, price) به جدول [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] به جای [insertCommand]، [updateCommand] است.
روش supprimerProduits
این متد با استفاده از کلید [id] که به عنوان پارامتر ارسال میشود، ردیف را از جدول [liste] حذف میکند. کد به شرح زیر است:
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)
' جدول محصولات را در یک DataTable فراخوانی میکند
Try
contenu = objProduits.getProduits()
Catch ex As ExceptionProduits
afficheErreurs(ex.erreurs)
Environment.Exit(2)
End Try
Dim lignes As DataRowCollection = contenu.Rows
For i As Integer = 0 To lignes.Count - 1
' ردیف i از جدول
Console.Out.WriteLine(lignes(i).Item("id").ToString + "," + lignes(i).Item("nom").ToString + _
"," + lignes(i).Item("prix").ToString)
Next
'خروجی کنسول را متوقف میکند
Console.WriteLine("...")
Console.ReadLine()
End Sub
Sub afficheErreurs(ByRef erreurs As ArrayList)
'خطاها را در کنسول نمایش میدهد
If erreurs.Count <> 0 Then
Console.WriteLine("Les erreurs suivantes se sont produites :")
For i As Integer = 0 To erreurs.Count - 1
Console.WriteLine(String.Format("-- {0}", CType(erreurs(i), String)))
Next
End If
'خروجی کنسول را متوقف میکند
Console.WriteLine("...")
Console.ReadLine()
End Sub
End Module
End Namespace
ما دو فایل منبع را کامپایل میکنیم:
dos>vbc /t:library /r:system.dll /r:system.data.dll /r:system.xml.dll produits.vb
dos >vbc /r:produits.dll /r:system.dll /r:system.data.dll /r:system.xml.dll testproduits.vb
dos>dir
07/04/2004 08:40 7 168 produits.dll
04/04/2004 16:38 118 784 produits.mdb
07/04/2004 08:31 6 209 produits.vb
07/04/2004 08:40 5 120 testproduits.exe
03/04/2004 19:02 3 312 testproduits.vb
سپس آزمایش میکنیم:
dos>testproduits produits.mdb
1,produit1,10
2,produit2,20
3,produit3,30
...
1,produit1,10
2,produit2,20
3,produit3,30
8,xxx,1
...
1,produit1,10
2,produit2,20
3,produit3,30
8,xxx,200
...
1,produit1,10
2,produit2,20
3,produit3,30
...
از خوانندگان دعوت میشود خروجی صفحه بالا را با کد برنامه آزمایشی مقایسه کنند.
9.6. برنامه وب برای بهروزرسانی جدول محصولات کششده
9.6.1. مقدمه
ما اکنون در حال نوشتن یک برنامه وب برای بهروزرسانی جدول محصولات (افزودن، حذف، تغییر) هستیم. جدول بهروزرسانیشده در حافظه یک شیء [DataTable] باقی میماند و توسط همه کاربران به اشتراک گذاشته میشود. ما میخواهیم دو نکته را برجسته کنیم:
- مدیریت یک شیء [DataTable]
- مسائل مربوط به بهروزرسانی همزمان یک جدول توسط چندین کاربر.
معماری MVC برنامه به شرح زیر خواهد بود:
![]() |
9.6.2. کارکرد و نماها
صفحه اصلی برنامه به شرح زیر است:

این نما، با نام [formulaire]، به کاربر امکان میدهد تا یک شرط فیلتر را روی محصولات اعمال کند و تعداد محصولات در هر صفحه را که مایل است ببیند، تنظیم نماید.
خیر. | نام | نوع | نقش |
LinkButton | نما [Formulaire] را نمایش میدهد که برای تنظیم شرط فیلتر استفاده میشود | ||
LinkButton | نما [Produits] را نمایش میدهد که برای مشاهده و بهروزرسانی جدول محصولات (اصلاح و حذف) استفاده میشود | ||
LinkButton | نما [Ajout] را نمایش میدهد که برای افزودن یک محصول استفاده میشود | ||
vueFormulaire | نما [Formulaire] | ||
TextBox | شرط فیلتر | ||
TextBox | تعداد محصولات در هر صفحه | ||
RequiredFieldValidator | بررسی میکند که آیا یک مقدار در [txtPages] موجود است | ||
RangeValidator | بررسی میکند که آیا txtPages در محدوده [3,10] قرار دارد | ||
دکمه [submit]، که نمای [produits] را بر اساس شرط (۵) فیلتر میکند | |||
برچسب | متن اطلاعاتی در صورت بروز خطا |
برای مثال، اگر نما [formulaire] به صورت زیر پر شود:

نتیجه زیر به دست میآید:
![]() |
خیر. | نام | نوع | نقش |
پنل | |||
RadioButton | به کاربر امکان میدهد ترتیب مرتبسازی دلخواه را هنگام کلیک بر روی سرستون [nom] یا [prix] تنظیم کند. هر دو دکمه بخشی از گروه [rdTri] هستند. | ||
DataGrid | نمایش شبکه برای نمای فیلترشده جدول محصولات. فیلتر همان فیلتری است که توسط نما [formulaire] تنظیم شده است. ما همچنین داریم .AllowPaging=true, .AllowSorting=true | ||
DataGrid | کل جدول محصولات را نمایش میدهد – به شما امکان میدهد بهروزرسانیها را پیگیری کنید | ||
برچسب | متن آموزنده، بهویژه در صورت بروز خطا | ||
DataGrid | محصولات حذفشده را در جدول محصولات نمایش میدهد | ||
DataGrid | محصولات اصلاحشده را در جدول محصولات نمایش میدهد | ||
DataGrid | محصولات اضافه شده را در جدول محصولات نمایش خواهد داد |
در این صفحه پنج مخزن داده وجود دارد. همه آنها یک جدول یکسان، [dtProduits]، را از طریق یک نمای متفاوت، [DataView]، نمایش میدهند. یک نما، زیرمجموعهای از سطرهای جدول منبع را نشان میدهد. این زیرمجموعه با استفاده از خواص [RowFilter] و [RowStateFilter] از کلاس [DataView] ایجاد میشود:
- [RowFilter] به شما امکان میدهد فیلتری را روی سطرها اعمال کنید، مانند [prix>30] که در بالا نشان داده شده است. این نوع فیلترسازی توسط [DataGrid1] استفاده خواهد شد.
- [RowStateFilter] به شما امکان میدهد فیلتری بر اساس وضعیت سطر جدول تنظیم کنید. این وضعیت، وضعیت سطر را نسبت به وضعیت اصلی آن در زمان ایجاد نمای جدول نشان میدهد. در اینجا، جدول [dtProduits] از یک پایگاه داده استخراج شده است. در ابتدا، تمام سطرهای آن وضعیت مساوی با [Original] خواهند داشت تا نشان دهند که اینها سطرهای اصلی جدول هستند. این وضعیت ممکن است بعداً تغییر کرده و مقادیر مختلفی به خود بگیرد که برخی از آنها در زیر فهرست شده است:
- [Added]: سطر اضافه شده است – این سطر پیش از این جزئی از جدول اصلی نبوده است
- [Deleted]: سطر حذف شده است – این سطر هنوز در جدول موجود است اما بهعنوان «برای حذف» علامتگذاری شده است
- [Modified]: سطر اصلاح شده است
[RowStateFilter] سطرها را در جدول با وضعیت خاصی نمایش میدهد:
- (ادامه)
- [DataViewRowState.Added]: فقط ردیفهای اضافه شده نمایش داده میشوند. آنها با مقادیر فعلی خود نشان داده میشوند.
- [DataViewRowState.ModifiedOriginal]: فقط سطرهای اصلاحشده نمایش داده میشوند. آنها با مقادیر اصلی خود نشان داده میشوند.
- [DataViewRowState.ModifiedCurrent]: فقط سطرهای اصلاحشده نمایش داده میشوند. آنها با مقادیر فعلی خود نشان داده میشوند.
- [DataViewRowState.Deleted]: فقط سطرهای حذفشده نمایش داده میشوند. آنها با مقادیر اصلی خود نشان داده میشوند.
- [DataViewRowState.CurrentRows]: سطرهایی که حذف نشدهاند نمایش داده میشوند. آنها با مقادیر فعلی خود نشان داده میشوند.
بنابراین، فیلتر [RowStateFilter] مقادیر زیر را خواهد داشت:
نداشتن فیلتر برای وضعیت خط | ||
DataViewRowState.CurrentRows | وضعیت فعلی جدول محصولات را نمایش میدهد | |
DataViewRowState.Deleted | ردههای حذفشده از جدول محصول را نمایش میدهد | |
DataViewRowState.ModifiedOriginal | ردههای اصلاحشده در جدول محصولات را به همراه مقادیر اصلیشان نمایش میدهد | |
DataViewRowState.Added | ردههای افزوده شده به جدول اصلی محصولات را نمایش میدهد |
کانتینرهای [DataGrid1-5] به ما امکان میدهند تا بهروزرسانیهای جدول [dtProduits] را پیگیری کنیم. کامپوننت [DataGrid1] امکان ویرایش یا حذف یک محصول را فراهم میکند. خواهیم دید که پیکربندی این کامپوننت چگونه این بهروزرسانی را ممکن میسازد. این کامپوننت همچنین صفحهبندی و مرتبسازی شده است. این دو جنبه قبلاً در یک مثال قبلی پوشش داده شدهاند.
لینک [Ajout] دسترسی به فرمی برای افزودن محصول را فراهم میکند:
![]() |
خیر. | نام | نوع | نقش |
پنل | |||
TextBox | نام محصول | ||
RequiredFieldValidator | بررسی وجود مقدار در [txtNom] | ||
TextBox | قیمت محصول | ||
RequiredFieldValidator | بررسی میکند که آیا یک مقدار در [txtPrix] موجود است | ||
CompareValidator | بررسی میکند که قیمت ≥ ۰ باشد | ||
دکمه | [submit] دکمه «افزودن محصول» | ||
برچسب | متن اطلاعاتی مربوط به نتیجه عملیات افزودن |
پایگاه داده [produits] ممکن است هنگام راهاندازی برنامه در دسترس نباشد. در این صورت، نما [erreurs] به کاربر نمایش داده میشود:
![]() |
خیر. | نام | نوع | نقش |
پنل | |||
تکرارکننده | فهرست خطاها |
9.6.3. پیکربندی کانتینر داده
پنج کانتینر [DataGrid] زیر [WebMatrix] پیکربندی شدهاند. آنها با استفاده از لینک [Configuration automatique] در پنل ویژگیهایشان قالببندی شدهاند (رنگها و حاشیهها). کانتینر [DataGrid1] با استفاده از لینک [Générateur de propriétés] در همان پنل پیکربندی شده است. مرتبسازی فعال شده است (زبانهی [Général]):

برخلاف چهار کانتینر دیگر، ستونها بهطور خودکار ایجاد نمیشوند. آنها بهصورت دستی با استفاده از ویزارد تعریف شدهاند:

ابتدا، دو ستون از نوع [Colonne connexe] با نامهای [nom] و [prix] ایجاد شدند. اینها به ترتیب به فیلدهای [nom] و [prix] در منبع دادهای که کانتینرها نمایش خواهند داد، متصل شدند. در اینجا، برای مثال، پیکربندی ستون [nom] آمده است:

بیان مرتبسازی، عبارتی است که باید پس از عبارت [order by] در دستور SQL [select] قرار گیرد، که هنگام ... اجرا خواهد شد.کاربر روی نام ستون سربرگ [nom] که با فیلد [nom] در [DataGrid] مرتبط است، کلیک میکند. در اینجا [nom] را تنظیم کردهایم تا عبارت مرتبسازی [order by nom] باشد. خواهیم دید که این را طوری تغییر میدهیم که بسته به ترتیب مرتبسازی انتخابشده توسط کاربر، به [order by nom asc] یا [order by nom desc] تبدیل شود.
ما همچنین دو ستون دکمه ایجاد کردهایم:

ستون [Modifier, Mettre à jour, Annuler] به ما امکان ویرایش یک محصول را میدهد و ستون [Supprimer] امکان حذف آن را فراهم میکند. هر یک از این ستونها قابل پیکربندی هستند. ستون [Modifier, Mettre à jour, Annuler] پیکربندیهای زیر را ارائه میدهد:

میتوانیم ببینیم که متن دکمه قابل ویرایش است. در مورد خود دکمهها، میتوانیم بین لینکها و دکمهها (لیست کشویی بالا) انتخاب کنیم. در اینجا لینکها انتخاب شدهاند. پیکربندی ستون [Supprimer] مشابه است. علاوه بر این جادوگر پیکربندی، ما مستقیماً از پنجرهٔ ویژگیها برای [DataGrid] استفاده کردیم تا ویژگی [DataKeyField] را تنظیم کنیم، که مشخص میکند کدام فیلد در منبع داده برای نمایه کردن سطرها در [DataGrid] استفاده میشود. در این مورد، از کلید اصلی جدول محصولات استفاده میشود:

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

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

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

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

در این حالت، نما [formulaire] دوباره نمایش داده میشود. اگر هر دو اطلاعات وارد شده صحیح باشند، دو مقدار داده در جلسه ذخیره میشوند:
- تعداد محصولات در هر صفحه که توسط کاربر انتخاب شده است
- شماره صفحهای که باید نمایش داده شود – در ابتدا صفحه 0
این دو مورد اطلاعات هر بار که نما [produits] نمایش داده میشود، استفاده میشوند.
9.6.11. نمایش نما [produits]
نما [produits] به شرح زیر است:

ما ۵ کامپوننت داریم، [DataGrid]، که هر کدام نمای مشخصی از جدول محصول [dtProduits] را نمایش میدهند. از سمت چپ شروع میکنیم:
نام | نقش |
جدول نمایشدهنده نمای فیلترشده از جدول محصولات. فیلتر همان فیلتری است که توسط نمای [formulaire] تنظیم شده است. همچنین داریم .AllowPaging=true, .AllowSorting=true | |
کل جدول محصولات را نمایش میدهد – امکان پیگیری بهروزرسانیها را برای شما فراهم میکند | |
محصولات حذفشده را در جدول محصولات نمایش میدهد | |
محصولاتی را که در جدول محصولات اصلاح شدهاند نمایش میدهد | |
محصولات اضافه شده به جدول محصولات را نمایش خواهد داد |
روال [afficheProduits] مسئول نمایش نمای قبلی است:
Private Sub afficheProduits(ByVal page As Integer, ByVal taillePage As Integer)
' دادهها را به هر دو مؤلفه متصل کنید [DataGrid]
Application.Lock()
With DataGrid1
.DataSource = dvProduits
.PageSize = taillePage
.CurrentPageIndex = page
.DataBind()
End With
Dim dvCurrent As New DataView(dtProduits)
dvCurrent.RowStateFilter = DataViewRowState.CurrentRows
With DataGrid2
.DataSource = dvCurrent
.DataBind()
End With
Dim dvSupp As DataView = New DataView(dtProduits)
dvSupp.RowStateFilter = DataViewRowState.Deleted
With DataGrid3
.DataSource = dvSupp
.DataBind()
End With
Dim dvModif As DataView = New DataView(dtProduits)
dvModif.RowStateFilter = DataViewRowState.ModifiedOriginal
With DataGrid4
.DataSource = dvModif
.DataBind()
End With
Dim dvAjout As DataView = New DataView(dtProduits)
dvAjout.RowStateFilter = DataViewRowState.Added
With DataGrid5
.DataSource = dvAjout
.DataBind()
End With
Application.UnLock()
'نما نمایش داده میشود [produits]
vueProduits.Visible = True
vueFormulaire.Visible = False
vueErreurs.Visible = False
vueAjout.Visible = False
'ذخیرهٔ صفحهٔ جاری
Session("pageCourante") = page
End Sub
این رویه دو پارامتر را میپذیرد:
- شماره صفحهای که باید در [DataGrid1] نمایش داده شود [page]
- تعداد محصولات [taillePage] در هر صفحه
ارتباط بین [DataGrid] و جدول محصولات از طریق پنج نمای مختلف برقرار میشود.
- [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: سطرهای اصلاحشده با مقادیر فعلی خود نمایش داده میشوند
یک سطر اصلاحشده دو مقدار دارد: مقدار اصلی، که مقداری است که سطر قبل از اولین اصلاح داشت، و مقدار فعلی، که مقداری است که پس از یک یا چند اصلاح به دست آمده است. هر دو مقدار به طور همزمان حفظ میشوند.
DataGrid 2 تا 5 نمایی از جدول [dtProduits] را که توسط ویژگی [RowStateFilter] فیلتر شده است، نمایش میدهند
DataGrid | فیلتر |
RowStateFilter= DataRowViewState.CurrentRows | |
RowStateFilter= DataRowViewState.Deleted | |
RowStateFilter = DataRowViewState.ModifiedOriginal | |
RowStateFilter= DataRowViewState.Added |
جدول [dtPoduits] بهطور همزمان توسط کلاینتهای وب مختلف بهروزرسانی میشود که میتواند به تضادهای دسترسی روی جدول منجر شود. وقتی یک کلاینت نماهای جدول [dtProduits] را نمایش میدهد، میخواهیم از وقوع این وضعیت جلوگیری کنیم در حالی که جدول در حالت ناپایدار قرار دارد زیرا توسط کلاینت وب دیگری در حال تغییر است. بنابراین، هرگاه یک کلاینت نیاز به دسترسی به جدول [dtProduits] برای عملیات خواندن طبق توضیحات فوق، یا برای عملیات نوشتن هنگام افزودن، اصلاح یا حذف محصولات داشته باشد، با استفاده از توالی زیر با سایر کلاینتها همگامسازی خواهد کرد:
ممکن است چندین بخش بحرانی در کد برنامه وجود داشته باشد:
مکانیزم به شرح زیر است:
- یک یا چند کلاینت به دستور [Application.Lock] در بخش بحرانی ۱ میرسند. این یک دستگاه توزیعکننده توکن با ورود یکتایی است. یک کلاینت این توکن را دریافت میکند. بیایید آن را C1 بنامیم.
- تا زمانی که مشتری C1 توکن را از طریق [Application.Unlock] بازنگردانده است، هیچ مشتری دیگری مجاز به ورود به بخش بحرانی تحت کنترل [Application.Lock] نیست. بنابراین در مثال بالا، هیچ کلاینتی نمیتواند وارد بخشهای بحرانی ۱ و ۲ شود.
- کلاینت C1، [Application.Unlock] را اجرا میکند و بدین ترتیب توکن ورود را بازمیگرداند. این توکن سپس میتواند به کلاینت دیگری اختصاص یابد. مراحل ۱ تا ۳ تکرار میشوند.
با این مکانیزم، ما تضمین میکنیم که تنها یک کلاینت به جدول [dtProduits]، چه برای خواندن و چه برای نوشتن، دسترسی داشته باشد.
لینک [Mise à jour] همچنین نما [Produits] را نمایش میدهد:

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

اگر عبارت مرتبسازی هنگام طراحی [DataGrid] تعریف نشده باشد، از نام فیلد داده مرتبط با ستون در [DataGrid] استفاده میشود. ترتیب مرتبسازی (صعودی، نزولی) توسط کاربر با استفاده از دکمههای رادیویی [rdCroissant, rd Décroissant] در اینجا تنظیم میشود:

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

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

ما همین کار را با کلاینت [Internet Explorer] (نمایش جزئی) انجام میدهیم:

با مشتری [Mozilla]، محصول [produit2] را حذف میکنیم. صفحهٔ جدید زیر به ما نمایش داده میشود:

عملیات حذف با موفقیت انجام شد. محصول حذفشده به درستی در [DataGrid] (محصولات حذفشده) نمایش داده میشود و دیگر در لیست محصولات برای اجزای [DataGrid1] و [DataGrid2]، که محصولات موجود در جدول را نمایش میدهند، دیده نمیشود. بیایید همین کار را با [Interbet Explorer] انجام دهیم. عنصر [produit2] را حذف خواهیم کرد:

خروجی حاصل به شرح زیر است:

یک پیام خطا به کاربر اطلاع میدهد که محصول با کلید [2] وجود ندارد. پاسخ یک نمای جدید را به کلاینت بازمیگرداند که وضعیت فعلی جدول را منعکس میکند. میتوانیم ببینیم که محصول با کلید [2] واقعاً در لیست محصولات حذفشده قرار دارد. بنابراین نما [produits] بهروزرسانیهای انجامشده توسط همه کلاینتها را منعکس میکند، نه فقط یک کلاینت.
9.6.14. افزودن محصول
برای افزودن یک محصول، کاربر روی لینک [Ajout] در گزینهها کلیک میکند. این کار صرفاً نمای [Ajout] را نمایش میدهد:


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

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

این شما را به حالت ویرایش میبرد:

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

پاسخ بهدستآمده به شرح زیر است:

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

استفاده از لینک [Mettre à jour] بالا پاسخ زیر را تولید میکند:

این تغییر در کامپوننتهای [DataGrid] 1 و 2 که وضعیت فعلی جدول [dtProduits] را منعکس میکنند، به وضوح قابل مشاهده است. این تغییر همچنین در کامپوننت [DataGrid4] وجود دارد که نمایی از سطرهای اصلاحشده است و با مقادیر اصلی آنها نمایش داده میشود. اکنون بیایید به موردی از تعارض دسترسی نگاهی بیندازیم. همانند مثال حذف، از دو کلاینت وب مختلف استفاده خواهیم کرد. یک کلاینت [Mozilla] جدول [dtProduits] را میخواند و برای محصول [produit1] وارد حالت ویرایش میشود:

مشتری با شناسه [Internet Explorer] در شرف حذف محصول [produit1] است:

کلاینت [Internet Explorer] در حال حذف [produit1] است:

توجه کنید که [produit1] دیگر در جدول ردیفهای اصلاحشده نیست، بلکه در جدول ردیفهای حذفشده قرار دارد. سپس کلاینت [Mozilla] بهروزرسانی خود را اعتبارسنجی میکند:

کلاینت [Mozilla] پاسخ زیر را برای بهروزرسانی خود دریافت میکند:

او میتواند ببیند که [produit1] حذف شده است، زیرا در لیست محصولات حذفشده ظاهر میشود.
9.7. برنامه وب برای بهروزرسانی جدول محصول فیزیکی
9.7.1. راهحلهای پیشنهادی
برنامه قبلی بیشتر یک مثال کتاب درسی بود که برای نمایش مدیریت یک شی [DataTable] کششده طراحی شده بود تا یک سناریوی دنیای واقعی. در واقع، در مقطعی، منبع داده واقعی باید بهروزرسانی شود. دو استراتژی متفاوت میتوان اتخاذ کرد:
- از کش درونحافظهای [dtProduits] برای بهروزرسانی منبع داده استفاده کنید. به طور جایگزین، میتوان یک صفحه در ساختار درختی وب برنامه قبلی ایجاد کرد تا دسترسی به کش [dtProduits] آن فراهم شود. این صفحه به یک مدیر اجازه میدهد تا تغییرات اعمالشده بر روی کش [dtProduits] را با منبع داده فیزیکی همگامسازی کند. برای این کار، میتوان یک متد جدید به کلاس دسترسی [produits] اضافه کرد که کش [dtProduits] را به عنوان پارامتر دریافت کرده و از این کش برای بهروزرسانی منبع داده فیزیکی استفاده کند.
- منبع داده فیزیکی همزمان با کش بهروزرسانی میشود.
استراتژی شمارهٔ ۱ اجازه میدهد تنها یک اتصال به منبع دادهٔ فیزیکی باز شود. استراتژی شمارهٔ ۲ برای هر بهروزرسانی به یک اتصال نیاز دارد. بسته به در دسترس بودن اتصال، ممکن است یکی از استراتژیها بر دیگری ترجیح داده شود. از آنجایی که ابزارهای لازم برای پیادهسازی آن (کلاس [produits]) را در اختیار داریم، استراتژی شماره ۲ را انتخاب کردهایم.
9.7.2. راه حل ۱
به منظور حفظ پیوستگی با برنامه قبلی، استراتژی زیر را انتخاب کردهایم:
- منبع فیزیکی همزمان با کش بهروزرسانی میشود
- پیشحافظه فقط یک بار، هنگام راهاندازی برنامه در [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] برای همه متدهای صفحه قابل دسترسی است. این نمونه برای سه عملیات بهروزرسانی: درج، حذف و بهروزرسانی استفاده خواهد شد.
روند add به شرح زیر اصلاح شده است:
Private Sub btnAjouter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAjouter.Click
...
' افزودن سطر
Application.Lock()
Try
' اضافه کردن سطر به جدول پُرکنندهای
dtProduits.Rows.Add(produit)
' ردیف به منبع فیزیکی اضافه میشود
Dim nouveauProduit As sProduit
With nouveauProduit
.nom = CType(produit("nom"), String)
.prix = CType(produit("prix"), Double)
End With
objProduits.ajouterProduit(nouveauProduit)
' نظارت
lblInfo3.Text = "Ajout réussi"
'پاکسازی
txtNom.Text = ""
txtPrix.Text = ""
Catch ex As Exception
'خطا
lblInfo3.Text = String.Format("Erreur : {0}", ex.Message)
End Try
Application.UnLock()
End Sub
رویه اصلاح به شرح زیر اصلاح میشود:
Private Sub modifierProduit(ByVal idProduit As Integer, ByVal item As DataGridItem)
Dim erreur As String
Try
'همگامسازی
Application.Lock()
' جستجو برای خطی که قرار است اصلاح شود
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' تغییر خط در کش
With ligne
.Item("nom") = CType(item.Cells(0).Controls(0), TextBox).Text.Trim
.Item("prix") = CType(item.Cells(1).Controls(0), TextBox).Text.Trim
End With
' ردیف در منبع فیزیکی بهروزرسانی میشود
Dim nouveauProduit As sProduit
With nouveauProduit
.id = idProduit
.nom = CType(ligne.Item("nom"), String)
.prix = CType(ligne.Item("prix"), Double)
End With
objProduits.modifierProduit(nouveauProduit)
End If
Catch ex As Exception
erreur = String.Format("Erreur lors de la modification : {0}", ex.Message)
Finally
'پایان همگامسازی
Application.UnLock()
End Try
' در صورت رخ دادن خطا، استثنا پرتاب میکند
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
رویه حذف به شرح زیر اصلاح میشود:
Private Sub supprimerProduit(ByVal idProduit As Integer)
Dim erreur As String
Try
'همگامسازی
Application.Lock()
' جستجو برای سطر جهت حذف
Dim ligne As DataRow = dtProduits.Rows.Find(idProduit)
If ligne Is Nothing Then
erreur = String.Format("Produit [{0}] inexistant", idProduit)
Else
' حذف سطر از کش
ligne.Delete()
' حذف سطر از منبع فیزیکی
objProduits.supprimerProduit(idProduit)
End If
Catch ex As Exception
erreur = String.Format("Erreur de suppression : {0}", ex.Message)
Finally
'پایان همگامسازی
Application.UnLock()
End Try
' در صورت رخ دادن خطا، یک استثنا پرتاب میشود
If erreur <> String.Empty Then Throw New Exception(erreur)
End Sub
9.7.3. آزمایشها
ما با جدول داده زیر در یک فایل ACCESS شروع میکنیم:

یک کلاینت وب راهاندازی میشود:

ما یک محصول اضافه میکنیم:

ما [produit1] را حذف میکنیم:

ما قیمت [produit2] را تغییر میدهیم:

پس از این تغییرات، محتویات جدول [liste] را در پایگاه داده ACCESS بررسی میکنیم:

سه تغییر بهدرستی در جدول فیزیکی منعکس شدهاند. اکنون بیایید یک سناریوی تداخل را بررسی کنیم. ما ردیف مربوط به [produit2] را که درست زیر ACCESS قرار دارد، حذف میکنیم:

بیایید به کلاینت وب خود بازگردیم. آنها نمیتوانند ببینند که حذف انجام شده و میخواهند [produit2] را خودشان حذف کنند:

آنها پاسخ زیر را دریافت میکنند:

خط [produit2] در واقع از کش حذف شده است، همانطور که در لیست محصولات حذف شده نشان داده شده است. با این حال، حذف [produit2] از منبع فیزیکی با شکست مواجه شده است، همانطور که از پیام خطا مشخص است.
9.7.4. راه حل ۲
در راهحل قبلی، کلاینتهای وب بهطور همزمان منبع داده فیزیکی را بهروزرسانی میکنند اما تغییرات اعمالشده توسط سایر کلاینتها را نمیبینند. آنها تنها تغییرات خود را مشاهده میکنند. اکنون میخواهیم کلاینت بتواند منبع داده فیزیکی را همانطور که در حال حاضر است ببیند، نه همانطور که هنگام راهاندازی برنامه بود. برای دستیابی به این هدف، گزینهای جدید در اختیار کلاینت قرار میدهیم:

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

یک کلاینت به نام [Internet Explorer] همین کار را انجام میدهد:

کلاینت [Mozilla]، [produit1] را حذف میکند، [produit2] را اصلاح میکند و یک محصول جدید اضافه میکند. این کار صفحه جدید زیر را تولید میکند:

کلاینت [Internet Explorer] میخواهد [produit1] را حذف کند.

پاسخ زیر را دریافت میکند:

به آنها اطلاع داده شد که [produit1] دیگر وجود ندارد. کاربر سپس تصمیم میگیرد با استفاده از لینک [Rafraîchir] بالا، کش خود را تازه کند. آنها پاسخ زیر را دریافت میکنند:

آنها اکنون همان منبع داده را مانند مشتری [Mozilla] دارند.
9.8. Conclusion
در این فصل، زمان قابل توجهی را به کانتینرهای داده و ارتباط آنها با منابع داده اختصاص دادیم. در پایان، با نمایش نحوه بهروزرسانی یک منبع داده با استفاده از کامپوننت [DataGrid]، به کار خود پایان دادیم. ما از یک جدول پایگاه داده بهعنوان منبع داده خود استفاده کردیم. اگرچه کامپوننت [DataGrid] ارائه دادهها را کمی آسانتر میکند، چالش واقعی نه در خود ارائه، بلکه در مدیریت بهروزرسانیهای منبع داده است که توسط کلاینتهای مختلف انجام میشود. ممکن است تعارضات دسترسی پیش بیاید و باید مدیریت شوند. در اینجا، ما آنها را در کنترلکننده با استفاده از [Application.Lock] مدیریت کردهایم. احتمالاً عاقلانهتر است که دسترسی به منبع داده را در کلاس دسترسی به داده همگامسازی کنیم تا کنترلکننده مجبور نباشد به چنین جزئیاتی که خارج از حوزه وظایف آن است، بپردازد.
در عمل، جداول در یک پایگاه داده از طریق روابط به یکدیگر متصل هستند و بهروزرسانیها باید این روابط را در نظر بگیرند. اولاً، این موضوع بر کلاس دسترسی به داده تأثیر میگذارد، زیرا این کلاس برای یک جدول مستقل، پیچیدهتر از حد لازم میشود. همچنین این موضوع عموماً بر لایه ارائه (presentation layer) وباپلیکیشن نیز تأثیر میگذارد، زیرا اغلب لازم است نه فقط یک جدول، بلکه جداول متعددی که از طریق روابط به یکدیگر متصل هستند، نمایش داده شوند.
این فصل همچنین برای معرفی ساختارهای داده مختلف مانند [DataTable, DataView] به کار رفته است.









