October 22, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Managing Nested GridView Controls

  • September 27, 2006
  • By Paul Kimmel
  • Send Email »
  • More Articles »

The new nullable types are used for value type objects to permit assigning a null value to them without throwing an exception. Microsoft uses this technique when you use generated typed DataSet objects too. Nullable types can be assigned Nothing and work fine between nullable and non-nullable value types. You don't need to make string-types nullable because they are reference types and thus already nullable.

Listing 9: The Custom Data Access Class

Imports Microsoft.VisualBasic
Imports System.Collections.Generic
Imports System.Data
Imports System.Data.SqlClient

Public Class Data
   Private Const connectionString As String = _
      "Password=test;Persist Security Info=True;User ID=dummy;" + _
      "Initial Catalog=Northwind;Data Source=localhost;"
   Public Shared Function GetCustomerList() As List(Of Customer)
      Dim customers As List(Of Customer) = GetCustomer()
      Dim orders As List(Of _Order) = GetOrder()
      Dim c As Customer
      Dim o As _Order
      For Each c In customers
         For Each o In orders
            If (c.CustomerID = o.CustomerID) Then
               c.MyOrders.Add(o)
            End If
         Next
      Next
      Return customers
   End Function
   Private Shared Function GetCustomer() As List(Of Customer)
      Using connection As SqlConnection = _
         New SqlConnection(connectionString)
         connection.Open()
         Dim command As SqlCommand = _
            New SqlCommand("SELECT * FROM Customers", connection)
         Dim reader As SqlDataReader = command.ExecuteReader()
         Dim customers As List(Of Customer) = New List(Of Customer)
         While (reader.Read())
            Dim customer As Customer = New Customer
            customer.Address      = HandleDBNull(reader("Address"))
            customer.City         = HandleDBNull(reader("City"))
            customer.CompanyName  = HandleDBNull(reader("CompanyName"))
            customer.ContactName  = HandleDBNull(reader("ContactName"))
            customer.ContactTitle = HandleDBNull(reader("ContactTitle"))
            customer.Country      = HandleDBNull(reader("Country"))
            customer.CustomerID   = HandleDBNull(reader("CustomerID"))
            customer.Fax          = HandleDBNull(reader("Fax"))
            customer.Phone        = HandleDBNull(reader("Phone"))
            customer.PostalCode   = HandleDBNull(reader("PostalCode"))
            customer.Region       = HandleDBNull(reader("Region"))
            customers.Add(customer)
         End While
         Return customers
      End Using
   End Function
   Private Shared Function HandleDBNull(ByVal o As Object)
      If (o Is System.DBNull.Value) Then
         Return Nothing
      Else
         Return o
      End If
   End Function

   Private Shared Function GetOrder() As List(Of _Order)
      Using connection As SqlConnection = _
         New SqlConnection(connectionString)
         connection.Open()
         Dim command As SqlCommand = New SqlCommand( _
            "SELECT * FROM Orders Order BY CustomerID", connection)
         Dim reader As SqlDataReader = command.ExecuteReader()
         Dim orders As List(Of _Order) = New List(Of _Order)
         While (reader.Read())
            Dim o As _Order  = New _Order()
            o.CustomerID     = HandleDBNull(reader("CustomerID"))
            o.EmployeeID     = HandleDBNull(reader("EmployeeID"))
            o.Freight        = HandleDBNull(reader("Freight"))
            o.OrderDate      = HandleDBNull(reader("OrderDate"))
            o.OrderID        = HandleDBNull(reader("OrderID"))
            o.RequiredDate   = HandleDBNull(reader("RequiredDate"))
            o.ShipAddress    = HandleDBNull(reader("ShipAddress"))
            o.ShipCity       = HandleDBNull(reader("ShipCity"))
            o.ShipCountry    = HandleDBNull(reader("ShipCountry"))
            o.ShipName       = HandleDBNull(reader("ShipName"))
            o.ShippedDate    = HandleDBNull(reader("ShippedDate"))
            o.ShipPostalCode = HandleDBNull(reader("ShipPostalCode"))
            o.ShipRegion     = HandleDBNull(reader("ShipRegion"))
            o.ShipVia        = HandleDBNull(reader("ShipVia"))
            orders.Add(o)
         End While
         Return orders
      End Using
   End Function
