Click here to Skip to main content
15,891,513 members
Please Sign up or sign in to vote.
4.33/5 (3 votes)
See more:
I have a custom control that is basically intended to be exactly like a tabcontrol, but without the tabs. I have a parent container named "LVPanelControl" which inherits from Panel, and adds an internal List of type LVPanel

List<LVPanel>

LVPanel also derives from Panel, but doesn't currently add any other functionality.

I have a custom editor that derives from CollectionEditor that I'm using to edit the collection of LVPanels from the designer. Everything seems to be working perfectly, so far, except when I add panels through the custom editor, I don't know how to trigger it to add the new panels to the parent LVPanelControl. The panels lvPanel1 and lvPanel2 etc are created, and added to the form design document, but neither of the LVPanels are added to the LVPanelControl via lvPanelControl1.Controls.Add(). How can I trigger this after clicking 'Add' on my editor?

Here's my LVPanelControl:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.ComponentModel.Design;
using System.Windows.Forms.Design;
using System.Design;

namespace SNS.UI.ListViewTabControl
{
    [Designer(typeof(LVPanelControlDesigner))]
    public class LVPanelControl : Panel
    {
        private List<LVPanel> _lvPanelCollection;

        [Editor(typeof(LVPanelEditor), typeof(UITypeEditor))]
        [Category("Behavior")]
        public List<LVPanel> Panels
        {
            get { return this._lvPanelCollection; }
        }

        public LVPanelControl()
            : base()
        {
            _lvPanelCollection = new List<LVPanel>();     
        }
    }
}


And here's the editor:

using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;
using System;

namespace SNS.UI.ListViewTabControl
{
    public class LVPanelEditor : CollectionEditor
    {
        // Define a static event to expose the inner PropertyGrid's
        // PropertyValueChanged event args...
        public delegate void MyPropertyValueChangedEventHandler(object sender,
                                            PropertyValueChangedEventArgs e);

        public static event MyPropertyValueChangedEventHandler MyPropertyValueChanged;

        // Inherit the default constructor from the standard
        // Collection Editor...
        public LVPanelEditor(Type type) : base(type) { }

        // Override this method in order to access the containing user controls
        // from the default Collection Editor form or to add new ones...
        protected override CollectionForm CreateCollectionForm()
        {

            // Getting the default layout of the Collection Editor...
            CollectionForm collectionForm = base.CreateCollectionForm();

            Form frmCollectionEditorForm = collectionForm as Form;
            TableLayoutPanel tlpLayout = frmCollectionEditorForm.Controls[0] as TableLayoutPanel;

            if (tlpLayout != null)
            {
                // Get a reference to the inner PropertyGrid and hook
                // an event handler to it.
                if (tlpLayout.Controls[5] is PropertyGrid)
                {
                    PropertyGrid propertyGrid = tlpLayout.Controls[5] as PropertyGrid;
                    propertyGrid.PropertyValueChanged += new PropertyValueChangedEventHandler(propertyGrid_PropertyValueChanged);
                }
            }

            return collectionForm;
        }

        void propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
        {
            // Fire our customized collection event...
            if (LVPanelEditor.MyPropertyValueChanged != null)
            {
                LVPanelEditor.MyPropertyValueChanged(this, e);
            }
        }
    }
}


Panel:

C#
using System;
using System.Collections;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;

namespace SNS.UI.ListViewTabControl
{
    [ToolboxItem(false)]
    public class LVPanel : Panel
    {
        private string _text;

        [Category("Appearance")]
        public new string Text
        {
            get { return _text; }
            set { _text = value; }
        }

        public LVPanel()
            : base()
        {
            this.Dock = DockStyle.Fill;
        }
    }
}


Designer:

