Wednesday, February 23, 2011

Programmatically connect two ListViewWebParts to Filter based on value of Provider Web Part

When you want to connect two ListViewWebParts on a page using the user interface is easy. However doing it programmatically was a bit more complicated. It’s not complex, but you need to know what classes you need to use.

Connecting Web Parts using the User interface:

image
Select the provider Web Part and connect to the consumer web part with the ‘Send Row of Data To’ option.

image
Step 1:
Configure the connection on the Consumer Web Part.
Select ‘Get Filter Values From’

image

Step 2:
Provider Field Name: Title(linked to item)
Consumer Field Name: ArticleEditionTitle

The provider and consumer field names are dependent on your needs. In this case the value of the Title of the provider Web Part is passed to the consumer web part. The consumer web part will only show rows where ArticleEditionTitle is equal to the passed value.

 Connecting Web Parts programmatically:
Use the SPRowToParametersTransformer class, provide the correct provider and consumer fieldnames. These fields are the same as the ones used in step 2 of the user interface procedure.

SPRowToParametersTransformer transformer = new SPRowToParametersTransformer();
transformer.ProviderFieldNames = new string[] { "LinkTitleNoMenu" };
transformer.ConsumerFieldNames = new string[] { "ArticleEditionTitle" };

Get the WebParts that need to get connected
string providerWebPartTitle = "Editions";
string consumerWebPartTitle = "Articles";

WebPart providerPart = (from WebPart w in mgr.WebParts where w.Title == providerWebPartTitle select w).FirstOrDefault();
WebPart consumerPart = (from WebPart w in mgr.WebParts where w.Title == consumerWebPartTitle select w).FirstOrDefault();

Determine the ConnectionPoints of the webparts. Note the provider/consumer ConnectionIds
string providerConnectionId = "DFWP Row Provider ID";
string consumerConnectionId = "DFWP Filter Consumer ID";

//get connectionpoints
ProviderConnectionPoint providerConnectionPoint = (from ProviderConnectionPoint conn in manager.GetProviderConnectionPoints(providerPart)
where conn.ID == providerConnectionId
select conn).FirstOrDefault();

ConsumerConnectionPoint consumerConnectionPoint = (from ConsumerConnectionPoint conn in manager.GetConsumerConnectionPoints(consumerPart)
where conn.ID == consumerConnectionId
select conn).FirstOrDefault();

Add a WebPartConnection using WebPartManager.SPConnectWebParts, and pass along the transformer.

// connect the webparts
manager.SPConnectWebParts(providerPart, providerConnectionPoint, consumerPart, consumerConnectionPoint, transformer);

That’s it.

Friday, February 18, 2011

Add SPNavigation Heading without link functionality to QuickLaunch

Adding items to the Quicklaunch or TopNavigation is easy. However adding a heading to the Quicklaunch without the link functionality is not possible by using the basic SharePoint 2010 API. In this post I’ll show you how to create a navigation item, transform it into a heading without link functionality, and add it to the Quicklaunch.

The options:

  1. Create a SPNavigationNode
    add it to navigation using spWeb.Navigation.AddToQuickLaunch(SPNavigationNode node, SPQuickLaunchHeading heading), where heading is an enum of some default SP2010 headings.
  2. Create a SPNavigationNode
    Get SPNavigationNodeCollection from spWeb.Navigation.QuickLaunch
    Add SPNavigationNode to the SPNavigationNodeCollection using methods like AddAsFirst, or AddAsLast

As option 1 can only to add items to default SP2010 headings. So we need to go for option 2.


The problem
Option 2 can only add a heading navigation node to the quicklaunch that has a link. When you use the following code it will create a heading, however it will link to the ‘base url’ of the current SPWeb.

// Create the node.
SPNavigationNodeCollection quicklaunchNav = web.Navigation.QuickLaunch;

// Create heading
SPNavigationNode headerNode = new SPNavigationNode(headerTitle, string.Empty, true);
headerNode = quicklaunchNav.AddAsLast(headerNode);

// Add subnode to heading.
SPNavigationNode node = new SPNavigationNode("title", "url", true);
headerNode.Children.AddAsLast(node);
 

 The Solution
