In part 1 of this series, I showed how to make one web part accept data from another.  In part 2, I showed that this can be extended so that one web part can provide data for many.  Now, in this final post of the series, I'll show how a single web part can accept data from multiple web parts.  As you will see, we need to make only a few changes for this to happen.

Let's continue from where we left off.  Our page now has three web parts.  The first, HanoiDisks, allows the user to select the number of disks that should be used for our Towers of Hanoi puzzle.  The second web part, HanoiSteps, displays the steps needed to solve the puzzle.  The third web part, HanoiCount, displays the number of steps that will be needed to complete the puzzle.  Now we'll add a fourth web part.  This web part, HanoiColor, will allow the user to select the font color used by the HanoiCount web part.  HanoiCount will then be using data from both the the HanoiColor and the HanoiDisk web parts.

So far, we've been passing around instances of the IDisks interface.  We're now going to pass color information, so we need a new interface:

IColor.cb 

Public Interface IColor

    ReadOnly Property Color() As Integer

End Interface

This interface will allow a color number to be passed from HanoiColor to HanoiCount.  First, lets look at the new HanoiColor web part:

HanoiColor.vb 

Imports System
Imports System.Drawing
Imports System.ComponentModel
Imports System.Web.UI.HtmlControls
Imports System.Web.UI.WebControls
Imports System.Web.UI.WebControls.WebParts

Public Class HanoiColor
    Inherits WebPart
    Implements IColor

    Protected _colorList As DropDownList = Nothing

    Protected Overrides Sub CreateChildControls()
        MyBase.CreateChildControls()

        Try
            _colorList = New DropDownList

            With _colorList
                .AutoPostBack = True

                .Items.Clear()

                .Items.Add(New ListItem("Black", 0))
                .Items.Add(New ListItem("Blue", 1))
                .Items.Add(New ListItem("Green", 2))
                .Items.Add(New ListItem("Red", 3))

                .SelectedIndex = 0
            End With

            Dim lit As New Literal()
            lit.Text = "Text Color: "
            Me.Controls.Add(lit)

            Me.Controls.Add(_colorList)
        Catch ex As Exception
            Me.Controls.Clear()

            Dim msg As New Literal()
            msg.Text = ex.Message
            Me.Controls.Add(msg)
        End Try

    End Sub

    <ConnectionProvider("Text Color")> _
    Public Function GetColorInterface() As IColor
        Return Me
    End Function

    Public ReadOnly Property Color() As Integer Implements IColor.Color
        Get
            Return _colorList.SelectedValue
        End Get
    End Property
End Class

By now, you should recognize all of the features of this code.  The GetColorInterface method provides an instance of the IColor interface, and uses the ConnectionProvider attribute to communicate this fact to WSS.

Now, let's look at the HanoiCount web part.  It now needs to accept data from two different sources, so some code changes are needed:

HanoiCount.vb

Imports System
Imports System.Drawing
Imports System.ComponentModel
Imports System.Web.UI.HtmlControls
Imports System.Web.UI.WebControls
Imports System.Web.UI.WebControls.WebParts

Public Class HanoiCount
    Inherits WebPart

    Protected _diskInterface As IDisks = Nothing
    Protected _disks As Integer = 0

    Protected _colorInterface As IColor = Nothing
    Protected _color As String = "#000000;"

    Protected _headerMessage As Literal = Nothing

    Protected Overrides Sub CreateChildControls()
        MyBase.CreateChildControls()

        Try
            _headerMessage = New Literal
            Me.Controls.Add(_headerMessage)
        Catch ex As Exception
            Me.Controls.Clear()

            Dim msg As New Literal()
            msg.Text = ex.Message
            Me.Controls.Add(msg)
        End Try

    End Sub

    Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)
        MyBase.OnPreRender(e)

        If _diskInterface IsNot Nothing Then
            _disks = _diskInterface.NumberOfDisks
        End If

        If _colorInterface IsNot Nothing Then
            _color = GetColorFromNumber(_colorInterface.Color)
        End If

    End Sub

    Private Function GetColorFromNumber(ByVal colorNumber As Integer) As String

        Dim selectedColor As String = "#000000;"

        Select Case colorNumber
            Case 0
                selectedColor = "#000000;"
            Case 1
                selectedColor = "#0000FF;"
            Case 2
                selectedColor = "#00FF00;"
            Case 3
                selectedColor = "#FF0000;"
        End Select

        Return selectedColor

    End Function

    Protected Overrides Sub RenderContents(ByVal writer As System.Web.UI.HtmlTextWriter)

        If _headerMessage IsNot Nothing AndAlso _disks > 1 Then
            Dim steps As Integer = 2 ^ _disks - 1
            With _headerMessage
                .Text = "<span style=""color:" & _color & """>"
                .Text &= "There are " & steps & " steps for " & _disks & " disks."
                .Text &= "</span>"
            End With
        End If

        MyBase.RenderContents(writer)

    End Sub

    <ConnectionConsumer("Number of Disks", "1")> _
    Public Sub AcceptDiskInterface(ByVal diskInterface As IDisks)
        _diskInterface = diskInterface
    End Sub

    <ConnectionConsumer("Text Color", "2")> _
    Public Sub AcceptColorInterface(ByVal colorInterface As IColor)
        _colorInterface = colorInterface
    End Sub

End Class

Notice that two interfaces are used here.  This fact is reflected throughout most of the code.  Also notice that there are two methods that accept interfaces; AcceptDiskInterface and AcceptColorInterface.  Look closely at the ConnectionConsumer attributes.  Up until now, I've supplied only one parameter to the attribute constructor.  This works just fine when there is only one ConnectionConsumer or ConnectionProvider attribute used by a web part.  When you have more than one, however, you need to also supply a unique identifier for each attribute.  This is the real trick to using multiple web part connections.

At this point, we've written four web parts that communicate with each other in various ways:

  • HanoiCount - provides an instance of the IColor interface to the HanoiCount web part
  • HanoiDisks - provides an instance of the IDisks interface to the HanoiCount and HanoiSteps web parts
  • HanoiCount - accepts an instance of the IColor interface from the HanoiColor web part, and an instance of the IDisks interface from the HanoiDisks web part
  • HanoiSteps - accepts an instance of the IDisks interface from the HanoiDisks web part

I leave it to the reader to create a final web part; one that is both a provider and a consumer of data.  The can be accomplished by using the same techniques presented in this series of posts.

I hope these posts have helped you get started with web part communication.  This can be a very useful feature of WSS 3.0.