Implementing the Default SharePoint Interfaces with Lists
Peter returns to creating a provider WebPart, but this time passes SharePoint list data from one WebPart to another.
In my previous column, I showed how to implement the IWebPartParameters interface to provide data to other WebParts. That WebPart used a lot of non-native SharePoint technology: it accessed a relational database, stored the data in a DataSet, and displayed the data in an ASP.NET GridView (not even an SPGridView).
This case study shows how to create a provider WebPart but uses native SharePoint technology: it pulls data from a SharePoint List, stores the data in a collection of SPListItems, and displays the data in a SharePoint list.
For my case study, I created a Visual WebPart that displays, from SharePoint's Tasks List, only the Task's Title and only lists the Tasks that are more than 49 percent complete. While I used an ASP.NET GridView for displaying my data in my last column, this Visual WebPart will use the native SharePoint ListViewByQuery.
I could add the ListViewByQuery to my Visual WebPart's UserControl in the .ascx file's markup, but just to show another option, I'll add the control in code in the UserControl's CreateChildControls method. That code first creates a ListViewByQuery and attaches the Tasks list to it:
Protected Overrides Sub CreateChildControls()
Dim lvq As New Microsoft.SharePoint.WebControls.ListViewByQuery
lvq.List = SPContext.Current.Web.Lists("Tasks")
The next step is to create a query that specifies that the Title field is to be displayed, sets the criteria for selecting the Tasks, and specifies the order in which the Tasks are to be displayed. To make the query run a little faster, I limit the query to retrieving only the fields normally displayed for this List:
Dim qry As New SPQuery(lvq.List.DefaultView)
Dim camlXML As XElement
camlXML = <FieldRef Name='Title'/>
qry.ViewFields = camlXML.ToString
qry.ViewFieldsOnly = True
camlXML = <Where>
qry.Query = camlXML.ToString
camlXML = <OrderBy>
<FieldRef Name='Title' Ascending='True' />
qry.Query += camlXML.ToString
I then attach the query to the ListViewByQuery and add the control to my UserControl's collection of controls to display my Tasks as a SharePoint list in my Visual WebPart:
lvq.Query = qry
However, for this WebPart to act as a provider to other WebParts, I also need the List contained within the ListViewByQuery. I get that with the following code and pass it to a property on the code-only part of my Visual WebPart:
CType(Me.Parent, ListQParms).lst = lvq.List.GetItems(lvq.Query)
As I've made pretty clear, I think that any WebPart that retrieves data should implement at least some of the default interfaces that SharePoint defines, so that other WebParts can access that data. So in the code-only portion of my WebPart, I'll have it implement the IWebPartTable interface (since it's difficult to determine which ListItem a user has selected in the ListViewByQuery, this is the easiest interface to implement):
Public Class ListQParms
I need to set up a connection methods for the interface. As you add more interfaces, the only thing that has to change from one interface's connection method to another is the name of the interface (I have this set up as code snippet):
<ConnectionProvider("Task data", "ConnectionPointIWebpartTable", _
Public Function MyTableConnection() As IWebPartTable
Before looking at the members required by the interfaces, let's look at the property that the UserControl uses to pass the retrieved collection of ListItems to this part of the Visual WebPart. All the interesting code for this property is in the property's Setter. That code first converts the collection into a DataTable, making the rest of the code easier. The code then uses the TypeDescriptor's GetProperties method to generate a collection of PropertyDescriptors for each column in the DataTable, which is stored to be used later:
Private _lst As SPListItemCollection
Private _pds As PropertyDescriptorCollection
Dim _dt As DataTable
Public Property lst() As SPListItemCollection
Set(value As SPListItemCollection)
_lst = value
_dt = _lst.GetDataTable
_pds = TypeDescriptor.GetProperties(_dt.DefaultView)
Now I can implement the Schema property that Visual Studio will have added to my WebPart as part of supporting the interface. All I have to do in the property interface is return the PropertyDescriptionCollection:
Public ReadOnly Property Schema As PropertyDescriptorCollection _
I also need to implement the GetTableData method that accepts the function in the connecting WebPart that I'm to call when it's time to pass the data. Here's that method:
Private tcb As TableCallback
Public Sub GetTableData(callback As WebParts.TableCallback) _
rcb = callback
Again, this code is identical from one WebPart to another.
Finally, in the PreRender event, I use the callback to pass data to the connecting WebPart (after determining that I have some data and that there is a part connected). I check whether the _lst is Nothing separately from checking the callback method so that I can add more callbacks to support other interfaces:
Private Sub ListQParms_PreRender( _
sender As Object, e As System.EventArgs) Handles Me.PreRender
If _lst IsNot Nothing Then
If tcb IsNot Nothing Then
More sophisticated UIs could be created with more sophisticated SharePoint controls (the SPGridView, for instance). But those wouldn't make much difference to the code required to implement the IWebPartTable interface: While your UI code might change, this code to deliver data to other WebParts would hold still -- always a nice feature.
Peter Vogel is a principal in PH&V Information Services, specializing in Web development with expertise in SOA, client-side development, and user interface design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His most recent book ("rtfm*") is on writing effective user manuals, and his blog posts on communicating effectively can be found at http://blog.learningtree.com/category/communication-2/.