The difference between a navigation item and a heading navigation item are some value in the NavigationNode.Properties HashTable.

To create a heading use the following code.

public static class SPNavigationNodeExtensions
{
public static void MakeHeaderNode(this SPNavigationNode node)
{
node.Properties["BlankUrl"] = "True";
node.Properties["LastModifiedDate"] = DateTime.Now;
node.Properties["Target"] = "";
node.Properties["vti_navsequencechild"] = "true";
node.Properties["UrlQueryString"] = "";
node.Properties["CreatedDate"] = DateTime.Now;
node.Properties["Description"] = "";
node.Properties["UrlFragment"] = "";
node.Properties["NodeType"] = "Heading";
node.Properties["Audience"] = "";
node.Update();
}
}

As you can see, the “BlankUrl'” = True, and NodeType = “Heading”. Using the Update method it saves changes of the properties.
It’s now possible to transform the heading node using this extension method.
//Transform Node into Header.
headerNode.MakeHeaderNode();

The final code is:

// Create the node.
SPNavigationNodeCollection quicklaunchNav = web.Navigation.QuickLaunch;

// Create heading
SPNavigationNode headerNode = new SPNavigationNode("HeadingTitle", string.Empty, true);
headerNode = quicklaunchNav.AddAsLast(headerNode);

//Transform Node into Header.
headerNode.MakeHeaderNode();

// Add subnode to heading.
SPNavigationNode node = new SPNavigationNode("title", "url", true);
headerNode.Children.AddAsLast(node);

At this point you’ll have a heading with '”HeadingTitle” that doesn’t link. It has a sub navigation item “title”, that links to “url”. Also thanks to Matthijs Woolderink (@MWoolderink) for some background information.

Wednesday, February 16, 2011

Reminder about Content Types

When constructing a ContentType in the elements file you’ll reference to existing Site Columns.

Example Content Type:
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ContentType ID="0x010055180D0A5202894F9E64FC9D6D4A86B7" Name="EditionCT" Group="Test" Inherits="true" Hidden="false" ReadOnly="false" Sealed="false">
<FieldRefs>
<FieldRef ID="71316cea-40a0-49f3-8659-f0cefdbdbd4f" Name="ArticleStartDate" DisplayName="Article Date" />
<FieldRef ID="64cd368d-2f95-4bfc-a1f9-8d4324ecb007" Name="StartDate" DisplayName="Start Date" />
<FieldRef ID="919f30d5-31f1-478f-bda5-84f595340392" Name="EndDate1" DisplayName="End Date" />
<FieldRef ID="d2132670-ec6e-4fc7-af38-0ca09a364b80" Name="Send" DisplayName="Send" />
<FieldRef ID="8adac618-8387-4c39-ad9a-ed6c17084d3a" Name="Send_x0020_Date" DisplayName="Send Date" />
<FieldRef ID="d1fc4f17-b0f7-4177-9f94-5b32cbdd2d5a" Name="Send_x0020_To" DisplayName="Send To" />
<FieldRef ID="cc69c48f-5536-4478-9172-a79468a32afa" Name="FinalSubmissionDate" DisplayName="FinalSubmissionDate" />
<FieldRef ID="cd8a6351-3235-4eb2-857d-87975bb8cb90" Name="ReminderDays" DisplayName="ReminderDays" />
<FieldRef ID="bcf32df2-5191-43d7-a1a4-fdb17acccb98" Name="SendReminderAt" DisplayName="SendReminderAt" />
</FieldRefs>
</ContentType>
</Elements>
 
Example SiteColumn:
<Field Type="Boolean" DisplayName="Send" Group="Base" EnforceUniqueValues="FALSE" Indexed="FALSE" ID="{d2132670-ec6e-4fc7-af38-0ca09a364b80}" SourceID="{b6366c11-b4ce-408c-8175-952b9a7800d4}" StaticName="Send" Name="Send" ShowInNewForm="FALSE" ShowInEditForm="FALSE" />
 
One thing did cost me a few hours to find out. When activating the feature the Content Type EditionCT was corrupt. It showed the Send column was of type DateTime, it was defined as a Boolean. In the Site Column overview it did show as a boolean. So only the content type was corrupted.
Site Column:
image
 