using System;
using System.Collections;
using System.Text;
using System.Design;
using System.Windows.Forms.Design;
using System.Drawing.Design;
using System.ComponentModel.Design;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace SNS.UI.ListViewTabControl
{
    class LVPanelControlDesigner : ParentControlDesigner
    {
        private LVPanelControlActionList _actionList;
        private LVPanelControl _lvPanelControl;
        private IDesignerHost _designerHost;
        private ISelectionService _selectionService;
        private IToolboxService _toolboxService;
        private IMenuCommandService _menuService;
        private Pen mBorderPen;

        public LVPanelControlDesigner()
            : base()
        {
            mBorderPen = new Pen(SystemBrushes.ControlDarkDark, 1.5f);
            mBorderPen.DashStyle = DashStyle.Dash;
        }

        public override void Initialize(IComponent component)
        {
            base.Initialize(component);

            if (Control is LVPanelControl)
            {
                _lvPanelControl = (LVPanelControl)Control;
                _designerHost = (IDesignerHost)GetService(typeof(IDesignerHost));
                _selectionService = (ISelectionService)GetService(typeof(ISelectionService));
                _toolboxService = (IToolboxService)GetService(typeof(IToolboxService));
                _menuService = (IMenuCommandService)GetService(typeof(IMenuCommandService));

                _lvPanelControl.AllowDrop = true;
                this.EnableDragDrop(true);
            }
        }

        protected override void OnPaintAdornments(System.Windows.Forms.PaintEventArgs pe)
        {
            base.OnPaintAdornments(pe);

            // Draw a nice border around ourselves, so the user can see
            // where to put controls!  (If we didn't do this, it would
            // be hard to distinguish the PropertyPane surface from the
            // Form's surface).
            // 
            pe.Graphics.DrawRectangle(mBorderPen,
              1, 1, this.Control.Width - 2, this.Control.Height - 2);
        }

        public override DesignerActionListCollection ActionLists
        {
            get
            {
                if (_actionList == null)
                {
                    _actionList = new LVPanelControlActionList(this.Component);
                }
                return new DesignerActionListCollection(new DesignerActionList[] { _actionList });
            }
        }
    }

    public class LVPanelControlActionList : DesignerActionList, ITypeDescriptorContext, IWindowsFormsEditorService
    {
        private IDesignerHost _hostSvc;
        private IComponentChangeService _compSvc;
        private PropertyDescriptor _tabsPD;
        private IDesignerHost _designerHost;

        public LVPanelControlActionList(IComponent component)
            : base(component)
        {
            this._hostSvc = component.Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
            this._compSvc = component.Site.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
            this._tabsPD = TypeDescriptor.GetProperties(this.Component)["Panels"];

            this._designerHost = (IDesignerHost)GetService(typeof(IDesignerHost));
        }

        public override DesignerActionItemCollection GetSortedActionItems()
        {
            base.GetSortedActionItems();
            DesignerActionItemCollection col = new DesignerActionItemCollection();

            col.Add(new DesignerActionHeaderItem("Behavior"));
            col.Add(new DesignerActionTextItem("Properties that affect how the LVPanelControl behaves.", "Behavior"));
            col.Add(new DesignerActionMethodItem(this, "AddTab", "Add Tab", "Behavior", true));
            col.Add(new DesignerActionMethodItem(this, "DeleteTab", "Delete Tab", "Behavior", true));
            col.Add(new DesignerActionMethodItem(this, "EditTabs", "Edit tabs...", "Behavior", true));

            //col.Add(new DesignerActionHeaderItem("Design"));
            //col.Add(new DesignerActionTextItem("Properties that affect how the LVPanelControl acts in the designer.", "Design"));
            return col;
        }

        private void AddTab()
        {
            //string name = GenerateNewPanelName();

            //TabPage page = _designerHost.CreateComponent(typeof(TabPage), name) as TabPage;
            //page.Text = name;

            //((ListViewTabControl)this.Component).TabPages.Add(page);
        }

        private void DeleteTab()
        {
        }

        private void EditTabs()
        {
            UITypeEditor editor = (UITypeEditor)_tabsPD.GetEditor(typeof(UITypeEditor));
            editor.EditValue(this, this, ((LVPanelControl)this.Component).Panels);
        }

        /// <summary>
        ///   Generates a new unique name for a <see cref="SNSTabControl"/> tab that is 
        ///   suitable for use as a variable name.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///   This function returns names of the form "tabPageN" where N is a number.  This
        ///   function will start at "tabPage1" and keep incrementing the trailing number
        ///   until it finds a name that has not been used yet.
        ///   </para>
        ///   <para>
        ///   The <see cref="IReferenceService.GetName"/> is used to determine whether
        ///   or not a name is in use by the designer.
        ///   </para>
        /// </remarks>
        /// <returns>
        /// A unique name for a <see cref="SNSTabControl"/> tab that is suitable for 
        /// use as a variable name.
        /// </returns>
        protected string GenerateNewPanelName()
        {
            int curNum = 1;
            bool bDuplicateName;
            IReferenceService refsvc = GetService(typeof(IReferenceService)) as IReferenceService;
            string curTry;

            // Get a new component name
            do
            {
                curTry = "tabPage" + curNum;
                bDuplicateName = (refsvc.GetReference(curTry) != null);
                curNum++;
            }
            while (bDuplicateName);

            return curTry;
        }

        #region ITypeDescriptorContext Members
        IContainer ITypeDescriptorContext.Container
        {
            get { return this.Component.Site.Container; }
        }
        object ITypeDescriptorContext.Instance
        {
            get { return this.Component; }
        }
        void ITypeDescriptorContext.OnComponentChanged()
        {
            object value = ((LVPanelControl)this.Component).Panels;
            _compSvc.OnComponentChanged(this.Component, _tabsPD, value, value);
        }
        bool ITypeDescriptorContext.OnComponentChanging()
        {
            _compSvc.OnComponentChanging(this.Component, _tabsPD);
            return true;
        }
        PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor
        {
            get { return _tabsPD; }
        }
        #endregion

        #region IServiceProvider Members
        object IServiceProvider.GetService(Type serviceType)
        {
            if (serviceType.Equals(typeof(IWindowsFormsEditorService)))
            {
                return this;
            }
            else
            {
                return _hostSvc.GetService(serviceType);
            }
        }
        #endregion

        #region IWindowsFormsEditorService Members
        void IWindowsFormsEditorService.CloseDropDown()
        {
            throw new NotImplementedException();
        }
        void IWindowsFormsEditorService.DropDownControl(Control control)
        {
            throw new NotImplementedException();
        }
        DialogResult IWindowsFormsEditorService.ShowDialog(Form dialog)
        {
            return dialog.ShowDialog();
        }
        #endregion
    }
}



