posts - 57, comments - 257, trackbacks - 14, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

2008年6月22日

     摘要: 摘要之前在「Calendar 动态产生子控件的 Event Handler」一文中有提到如何处理 Calendar 在 DayRender 事件动态产生按钮的事件处理,文中最后有提及,若 Calendar 控件有类似 GridView 控件中有 RowCommand 事件,在使用上就可以更简化。所以本文将扩展 Calendar 控件,新增 DayCommand 事件,就动态产生的按钮可以可以引发 ...  阅读全文

posted @ 2008-06-22 20:00 jeff377 阅读(1394) | 评论 (0)编辑

2008年6月21日

摘要
在网络上看到很多 GridView 内含子 GridView 的范例,虽然方法不同不过程序代码都有点小复杂,想说难道没有更简单易懂的方法吗?
在此文章中将用十几行的程序代码,就教你快速学会并搞定这种 Master-Detail GridView 的需求。
 
程序代码实作
我们以 Northwind 数据库为例,首先在页面上放置二组 GridView+SqlDataSoruce,分别系结至 [Orders] 、[Order Details] 这二个资料表。



我們先來看一下它的程式碼及執行結果,後續再做進一步的詳細說明。
我們只要在 GridView 的 RowDataBound 事件撰寫下面十幾行的程式碼就完成了,不要懷疑這已經是全部的程式碼。

    Protected Sub GridView1_RowDataBound(ByVal sender As ObjectByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles GridView1.RowDataBound
        
If (e.Row.RowState And DataControlRowState.Selected) = DataControlRowState.Selected Then
            
Dim oRow As New GridViewRow(0-1, DataControlRowType.DataRow, e.Row.RowType)
            GridView2.Visible 
= True
            GridView2.DataBind()
            
Dim sHTML As String = Bee.Web.WebFunc.ControlToHTML(GridView2)
            GridView2.Visible 
= False
            
Dim oCell As New TableCell
            oCell.Text 
= sHTML
            oCell.ColumnSpan 
= e.Row.Cells.Count
            oRow.Cells.Add(oCell)
            e.Row.Parent.Controls.AddAt(e.Row.RowIndex 
+ 2, oRow)
        
End If
    
End Sub

执行程序,选取 GridView 的某一数据列,就会展开其子 GridView 。执行换页动作,一样可以正常展开子 GridView。





接下来说明这个范例的一些细节,Master GridView 系结 [Orders] 数据表,设定 DataKeyNames="OrderID",当选取某笔数据时,GridView 的 SelectedValue 就是该笔数据的 OrderID 字段值。另外设定 GridView 的 EnableViewState="False",这个设定主要是让 GridView 选取时都会重新做 DataBind 的动作。

        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CellPadding="4"
            DataKeyNames
="OrderID" DataSourceID="SqlDataSource1" EmptyDataText="沒有資料錄可顯示。"
            ForeColor
="#333333" GridLines="None" AllowPaging="True" EnableViewState="False">

再来就是 Detail GridView 系结 [Order Details] 数据表,一样设定 GridView 的 EnableViewState="False",且设定 Visible="False",也就是初始状态 Detail GridView 是隐藏的。

        <asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False" BackColor="LightGoldenrodYellow"
            BorderColor
="Tan" BorderWidth="1px" CellPadding="2" DataKeyNames="OrderID,ProductID"
            DataSourceID
="SqlDataSource2" EmptyDataText="沒有資料錄可顯示。" ForeColor="Black" GridLines="None" Visible="False" EnableViewState="False">


Detail GridView 所系结的 SqlDataSource 控件,将其 SelectParameters 的 @OrderID 参数关连至 Master GridView 的 SelectedValue。




            <SelectParameters>
                
<asp:ControlParameter ControlID="GridView1" Name="OrderID" PropertyName="SelectedValue"
                    Type
="Int32" />
            
</SelectParameters>


再来还有一个重要步骤,就是在 aspx 程序代码中,要设定 Page 的 EnableEventValidation="false",因为我们动态将 Detail GridView Render 出来插入 Master GridView 中,整个控件阶层都被异动,这样会造成事件验证失败。


<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default5.aspx.vb" Inherits="Default5" EnableEventValidation="false" %>


另外在 Master GridView 的 RowDataBound 事件中,有一个 ControlToHTML 方法是取得 Detail GridView 的 HTML 码,这个方法可以参考笔者另一篇「使用 BasePage 来解决 GridView 执行 RenderControl 产生的错误」文章有详细说明。

Dim sHTML As String = Bee.Web.WebFunc.ControlToHTML(GridView2)

 

posted @ 2008-06-21 01:51 jeff377 阅读(2260) | 评论 (16)编辑

2008年6月18日

若要计算二个日期经过几月又几天,需要考虑期间每个月份的天数,每个月的天数是不一样的。
所以我们可以先计算经过几个月,然后把起始日期累加异差月份数,再计算差异天数。

        Dim oDate1 As Date = Date.Parse("2008/5/1")
        
Dim oDate2 As Date = Date.Parse("2008/7/10")

        
'先算月份
        Dim iMonths As Integer = DateDiff(DateInterval.Month, oDate1, oDate2)
        
'再算天数
        Dim iDays As Integer = DateDiff(DateInterval.Day, DateAdd(DateInterval.Month, iMonths, oDate1), oDate2)

        
MsgBox(String.Format("经过{0}月{1}天", iMonths, iDays))

posted @ 2008-06-18 20:01 jeff377 阅读(157) | 评论 (0)编辑

2008年6月17日

我们常用 Hashtable 来描述索引键/值组的集合,Hashtable 的键值是区分大小写的。

        Dim oHash As New Hashtable()
        
Dim bIsFind As Boolean

        oHash.Add(
"A""ValueA")
        oHash.Add(
"B""ValueA")
        oHash.Add(
"C""ValueA")

        bIsFind 
= oHash.Contains("a"'鍵值 "a" 不存在
        bIsFind = oHash.Contains("A"'鍵值 "A" 存在


若我们需要让 Hasktable 不区分大小写怎么处理呢?常看到的一种作法就是在 Hasktable 的加入/移除时先将键值全转为小写(或大写),而判断时也将键值同样转为小写(或大写)。

        Dim oHash As New Hashtable()
        
Dim bIsFind As Boolean

        oHash.Add(
"A".ToLower, "ValueA")
        oHash.Add(
"B".ToLower, "ValueA")
        oHash.Add(
"C".ToLower, "ValueA")

        bIsFind 
= oHash.Contains("a".ToLower) '鍵值 "a" 存在
        bIsFind = oHash.Contains("A".ToLower) '鍵值 "A" 存在


当然上述的方式是可行为,不过略显麻烦,若键值忘了转换就加入就容易导致找不到的情形,最好的方式就是由 Hashtable 本身自行处理,让开发人员无需去理会键值大小写。
在 Hasktable 的有一多载的建构函式如下

Public Sub New ( _
    equalityComparer 
As IEqualityComparer _
)


所以我们只要撰写一个具 IEqualityComparer 接口的类别来做键值比对就可以了。

Public Class THashKeyComparer
    
Implements IEqualityComparer

    
Public Function Equals1(ByVal x As ObjectByVal y As Object) _
        
As Boolean Implements IEqualityComparer.Equals
        
Return (CStr(x).ToLower = CStr(y).ToLower)
    
End Function


    
Public Function GetHashCode1(ByVal obj As Object) _
        
As Integer Implements IEqualityComparer.GetHashCode
        
Return obj.ToString().ToLower().GetHashCode()
    
End Function

End Class


当我们要使用 Hasktable 时,只要使用自定义具 IEqualityComparer 的类别来初始化 Hasktable 即可。


        
Dim oHash As New Hashtable(New THashKeyComparer())
        
Dim bIsFind As Boolean

        oHash.Add(
"A""ValueA")
        oHash.Add(
"B""ValueA")
        oHash.Add(
"C""ValueA")

        bIsFind 
= oHash.Contains("a"'鍵值 "a" 存在
        bIsFind = oHash.Contains("A"'鍵值 "A" 存在


 

posted @ 2008-06-17 00:54 jeff377 阅读(39) | 评论 (0)编辑

2008年6月13日

摘要
一般执行新增/修改完毕准备异动数据库前,通常会做一层数据正确性检查的动作;当我们使用 GridView 系结 SqlDataSoruce 来呈现数据,若 GridView 进行数据编辑存盘前也要做字段值的检查应该在如何做呢?应该在那个控件的那个事件去处理这个检查动作呢?

程序说明及实作
首先在页面上放置一个 GridView 及 SqlDataSource 控件,GridView 设为可编辑状态。





当 GridView 编辑储存时,我们要先做一些字段值正确性的检查动作,以下的范例为测试示范,只判断 LastName 字段不得为空,这种必填字段的判断一般只要使用 RequiredFieldValidator 控件在 Client 端即可。

方法一:在 GridView 的 RowUpdating 事件处理字段检查
GridView 在编辑储存前会引发 RowUpdating 事件,我们可以在此事件中处理字段检查,字段检查的程序代码如下。当字段检查不合法时,设定 e.Cancel = True 即可中断数据更新的动作。

    Protected Sub GridView1_RowUpdating(ByVal sender As ObjectByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs) Handles GridView1.RowUpdating
        
Dim sScript As String

        
If String.IsNullOrEmpty(e.NewValues.Item("LastName")) Then
            sScript 
= "alert('LastName 欄位不得為空')"
            
Me.ClientScript.RegisterStartupScript(Me.GetType, "error", sScript, True)
            e.Cancel 
= True
        
End If
    
End Sub

执行程序,故意将 LastName 清空,然后按 [更新] 钮进行储存,字段检查的动作就被执行了。



方法二:在 SqlDataSource 的 Updating 事件处理字段检查
对于 SqlDataSource 来说,编辑数据异动数据库之前,会引发 SqlDataSource 的 Updating 事件,字段检查的程序代码如下。当字段检查不合法时,设定 e.Cancel = True 即可中断数据更新的动作,这种方式的执行结果跟上个作法一样。

    Protected Sub SqlDataSource1_Updating(ByVal sender As ObjectByVal e As System.Web.UI.WebControls.SqlDataSourceCommandEventArgs) Handles SqlDataSource1.Updating
        
Dim sScript As String
        
If String.IsNullOrEmpty(e.Command.Parameters("@LastName").Value) Then
            sScript 
= "alert('LastName 欄位不得為空')"
            
Me.ClientScript.RegisterStartupScript(Me.GetType, "error", sScript, True)
            e.Cancel 
= True
        
End If
    
End Sub


结论

以上的说明可以知道在 GridView.RowUpdating 事件及 SqlDataSource.Updating 事件都可以处理字段检查,在那个地方处理比较好呢?答案是由数据源头的 SqlDataSource 控件来处理会比较好,主要原因如下:
1.尽量不要在 UI 控件处理数据验证动作,因为一旦更换其它 UI 控件(如更换其它厂商开发的 GridView 控件),程序代码就又要改写。
2.不同的 UI 控件系结 SqlDataSource 控件,都可以适用相同方法。例如 FormView 及 GridView 不同的 UI 控件,都可以在 SqlDataSource.Updating 统一处理。
3.如果直接使用 SqlDataSource.Update 方法去执行更新动作,不会引发 UI 控件的相关事件,但是一样会引发 SqlDataSource.Updating 事件。

posted @ 2008-06-13 15:54 jeff377 阅读(1232) | 评论 (6)编辑

2008年6月12日

承上一篇「SqlDataSource 执行数据筛选」中,可以知道 SqlDataSource 的 SelectCommand 属性值不会被保留在 ViewState 中,所以我们在 Page 中覆写 LoadViewState 及 SaveViewState 方法来维护 SqlDataSource 的 SelectCommand 属性值。
不过这样每次动态设定 SelectCommand 属性值都要这样处理,感觉上会比较麻烦点;是否能让 SqlDataSource 控件的 SelectCommand 属性值可以由控件本身自行维护状态呢?当然可以,而且非常简单,只要把上篇中覆写 LoadViewState 及 SaveViewState 方法的作法直接搬到自订 SqlDataSource 控件即可。
继承 SqlDataSource 命名为 TBSqlDataSource,覆写 LoadViewState 及 SaveViewState 方法来维护 SelectCommand 属性,这样就可以更方便使用 SelectCommand 属性,而无需由每个页面去自行处理。

Namespace WebControls
    
Public Class TBSqlDataSource
        
Inherits SqlDataSource

ViewState 状态管理

    
End Class

End Namespace

posted @ 2008-06-12 19:54 jeff377 阅读(1209) | 评论 (0)编辑

2008年6月11日

摘要
使用 SqlDataSource 控件可以很方便的与 UI 控件 (如 GridView、FormView) 系结来呈现数据,若需要针对 SqlDataSource 做数据筛选时,最直觉的方式就是去修改 SqlDataSoruce.SelectCommand 的 SQL 命令来执行数据筛选,这样设定的呈现结果感觉是正确的,数据真得依设定的条件来筛选过滤。可以当 UI 控件重新做 DataBind 时,如 GridView 换页的动作,会发生数据又全部跑出来了。为何会有这样异常的结果呢?本文就是来说明发生这种情形的原因及正确的程序撰写方式。

SqlDataSource 执行数据筛选
在页面上放置 SqlDataSource 及 GridView 来呈现数据,并将 GridView 设定分页,页面控件的配置如下所示,当按下 [Filter] 按钮时,会依选取的字段名称及输入的筛选值来过滤数据。



在 [Filter] 按钮的 Click 事件撰写如下程序代码,主要是依设定的字段及筛选值产生 SQL 语法,并设定给  SqlDataSource.SelectCommand 属性。

    Protected Sub btnFilter_Click(ByVal sender As ObjectByVal e As System.EventArgs) Handles btnFilter.Click
        
Dim sSQL As String
        
Dim sFilter As String

        
Select Case ddlFile.SelectedValue.ToUpper
            
Case "EmployeeID".ToUpper
                sFilter 
= "[EmployeeID]=" & txtValue.Text
            
Case Else
                sFilter 
= "[" & ddlFile.SelectedValue & "] Like '%" & txtValue.Text & "%'"
        
End Select

        sSQL 
= "SELECT [EmployeeID], [LastName], [FirstName], [Title], [City] FROM [Employees] Where " & sFilter
        SqlDataSource1.SelectCommand 
= sSQL
    
End Sub

在 [DataBind] 按钮的 Click 事件撰写如下程序代码,主要是在显示 SqlDataSource.SelectCommand 属性值及执行 GridView.DataBind,使 GridView 重新系结数据。

    Protected Sub btnDataBind_Click(ByVal sender As ObjectByVal e As System.EventArgs) Handles btnDataBind.Click
        Response.Write(
"SelectCommand: " & SqlDataSource1.SelectCommand)
        GridView1.DataBind()
    
End Sub

执行程序,选择 EmployeeID 字段,输入筛选值为 "3",按下 [Filter] 按钮来执行筛选,执行结果可以正确筛选资料。



可是这时我们按下 [DataBind] 按钮,让 GridView 重新做数据系结。这时发生了奇怪的现象,资料怎么全又跑出来了呢?仔细看一下输出的 SqlDataSource.SelectCommand 属性值,它怎么不是我们刚刚设定的筛选 SQL 语法呢?若筛选的数据可以分页,你会发现按换页的结果,也会发生同样的情形,因为 换页也需要 DataBind,所以跟自行去设定 DataBind 的结果是一样的。



为什么设定的 SelectCommand 属性值不见了呢?主要原因就是 SelectCommand 属性并没有被保留在 ViewState 中,所以每次 PostBack 时,它的值就会还原为设计阶段的初始值。
 
SqlDataSource 执行数据筛选正确作法
即然我们知道原因是「SelectCommand 属性并没有被保留在 ViewState 中」,那最简单的方式就是我们自行撰写程序将其保留在 ViewState 中,故页面覆写 LoadViewState 及 SaveViewState 方法来保留 SelectCommand 属性。

    ''' <summary>
    
''' 由 ViewState 还原控件的状态。
    
''' </summary>
    
''' <param name="savedState">要还原的控件状态。</param>

    Protected Overrides Sub LoadViewState(ByVal savedState As Object)
        
If Not (savedState Is NothingThen
            
' Load State from the array of objects that was saved at ;
            ' SavedViewState.
            Dim myState As Object() = CType(savedState, Object())

            
If Not (myState(0Is NothingThen
                
MyBase.LoadViewState(myState(0))
            
End If

            
If Not (myState(1Is NothingThen
                SqlDataSource1.SelectCommand 
= CType(myState(1), String)
            
End If
        
End If
    
End Sub


    
''' <summary>
    
''' 控件的状态储存至 ViewState。
    
''' </summary>
    
''' <returns>含有控件之目前检视状态的对象。</returns>

    Protected Overrides Function SaveViewState() As Object
        
Dim baseState As Object = MyBase.SaveViewState()
        
Dim myState(1As Object
        myState(
0= baseState
        myState(
1= SqlDataSource1.SelectCommand
        
Return myState
    
End Function

重新执行程序,与之前的做法一样来筛选数据,最后按下 [DataBind] 按钮重新做数据系结,可以发现结果正确了,而输出的 SelectCommand 属性值也是我们最后设定的值。



posted @ 2008-06-11 21:32 jeff377 阅读(1195) | 评论 (5)编辑

2008年6月10日

     摘要: 摘要若窗体(页面)具有「执行、新增、修改、删除」等操作权限控管,可以使用列举来描述使用者在该窗体的权限,详细的作法可以参考下面的「Enum 的设计与应用 - 简易权限设计」这篇文章。在此我们将利用这种列举的方式来描述窗体权限,并由 BasePage 来处理窗体权限的控管。程序说明及实作首先定义 EFormActions 列举,来描述窗体操作权限。/**/'''<summary>'''窗...  阅读全文

posted @ 2008-06-10 22:56 jeff377 阅读(2480) | 评论 (17)编辑

2008年6月6日

摘要
在 ASP.NET 中,ObjectDataSoruce 控件是实现三层式的重要关键,我们可以透过 ObjectDataSoruce 控件使用的自订中间层商务对象。不过一般找到的范例都是直接系结中间层商务对象,虽然范例通常写得相当符合对象导向,可是在实际运用上有下列几个问题。

问题一:维护性不佳
例如 Employee 商务对象的 Update 方法,可能有下列二种写法
[写法一] Update 方法中,每个字段皆为自变量
Public Function UpdateEmployee(EmployeeID As Integer, LastName As String, FirstName As String, _
                                   Address As String, City As String, Region As String, _
                                   PostalCode As String) As Integer
参考:http://msdn.microsoft.com/zh-tw/library/ms178538(VS.80).aspx
 
[写法二] Update 方法使用强型别,每个字段对应到类别的属性
Public Function UpdateEmployee(employee As NorthwindEmployee) As Integer
参考:http://msdn.microsoft.com/zh-tw/library/ms227562(VS.80).aspx
 
可是实际上真能这样用吗?以[方法一]为例,若异动字段非常多,Update 方法的自变量不就多到吓死人;[方法二]使用强型别可以解决字段多的问题,不过字段增减时,都需要同时维护 NorthwindEmployee 类别不是很麻烦吗?当你的系统非常庞大时,例如有上千个窗体,当这些窗体若需同时新增一个字段时,那就需要同时更新上千个对应的类别,可能会搞到你疯掉。
 
问题二:批次异动问题
当 GridView