End Class
Tip: For the custom data-access class in a production system, you would move the connection string to the web.config file and encrypt it. That technique is fodder for another article.

Building the Publisher

Now you know what a publisher should look like to subscribers. However, you need additional plumbing. You need methods to permit downstream nested controls to send events, and you need code to publish those events to subscribers. Listing 10 contains the implementation of the publisher (I named Broadcaster by personal convention).

Listing 10: An Implementation of IPublisher

Public Class Broadcaster
   Implements IPublisher
   Private Const KEY As String = "BROADCASTER"
   Public Shared Function GetBroadcaster( _
      ByVal session As HttpSessionState) As Broadcaster
      If (session Is Nothing) Then Return Nothing
      If (session(KEY) Is Nothing) Then
         session(KEY) = New Broadcaster
      End If
      Return CType(session(KEY), Broadcaster)
   End Function
   Public Shared Sub PostalCodeFieldChanged( _
      ByVal session As HttpSessionState, ByVal sender As Object, _
      ByVal e As ChangeEventArgs)
      If (session Is Nothing) Then Return
      Dim instance As Broadcaster = GetBroadcaster(session)
      If (instance Is Nothing = False) Then
         instance.PostalCodeFieldChanged(sender, e)
      End If
   End Sub
   Private Sub PostalCodeFieldChanged( _
      ByVal sender As Object, ByVal e As ChangeEventArgs)
      RaiseEvent PostalCodeFieldChangedEvent(sender, e)
   End Sub

   Public Shared Sub Subscribe(ByVal session As HttpSessionState, _
      ByVal subscriber As ISubscriber)
      subscriber.Subscribe(GetBroadcaster(session))
   End Sub
   Public Shared Sub Unscubscribe(ByVal Session As HttpSessionState, _
      ByVal subscriber As ISubscriber)
      subscriber.Unsubscribe(GetBroadcaster(Session))
   End Sub
   Public Event PostalCodeFieldChangedEvent( _
      ByVal sender As Object, ByVal e As ChangeEventArgs) _
      Implements IPublisher.PostalCodeFieldChangedEvent
End Class

The code in bold implements IPublisher. This code literally just re-raises the event to anything that might be listening. Generally, the listener is the main page, as previously mentioned.

Because you need only one publisher, shared methods are defined to get a single Broadcaster/IPublisher instance from session. To receive these published events, the main page will subscribe and unsubscribe as need be.

Implementing the Subscriber

The subscriber is the main Page itself. To create the subscriber, all you need to do is implement ISubscriber in the Page and write the calls to Broadcaster.Subscribe and Broadcaster.Unsubscribe. Listing 11 shows the implementation of the main page representing the subscriber.

Listing 11: The Page Containing All of the Nested Controls Implements ISubscriber