Thanks for any help.

[Added appropriate tag: WinForms — SA]
Posted
Updated 28-Jan-12 11:38am
v3
Comments
Sergey Alexandrovich Kryukov 28-Jan-12 17:00pm    
You problem is not clear. Implementation of this control is not very difficult. What does it mean "how to trigger it"? Trigger what? "Add" could be a control (like a context menu item, a button), a method of the API or both.
--SA
agent154 28-Jan-12 17:02pm    
"Add" is on the collection editor window. If you want to see what I mean, drop a TabControl on an empty form and then look for the "TabPages" property in the properties window.
agent154 28-Jan-12 17:32pm    
If it helps any, here is my implementation for the interior panel. You could compile the code and see what I mean. I want to be able to add a new panel through the smart tag "Edit tabs..." and have that panel get added to the parent panel afterward. As it stands right now, it only creates the panel but does not add it to the parent.

using System;
using System.Collections;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;

namespace SNS.UI.ListViewTabControl
{
[ToolboxItem(false)]
public class LVPanel : Panel
{
private string _text;

[Category("Appearance")]
public new string Text
{
get { return _text; }
set { _text = value; }
}

public LVPanel()
: base()
{
this.Dock = DockStyle.Fill;
}
}
}
BillWoodruff 28-Jan-12 21:35pm    
+5 for this question, which is already at a high-level of sophistication in terms of adding design-time functionality to a custom control ! I will try to locate my 2004 version of Matthew MacDonald's book:

http://www.amazon.com/NET-Windows-Forms-Custom-Controls/dp/1590594398/ref=pd_rhf_gw_p_t_4

in my library: where I am pretty sure he gives an example of the design-time ability to add a new instance of a Type via a special custom editor in the Property Browser, and will respond, if I can find a satisfactory example.
agent154 30-Jan-12 0:14am    
I understand how it works now... After using ILspy to decompile the source for TabControlDesigner and TabControl itself, I can see that I need to re-design my collection of Panels. Instead of using a simple ArrayList<lvpanel> collection, I need to build a collection myself and overload the .Add() methods so that it will simultaniously add the LVPanel to its own collection, and to the Control.Controls collection. Took a lot of looking around, but I get it now.

1 solution

I understand how it works now... After using ILspy to decompile the source for TabControlDesigner and TabControl itself, I can see that I need to re-design my collection of Panels. Instead of using a simple ArrayList collection, I need to build a collection myself and overload the .Add() methods so that it will simultaneously add the LVPanel to its own collection, and to the Control.Controls collection. Took a lot of looking around, but I get it now.