Content Type in Site Settings (nothing wrong, you think):
image
 
Content Type in SharePoint Manager 2010 (its corrupt):
image
 
Content Type when added to a list (also corrupted):
image
 
 
Than I noticed the ArticleStartDate was a site column from the Publishing framework. This site collection feature wasn’t activated and so that appropriate fields were not available, one of them was the ArticleStartDate. Strange thing was that SharePoint didn’t report an error when constructing the content type, but just corrupted the Content Type.
 
So keep in mind to have the fields available when provisioning the Content Type, and as you can see, strange things can happen.

Sunday, February 13, 2011

How to Customize the SharePoint List "NewForm" using VS2010 instead of SharePoint Designer

Most blogs show you how to customize the NewForm of a SharePoint List by using SharePoint Designer. However when you need to support/maintain a SharePoint Solution you really should use a Solution (.wsp). Easiest way is to use VS2010. In this blog I demonstrate how to create a customized NewForm, this means. When a user clicks 'New Item' in the list it will render a customized version of the 'New Form'. It's possible to add additional Web Parts and content. In this version it will however still use the default ListFormWebPart to render the new-item form.

I assume you already have created custom Fields (Site Columns) and Content Types in VS2010.

The steps to take are:

  1. Create ListDefinition
  2. (optional) Create ListInstance
  3. Create a custom page that will hold the customizations
  4. Customize ListDefinition schema
  5. Deploy the solution, activate feature

Step 1: Create ListDefinition

In VS2010, you have a choice to create a new ListDefinition based on Content Type and just a plain ListDefinition. In this demo I chose the latter.

image

I want to create a List Definition based on Custom list. I also create to create a List Instance (Step 2) so after deployment I immediately have a custom list created.

image

Step 3: Create a customized NewForm

In schema.xml of the ListDefinition you see this line:
<Form Type="NewForm" Url="NewForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />

The SetupPath point to the 14-hive\templates folder. So In this case it will provision a NewForm.aspx based on the C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\Pages\form.aspx
You don't want to change this out-of-the-box page. It will void you support from Microsoft ;-).

So make a copy, copy it into the ListDefinition project and rename it to MyNewForm.aspx. Make sure you include MyNewForm.aspx into the VS2010 project and set the 'Deployment Type' to ElementFile, this will deployed the file to the feature directory. It should now look like this in VS2010.

image

Add some content to the NewForm:
Open the MyNewForm.aspx and look for the PlaceHolderMain ContentPlaceHolder (Line 244).
Put some content like <h1>Hello Customized World</h1> in front of the the <table …> a few line below.

Add a Web Part Zone to the NewForm:
Add: <WebPartPages:WebPartZone runat="server" FrameType="None" ID="Top" Title="loc:Top" /> after the <h1>Hello…

So it will look like this:

<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">
<SharePoint:UIVersionedContent UIVersion="4" runat="server">
<ContentTemplate>
<div style="padding-left:5px">
</ContentTemplate>
</SharePoint:UIVersionedContent>
<h1>Hello Customized World</h1>
<WebPartPages:WebPartZone runat="server" FrameType="None" ID="Top" Title="loc:Top" />
<table cellpadding="0" cellspacing="0" id="onetIDListForm" style="width:100%">
<tr>
<td>
<WebPartPages:WebPartZone runat="server" FrameType="None" ID="Main" Title="loc:Main" />

Save the changes ;-).

Step 4 Change the Schema.xml

Now we have created a custom page, however we need to tell the ListDefinition to use the customize NewForm. To do this, we need to change the Form element in the ListDefinition schema.xml

Code Before:

<Form Type="NewForm" Url="NewForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />

Code After:

