⬆️ ⬇️

Extend the functionality of standard WinForms TabControl

It happened recently that it was necessary to use the TabControl component in one project. Standard component, nothing unusual, quite convenient. The nuance was that you had to use your own type of tabs based on the overloaded TabPage. In addition, it was necessary to allow the user to add tabs himself in the process. It should look like this:

image



I am glad that the standard components allow you to do quite a lot of perversions with them.





Link to a test project with an example. Under the 2011 2010 studio.

')

Let's start with custom tabs .

Unfortunately, TabControl, unlike the DataGridViewColumn, does not allow you to specify the type of the nested element through an internal variable. So it is done in the DataGridViewColumn:

public class CustomDataGridViewColumn : DataGridViewTextBoxColumn { public CustomDataGridViewColumn() { this->CellTemplate = CustomDataGridViewCell; } } 




The project should be changed to Net 4 Full profile, because you need to connect two assemblies - System.Design and System.Drawing.Design.



Alas, it is impossible to do the same in TabControl; you will have to override the type TabPageCollection declared inside TabControl. So, we declare the class itself and the necessary methods, then we analyze in turn. Yes, and I’ll warn you right away that there was no line “using System.Windows.Forms;” inside the code file, so the reference to standard components in the sample code comes with the prefix “System.Windows.Forms.”. And TabControl and TabPage are custom redefined types.

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel; using System.Drawing.Design; using System.ComponentModel.Design; namespace WindowsFormsApplication1 { public class TabControl : System.Windows.Forms.TabControl { //    . public new class TabPageCollection : System.Windows.Forms.TabControl.TabPageCollection { public TabPageCollection(TabControl owner) : base(owner) { } } public TabControl() { } private TabPageCollection mTabCollection = null; //      ,    Editor   . [Editor(typeof(TabPageCollectionEditor), typeof(UITypeEditor))] public new System.Windows.Forms.TabControl.TabPageCollection TabPages { get { if (mTabCollection == null) mTabCollection = new TabPageCollection(this); return mTabCollection; } } } //     .      . public class TabPageCollectionEditor : System.ComponentModel.Design.CollectionEditor { public TabPageCollectionEditor(Type type) : base(type) { } protected override Type CreateCollectionItemType() { return typeof(TabPage); } protected override Type[] CreateNewItemTypes() { return new Type[] { typeof(TabPage) }; } } } 




And a simple TabPage:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace WindowsFormsApplication1 { public class TabPage : System.Windows.Forms.TabPage { public TabPage() { } } } 




I think everything is clear. Go to the next step.



Add tabs by user.

To provide this opportunity, we went the easiest way: in TabControl there is always an additional empty tab with the title "+". When it is pressed, a tab add event is generated.



First, in the TabPageCollection type, we overload the Clear method - when clearing tabs, we do NOT need to delete the "+" tab.

 public override void Clear() { System.Windows.Forms.TabPage page = null; if (this.ContainsKey(TabControl.KeyPageAllowAddName)) page = this[TabControl.KeyPageAllowAddName]; base.Clear(); if (page != null) this.Add(page); } 




In the code TabControl declare a variable that stores the name of the tab "+".

 public static string KeyPageAllowAddName = "___page_allow_to_add_name___"; 




We declare the AllowUserToAddTab property of the component in order to enable / disable the new operation mode. Well, the actual method that checks for the presence of such a tab and, if necessary, adds / removes it.



 public TabControl() { this.Enter += new EventHandler((sender, e) => { CheckAllowUserToAddTab(); }); this.Selecting += new System.Windows.Forms.TabControlCancelEventHandler(TabControl_Selecting); } private bool mAllowUserToAddTab = false; [Browsable(true), Description("   .   +,       OnUserAddedTab."), Category("Action")] public virtual bool AllowUserToAddTab { get { return mAllowUserToAddTab; } set { mAllowUserToAddTab = value; } } void CheckAllowUserToAddTab() { if (mAllowUserToAddTab) { System.Windows.Forms.TabPage page_allow_to_add = TabPages[KeyPageAllowAddName]; if (mAllowUserToAddTab) { if (page_allow_to_add == null) { page_allow_to_add = new TabPage(); page_allow_to_add.Name = KeyPageAllowAddName; page_allow_to_add.Text = "+"; TabPages.Insert(0, page_allow_to_add); } } else { if (page_allow_to_add != null) TabPages.Remove(page_allow_to_add); } } } 




Describe the delegate for the tab add event:

 public delegate bool TabPageAdding(TabControl control, TabPage page); 




Event and method for adding a tab:

 public event TabPageAdding PageAdding; void TabControl_Selecting(object sender, System.Windows.Forms.TabControlCancelEventArgs e) { if (TabPages.Count > 0 && e.TabPage.Name == KeyPageAllowAddName) { e.Cancel = true; TabPage page = new TabPage(); if (PageAdding != null) foreach (TabPageAdding _delegate in PageAdding.GetInvocationList()) { try { if (!_delegate.Invoke(this, page)) return; } catch (Exception ex) { System.Windows.Forms.MessageBox.Show(ex.Message); } } TabPages.Add(page); } } 


A list of all subscribers to the add event is searched, if at least one of them returns False - the add is canceled.



In the TabPageCollectionEditor class, you must overload the GetItems and SetItems methods. They are needed to transfer an array of tabs from the editor to the component and vice versa, while excluding the "+" tab.

 protected override object[] GetItems(object editValue) { try { object[] values = base.GetItems(editValue); List<object> values2 = new List<object>(); foreach (var element in values) { if (element.GetType() == typeof(TabPage)) { TabPage tp = (TabPage)element; if (tp.Name == TabControl.KeyPageAllowAddName) continue; } values2.Add(element); } return values2.ToArray(); } catch (Exception ex){System.Windows.Forms.MessageBox.Show(ex.Message);} return base.GetItems(editValue); } protected override object SetItems(object editValue, object[] value) { try { List<object> values2 = new List<object>(); foreach (var element in value) { if (element.GetType() == typeof(TabPage)) { TabPage tp = (TabPage)element; if (tp.Name == TabControl.KeyPageAllowAddName) continue; } values2.Add(element); } return base.SetItems(editValue, values2.ToArray()); } catch (Exception ex) { System.Windows.Forms.MessageBox.Show(ex.Message); } return base.SetItems(editValue, value); } 




Further added another possibility: tracking the first opening of a tab (the first entry into the tab initialized the tins)



Here one more event is added, the list and the event handler SelectedIndexChanged / Enter / Click:

 public TabControl() { this.Enter += new EventHandler(TabControl_PageEvent); this.Click += new EventHandler(TabControl_PageEvent); this.SelectedIndexChanged += new EventHandler(TabControl_PageEvent); this.Enter += new EventHandler((sender, e) => { CheckAllowUserToAddTab(); }); this.Selecting += new System.Windows.Forms.TabControlCancelEventHandler(TabControl_Selecting); } .... private Dictionary<System.Windows.Forms.TabPage, bool> mLoaded = new Dictionary<System.Windows.Forms.TabPage, bool>(); public delegate void TabPageLoadedEventHandler(TabControl control, System.Windows.Forms.TabPage page); public event TabPageLoadedEventHandler PageLoad; void TabControl_PageEvent(object sender, EventArgs e) { if (this.SelectedTab != null && !mLoaded.ContainsKey(this.SelectedTab)) { mLoaded.Add(this.SelectedTab, true); if (PageLoad != null) PageLoad(this, this.SelectedTab); } } 




Of course, you can refine a lot of things here - for example, in the TabPageControlCollection, overload methods to more accurately determine the tab number (to exclude the "+" tab).

Unfortunately, I have not found a way to implement the functionality of the "+" tab through overloading the component's rendering methods, so do not blame the curved way. IMHO as an alternative to installing DevExpress, for example - will come down.

Source: https://habr.com/ru/post/139027/



All Articles