Out of the box SharePoint comes with a lot of Web 2.0 features that empower the end users to customize and personalize their pages and the web parts on those pages. In WSS v3 much of this power comes from ASP.NET 2.0 and the web part infrastructure including many EditorPart controls found in the System.Web.UI.WebControls.WebParts namespace. Some of these controls include the AppearanceEditorPart, BehaviorEditorWebPart and LayoutEditorPart all used to define characteristics about how the web part is rendered to the end user.
Additionally, there is the PropertyGridEditorPart which provides a great deal of power and flexibility to web part developers by allowing them to configure additional web part properties that are persisted to durable storage on either a shared or per user basis. While, I love this power I didn’t like having a separate zone to edit web part properties. I felt it would be more user friendly if certain properties were editable within the web part frame rather than in a separate tool part.
Custom Verb in the WebPartVerbsCollection used to initiate an expand of the custom web part properties editor panel. 
Expanded web part properties editor panel showing editable properties available for a web part
Based on the functionality of the PropertyGridEditorPart a base web part was created allowing all inheriting web parts to reap the rewards of an expand/collapse property editor that when wrapped with an Update Panel provides a clean no visible post back way of editing web part properties. In the code snippets below I have broken down my approach into steps that correlate to the lifecycle of the web part.
First the controls for each of the properties needed to be created.
In the CreateChildControls override a filtered list of editable properties for the user are retrieved for the web part using GetEditableProperties method and then controls are created for each of the properties using CreateEditorControl
protected override void CreateChildControls()
{
//... Additional code omitted for clarity
// Clear editor controls before populating
this.EditorControls.Clear();
// Loop through filtered list of editable properties to generate a property editor control for it
foreach (PropertyDescriptor descriptor in this.GetEditableProperties(true))
{
// Create the control
Control control = this.CreateEditorControl(descriptor);
if (control != null)
{
// Add to the editor controls array for user later when applying changes and handling errors
this.EditorControls.Add(control);
// Get the display name and description to use for the control label
String displayName = this.GetDisplayName(descriptor);
String description = this.GetDescription(descriptor);
// Add the control to the property table with the label and description
this.AddControlToPropertiesTable(displayName, description, control);
}
}
// Initialize error messages array to the number of editor controls
this._errorMessages = new String[this.EditorControls.Count];
//... Additional code omitted for clarity
}
private PropertyDescriptorCollection GetEditableProperties(bool sort)
{
// Get a filtered list of properties
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(this,
new Attribute[] { WebPartBrowsableAttribute.Yes });
// Sort the list if necessary
if (sort)
{
properties = properties.Sort();
}
// Loop through list of properties and determine if this user can edit (personalizible vs shared)
PropertyDescriptorCollection descriptors2 = new PropertyDescriptorCollection(null);
foreach (PropertyDescriptor descriptor in properties)
{
if (this.CanEditProperty(descriptor))
{
descriptors2.Add(descriptor);
}
}
return descriptors2;
}
private Control CreateEditorControl(PropertyDescriptor property)
{
// Get the type for the property to determine which control to create
Type propertyType = property.PropertyType;
// Return a CheckBox if the property is a boolean
if (propertyType == typeof(bool))
{
CheckBox checkBox = new CheckBox();
checkBox.ID = controlId;
return checkBox;
}
// Return a DropDownList if the property is an enumeration
if (typeof(Enum).IsAssignableFrom(propertyType))
{
DropDownList list = new DropDownList();
list.ID = controlId;
// Create the list of enumeration items add populate the DropDownList
foreach (Object obj in property.Converter.GetStandardValues())
{
String text = property.Converter.ConvertToString(obj);
list.Items.Add(new ListItem(text));
}
return list;
}
// Return a TextBox if no other control match was found
TextBox textbox = new TextBox();
textbox.ID = controlId;
textbox.Columns = TextBoxColumns;
return textbox;
}
Now that the controls have been created they need to be populated with values from the web part properties.
In the OnPreRender event which occurs after CreateChildControls() and is a recommended location for your data interaction activities the properties are synced to the controls so when they are rendered they have the updated property information. If you’ve ever created a custom EditorPart, SyncChanges will be very familiar since that is a method you override for syncing your properties to your custom controls.
protected override void OnPreRender(EventArgs e)
{
//... Additional code omitted for clarity
// If the web part is visible and there are no errors load property editor controls with their data
if (this.Visible && !this.HasError)
{
this.SyncChanges();
}
//... Additional code omitted for clarity
}
private void SyncChanges()
{
// Make sure the controls exist
this.EnsureChildControls();
int num = 0;
// Get all the editable properties and set the control value
foreach (PropertyDescriptor descriptor in this.GetEditableProperties(true))
{
if (this.CanEditProperty(descriptor))
{
// Get the control from the editor controls collection
Control control = (Control)this.EditorControls[num];
// Set the value of the control based on type
this.SyncChanges(control, descriptor);
num++;
}
}
}
private void SyncChanges(Control control, PropertyDescriptor property)
{
if (propertyType == typeof(bool))
{
CheckBox box = (CheckBox)control;
box.Checked = (bool)property.GetValue(this);
}
else if (typeof(Enum).IsAssignableFrom(propertyType))
{
DropDownList list = (DropDownList)control;
list.SelectedValue = property.Converter.ConvertToString(property.GetValue(this));
}
else
{
TextBox box2 = (TextBox)control;
box2.Text = property.Converter.ConvertToString(property.GetValue(this));
}
}
Finally, when you make changes to the property values in the web controls the changes need to be applied to the web part properties.
In the save event handler this is accomplished by calling an ApplyChanges method. Again ApplyChanges comes from its EditorPart roots meaning to apply control changes to the properties. Within the ApplyChanges method the GetEditableProperties method is used again to retrieve the properties and then an Array of controls populated during CreateChildControls is used to retrieve the control values and set the property value. Note that much of the error handling and try catch blocks have been removed for clarity.
protected void SaveLinkButton_Click(Object sender, EventArgs e)
{
//... Additional code omitted for clarity
// Update the properties
this.ApplyChanges();
}
private bool ApplyChanges()
{
//... Additional code omitted for clarity
// Loop through each editible property, get the control for it and set its value
PropertyDescriptorCollection editableProperties = this.GetEditableProperties(true);
for (int i = 0; i < editableProperties.Count; i++)
{
// Get the property
PropertyDescriptor property = editableProperties[i];
// Get the editor control
Control editorControl = (Control)this.EditorControls[i];
// Get the value from the control
Object editorControlValue = this.GetEditorControlValue(editorControl, property);
property.SetValue(this, editorControlValue);
//... Additional code omitted for clarity
}
//... Additional code omitted for clarity
}
private Object GetEditorControlValue(Control editorControl, PropertyDescriptor property)
{
CheckBox box = editorControl as CheckBox;
if (box != null)
{
return box.Checked;
}
DropDownList list = editorControl as DropDownList;
if (list != null)
{
String selectedValue = list.SelectedValue;
return property.Converter.ConvertFromString(selectedValue);
}
TextBox box2 = (TextBox)editorControl;
return property.Converter.ConvertFromString(box2.Text);
}
As a part of this exercise a custom attribute was introduced to allow showing properties only in the custom web part properties panel and not in the standard PropertyGridEditorPart the screen shots below demonstrate this.
ShowInWebPartPanel property shows here but the ShowInPropertyGrid property does not
ShowInPropertyGrid property shows in the PropertyGridEditorPart but the ShowInWebPartPanel property does not