C#
    public class TabControl : Control
    {

        public class TabPageCollection : IList, ICollection, IEnumerable
        {
            private TabControlNew owner;
            private int lastAccessedIndex = -1;
            
            public virtual TabPage this[int index]
            {
                get
                {
                    return this.owner.GetTabPage(index);
                }
                set
                {
                    this.owner.SetTabPage(index, value, value.GetTCITEM());
                }
            }
            object IList.this[int index]
            {
                get
                {
                    return this[index];
                }
                set
                {
                    if (value is TabPage)
                    {
                        this[index] = (TabPage)value;
                        return;
                    }
                    throw new ArgumentException("value");
                }
            }
            
            public virtual TabPage this[string key]
            {
                get
                {
                    if (string.IsNullOrEmpty(key))
                    {
                        return null;
                    }
                    int index = this.IndexOfKey(key);
                    if (this.IsValidIndex(index))
                    {
                        return this[index];
                    }
                    return null;
                }
            }
            
            [Browsable(false)]
            public int Count
            {
                get
                {
                    return this.owner.tabPageCount;
                }
            }
            object ICollection.SyncRoot
            {
                get
                {
                    return this;
                }
            }
            bool ICollection.IsSynchronized
            {
                get
                {
                    return false;
                }
            }
            bool IList.IsFixedSize
            {
                get
                {
                    return false;
                }
            }
            
            public bool IsReadOnly
            {
                get
                {
                    return false;
                }
            }
            
            public TabPageCollection(TabControlNew owner)
            {
                if (owner == null)
                {
                    throw new ArgumentNullException("owner");
                }
                this.owner = owner;
            }
            
            public void Add(TabPage value)
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                this.owner.Controls.Add(value);
            }
            int IList.Add(object value)
            {
                if (value is TabPage)
                {
                    this.Add((TabPage)value);
                    return this.IndexOf((TabPage)value);
                }
                throw new ArgumentException("value");
            }
            
            public void Add(string text)
            {
                this.Add(new TabPage
                {
                    Text = text
                });
            }
            
            public void Add(string key, string text)
            {
                this.Add(new TabPage
                {
                    Name = key,
                    Text = text
                });
            }
            
            public void Add(string key, string text, int imageIndex)
            {
                this.Add(new TabPage
                {
                    Name = key,
                    Text = text,
                    ImageIndex = imageIndex
                });
            }
            
            public void Add(string key, string text, string imageKey)
            {
                this.Add(new TabPage
                {
                    Name = key,
                    Text = text,
                    ImageKey = imageKey
                });
            }
            
            public void AddRange(TabPage[] pages)
            {
                if (pages == null)
                {
                    throw new ArgumentNullException("pages");
                }
                for (int i = 0; i < pages.Length; i++)
                {
                    TabPage value = pages[i];
                    this.Add(value);
                }
            }
            
            public bool Contains(TabPage page)
            {
                if (page == null)
                {
                    throw new ArgumentNullException("value");
                }
                return this.IndexOf(page) != -1;
            }
            bool IList.Contains(object page)
            {
                return page is TabPage && this.Contains((TabPage)page);
            }
            
            public virtual bool ContainsKey(string key)
            {
                return this.IsValidIndex(this.IndexOfKey(key));
            }
            
            public int IndexOf(TabPage page)
            {
                if (page == null)
                {
                    throw new ArgumentNullException("value");
                }
                for (int i = 0; i < this.Count; i++)
                {
                    if (this[i] == page)
                    {
                        return i;
                    }
                }
                return -1;
            }
            int IList.IndexOf(object page)
            {
                if (page is TabPage)
                {
                    return this.IndexOf((TabPage)page);
                }
                return -1;
            }
            
            public virtual int IndexOfKey(string key)
            {
                if (string.IsNullOrEmpty(key))
                {
                    return -1;
                }
                if (this.IsValidIndex(this.lastAccessedIndex) && WindowsFormsUtils.SafeCompareStrings(this[this.lastAccessedIndex].Name, key, true))
                {
                    return this.lastAccessedIndex;
                }
                for (int i = 0; i < this.Count; i++)
                {
                    if (WindowsFormsUtils.SafeCompareStrings(this[i].Name, key, true))
                    {
                        this.lastAccessedIndex = i;
                        return i;
                    }
                }
                this.lastAccessedIndex = -1;
                return -1;
            }
            
            public void Insert(int index, TabPage tabPage)
            {
                this.owner.InsertItem(index, tabPage);
                try
                {
                    this.owner.InsertingItem = true;
                    this.owner.Controls.Add(tabPage);
                }
                finally
                {
                    this.owner.InsertingItem = false;
                }
                this.owner.Controls.SetChildIndex(tabPage, index);
            }
            void IList.Insert(int index, object tabPage)
            {
                if (tabPage is TabPage)
                {
                    this.Insert(index, (TabPage)tabPage);
                    return;
                }
                throw new ArgumentException("tabPage");
            }
            
            public void Insert(int index, string text)
            {
                this.Insert(index, new TabPage
                {
                    Text = text
                });
            }
            
            public void Insert(int index, string key, string text)
            {
                this.Insert(index, new TabPage
                {
                    Name = key,
                    Text = text
                });
            }
            
            public void Insert(int index, string key, string text, int imageIndex)
            {
                TabPage tabPage = new TabPage();
                tabPage.Name = key;
                tabPage.Text = text;
                this.Insert(index, tabPage);
                tabPage.ImageIndex = imageIndex;
            }
            
            public void Insert(int index, string key, string text, string imageKey)
            {
                TabPage tabPage = new TabPage();
                tabPage.Name = key;
                tabPage.Text = text;
                this.Insert(index, tabPage);
                tabPage.ImageKey = imageKey;
            }
            private bool IsValidIndex(int index)
            {
                return index >= 0 && index < this.Count;
            }
            
            public virtual void Clear()
            {
                this.owner.RemoveAll();
            }
            void ICollection.CopyTo(Array dest, int index)
            {
                if (this.Count > 0)
                {
                    Array.Copy(this.owner.GetTabPages(), 0, dest, index, this.Count);
                }
            }
            
            public IEnumerator GetEnumerator()
            {
                TabPage[] tabPages = this.owner.GetTabPages();
                if (tabPages != null)
                {
                    return tabPages.GetEnumerator();
                }
                return new TabPage[0].GetEnumerator();
            }
            
            public void Remove(TabPage value)
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                this.owner.Controls.Remove(value);
            }
            void IList.Remove(object value)
            {
                if (value is TabPage)
                {
                    this.Remove((TabPage)value);
                }
            }
            
            public void RemoveAt(int index)
            {
                this.owner.Controls.RemoveAt(index);
            }
            
            public virtual void RemoveByKey(string key)
            {
                int index = this.IndexOfKey(key);
                if (this.IsValidIndex(index))
                {
                    this.RemoveAt(index);
                }
            }
        }

        public new class ControlCollection : Control.ControlCollection
        {
            private TabControlNew owner;
            
            public ControlCollection(TabControlNew owner)
                : base(owner)
            {
                this.owner = owner;
            }
            
            public override void Add(Control value)
            {
                if (!(value is TabPage))
                {
                    throw new ArgumentException(SR.GetString("TabControlInvalidTabPageType", new object[]
					{
						value.GetType().Name
					}));
                }
                TabPage tabPage = (TabPage)value;
                if (!this.owner.InsertingItem)
                {
                    if (this.owner.IsHandleCreated)
                    {
                        this.owner.AddTabPage(tabPage, tabPage.GetTCITEM());
                    }
                    else
                    {
                        this.owner.Insert(this.owner.TabCount, tabPage);
                    }
                }
                base.Add(tabPage);
                tabPage.Visible = false;
                if (this.owner.IsHandleCreated)
                {
                    tabPage.Bounds = this.owner.DisplayRectangle;
                }
                ISite site = this.owner.Site;
                if (site != null && tabPage.Site == null)
                {
                    IContainer container = site.Container;
                    if (container != null)
                    {
                        container.Add(tabPage);
                    }
                }
                this.owner.ApplyItemSize();
                this.owner.UpdateTabSelection(false);
            }
            
            public override void Remove(Control value)
            {
                base.Remove(value);
                if (!(value is TabPage))
                {
                    return;
                }
                int num = this.owner.FindTabPage((TabPage)value);
                int selectedIndex = this.owner.SelectedIndex;
                if (num != -1)
                {
                    this.owner.RemoveTabPage(num);
                    if (num == selectedIndex)
                    {
                        this.owner.SelectedIndex = 0;
                    }
                }
                this.owner.UpdateTabSelection(false);
            }
        }

private TabControl.TabPageCollection tabCollection;

public TabControl.TabPageCollection TabPages
{
    get
    {
        return this.tabCollection;
    }
}
 
Share this answer
 
Comments
BillWoodruff 30-Jan-12 3:25am    
+5 It has been a long time since I have looked for resources on CP for adding design-time extensions, like custom selection editors, in the Property Browser; so, congratulations for having reached a solution !

You might consider, if you have the time, writing up some particular technique, or insight into how to reach your solution, as a Tip/Trick or article here on CP.

Although, perhaps, this topic is so complex that it would be a major task to cover well.

best, Bill

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900