<Form Type="NewForm" Url="NewForm.aspx" Path="MyNewForm.aspx" WebPartZoneID="Main" Default="TRUE" UseDefaultListFormWebPart="False" WebPartOrder="1">
<WebParts>
<AllUsersWebPart WebPartZoneID="Main" WebPartOrder="1">
<![CDATA[
<WebPart xmlns="http://schemas.microsoft.com/WebPart/v2">
<Assembly>Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
<TypeName>Microsoft.SharePoint.WebPartPages.ListFormWebPart</TypeName>
<PageType>PAGE_NEWFORM</PageType>
</WebPart>]]>
</AllUsersWebPart>
<AllUsersWebPart WebPartZoneID="Top" WebPartOrder="1">
<![CDATA[
<WebPart xmlns="http://schemas.microsoft.com/WebPart/v2" xmlns:iwp="http://schemas.microsoft.com/WebPart/v2/Image">
<Assembly>Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
<TypeName>Microsoft.SharePoint.WebPartPages.ImageWebPart</TypeName>
<FrameType>None</FrameType>
<Title>My Custom Image</Title>
<iwp:ImageLink>/_layouts/images/homepage.gif</iwp:ImageLink>
<iwp:AlternativeText>Logo</iwp:AlternativeText>
</WebPart>]]>
</AllUsersWebPart>
</WebParts>
</Form>

What I've done is:

  • instead of using the default /pages/form.aspx, I told it to use the MyNewForm.aspx (So I changed SetupPath="pages/form.aspx" to Path="MyNewForm.aspx", it should not look into the template directory but in the feature directory, so SetupPath => Path…). The file will still be accessible with url NewForm.aspx. You can change that if you want.
  • UseDefaultListFormWebPart="False", it will not add a default ListFormWebPart, but it will process the <WebParts> child elements.
  • The <WebParts> child elements are used to place web part on the Custom MyNewForm.aspx
    • first web part is a ListFormWebPart. When setting the PageType to PAGE_NEWFORM, it will add a ListFormWebPart that will handle the 'New Item' rendering, make sure the WebPartZoneID matches with the ZoneID's in the customized form. Default there's a Main ZoneID.
    • second web part is a Image Web Part, showing a default MS SharePoint logo. This web part will be placed in the Top WebPartZoneID (our custom WebPartZone).

Final Step: Deploy solution and activate feature.

Deploy the solution using VS2010.
Visit the site where you deployed the solution. If VS2010 didn't automatically activated the feature, you should go to Site Settings, Manage Site Features and look for feature 'CustomListDemo Feature1' (This is why a naming convention is recommended ;-)). So make sure it's activated.

As the feature included a listinstance a list is created. The list name is: "CustomListDemo - ListInstance1".
Go to this list and click on 'New Item'.

This will be the result:

image

As you can see. The custom NewForm, including some custom content, a custom Web Part and the default ListFormWebPart. This is just a simple example. But you could go wild on the content and on adding your own Web Parts. It's also possible to change the template the ListFormWebPart uses. But that's for another day…

Read some more about Customizing Form.

You can follow me on twitter

Thursday, February 3, 2011

Tombstoning MVVMLight ViewModels with an ApplicationBar using SilverlightSerializer

I've first implemented Tombstoning MVVMLight ViewModels using the default DataContractSerializer. I save the ViewModel when closing the application to persistent storage by using the IsolatedStorageSettings.ApplicationSettings. When deactivating the application I saved the ViewModels to transient storage using PhoneApplicationService.Current.State. This method has some limitations regarding maximum storage size. Second problem was that the MVVMLight ViewModelBase class constructor was not public and therefor could not be serialized. I fixed this by just downloading the sources and use the new assemblies.

I wasn't really satisfied. When I was at the DotNed meeting at Qurius there was a discussion about saving states and what serializers would be a good option. The SilverLightSerializer was the one I remembered, so back at home I did some digging and found that LocalJoost blogged about it.

I implemented the SilverLightSerializer and just needed to replace the saving and loading state parts. Than I tested the application and found out the application bar was gone after a deactivate / activate cycle.

Before deactivating:
image

Then do a 'Start' and 'Back', it will activate in a state like this:
image

I also had this problem before using the DataContractSerializer. That was fixed by decorating the public ApplicationBar properties with [IgnoreDataMember] attribute. The SilverLightSerializer skips properties from being serialized which are decorated with the [DoNotSerialize] attribute. So I replaced the [IgnoreDataMember] with [DoNotSerialize] and the ApplicationBar was back again.

Hopes this saved you some hours of searching and testing.