Files
ObjectListView/ObjectListView.DesignTime.cs
2017-02-04 13:47:58 +01:00

551 lines
25 KiB
C#

/*
* DesignSupport - Design time support for the various classes within ObjectListView
*
* Author: Phillip Piper
* Date: 12/08/2009 8:36 PM
*
* Change log:
* 2012-08-27 JPP - Fall back to more specific type name for the ListViewDesigner if
* the first GetType() fails.
* v2.5.1
* 2012-04-26 JPP - Filter group events from TreeListView since it can't have groups
* 2011-06-06 JPP - Vastly improved ObjectListViewDesigner, based off information in
* "'Inheriting' from an Internal WinForms Designer" on CodeProject.
* v2.3
* 2009-08-12 JPP - Initial version
*
* To do:
*
* Copyright (C) 2009-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using System.Windows.Forms.Design;
namespace BrightIdeasSoftware.Design
{
/// <summary>
/// Designer for <see cref="ObjectListView"/> and its subclasses.
/// </summary>
/// <remarks>
/// <para>
/// This designer removes properties and events that are available on ListView but that are not
/// useful on ObjectListView.
/// </para>
/// <para>
/// We can't inherit from System.Windows.Forms.Design.ListViewDesigner, since it is marked internal.
/// So, this class uses reflection to create a ListViewDesigner and then forwards messages to that designer.
/// </para>
/// </remarks>
public class ObjectListViewDesigner : ControlDesigner
{
#region Initialize & Dispose
/// <summary>
/// Initializes the designer with the specified component.
/// </summary>
/// <param name="component">The <see cref="T:System.ComponentModel.IComponent"/> to associate the designer with. This component must always be an instance of, or derive from, <see cref="T:System.Windows.Forms.Control"/>. </param>
public override void Initialize(IComponent component) {
// Debug.WriteLine("ObjectListViewDesigner.Initialize");
// Use reflection to bypass the "internal" marker on ListViewDesigner
// If we can't get the unversioned designer, look specifically for .NET 4.0 version of it.
Type tListViewDesigner = Type.GetType("System.Windows.Forms.Design.ListViewDesigner, System.Design") ??
Type.GetType("System.Windows.Forms.Design.ListViewDesigner, System.Design, " +
"Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
if (tListViewDesigner == null) throw new ArgumentException("Could not load ListViewDesigner");
this.listViewDesigner = (ControlDesigner)Activator.CreateInstance(tListViewDesigner, BindingFlags.Instance | BindingFlags.Public, null, null, null);
this.designerFilter = this.listViewDesigner;
// Fetch the methods from the ListViewDesigner that we know we want to use
this.listViewDesignGetHitTest = tListViewDesigner.GetMethod("GetHitTest", BindingFlags.Instance | BindingFlags.NonPublic);
this.listViewDesignWndProc = tListViewDesigner.GetMethod("WndProc", BindingFlags.Instance | BindingFlags.NonPublic);
Debug.Assert(this.listViewDesignGetHitTest != null, "Required method (GetHitTest) not found on ListViewDesigner");
Debug.Assert(this.listViewDesignWndProc != null, "Required method (WndProc) not found on ListViewDesigner");
// Tell the Designer to use properties of default designer as well as the properties of this class (do before base.Initialize)
TypeDescriptor.CreateAssociation(component, this.listViewDesigner);
IServiceContainer site = (IServiceContainer)component.Site;
if (site != null && GetService(typeof(DesignerCommandSet)) == null) {
site.AddService(typeof(DesignerCommandSet), new CDDesignerCommandSet(this));
} else {
Debug.Fail("site != null && GetService(typeof (DesignerCommandSet)) == null");
}
this.listViewDesigner.Initialize(component);
base.Initialize(component);
RemoveDuplicateDockingActionList();
}
/// <summary>
/// Initializes a newly created component.
/// </summary>
/// <param name="defaultValues">A name/value dictionary of default values to apply to properties. May be null if no default values are specified.</param>
public override void InitializeNewComponent(IDictionary defaultValues) {
// Debug.WriteLine("ObjectListViewDesigner.InitializeNewComponent");
base.InitializeNewComponent(defaultValues);
this.listViewDesigner.InitializeNewComponent(defaultValues);
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.Windows.Forms.Design.ControlDesigner"/> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources. </param>
protected override void Dispose(bool disposing) {
// Debug.WriteLine("ObjectListViewDesigner.Dispose");
if (disposing) {
if (this.listViewDesigner != null) {
this.listViewDesigner.Dispose();
// Normally we would now null out the designer, but this designer
// still has methods called AFTER it is disposed.
}
}
base.Dispose(disposing);
}
/// <summary>
/// Removes the duplicate DockingActionList added by this designer to the <see cref="DesignerActionService"/>.
/// </summary>
/// <remarks>
/// <see cref="ControlDesigner.Initialize"/> adds an internal DockingActionList : 'Dock/Undock in Parent Container'.
/// But the default designer has already added that action list. So we need to remove one.
/// </remarks>
private void RemoveDuplicateDockingActionList() {
// This is a true hack -- in a class that is basically a huge hack itself.
// Reach into the bowel of our base class, get a private field, and use that fields value to
// remove an action from the designer.
// In ControlDesigner, there is "private DockingActionList dockingAction;"
// Don't you just love Reflector?!
FieldInfo fi = typeof(ControlDesigner).GetField("dockingAction", BindingFlags.Instance | BindingFlags.NonPublic);
if (fi != null) {
DesignerActionList dockingAction = (DesignerActionList)fi.GetValue(this);
if (dockingAction != null) {
DesignerActionService service = (DesignerActionService)GetService(typeof(DesignerActionService));
if (service != null) {
service.Remove(this.Control, dockingAction);
}
}
}
}
#endregion
#region IDesignerFilter overrides
/// <summary>
/// Adjusts the set of properties the component exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
/// </summary>
/// <param name="properties">An <see cref="T:System.Collections.IDictionary"/> containing the properties for the class of the component. </param>
protected override void PreFilterProperties(IDictionary properties) {
// Debug.WriteLine("ObjectListViewDesigner.PreFilterProperties");
// Always call the base PreFilterProperties implementation
// before you modify the properties collection.
base.PreFilterProperties(properties);
// Give the listviewdesigner a chance to filter the properties
// (though we already know it's not going to do anything)
this.designerFilter.PreFilterProperties(properties);
// I'd like to just remove the redundant properties, but that would
// break backward compatibility. The deserialiser that handles the XXX.Designer.cs file
// works off the designer, so even if the property exists in the class, the deserialiser will
// throw an error if the associated designer actually removes that property.
// So we shadow the unwanted properties, and give the replacement properties
// non-browsable attributes so that they are hidden from the user
List<string> unwantedProperties = new List<string>(new string[] {
"BackgroundImage", "BackgroundImageTiled", "HotTracking", "HoverSelection",
"LabelEdit", "VirtualListSize", "VirtualMode" });
// Also hid Tooltip properties, since giving a tooltip to the control through the IDE
// messes up the tooltip handling
foreach (string propertyName in properties.Keys) {
if (propertyName.StartsWith("ToolTip")) {
unwantedProperties.Add(propertyName);
}
}
// If we are looking at a TreeListView, remove group related properties
// since TreeListViews can't show groups
if (this.Control is TreeListView) {
unwantedProperties.AddRange(new string[] {
"GroupImageList", "GroupWithItemCountFormat", "GroupWithItemCountSingularFormat", "HasCollapsibleGroups",
"SpaceBetweenGroups", "ShowGroups", "SortGroupItemsByPrimaryColumn", "ShowItemCountOnGroups"
});
}
// Shadow the unwanted properties, and give the replacement properties
// non-browsable attributes so that they are hidden from the user
foreach (string unwantedProperty in unwantedProperties) {
PropertyDescriptor propertyDesc = TypeDescriptor.CreateProperty(
typeof(ObjectListView),
(PropertyDescriptor)properties[unwantedProperty],
new BrowsableAttribute(false));
properties[unwantedProperty] = propertyDesc;
}
}
/// <summary>
/// Allows a designer to add to the set of events that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
/// </summary>
/// <param name="events">The events for the class of the component. </param>
protected override void PreFilterEvents(IDictionary events) {
// Debug.WriteLine("ObjectListViewDesigner.PreFilterEvents");
base.PreFilterEvents(events);
this.designerFilter.PreFilterEvents(events);
// Remove the events that don't make sense for an ObjectListView.
// See PreFilterProperties() for why we do this dance rather than just remove the event.
List<string> unwanted = new List<string>(new string[] {
"AfterLabelEdit",
"BeforeLabelEdit",
"DrawColumnHeader",
"DrawItem",
"DrawSubItem",
"RetrieveVirtualItem",
"SearchForVirtualItem",
"VirtualItemsSelectionRangeChanged"
});
// If we are looking at a TreeListView, remove group related events
// since TreeListViews can't show groups
if (this.Control is TreeListView) {
unwanted.AddRange(new string[] {
"AboutToCreateGroups",
"AfterCreatingGroups",
"BeforeCreatingGroups",
"GroupTaskClicked",
"GroupExpandingCollapsing",
"GroupStateChanged"
});
}
foreach (string unwantedEvent in unwanted) {
EventDescriptor eventDesc = TypeDescriptor.CreateEvent(
typeof(ObjectListView),
(EventDescriptor)events[unwantedEvent],
new BrowsableAttribute(false));
events[unwantedEvent] = eventDesc;
}
}
/// <summary>
/// Allows a designer to change or remove items from the set of attributes that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
/// </summary>
/// <param name="attributes">The attributes for the class of the component. </param>
protected override void PostFilterAttributes(IDictionary attributes) {
// Debug.WriteLine("ObjectListViewDesigner.PostFilterAttributes");
this.designerFilter.PostFilterAttributes(attributes);
base.PostFilterAttributes(attributes);
}
/// <summary>
/// Allows a designer to change or remove items from the set of events that it exposes through a <see cref="T:System.ComponentModel.TypeDescriptor"/>.
/// </summary>
/// <param name="events">The events for the class of the component. </param>
protected override void PostFilterEvents(IDictionary events) {
// Debug.WriteLine("ObjectListViewDesigner.PostFilterEvents");
this.designerFilter.PostFilterEvents(events);
base.PostFilterEvents(events);
}
#endregion
#region Overrides
/// <summary>
/// Gets the design-time action lists supported by the component associated with the designer.
/// </summary>
/// <returns>
/// The design-time action lists supported by the component associated with the designer.
/// </returns>
public override DesignerActionListCollection ActionLists {
get {
// We want to change the first action list so it only has the commands we want
DesignerActionListCollection actionLists = this.listViewDesigner.ActionLists;
if (actionLists.Count > 0 && !(actionLists[0] is ListViewActionListAdapter)) {
actionLists[0] = new ListViewActionListAdapter(this, actionLists[0]);
}
return actionLists;
}
}
/// <summary>
/// Gets the collection of components associated with the component managed by the designer.
/// </summary>
/// <returns>
/// The components that are associated with the component managed by the designer.
/// </returns>
public override ICollection AssociatedComponents {
get {
ArrayList components = new ArrayList(base.AssociatedComponents);
components.AddRange(this.listViewDesigner.AssociatedComponents);
return components;
}
}
/// <summary>
/// Indicates whether a mouse click at the specified point should be handled by the control.
/// </summary>
/// <returns>
/// true if a click at the specified point is to be handled by the control; otherwise, false.
/// </returns>
/// <param name="point">A <see cref="T:System.Drawing.Point"/> indicating the position at which the mouse was clicked, in screen coordinates. </param>
protected override bool GetHitTest(Point point) {
// The ListViewDesigner wants to allow column dividers to be resized
return (bool)this.listViewDesignGetHitTest.Invoke(listViewDesigner, new object[] { point });
}
/// <summary>
/// Processes Windows messages and optionally routes them to the control.
/// </summary>
/// <param name="m">The <see cref="T:System.Windows.Forms.Message"/> to process. </param>
protected override void WndProc(ref Message m) {
switch (m.Msg) {
case 0x4e:
case 0x204e:
// The listview designer is interested in HDN_ENDTRACK notifications
this.listViewDesignWndProc.Invoke(listViewDesigner, new object[] { m });
break;
default:
base.WndProc(ref m);
break;
}
}
#endregion
#region Implementation variables
private ControlDesigner listViewDesigner;
private IDesignerFilter designerFilter;
private MethodInfo listViewDesignGetHitTest;
private MethodInfo listViewDesignWndProc;
#endregion
#region Custom action list
/// <summary>
/// This class modifies a ListViewActionList, by removing the "Edit Items" and "Edit Groups" actions.
/// </summary>
/// <remarks>
/// <para>
/// That class is internal, so we cannot simply subclass it, which would be simplier.
/// </para>
/// <para>
/// Action lists use reflection to determine if that action can be executed, so we not
/// only have to modify the returned collection of actions, but we have to implement
/// the properties and commands that the returned actions use. </para>
/// </remarks>
private class ListViewActionListAdapter : DesignerActionList
{
public ListViewActionListAdapter(ObjectListViewDesigner designer, DesignerActionList wrappedList)
: base(wrappedList.Component) {
this.designer = designer;
this.wrappedList = wrappedList;
}
public override DesignerActionItemCollection GetSortedActionItems() {
DesignerActionItemCollection items = wrappedList.GetSortedActionItems();
items.RemoveAt(2); // remove Edit Groups
items.RemoveAt(0); // remove Edit Items
return items;
}
private void EditValue(ComponentDesigner componentDesigner, IComponent iComponent, string propertyName) {
// One more complication. The ListViewActionList classes uses an internal class, EditorServiceContext, to
// edit the items/columns/groups collections. So, we use reflection to bypass the data hiding.
Type tEditorServiceContext = Type.GetType("System.Windows.Forms.Design.EditorServiceContext, System.Design");
tEditorServiceContext.InvokeMember("EditValue", BindingFlags.InvokeMethod | BindingFlags.Static, null, null, new object[] { componentDesigner, iComponent, propertyName });
}
private void SetValue(object target, string propertyName, object value) {
TypeDescriptor.GetProperties(target)[propertyName].SetValue(target, value);
}
public void InvokeColumnsDialog() {
EditValue(this.designer, base.Component, "Columns");
}
// Don't need these since we removed their corresponding actions from the list.
// Keep the methods just in case.
//public void InvokeGroupsDialog() {
// EditValue(this.designer, base.Component, "Groups");
//}
//public void InvokeItemsDialog() {
// EditValue(this.designer, base.Component, "Items");
//}
public ImageList LargeImageList {
get { return ((ListView)base.Component).LargeImageList; }
set { SetValue(base.Component, "LargeImageList", value); }
}
public ImageList SmallImageList {
get { return ((ListView)base.Component).SmallImageList; }
set { SetValue(base.Component, "SmallImageList", value); }
}
public View View {
get { return ((ListView)base.Component).View; }
set { SetValue(base.Component, "View", value); }
}
ObjectListViewDesigner designer;
DesignerActionList wrappedList;
}
#endregion
#region DesignerCommandSet
private class CDDesignerCommandSet : DesignerCommandSet
{
public CDDesignerCommandSet(ComponentDesigner componentDesigner) {
this.componentDesigner = componentDesigner;
}
public override ICollection GetCommands(string name) {
// Debug.WriteLine("CDDesignerCommandSet.GetCommands:" + name);
if (componentDesigner != null) {
if (name.Equals("Verbs")) {
return componentDesigner.Verbs;
}
if (name.Equals("ActionLists")) {
return componentDesigner.ActionLists;
}
}
return base.GetCommands(name);
}
private readonly ComponentDesigner componentDesigner;
}
#endregion
}
/// <summary>
/// This class works in conjunction with the OLVColumns property to allow OLVColumns
/// to be added to the ObjectListView.
/// </summary>
public class OLVColumnCollectionEditor : System.ComponentModel.Design.CollectionEditor
{
/// <summary>
/// Create a OLVColumnCollectionEditor
/// </summary>
/// <param name="t"></param>
public OLVColumnCollectionEditor(Type t)
: base(t) {
}
/// <summary>
/// What type of object does this editor create?
/// </summary>
/// <returns></returns>
protected override Type CreateCollectionItemType() {
return typeof(OLVColumn);
}
/// <summary>
/// Edit a given value
/// </summary>
/// <param name="context"></param>
/// <param name="provider"></param>
/// <param name="value"></param>
/// <returns></returns>
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
if (context == null)
throw new ArgumentNullException("context");
if (provider == null)
throw new ArgumentNullException("provider");
// Figure out which ObjectListView we are working on. This should be the Instance of the context.
ObjectListView olv = context.Instance as ObjectListView;
Debug.Assert(olv != null, "Instance must be an ObjectListView");
// Edit all the columns, not just the ones that are visible
base.EditValue(context, provider, olv.AllColumns);
// Set the columns on the ListView to just the visible columns
List<OLVColumn> newColumns = olv.GetFilteredColumns(View.Details);
olv.Columns.Clear();
olv.Columns.AddRange(newColumns.ToArray());
return olv.Columns;
}
/// <summary>
/// What text should be shown in the list for the given object?
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
protected override string GetDisplayText(object value) {
OLVColumn col = value as OLVColumn;
if (col == null || String.IsNullOrEmpty(col.AspectName))
return base.GetDisplayText(value);
return String.Format("{0} ({1})", base.GetDisplayText(value), col.AspectName);
}
}
/// <summary>
/// Control how the overlay is presented in the IDE
/// </summary>
internal class OverlayConverter : ExpandableObjectConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) {
if (destinationType == typeof(string)) {
ImageOverlay imageOverlay = value as ImageOverlay;
if (imageOverlay != null) {
return imageOverlay.Image == null ? "(none)" : "(set)";
}
TextOverlay textOverlay = value as TextOverlay;
if (textOverlay != null) {
return String.IsNullOrEmpty(textOverlay.Text) ? "(none)" : "(set)";
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}