Imports System.Collections.Generic
Partial Class _Default
   Inherits System.Web.UI.Page
   Implements ISubscriber
   Private customers As List(Of Customer)
   Private KEY As String = "Customers"
   Protected Sub Page_Load(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles Me.Load
      Broadcaster.Subscribe(Session, Me)
      If (IsPostBack = False) Then
         customers = Data.GetCustomerList
         GridView1.DataSource = customers
         GridView1.DataBind()
      Else
         customers = CType(Session(KEY), List(Of Customer))
      End If
   End Sub
   Protected Sub Page_Unload(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles Me.Unload
         Broadcaster.Unscubscribe(Session, Me)
   End Sub
   Public Sub OnPostalCodeFieldChangedEvent( _
      ByVal sender As Object, ByVal e As ChangeEventArgs)
      REM Update the data here

   End Sub
   Public Sub Subscribe(ByVal publisher As IPublisher) _
      Implements ISubscriber.Subscribe
      AddHandler publisher.PostalCodeFieldChangedEvent, _
         AddressOf OnPostalCodeFieldChangedEvent
   End Sub
   Public Sub Unsubscribe(ByVal publisher As IPublisher) _
      Implements ISubscriber.Unsubscribe
      RemoveHandler publisher.PostalCodeFieldChangedEvent, _
         AddressOf OnPostalCodeFieldChangedEvent
   End Sub

End Class

The code in bold coordinates "listening" to published events from anywhere in the nested control hierarchy via the broadcaster. You unsubscribe because delegates are multicast in .NET and if you repeatedly subscribe without unsubscribing you will get multiple event calls for the same event; you need only one.

Listing 12 shows the downline nested user control. The user control responds to events in its area of concern and forwards those to the publisher: your broadcaster object. This part of the process is shown in bold.

Listing 12: The OrdersControl Forwards its Events to the Broadcaster

Imports System.Collections.Generic

Partial Class OrdersControl
      Inherits System.Web.UI.UserControl
   Protected Sub Page_Load(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles Me.Load
   End Sub
   Public WriteOnly Property Data() As Object
      Set(ByVal value As Object)
         If (TypeOf value Is Customer) Then
            GridView1.DataSource = CType(value, Customer).MyOrders
            GridView1.DataBind()
         End If
      End Set
   End Property
   Protected Sub TextBox2_TextChanged(ByVal sender As Object, _
      ByVal e As System.EventArgs)
      Broadcaster.PostalCodeFieldChanged(Session, sender, _
         New ChangeEventArgs("ShipPostalCode", _
         CType(sender, TextBox).Text, GetParentID(sender), _
            GetChildID(sender)))
   End Sub
   Private Function GetChildID(ByVal sender As Object) As Object
      Try
         Return CType(sender.Parent.Parent.Cells(1), _
            DataControlFieldCell).Text
      Catch ex As Exception
         Return Nothing
      End Try
   End Function
   Private Function GetParentID(ByVal sender As Object) As Object
      Try
         Return CType(sender.Parent.Parent.Cells(0), _
                      DataControlFieldCell).Text
      Catch ex As Exception
         Return Nothing
      End Try
   End Function
End Class
Imports System.Collections.Generic

Partial Class OrdersControl
      Inherits System.Web.UI.UserControl
   Protected Sub Page_Load(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles Me.Load
   End Sub
   Public WriteOnly Property Data() As Object
      Set(ByVal value As Object)
         If (TypeOf value Is Customer) Then
            GridView1.DataSource = CType(value, Customer).MyOrders
            GridView1.DataBind()
         End If
      End Set
   End Property
   Protected Sub TextBox2_TextChanged(ByVal sender As Object, _
      ByVal e As System.EventArgs)
      Broadcaster.PostalCodeFieldChanged(Session, sender, _
         New ChangeEventArgs("ShipPostalCode", _
         CType(sender, TextBox).Text, GetParentID(sender), _
            GetChildID(sender)))
   End Sub
   Private Function GetChildID(ByVal sender As Object) As Object
      Try
         Return CType(sender.Parent.Parent.Cells(1), _
            DataControlFieldCell).Text
      Catch ex As Exception
         Return Nothing
      End Try
   End Function
   Private Function GetParentID(ByVal sender As Object) As Object
      Try
         Return CType(sender.Parent.Parent.Cells(0), _
            DataControlFieldCell).Text
      Catch ex As Exception
         Return Nothing
      End Try
   End Function
End Class

I am not going to insist that all of this plumbing is easy—nothing worth doing is. But, once you get the hang of patterns and wiring object-oriented code at this level of abstraction, you will find the results very orderly and predictable. It will become increasingly important that all VB programmers master OOP and design patterns.

As Advanced As You Wanna Be

This article demonstrated some pretty advanced techniques, such as the observer behavior pattern, block script, and (I hope you agree) an imaginative way to manage nested controls reliably. You won't need to nest controls and grids all the time, but you may want to sometimes. You can create some very advanced and imaginative user interfaces if you have this technique in your toolbox.

The real benefit is that no matter how complex your presentation layer gets, all you have to do is add more events. All events are raised by each user control, and then they go directly to the broadcaster and from there the main page. For your part, you will have to try the code and step through it a couple of times to get it—especially if interfaces and design patterns are unfamiliar territory.

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his new book UML DeMystified from McGraw-Hill/Osborne. Paul is an architect for Tri-State Hospital Supply Corporation. You may contact him for technology questions at pkimmel@softconcepts.com.

If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org.





Page 3 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel