WebUserControl之間值得傳遞(使用Interface)

在使用WebUserControl的時候,如果一個畫面有數個WebUserControl的話,他們彼此間要怎麼溝通呢,也許有人說,使用FindControl/Property啊!!的確,小喵以前是這樣處理的,不過小喵卻發現這會讓WebUserControl在程式撰寫上變得複雜,既然要寫成WebUserControl,就是要把這些東西抽出來獨立處理/重複使用。但是如果寫的過程讓他與別的東西關係太密切,那就失去了抽出獨立處理/重複使用的目的。

再一次偶然的機會與熱心的Allen大聊到了物件(物件小喵一直都還在學),Allen大十分熱心的提示小喵可以用Interface來解決這類的問題。後來小喵在Allen大的文章也找到相關的範例與影片教學。不過範例是C#的,小喵用VB.NET來說明一次。

緣起

在使用WebUserControl的時候,如果一個畫面有數個WebUserControl的話,他們彼此間要怎麼溝通呢,也許有人說,使用FindControl/Property啊!!的確,小喵以前是這樣處理的,不過小喵卻發現這會讓WebUserControl在程式撰寫上變得複雜,既然要寫成WebUserControl,就是要把這些東西抽出來獨立處理/重複使用。但是如果寫的過程讓他與別的東西關係太密切,那就失去了抽出獨立處理/重複使用的目的。

再一次偶然的機會與熱心的Allen大聊到了物件(物件小喵一直都還在學),Allen大十分熱心的提示小喵可以用Interface來解決這類的問題。後來小喵在Allen大的文章也找到相關的範例與影片教學。不過範例是C#的,小喵用VB.NET來說明一次。

兩種模式

這邊會介紹兩種方式,都是透過Interface來處理:

  1. 訂閱者模式:定義發行者(Publisher)與讀者(Reader)這兩種Interface處理
  2. 事件模式:定義一個Event來丟資料,然後由Reciver來承接事件丟出的資料並且處理

Allen大的範例與教學檔如下,有興趣的人或者是學C#的人可以到Allen大的網站上查看:

範例:MasterPage, Page, UserControl 如何互動, 傳值(事件模式)

教學影片:如何做到 User control 互動-觀察者模式(觀察者模式)


準備WebUserControl畫面

接著小喵分別描述一下使用這兩種方式的範例:兩個範例的動作是一樣的,只是使用不同的方式處理。範例中使用北風資料庫,分別有三個WebUserControl

  1. 選擇Employee的下拉式選單:wucGetEmployee.ascx
  2. 顯示某Employee的Orders:wucEmpOrder.ascx
  3. 顯示某筆Order的Detail資料:wucOdrDetails.ascx

這三個的畫面分別如下

wucGetEmployee.ascx


<asp:DropDownList ID="ddlEmployee" runat="server" AutoPostBack="True" 
    DataSourceID="sdsEmployeeID" DataTextField="EmpName" 
    DataValueField="EmployeeID">
</asp:DropDownList>
<asp:SqlDataSource ID="sdsEmployeeID" runat="server" 
    ConnectionString="<%$ ConnectionStrings:NWind %>" 
    SelectCommand="SELECT EmployeeID, CONVERT (varchar(10), EmployeeID) + '-' + LastName AS EmpName FROM Employees ORDER BY EmployeeID">
</asp:SqlDataSource>

--

wucEmpOrder.ascx


<asp:GridView ID="gvEmpOrder" SkinID="GV01" runat="server" AllowPaging="True" 
    AutoGenerateColumns="False" DataKeyNames="OrderID" DataSourceID="sdsOrders">
    <Columns>
        <asp:CommandField ShowSelectButton="True" />
        <asp:BoundField DataField="OrderID" HeaderText="OrderID" InsertVisible="False" 
            ReadOnly="True" SortExpression="OrderID" />
        <asp:BoundField DataField="CustomerID" HeaderText="CustomerID" 
            SortExpression="CustomerID" />
        <asp:BoundField DataField="EmployeeID" HeaderText="EmployeeID" 
            SortExpression="EmployeeID" />
        <asp:BoundField DataField="OrderDate" HeaderText="OrderDate" 
            SortExpression="OrderDate" />
        <asp:BoundField DataField="Freight" HeaderText="Freight" 
            SortExpression="Freight" />
        <asp:BoundField DataField="ShipName" HeaderText="ShipName" 
            SortExpression="ShipName" />
        <asp:BoundField DataField="ShipCity" HeaderText="ShipCity" 
            SortExpression="ShipCity" />
    </Columns>
