/* * 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 . * * 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 { /// /// Designer for and its subclasses. /// /// /// /// This designer removes properties and events that are available on ListView but that are not /// useful on ObjectListView. /// /// /// 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. /// /// public class ObjectListViewDesigner : ControlDesigner { #region Initialize & Dispose /// /// Initializes the designer with the specified component. /// /// The to associate the designer with. This component must always be an instance of, or derive from, . 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(); } /// /// Initializes a newly created component. /// /// A name/value dictionary of default values to apply to properties. May be null if no default values are specified. public override void InitializeNewComponent(IDictionary defaultValues) { // Debug.WriteLine("ObjectListViewDesigner.InitializeNewComponent"); base.InitializeNewComponent(defaultValues); this.listViewDesigner.InitializeNewComponent(defaultValues); } /// /// Releases the unmanaged resources used by the and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. 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); } /// /// Removes the duplicate DockingActionList added by this designer to the . /// /// /// 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. /// 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 /// /// Adjusts the set of properties the component exposes through a . /// /// An containing the properties for the class of the component. 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 unwantedProperties = new List(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; } } /// /// Allows a designer to add to the set of events that it exposes through a . /// /// The events for the class of the component. 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 unwanted = new List(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; } } /// /// Allows a designer to change or remove items from the set of attributes that it exposes through a . /// /// The attributes for the class of the component. protected override void PostFilterAttributes(IDictionary attributes) { // Debug.WriteLine("ObjectListViewDesigner.PostFilterAttributes"); this.designerFilter.PostFilterAttributes(attributes); base.PostFilterAttributes(attributes); } /// /// Allows a designer to change or remove items from the set of events that it exposes through a . /// /// The events for the class of the component. protected override void PostFilterEvents(IDictionary events) { // Debug.WriteLine("ObjectListViewDesigner.PostFilterEvents"); this.designerFilter.PostFilterEvents(events); base.PostFilterEvents(events); } #endregion #region Overrides /// /// Gets the design-time action lists supported by the component associated with the designer. /// /// /// The design-time action lists supported by the component associated with the designer. /// 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; } } /// /// Gets the collection of components associated with the component managed by the designer. /// /// /// The components that are associated with the component managed by the designer. /// public override ICollection AssociatedComponents { get { ArrayList components = new ArrayList(base.AssociatedComponents); components.AddRange(this.listViewDesigner.AssociatedComponents); return components; } } /// /// Indicates whether a mouse click at the specified point should be handled by the control. /// /// /// true if a click at the specified point is to be handled by the control; otherwise, false. /// /// A indicating the position at which the mouse was clicked, in screen coordinates. 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 }); } /// /// Processes Windows messages and optionally routes them to the control. /// /// The to process. 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 /// /// This class modifies a ListViewActionList, by removing the "Edit Items" and "Edit Groups" actions. /// /// /// /// That class is internal, so we cannot simply subclass it, which would be simplier. /// /// /// 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. /// 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 } /// /// This class works in conjunction with the OLVColumns property to allow OLVColumns /// to be added to the ObjectListView. /// public class OLVColumnCollectionEditor : System.ComponentModel.Design.CollectionEditor { /// /// Create a OLVColumnCollectionEditor /// /// public OLVColumnCollectionEditor(Type t) : base(t) { } /// /// What type of object does this editor create? /// /// protected override Type CreateCollectionItemType() { return typeof(OLVColumn); } /// /// Edit a given value /// /// /// /// /// 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 newColumns = olv.GetFilteredColumns(View.Details); olv.Columns.Clear(); olv.Columns.AddRange(newColumns.ToArray()); return olv.Columns; } /// /// What text should be shown in the list for the given object? /// /// /// 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); } } /// /// Control how the overlay is presented in the IDE /// 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); } } }