Monday, October 18, 2010

ListViewWebPart – set the toolbar with code without reflection

[update (Aug 25, 2011): this solution works only for standard SharePoint lists, whose schema definitions (in the schema.xml file) already contain view definitions which do not have the toolbar (usually view definitions with BaseViewID=0) – see details below]

After several postings about the XLV web part in SharePoint 2010, let’s take a step back and have a look at its predecessor – the ListViewWebPart from SharePoint 2007. And more precisely about the problem with the list view toolbar when you add the web part with code. Basically the problem is that the web part gets added with the toolbar containing the “New”, “Actions” and “Settings” buttons which you normally see in the default list view pages like the “AllItems.aspx” one, and which in probably more than 90% of the cases you don’t want to see in the custom page, to which you are adding the web part.

There is already a solution to this problem (i.e. to remove the toolbar after you add the web part), which you can find in at least a dozen SharePoing blogs in internet, but the thing is that the solution in question uses reflection. Which is not a good thing at all. And actually the original solution stopped working after the Infrastructure update for MOSS 2007, so a small extra bit of reflection was to be added to the original code, so that it works after the update.

And let’s see now, how it is possible to add a ListViewWebPart to a page without the commands toolbar. Actually there are two slightly different ways to add a ListViewWebPart to a page with code, let’s see the first one:

        static void AddListView(SPFile file, SPList list, string zoneID)

        {

            SPLimitedWebPartManager mngr = file.GetLimitedWebPartManager(System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);

            ListViewWebPart lvWebPart = new ListViewWebPart();

 

            lvWebPart.ListName = list.ID.ToString("B").ToUpper();

            // this is either the default view, or some of the other views that you get from the SPList.Views collection by name

            lvWebPart.ViewGuid = list.DefaultView.ID.ToString("B").ToUpper();

 

            mngr.AddWebPart(lvWebPart, zoneID, 1);

        }

Yes, this shouldn’t be something new for you, because you probably use something similar to this code if you have the problem with the commands toolbar.

And here is the second approach:

        static void AddListView2(SPFile file, SPList list, string zoneID)

        {

            SPLimitedWebPartManager mngr = file.GetLimitedWebPartManager(System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);

            ListViewWebPart lvWebPart = new ListViewWebPart();

 

            lvWebPart.ListName = list.ID.ToString("B").ToUpper();

            string schemaXml = list.GetUncustomizedViewByBaseViewId(0).HtmlSchemaXml;

            // you can modify the schema XML of the view before assigning it to the ListViewWebPart

            // e.g. you can change the ViewFields, the Query element, etc

            lvWebPart.ListViewXml = schemaXml;

 

            mngr.AddWebPart(lvWebPart, zoneID, 1);

        }

As you see, it is very similar to the first approach and the only difference is that instead of setting the ListViewWebPart.ViewGuid property you set the ListViewWebPart.ListViewXml property. And the outcome of this code snippet is that the list view web part gets added to the target page without the annoying toolbar.

So far so good, but let’s have a closer look at the two code snippets, so that you can understand what happens behind the curtains. In the first code example, the ListViewWebPart.ViewGuid property gets set – but this doesn’t imply that the LV web part gets bound somehow to the SPView instance whose ID was provided to the web part’s property. In reality what happens is that a new SPView gets created when the web part is added to the page – this SPView is hidden but you can still find it in the SPList.Views collection of the parent list. What’s more – this SPView is always associated with this LV web part and actually they are more or less two representations of one and the same internal entity (check this older posting of mine for more details). Another detail here is that when this hidden SPView is created its schema XML (containing the settings for the view fields, query, toolbar etc) is copied from the source SPView, whose ID you provided to the ViewGuid property of the web part. And this is why you end up with the commands toolbar in your LV web part – normally the SPList.Views collection contains the views from the standard list view pages of the SharePoint list (like the “All Items” one, etc), all of which do have a commands toolbar.

And now, let’s see what happens in the second code sample – you see that instead of providing a SPView’s ID to the web part, a full view schema XML is retrieved and then provided to the web part’s ListViewXml property – as you see in this case you don’t need to set the ViewGuid property at all. Let’s first see how the view schema XML gets retrieved – you can see the SPList.GetUncustomizedViewByBaseViewId method – this method returns a SPView object but this SPView instance is not a normal view that you can get from the SPList.Views collection. This is sorts of artificial SPView instance whose view schema XML is populated directly from the list’s “schema.xml” file. In the schema.xml of the list’s template definition you have a “Views” element which contains several “View” elements each of which defines the available view schema XML-s for that list. Each “View” element has a unique identifier – that’s its “BaseViewID” attribute – and the integer parameter that you specify in the GetUncustomizedViewByBaseViewId method references the “BaseViewID” attribute. There is one other detail here – some of the “View” elements in the “schema.xml” file of the list definition do provision list views for the list instances created from this list definition (for example the “AllItems.aspx”), but there are also “View” elements that actually don’t provision list view pages:

<View BaseViewID="1" Type="HTML" WebPartZoneID="Main" DisplayName="$Resources:core,objectiv_schema_mwsidcamlidC24;" DefaultView="TRUE" SetupPath="pages\viewpage.aspx" ImageUrl="/_layouts/images/generic.png" Url="AllItems.aspx">

This is the “View” element with BaseViewID=1 of the schema.xml of the standard CustomList – as you see from its attributes it provisions the “AllItems.aspx” view page for the lists based on that list definition.

<View BaseViewID="0" Type="HTML">

And this is the “View” element (it has many child elements but I omitted them because the XML is huge) with BaseViewID=0. As you can see, it contains much less attributes and doesn’t provision any of the list’s view pages. But this doesn’t mean that the view schema XML that it defines is not used, on the contrary – for example, when you use the standard SharePoint UI to add LV web parts to a page, it is the view schema with BaseViewID=0 that’s get copied to the hidden SPView of the web part. And also in the code sample above I specified the view schema with BaseViewID=0 in the GetUncustomizedViewByBaseViewId method for the same reason. If you have a closer look at the “View” elements in the schema.xml you will see the difference in their “Toolbar” elements definition and will see why the “View” with BaseViewID=0 doesn’t add a commands toolbar and the “View” elements with different BaseViewID values normally do.

[update (Aug 25, 2011): the difference between setting the LVP’s ViewGuid and ListViewXml properties is that in the first case, the whole schema of the provided SPView will be copied and used in the newly created LVP, which means that in most cases you will end up with the standard toolbar, because it is already present in most of the non-hidden views (normal view pages) of the list. In the second case although you provide a full view schema, the SharePoint object model will take into account only its BaseViewID property and will otherwise use the original view definition with the same BaseViewID from the list’s schema.xml file. The view definition with BaseViewID=0 for most of the standard SharePoint list templates just happens to come without a toolbar, so this will help you solve the issue with the LVP’s toolbar. Apart from the toolbar, if you want to use a modified view definition when creating a new LVP – this won’t be possible at the moment of the provisioning itself (the only options that you have is to choose between the existing view definitions or the already existing views, as I explained above). But once you have the LVP created it is possible to modify the hidden SPView instance associated with the LVP (check the line of code at the bottom of the posting as to how it is possible to retrieve this hidden SPView from the web part). You can modify the hidden SPView using its properties which expose most of the view’s schema. If you are interested in changing the whole schema you can see this sample code, which unfortunately resorts to reflection, contrary to the posting’s title]

One other advantage of the second approach for adding a LV web part is that after you retrieve the view schema XML you can modify it before assigning it to the ListViewWebPart.ListViewXml property. For example you can change the ViewFields XML node or the Query node and even the Toolbar XML element if you want to display some custom toolbar of yours.

One last thing that I want to mention – it is about the case when you already have you LV web part added to the page and want to change its toolbar (the solution above was intended for the scenario when you add a new LV web part, which is actually the more common case). In this case I would simply delete the existing web part and add a brand new LV web part to the page. The trick here will be that instead of getting the view schema XML with the SPList.GetUncustomizedViewByBaseViewId method, I will use the schema (SPView.HtmlSchemaXml) from the existing web part (actually from its hidden SPView), before I delete it (then the view schema should be modified accordingly). Here is a small code snippet of how your can get the LV web part’s hidden SPView:

SPView hiddenView = list.Views[new Guid(lvWebPart.ViewGuid)];

