Monday, October 13, 2008

SharePoint: Creating a Web Part with a Custom Tool Part

Today, I want to talk on, how to add multiple toolparts in a webpart property window. In one of my earlier post, I did mention a quick and dirty way as how one can add simple textbox in webpart property window without needing any toolparts for that matter. But when it comes to implementing reusable/pluggable collection of controls, using toolparts is best possible option. 
Now, the question is, how can one easily implement toolparts in custom or default SharePoint Webparts. I think, the best way to approach this problem is to dig into SharePoint object model. Please refer the image below:

WebPart class exposes an overridable method "GetToolParts" which returns an array of references to the new ToolPart objects that will be displayed in the property pane of the Web Part. The ToolPart objects are rendered by the tool pane in the order listed in the array. 
Following is a sample code for creating a toolpart:
[code]
  public class SampleToolPart : Microsoft.SharePoint.WebPartPages.ToolPart
    {
        // First, override the CreateChildControls method. This is where we create the controls. 
        protected override void CreateChildControls()
        {
            // create a panel that will hold all of our controls 
            Panel toolPartPanel = new Panel();            
           
            // create the actual control 
            DropDownList sampleDropDown1 = new DropDownList();
            sampleDropDown1.ID = "sampleDropDown1";
            sampleDropDown1.Items.Add("Item 1");
            sampleDropDown1.Items.Add("Item 2");
            sampleDropDown1.Items.Add("Item 3");

            toolPartPanel.Controls.Add(sampleDropDown1);            

            // finally add the panel to the controls collection of the tool part 
            Controls.Add(toolPartPanel);

            base.CreateChildControls();
        }

        // Next, override the ApplyChanges method. 
        // This method is where we will persist the values that the user selects. 
        public override void ApplyChanges()
        {
            // get the parent webpart 
            SampleWebPart parentWebPart = (SampleWebPart)this.ParentToolPane.SelectedWebPart;

            // loop thru this control's controls until we find the ones that we need to persist. 
            RetrievePropertyValues(this.Controls, parentWebPart);
        }

        // Recursive function that tries to locate the values set in the toolpart 
        private void RetrievePropertyValues(ControlCollection controls, SampleWebPart parentWebPart)
        {
            foreach (Control ctl in controls)
            {
                RetrievePropertyValue(ctl, parentWebPart);

                if (ctl.HasControls())
                {
                    RetrievePropertyValues(ctl.Controls, parentWebPart);
                }
            }
        }

        // Method for retrieving the values set by the user. 
        private void RetrievePropertyValue(Control ctl, SampleWebPart parentWebPart)
        {
            if (ctl is DropDownList)
            {
                if (ctl.ID.Equals("sampleDropDown1"))
                {
                    DropDownList drp = (DropDownList)ctl;
                    if (drp.SelectedItem.Value != "")
                    {
                        parentWebPart.myProperty = drp.SelectedItem.Value;
                    }
                }
            }
        }
    }
[/code]
All this toolpart would do is:
  • add dropdown box in webpart properties window
  • on applying changes, it assigns selected value from dropdown box to public property of base webpart class
Lets see out to add this custom toolpart in webpart's toolpart collection. To do this, we override the GetToolParts() method and place our new tool part in the ToolPart array. Take a look at the code below:
[code]
      public class SampleWebPart : Microsoft.SharePoint.WebPartPages.WebPart
    {
        private string _property1 = "Default Value";

        public SampleWebPart()
        {
            this.ExportMode = WebPartExportMode.All;
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            Label lbl = new Label();
            lbl.ID = "toolpart_webpart_lbl1";
            lbl.Text = _property1;
            Controls.Add(lbl);
        }

        public string myProperty
        {
            get
            {
                return _property1;
            }
            set
            {
                _property1 = value;
            }
        }        

        public override ToolPart[] GetToolParts()
        {
            // resize the tool part array 
            ToolPart[] toolparts = new ToolPart[3];
            // instantiate the standard SharePopint tool part 
            WebPartToolPart wptp = new WebPartToolPart();
            // instantiate the custom property toolpart if needed. 
            // this object is what renders our regular properties. 
            CustomPropertyToolPart custom = new CustomPropertyToolPart();
            // instantiate and add our tool part to the array. 
            // tool parts will render in the order they are added to this array. 
            toolparts[0] = new SampleToolPart();
            toolparts[1] = custom;
            toolparts[2] = wptp;
            return toolparts;
        }
    }