</asp:GridView>
<asp:SqlDataSource ID="sdsOrders" runat="server" 
    ConnectionString="<%$ ConnectionStrings:NWind %>" 
    SelectCommand="SELECT [OrderID], [CustomerID], [EmployeeID], [OrderDate], [Freight], [ShipName], [ShipCity] FROM [Orders] WHERE ([EmployeeID] = @EmployeeID)">
    <SelectParameters>
        <asp:Parameter Name="EmployeeID" Type="Int32" />
    </SelectParameters>
</asp:SqlDataSource>

--

wucOdrDetails.ascx


<asp:GridView ID="GridView1" SkinID="GV02" runat="server" 
    AutoGenerateColumns="False" DataKeyNames="OrderID,ProductID" 
    DataSourceID="sdsOrderDetails">
    <Columns>
        <asp:BoundField DataField="OrderID" HeaderText="OrderID" ReadOnly="True" 
            SortExpression="OrderID" />
        <asp:BoundField DataField="ProductID" HeaderText="ProductID" ReadOnly="True" 
            SortExpression="ProductID" />
        <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" 
            SortExpression="UnitPrice" />
        <asp:BoundField DataField="Quantity" HeaderText="Quantity" 
            SortExpression="Quantity" />
        <asp:BoundField DataField="Discount" HeaderText="Discount" 
            SortExpression="Discount" />
    </Columns>
</asp:GridView>
<asp:SqlDataSource ID="sdsOrderDetails" runat="server" 
    ConnectionString="<%$ ConnectionStrings:NWind %>" 
    SelectCommand="SELECT * FROM [Order Details] WHERE ([OrderID] = @OrderID)">
    <SelectParameters>
        <asp:Parameter Name="OrderID" Type="Int32" />
    </SelectParameters>
</asp:SqlDataSource>

兩種模式會用到的WebUserControl是一樣的,可以複製一份到另一個資料夾,以備等一下使用

接著就要開始來使用,兩種模是從這裡起會有不同的內容,以下分別說明

觀察者模式

首先先幫觀察者模式建立Interface,相關程式碼如下


Imports Microsoft.VisualBasic

Public Interface IOrder   '訂閱者
    '定閱後,要處理資料的Sub
    Sub ProcessData(ByVal Data As String)
End Interface

Public Interface IPublisher     '發行者
    '註冊有哪些訂閱者要訂閱我的發行
    Sub RegOrder(ByVal obj As IOrder)
End Interface

接著,一個一個的wuc來處理囉!!

首先是wucGetEmployee.ascx,這個wuc是個發行者。他選了某個Employee後,把EmployeeID發行出去,至於訂閱者收到Employee之後,要做什麼事情,這個跟目前的wucGetEmployee.ascx沒關係,那是別人的事情。

所以我們要在wucGetEmployee.ascx的CodeFile中Implements 這個Interface


Partial Class tOrderMode_wucGetEmployee
    Inherits System.Web.UI.UserControl
    Implements IPublisher

此時會在畫面中自動多出一段程式碼,這是配合Implements IPublisher的原因


    Public Sub RegOrder(ByVal obj As IOrder) Implements IPublisher.RegOrder
        
    End Sub

接著安排一個本頁的全域變數m_Orders,用這個來記錄有哪些訂閱者,訂閱了這個wuc


Dim m_Orders As New List(Of IOrder) '建立訂閱者的集合變數

再來,我們在剛剛的Regorder中,加上要註冊訂閱者的程式碼


    Public Sub RegOrder(ByVal obj As IOrder) Implements IPublisher.RegOrder
        m_Orders.Add(obj)   '註冊訂閱者
    End Sub

另外,這個下拉式選單,結合資料庫後,希望多出一筆請選擇。最後,希望在DropDownList的SelectedIndexChanged時,可以通知每一個訂閱者目前的EmployeeID


    Protected Sub ddlEmployee_DataBound(ByVal sender As Object, ByVal e As System.EventArgs) Handles ddlEmployee.DataBound
        Me.ddlEmployee.Items.Insert(0, "--請選擇--")
    End Sub

    Protected Sub ddlEmployee_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles ddlEmployee.SelectedIndexChanged
        If m_Orders.Count > 0 Then
            For Each odr In m_Orders
                odr.ProcessData(Me.ddlEmployee.SelectedValue)
            Next
        End If
    End Sub

這樣就完成的這個發行者wucGetEmployee.ascx的部分。

 

接著,撰寫wucEmpOrder.ascx的部分,對於wucGetEmployee.ascx來說,他是訂閱者(觀察者)所以一開始的時候,先在後端CodeFile加入Implements IOrder,因為Implements了IOrder,畫面會順便帶出ProcessData的部分


    Implements IOrder

    Public Sub ProcessData(ByVal Data As String) Implements IOrder.ProcessData

    End Sub

