緣起
在使用WebUserControl的時候,如果一個畫面有數個WebUserControl的話,他們彼此間要怎麼溝通呢,也許有人說,使用FindControl/Property啊!!的確,小喵以前是這樣處理的,不過小喵卻發現這會讓WebUserControl在程式撰寫上變得複雜,既然要寫成WebUserControl,就是要把這些東西抽出來獨立處理/重複使用。但是如果寫的過程讓他與別的東西關係太密切,那就失去了抽出獨立處理/重複使用的目的。
再一次偶然的機會與熱心的Allen大聊到了物件(物件小喵一直都還在學),Allen大十分熱心的提示小喵可以用Interface來解決這類的問題。後來小喵在Allen大的文章也找到相關的範例與影片教學。不過範例是C#的,小喵用VB.NET來說明一次。
兩種模式
這邊會介紹兩種方式,都是透過Interface來處理:
- 訂閱者模式:定義發行者(Publisher)與讀者(Reader)這兩種Interface處理
- 事件模式:定義一個Event來丟資料,然後由Reciver來承接事件丟出的資料並且處理
Allen大的範例與教學檔如下,有興趣的人或者是學C#的人可以到Allen大的網站上查看:
範例:MasterPage, Page, UserControl 如何互動, 傳值(事件模式)
教學影片:如何做到 User control 互動-觀察者模式(觀察者模式)
準備WebUserControl畫面
接著小喵分別描述一下使用這兩種方式的範例:兩個範例的動作是一樣的,只是使用不同的方式處理。範例中使用北風資料庫,分別有三個WebUserControl
- 選擇Employee的下拉式選單:wucGetEmployee.ascx
- 顯示某Employee的Orders:wucEmpOrder.ascx
- 顯示某筆Order的Detail資料:wucOdrDetails.ascx
這三個的畫面分別如下
wucGetEmployee.ascx
1 |
< asp:DropDownList ID = "ddlEmployee" runat = "server" AutoPostBack = "True" |
2 |
DataSourceID = "sdsEmployeeID" DataTextField = "EmpName" |
3 |
DataValueField = "EmployeeID" > |
5 |
< asp:SqlDataSource ID = "sdsEmployeeID" runat = "server" |
6 |
ConnectionString="<%$ ConnectionStrings:NWind %>" |
7 |
SelectCommand="SELECT EmployeeID, CONVERT (varchar(10), EmployeeID) + '-' + LastName AS EmpName FROM Employees ORDER BY EmployeeID"> |
--
wucEmpOrder.ascx
01 |
< asp:GridView ID = "gvEmpOrder" SkinID = "GV01" runat = "server" AllowPaging = "True" |
02 |
AutoGenerateColumns = "False" DataKeyNames = "OrderID" DataSourceID = "sdsOrders" > |
04 |
< asp:CommandField ShowSelectButton = "True" /> |
05 |
< asp:BoundField DataField = "OrderID" HeaderText = "OrderID" InsertVisible = "False" |
06 |
ReadOnly = "True" SortExpression = "OrderID" /> |
07 |
< asp:BoundField DataField = "CustomerID" HeaderText = "CustomerID" |
08 |
SortExpression = "CustomerID" /> |
09 |
< asp:BoundField DataField = "EmployeeID" HeaderText = "EmployeeID" |
10 |
SortExpression = "EmployeeID" /> |
11 |
< asp:BoundField DataField = "OrderDate" HeaderText = "OrderDate" |
12 |
SortExpression = "OrderDate" /> |
13 |
< asp:BoundField DataField = "Freight" HeaderText = "Freight" |
14 |
SortExpression = "Freight" /> |
15 |
< asp:BoundField DataField = "ShipName" HeaderText = "ShipName" |
16 |
SortExpression = "ShipName" /> |
17 |
< asp:BoundField DataField = "ShipCity" HeaderText = "ShipCity" |
18 |
SortExpression = "ShipCity" /> |
21 |
< asp:SqlDataSource ID = "sdsOrders" runat = "server" |
22 |
ConnectionString="<%$ ConnectionStrings:NWind %>" |
23 |
SelectCommand="SELECT [OrderID], [CustomerID], [EmployeeID], [OrderDate], [Freight], [ShipName], [ShipCity] FROM [Orders] WHERE ([EmployeeID] = @EmployeeID)"> |
25 |
< asp:Parameter Name = "EmployeeID" Type = "Int32" /> |
--
wucOdrDetails.ascx
01 |
< asp:GridView ID = "GridView1" SkinID = "GV02" runat = "server" |
02 |
AutoGenerateColumns = "False" DataKeyNames = "OrderID,ProductID" |
03 |
DataSourceID = "sdsOrderDetails" > |
05 |
< asp:BoundField DataField = "OrderID" HeaderText = "OrderID" ReadOnly = "True" |
06 |
SortExpression = "OrderID" /> |
07 |
< asp:BoundField DataField = "ProductID" HeaderText = "ProductID" ReadOnly = "True" |
08 |
SortExpression = "ProductID" /> |
09 |
< asp:BoundField DataField = "UnitPrice" HeaderText = "UnitPrice" |
10 |
SortExpression = "UnitPrice" /> |
11 |
< asp:BoundField DataField = "Quantity" HeaderText = "Quantity" |
12 |
SortExpression = "Quantity" /> |
13 |
< asp:BoundField DataField = "Discount" HeaderText = "Discount" |
14 |
SortExpression = "Discount" /> |
17 |
< asp:SqlDataSource ID = "sdsOrderDetails" runat = "server" |
18 |
ConnectionString="<%$ ConnectionStrings:NWind %>" |
19 |
SelectCommand="SELECT * FROM [Order Details] WHERE ([OrderID] = @OrderID)"> |
21 |
< asp:Parameter Name = "OrderID" Type = "Int32" /> |
兩種模式會用到的WebUserControl是一樣的,可以複製一份到另一個資料夾,以備等一下使用
接著就要開始來使用,兩種模是從這裡起會有不同的內容,以下分別說明
觀察者模式
首先先幫觀察者模式建立Interface,相關程式碼如下
01 |
Imports Microsoft.VisualBasic |
03 |
Public Interface IOrder |
05 |
Sub ProcessData( ByVal Data As String ) |
08 |
Public Interface IPublisher |
10 |
Sub RegOrder( ByVal obj As IOrder) |
接著,一個一個的wuc來處理囉!!
首先是wucGetEmployee.ascx,這個wuc是個發行者。他選了某個Employee後,把EmployeeID發行出去,至於訂閱者收到Employee之後,要做什麼事情,這個跟目前的wucGetEmployee.ascx沒關係,那是別人的事情。
所以我們要在wucGetEmployee.ascx的CodeFile中Implements 這個Interface
1 |
Partial Class tOrderMode_wucGetEmployee |
2 |
Inherits System.Web.UI.UserControl |
此時會在畫面中自動多出一段程式碼,這是配合Implements IPublisher的原因
1 |
Public Sub RegOrder( ByVal obj As IOrder) Implements IPublisher.RegOrder |
接著安排一個本頁的全域變數m_Orders,用這個來記錄有哪些訂閱者,訂閱了這個wuc
1 |
Dim m_Orders As New List(Of IOrder) |
再來,我們在剛剛的Regorder中,加上要註冊訂閱者的程式碼
1 |
Public Sub RegOrder( ByVal obj As IOrder) Implements IPublisher.RegOrder |
另外,這個下拉式選單,結合資料庫後,希望多出一筆請選擇。最後,希望在DropDownList的SelectedIndexChanged時,可以通知每一個訂閱者目前的EmployeeID
01 |
Protected Sub ddlEmployee_DataBound( ByVal sender As Object , ByVal e As System.EventArgs) Handles ddlEmployee.DataBound |
02 |
Me .ddlEmployee.Items.Insert(0, "--請選擇--" ) |
05 |
Protected Sub ddlEmployee_SelectedIndexChanged( ByVal sender As Object , ByVal e As System.EventArgs) Handles ddlEmployee.SelectedIndexChanged |
06 |
If m_Orders.Count > 0 Then |
07 |
For Each odr In m_Orders |
08 |
odr.ProcessData( Me .ddlEmployee.SelectedValue) |
這樣就完成的這個發行者wucGetEmployee.ascx的部分。
接著,撰寫wucEmpOrder.ascx的部分,對於wucGetEmployee.ascx來說,他是訂閱者(觀察者)所以一開始的時候,先在後端CodeFile加入Implements IOrder,因為Implements了IOrder,畫面會順便帶出ProcessData的部分
3 |
Public Sub ProcessData( ByVal Data As String ) Implements IOrder.ProcessData |
我們希望他訂閱到資料後,將得到的EmployeeID給畫面中的SqlDataSouce於是程式碼
1 |
Public Sub ProcessData( ByVal Data As String ) Implements IOrder.ProcessData |
3 |
Me .sdsOrders.SelectParameters( "EmployeeID" ).DefaultValue = Data |
事實上,wucEmpOrder.ascx他不只是個訂閱者,對OrderDetail來說他也是個發行者,希望選擇GridView裡面的Order之後,就顯是相對應的OrderDetail
過程與上面相似,就不贅述...詳細程式內容請參考本文最後面的範例檔。
最後把這兩個wuc放到畫面後,還需要處理一些些程式。
我們先把這三個wuc拉到畫面中,畫面如下
1 |
< uc1:wucGetEmployee ID = "wucGetEmployee1" runat = "server" /> |
2 |
< uc2:wucEmpOrder ID = "wucEmpOrder1" runat = "server" /> |
3 |
< uc3:wucOdrDetails ID = "wucOdrDetails1" runat = "server" /> |
註冊上去後,他們彼此的關係還沒有設定,因此我們在PageLoad事件中,直行發行者的RegOrder來註冊訂閱者
1 |
Protected Sub Page_Load( ByVal sender As Object , ByVal e As System.EventArgs) Handles Me .Load |
2 |
Me .wucGetEmployee1.RegOrder( Me .wucEmpOrder1) |
3 |
Me .wucEmpOrder1.RegOrder( Me .wucOdrDetails1) |
這樣在PageLoad的時候,wucGetEmployee1知道了wucEmpOrder1訂閱了他,wucEmpOrder1也知道了wucOdrDetails1訂閱了他。
事件模式
上面說明了觀察者模式後,接著說明事件模式。這兩個模是要做的事情是一樣的,不過裡面運作的概念上卻不太相同。這裡改以觸發事件的方式來處理。
一樣的先撰寫需要的Interface,首先建立一個類別,清除類別的程式碼,改成這樣
01 |
Imports Microsoft.VisualBasic |
04 |
Public Delegate Sub SetDataHandler( ByVal sender As Object , ByVal Data As String ) |
06 |
Public Interface IEventMode |
08 |
Event SetData As SetDataHandler |
11 |
Public Interface IReciver |
13 |
Sub UseData( ByVal Data As String ) |
設定好後,接著一樣的,先從源頭的wucGetEmployee.ascx開始撰寫起
一樣的,先幫這個wuc的CodeFile安排Implements IEventMode此時會自動產生一個Event的語法
3 |
Public Event SetData( ByVal sender As Object , ByVal Data As String ) Implements IEventMode.SetData |
接著,在DorpDownList的SelectedIndexChanged事件中,要去觸發這個事件,並且將目前的SelectedValue透過事件傳出去
1 |
Protected Sub ddlEmployee_SelectedIndexChanged( ByVal sender As Object , ByVal e As System.EventArgs) Handles ddlEmployee.SelectedIndexChanged |
2 |
RaiseEvent SetData( Me .ddlEmployee, Me .ddlEmployee.SelectedValue) |
接著安排wucEmpOrder.ascx這個,對於這個而言,當wucGetEmployee.ascx觸發了事件後,他要透過Reciver的UseData把事件中的值給予SqlDataSouce,一樣的要先Implements IReciver介面
3 |
Public Sub UseData( ByVal Data As String ) Implements IReciver.UseData |
4 |
Me .sdsOrders.SelectParameters( "EmployeeID" ).DefaultValue = Data |
而對於此wuc他不只是Reciver也是EventMode因此也要Implements IEventMode介面,這部分與上面一樣,就不贅述了。
最後要把三個wuc放在畫面上,並且在aspx的CodeFile要處理一下,當三個wuc放到畫面後,畫面並不知道事件觸發應該要影響其他哪些wuc,因此要撰寫SetOrderData與SetOrderDetail這兩個Sub來通知Reciver開始使用事件發生的資料相關的程式碼如下(這部分與C#的程式碼差異度較大,如果是C#的使用者請參考Allen大的範例)
01 |
Protected Overrides Sub OnInit( ByVal e As System.EventArgs) |
03 |
AddHandler Me .wucGetEmployee1.SetData, AddressOf SetOrderData |
04 |
AddHandler Me .wucEmpOrder1.SetData1, AddressOf SetOrderDetailData |
08 |
Public Sub SetOrderData( ByVal sender As Object , ByVal Data As String ) |
10 |
Me .wucEmpOrder1.UseData(Data) |
11 |
Me .wucOdrDetails1.UseData( "" ) |
14 |
Public Sub SetOrderDetailData( ByVal sender As Object , ByVal Data As String ) |
15 |
Me .wucOdrDetails1.UseData(Data) |
最後小喵將相關程式碼壓縮在此
請下載參考
http://vip2.blueshop.com.tw/topcat/DEMO/UC2UC/UC2UC.zip
留言列表