[/code]

WebPart Added On Page:
[before updation]

[after updation]

Hope this helped.

cheers

5 comments:

Saurabh said...

Hi Sandeep........

nice article.....can u plz tell us how to add a new Content Query Webpart Programmatically at ASPX page with our own SiteColumns.

Thanx n Regards
Saurabh

Sandeep said...

sure. All we need to do is perform few basic steps.
Step 1. Fetch SPLimitedWebPartManager object for the aspx page on which you need to dynamically add CQWP
Step 2. Fetch CQWP object from WebPart Gallery
Step 3. Add retrieved webpart on aspx page using SPLimitedWebPartManager.AddWebPart method

take a look at the code below:

Program.cs
[code]
using (SPSite mysite = new SPSite("http://localhost/"))
{
using (SPWeb mysweb = mysite.RootWeb)
{
SPFile file = mysweb.GetFile("/Pages/Default.aspx");

if (file.CheckOutStatus == SPFile.SPCheckOutStatus.None)
{
file.CheckOut();
}

Class1 cs = new Class1();

cs.AddWebPart(mysweb, "/Pages/Default.aspx", "ContentByQueryWebPart", "MiddleLeftZone", 1);

if (file.CheckOutStatus != SPFile.SPCheckOutStatus.None)
{
file.CheckIn("custom check in", SPCheckinType.MajorCheckIn);
}
}
}
[/code]

Class1.cs
[code]
public string AddWebPart(
SPWeb web,
String pageUrl,
String webPartName,
String zoneID,
int zoneIndex)
{
using (System.Web.UI.WebControls.WebParts.WebPart webPart =
FetchWebPart(web, webPartName))
{
using (SPLimitedWebPartManager manager = web.GetLimitedWebPartManager(
pageUrl, System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared))
{
manager.AddWebPart(webPart, zoneID, zoneIndex);
return webPart.ID;
}
}
}

private System.Web.UI.WebControls.WebParts.WebPart
FetchWebPart(SPWeb web, String webPartName)
{
SPQuery query = new SPQuery();
// CAML query fetch list item with name equal to webPartName
query.Query = "<Where><Eq><FieldRef Name='Title'/><Value Type='Text'>" + webPartName + "</Value></Eq></Where>";

SPList webPartGallery = null;
// webpart gallery is available at root web only
if (null == web.ParentWeb)
{
// This is the root web.
webPartGallery = web.GetCatalog(
SPListTemplateType.WebPartCatalog);
}
else
{
SPWeb fetchRootWeb = web;
// fetch root web
while (fetchRootWeb.ParentWeb != null)
{
fetchRootWeb = fetchRootWeb.ParentWeb;
}
webPartGallery = fetchRootWeb.GetCatalog(
SPListTemplateType.WebPartCatalog);
}
// pass query object to item collection
SPListItemCollection webParts = webPartGallery.GetItems(query);

String typeName = webParts[0].GetFormattedValue("WebPartTypeName");
String assemblyName = webParts[0].GetFormattedValue("WebPartAssembly");
ObjectHandle webPartHandle = Activator.CreateInstance(
assemblyName, typeName);
if (webPartName == "ContentByQueryWebPart")
{
Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart webPart =
(Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart)webPartHandle.Unwrap();
webPart.Title = "Sandeep's content by query webpart";
webPart.ListName = "Pages";
webPart.WebUrl = "/";
webPart.CommonViewFields = "list of your custom site columns";
return webPart;
}
else
{
Microsoft.SharePoint.WebPartPages.WebPart webpart =
(Microsoft.SharePoint.WebPartPages.WebPart)webPartHandle.Unwrap();
return webpart;
}
}
}
[/code]

one you have the CQWP instance, then you can play around with its properties to attain your business needs. For e.g. take a look below
webPart.Title = "Sandeep's content by query webpart";
webPart.ListName = "Pages";
webPart.WebUrl = "/";
webPart.CommonViewFields = "list of your custom site columns";

-------
Hope this helped.
Cheers,

Anonymous said...

You should be using EditorParts http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.webparts.editorpart.aspx instead of ToolParts. ToolParts were used in V2 and are still available but only for legacy reasons. WebParts should also inherit from the System.Web.UI.WebControls.WebParts namespace.

Ahmet said...

thank you;

it is very simple and important and understandable..


greeting from Turkey :)

Custom Paper Writing said...

Many institutions limit access to their online information. Making this information available will be an asset to all.