9 comments:

  1. Hi Stefan,

    This is an excellent post! Exactly the same that I was looking for. I had problems in adding a new LV web part with a desired (hidden) view that was available in the list schema.xml.
    Thanks a lot and please keep up the good work!

    ReplyDelete
  2. I'm trying to accomplish what you are describing above. My steps:

    1. Retrieve the existing "Comments" ListView WebPart from a "blog/list/posts/post.aspx" page (sucess)
    2. Get the CAML view schema from the Blog "Comments" list via GetUncustomizedViewByBaseViewId(0).HtmlSchemaXml; (success)
    3. Change a single line of the schema XML via a simple string replace (success)
    4. Now I am creating a new LV WebPart (success)
    5. Put all the same properties from the original LV WebPart on the newly created one (sucess)
    6. Set the new schema XML on the newly created LV WebPart via .ListViewXml property (success)
    7. Add the newly created LV WebPart to the page and hide the original one (success)

    While all steps succeed, the changed schema XML does not take effect. While the new XML is put into the .ListViewXml successfully (and it can be retrieved afterwards) it is simply gone once the page is reloaded and the newly added LV WebPart .ListViewXml is retrieved.

    I verified that my schema XML change is correct by temporarily modifying the schema.xml of the Blog "Comments" list. After an iisreset the built-in Blog "Comments" LV WebPart shows my changes, so the CAML appears to be valid. However, it is not an option to modify built-in files or rollout a custom Blog site definition just for this one-line CAML tweak (plus it should work farm-wide on all existing Blogs).

    Here is my code (simplified a little bit):

    SPWeb currentWeb = SPControl.GetContextWeb(Context);
    SPList blogCommentsList = currentWeb.GetList("/lists/comments/allcomments.aspx");
    string = blogCommentsListId = "{" + blogCommentsList.ID.ToString().ToUpper() + "}";
    currentWeb.AllowUnsafeUpdates = true;
    using (SPLimitedWebPartManager webPartManagerLimited = currentWeb.GetLimitedWebPartManager("http://server/blog/posts/post.aspx", PersonalizationScope.Shared))
    {
    SPLimitedWebPartCollection webPartCollection = webPartManagerLimited.WebParts;
    foreach (System.Web.UI.WebControls.WebParts.WebPart webPart in webPartCollection)
    {
    if (typeof(Microsoft.SharePoint.WebPartPages.ListViewWebPart) == webPart.GetType())
    {
    // After reloading the page lvWebPart.ListViewXml is back to the original
    ListViewWebPart lvWebPart = (ListViewWebPart)webPart;
    if (lvWebPart.ListName == blogCommentsListId && !webPart.Title.Contains("Enhanced - "))
    {
    string schemaXml = blogCommentsList.GetUncustomizedViewByBaseViewId(0).HtmlSchemaXml;
    string newViewXml = schemaXml.Replace("oldHTML", "newHTML");

    ListViewWebPart newLvWebPart = new ListViewWebPart();
    newLvWebPart.Title = "Enhanced - " + lvWebPart.Title;
    newLvWebPart.ListName = blogCommentsListId;
    newLvWebPart.ChromeState = lvWebPart.ChromeState;
    newLvWebPart.ChromeType = lvWebPart.ChromeType;
    newLvWebPart.AllowClose = lvWebPart.AllowClose;
    newLvWebPart.AllowConnect = lvWebPart.AllowConnect;
    newLvWebPart.AllowEdit = lvWebPart.AllowEdit;
    newLvWebPart.AllowHide = lvWebPart.AllowHide;
    newLvWebPart.AllowMinimize = lvWebPart.AllowMinimize;
    newLvWebPart.AllowRemove = lvWebPart.AllowRemove;
    newLvWebPart.AllowZoneChange = lvWebPart.AllowZoneChange;
    newLvWebPart.ListViewXml = newViewXml;
    // At this point newLvWebPart.ListViewXml contains the new CAML!!!
    webPartManagerLimited.AddWebPart(newLvWebPart, lvWebPart.ZoneID, lvWebPart.ZoneIndex);
    newLvWebPart.ListViewXml = newViewXml;
    webPartManagerLimited.SaveChanges(newLvWebPart);
    break;
    }
    }
    }
    }
    currentWeb.AllowUnsafeUpdates = false;


    Any pointer is much appreciated!

    ReplyDelete
  3. Hi Anonymous,
    can you show me (post) the line in the view schema XML that you change, so that I can try to reproduce your steps.

    ReplyDelete
  4. The comments field of your Blog refuses to accept my CAML code. Any suggestions?

    ReplyDelete
  5. Hi,
    you can escape the > and < with &gt; and &lt; or send the CAML to code@stefan-stanev.com.

    ReplyDelete
  6. Did you end up helping the problem that the Anonymous poster from 3/2/2011?
    I'm having the same problem. Even though I set the lvwp.ListViewXml to something else, once the page is checked in, the ListViewWebPart reverts back to the default view.

    ReplyDelete
  7. Hi Wie,
    I was terribly wrong with the suggestion that you can provide a modified version of a base view schema which will be applied to the LVP. Actually by providing a view schema to the ListViewXml property you only specify the BaseViewID of a view definition from the list's schema and that's it. All other modifications to the original view schema will be simply ignored. So this may solve generally the problem with the toolbar (since normally the view definition with BaseViewID=0 comes with no toolbar) but will be virtually useless for other sorts of customizations that are not present in the standard view definitions in the schema.xml of the SharePoint list. I created a small solution for the issue that you have, but unfortunately it resorts to reflection. You can find the sample code here: https://sites.google.com/site/stefanstanev/sharepoint-samples-1/Program.cs?attredirects=0&d=1
    I will also update the posting accordingly.

    ReplyDelete
  8. Hi Stefan
    I want to create a list definition in Visual Studio 2010 for SharePoint 2010. I have achieved it by seleting ListDefinition item, and stucked in creating a View definition. Can you suggest me how to define a view in the list definition...the requirement is for document library. I want to create a view called "Summary View".

    ReplyDelete
  9. Hi Krisna,
    I am not sure if you need just a regular type of view or some other special type, thus I am not sure whether you need some very basic stuff which can be found in MSDN like - http://msdn.microsoft.com/en-us/library/ff728096.aspx or you need something more advanced. Another good hint that I can think of is to check the schema.xml files of the standard list definition features. For instance you can check the schema.xml of the standard document library feature located in /14/TEMPLATE/features/DocumentLibrary/DocLib. You can see for example the definition of the "all items" view in it - it is the "View" element whose "BaseViewID" attribute equals to 1. The view definition is very concise and easy to follow. Let me know if this is of help for you.

    ReplyDelete