我們希望他訂閱到資料後,將得到的EmployeeID給畫面中的SqlDataSouce於是程式碼


    Public Sub ProcessData(ByVal Data As String) Implements IOrder.ProcessData
        '將訂閱到的資料給SqlDataSouce的SelectParameters("EmployeeID")
        Me.sdsOrders.SelectParameters("EmployeeID").DefaultValue = Data
    End Sub

事實上,wucEmpOrder.ascx他不只是個訂閱者,對OrderDetail來說他也是個發行者,希望選擇GridView裡面的Order之後,就顯是相對應的OrderDetail

過程與上面相似,就不贅述...詳細程式內容請參考本文最後面的範例檔。

最後把這兩個wuc放到畫面後,還需要處理一些些程式。

我們先把這三個wuc拉到畫面中,畫面如下

 


        <uc1:wucGetEmployee ID="wucGetEmployee1" runat="server" />
        <uc2:wucEmpOrder ID="wucEmpOrder1" runat="server" />
        <uc3:wucOdrDetails ID="wucOdrDetails1" runat="server" />

 

註冊上去後,他們彼此的關係還沒有設定,因此我們在PageLoad事件中,直行發行者的RegOrder來註冊訂閱者


    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Me.wucGetEmployee1.RegOrder(Me.wucEmpOrder1)
        Me.wucEmpOrder1.RegOrder(Me.wucOdrDetails1)
    End Sub

這樣在PageLoad的時候,wucGetEmployee1知道了wucEmpOrder1訂閱了他,wucEmpOrder1也知道了wucOdrDetails1訂閱了他。

 

 


 

事件模式

上面說明了觀察者模式後,接著說明事件模式。這兩個模是要做的事情是一樣的,不過裡面運作的概念上卻不太相同。這裡改以觸發事件的方式來處理。

 

一樣的先撰寫需要的Interface,首先建立一個類別,清除類別的程式碼,改成這樣


Imports Microsoft.VisualBasic

'宣告一個事件的Handler
Public Delegate Sub SetDataHandler(ByVal sender As Object, ByVal Data As String)

Public Interface IEventMode
    '設定這個介面有個SetData的事件
    Event SetData As SetDataHandler
End Interface

Public Interface IReciver
    '設定一個IReciver介面,裡面有UseData的Sub
    Sub UseData(ByVal Data As String)
End Interface

設定好後,接著一樣的,先從源頭的wucGetEmployee.ascx開始撰寫起

一樣的,先幫這個wuc的CodeFile安排Implements IEventMode此時會自動產生一個Event的語法


    Implements IEventMode

    Public Event SetData(ByVal sender As Object, ByVal Data As String) Implements IEventMode.SetData

接著,在DorpDownList的SelectedIndexChanged事件中,要去觸發這個事件,並且將目前的SelectedValue透過事件傳出去


    Protected Sub ddlEmployee_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles ddlEmployee.SelectedIndexChanged
        RaiseEvent SetData(Me.ddlEmployee, Me.ddlEmployee.SelectedValue)
    End Sub

接著安排wucEmpOrder.ascx這個,對於這個而言,當wucGetEmployee.ascx觸發了事件後,他要透過Reciver的UseData把事件中的值給予SqlDataSouce,一樣的要先Implements IReciver介面


    Implements IReciver

    Public Sub UseData(ByVal Data As String) Implements IReciver.UseData
        Me.sdsOrders.SelectParameters("EmployeeID").DefaultValue = Data
    End Sub

而對於此wuc他不只是Reciver也是EventMode因此也要Implements IEventMode介面,這部分與上面一樣,就不贅述了。

最後要把三個wuc放在畫面上,並且在aspx的CodeFile要處理一下,當三個wuc放到畫面後,畫面並不知道事件觸發應該要影響其他哪些wuc,因此要撰寫SetOrderData與SetOrderDetail這兩個Sub來通知Reciver開始使用事件發生的資料相關的程式碼如下(這部分與C#的程式碼差異度較大,如果是C#的使用者請參考Allen大的範例)


    Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
        MyBase.OnInit(e)
        AddHandler Me.wucGetEmployee1.SetData, AddressOf SetOrderData
        AddHandler Me.wucEmpOrder1.SetData1, AddressOf SetOrderDetailData

    End Sub

    Public Sub SetOrderData(ByVal sender As Object, ByVal Data As String)
        '設定
        Me.wucEmpOrder1.UseData(Data)
        Me.wucOdrDetails1.UseData("")

    End Sub
    Public Sub SetOrderDetailData(ByVal sender As Object, ByVal Data As String)
        Me.wucOdrDetails1.UseData(Data)
    End Sub

 


最後小喵將相關程式碼壓縮在此

請下載參考

http://vip2.blueshop.com.tw/topcat/DEMO/UC2UC/UC2UC.zip

 


以下是簽名:


Microsoft MVP
Microsoft MVP