├── .gitignore ├── LICENSE.htm ├── README.md └── TabControl ├── TabControl ├── Converters.cs ├── Helper.cs ├── Images │ └── newtab.ico ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── TabControl.cs ├── TabEventArgs.cs ├── TabItem.cs ├── TabPanel.cs ├── Themes │ └── Generic.xaml └── Wpf.TabControl.csproj ├── Test ├── App.xaml ├── App.xaml.cs ├── Images │ └── ie.ico ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── Test.csproj ├── Window1.xaml ├── Window1.xaml.cs ├── WindowUsingItemsProperty.xaml ├── WindowUsingItemsProperty.xaml.cs ├── WindowUsingItemsSourceProperty.xaml └── WindowUsingItemsSourceProperty.xaml.cs └── Wpf.TabControl.sln /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | [Rr]eleases/ 14 | x64/ 15 | x86/ 16 | build/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Roslyn cache directories 22 | *.ide/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | #NUNIT 29 | *.VisualState.xml 30 | TestResult.xml 31 | 32 | # Build Results of an ATL Project 33 | [Dd]ebugPS/ 34 | [Rr]eleasePS/ 35 | dlldata.c 36 | 37 | *_i.c 38 | *_p.c 39 | *_i.h 40 | *.ilk 41 | *.meta 42 | *.obj 43 | *.pch 44 | *.pdb 45 | *.pgc 46 | *.pgd 47 | *.rsp 48 | *.sbr 49 | *.tlb 50 | *.tli 51 | *.tlh 52 | *.tmp 53 | *.tmp_proj 54 | *.log 55 | *.vspscc 56 | *.vssscc 57 | .builds 58 | *.pidb 59 | *.svclog 60 | *.scc 61 | 62 | # Chutzpah Test files 63 | _Chutzpah* 64 | 65 | # Visual C++ cache files 66 | ipch/ 67 | *.aps 68 | *.ncb 69 | *.opensdf 70 | *.sdf 71 | *.cachefile 72 | 73 | # Visual Studio profiler 74 | *.psess 75 | *.vsp 76 | *.vspx 77 | 78 | # TFS 2012 Local Workspace 79 | $tf/ 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | *.DotSettings.user 88 | 89 | # JustCode is a .NET coding addin-in 90 | .JustCode 91 | 92 | # TeamCity is a build add-in 93 | _TeamCity* 94 | 95 | # DotCover is a Code Coverage Tool 96 | *.dotCover 97 | 98 | # NCrunch 99 | _NCrunch_* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.[Pp]ublish.xml 127 | *.azurePubxml 128 | # TODO: Comment the next line if you want to checkin your web deploy settings 129 | # but database connection strings (with potential passwords) will be unencrypted 130 | *.pubxml 131 | *.publishproj 132 | 133 | # NuGet Packages 134 | *.nupkg 135 | # The packages folder can be ignored because of Package Restore 136 | **/packages/* 137 | # except build/, which is used as an MSBuild target. 138 | !**/packages/build/ 139 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 140 | #!**/packages/repositories.config 141 | 142 | # Windows Azure Build Output 143 | csx/ 144 | *.build.csdef 145 | 146 | # Windows Store app package directory 147 | AppPackages/ 148 | 149 | # Others 150 | sql/ 151 | *.Cache 152 | ClientBin/ 153 | [Ss]tyle[Cc]op.* 154 | ~$* 155 | *~ 156 | *.dbmdl 157 | *.dbproj.schemaview 158 | *.pfx 159 | *.publishsettings 160 | node_modules/ 161 | 162 | # RIA/Silverlight projects 163 | Generated_Code/ 164 | 165 | # Backup & report files from converting an old project file 166 | # to a newer Visual Studio version. Backup files are not needed, 167 | # because we have git ;-) 168 | _UpgradeReport_Files/ 169 | Backup*/ 170 | UpgradeLog*.XML 171 | UpgradeLog*.htm 172 | 173 | # SQL Server files 174 | *.mdf 175 | *.ldf 176 | 177 | # Business Intelligence projects 178 | *.rdl.data 179 | *.bim.layout 180 | *.bim_*.settings 181 | 182 | # Microsoft Fakes 183 | FakesAssemblies/ 184 | -------------------------------------------------------------------------------- /LICENSE.htm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluentNotepad/TabControl/438271851a4bfbc0a57f8b16ba2212b0ceb59f2e/LICENSE.htm -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tab Control 2 | =========== 3 | ![WpfTabControl](http://www.codeproject.com/KB/WPF/WpfTabControl/TabControl_Default.jpg) 4 | 5 | An Extended **WPF TabControl** 6 | 7 | License 8 | ------- 9 | The original _TabControl_ was created by [@alrh](http://www.codeproject.com/Articles/20860/An-Extended-WPF-TabControl) and was released under **The Code Project Open License (CPOL)**. 10 | 11 | This version, used in _FluentNotepad_, is maintained by [Salvatore Gentile](https://twitter.com/_sgentile). 12 | -------------------------------------------------------------------------------- /TabControl/TabControl/Converters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Text; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using System.Windows.Data; 8 | 9 | namespace Wpf.Controls 10 | { 11 | class InverseBooleanConverter : IValueConverter 12 | { 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 14 | { 15 | bool flag = false; 16 | if (value is bool) 17 | { 18 | flag = (bool)value; 19 | } 20 | else if (value is bool?) 21 | { 22 | bool? nullable = (bool?)value; 23 | flag = nullable.Value; 24 | } 25 | return (flag ? Visibility.Collapsed : Visibility.Visible); 26 | 27 | } 28 | 29 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 30 | { 31 | return ((value is Visibility) && (((Visibility)value) == Visibility.Collapsed)); 32 | 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /TabControl/TabControl/Helper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Media; 4 | using System.Windows.Markup; 5 | using System.Xml; 6 | using System.IO; 7 | 8 | namespace Wpf.Controls 9 | { 10 | class Dimension 11 | { 12 | public double Height; 13 | public double MaxHeight = double.PositiveInfinity; 14 | public double MinHeight; 15 | public double Width; 16 | public double MaxWidth = double.PositiveInfinity; 17 | public double MinWidth; 18 | } 19 | 20 | class Helper 21 | { 22 | /// 23 | /// Find a specific parent object type in the visual tree 24 | /// 25 | public static T FindParentControl(DependencyObject outerDepObj) where T : DependencyObject 26 | { 27 | DependencyObject dObj = VisualTreeHelper.GetParent(outerDepObj); 28 | if (dObj == null) 29 | return null; 30 | 31 | if (dObj is T) 32 | return dObj as T; 33 | 34 | while ((dObj = VisualTreeHelper.GetParent(dObj)) != null) 35 | { 36 | if (dObj is T) 37 | return dObj as T; 38 | } 39 | 40 | return null; 41 | } 42 | 43 | /// 44 | /// Find the Panel for the TabControl 45 | /// 46 | public static TabPanel FindVirtualizingTabPanel(Visual visual) 47 | { 48 | if (visual == null) 49 | return null; 50 | 51 | for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++) 52 | { 53 | Visual child = VisualTreeHelper.GetChild(visual, i) as Visual; 54 | 55 | if (child != null) 56 | { 57 | if (child is TabPanel) 58 | { 59 | object temp = child; 60 | return (TabPanel)temp; 61 | } 62 | 63 | TabPanel panel = FindVirtualizingTabPanel(child); 64 | if (panel != null) 65 | { 66 | object temp = panel; 67 | return (TabPanel)temp; // return the panel up the call stack 68 | } 69 | } 70 | } 71 | return null; 72 | } 73 | 74 | /// 75 | /// Clone an element 76 | /// 77 | /// 78 | /// 79 | public static object CloneElement(object elementToClone) 80 | { 81 | string xaml = XamlWriter.Save(elementToClone); 82 | return XamlReader.Load(new XmlTextReader(new StringReader(xaml))); 83 | } 84 | 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /TabControl/TabControl/Images/newtab.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluentNotepad/TabControl/438271851a4bfbc0a57f8b16ba2212b0ceb59f2e/TabControl/TabControl/Images/newtab.ico -------------------------------------------------------------------------------- /TabControl/TabControl/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("Sid4.TabControl")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("Sid4.TabControl")] 15 | [assembly: AssemblyCopyright("Copyright © 2007")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /TabControl/TabControl/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:2.0.50727.1378 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Wpf.Controls.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Wpf.Controls.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /TabControl/TabControl/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /TabControl/TabControl/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:2.0.50727.1378 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Wpf.Controls.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TabControl/TabControl/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /TabControl/TabControl/TabControl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Specialized; 4 | using System.ComponentModel; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using System.Windows.Controls.Primitives; 8 | using System.Windows.Data; 9 | using System.Windows.Media; 10 | using System.Windows.Input; 11 | 12 | namespace Wpf.Controls 13 | { 14 | [TemplatePart(Name = "PART_DropDown", Type = typeof(ToggleButton))] 15 | [TemplatePart(Name = "PART_RepeatLeft", Type = typeof(RepeatButton))] 16 | [TemplatePart(Name = "PART_RepeatRight", Type = typeof(RepeatButton))] 17 | [TemplatePart(Name = "PART_NewTabButton", Type = typeof(ButtonBase))] 18 | [TemplatePart(Name = "PART_ScrollViewer", Type = typeof(ScrollViewer))] 19 | public class TabControl : System.Windows.Controls.TabControl 20 | { 21 | // public Events 22 | #region Events 23 | 24 | public event EventHandler TabItemAdding; 25 | public event EventHandler TabItemAdded; 26 | public event EventHandler TabItemClosing; 27 | public event EventHandler TabItemClosed; 28 | public event EventHandler NewTabItem; 29 | 30 | #endregion 31 | 32 | // TemplatePart controls 33 | private ToggleButton _toggleButton; 34 | private ButtonBase _addNewButton; 35 | 36 | static TabControl() 37 | { 38 | DefaultStyleKeyProperty.OverrideMetadata(typeof(TabControl), new FrameworkPropertyMetadata(typeof(TabControl))); 39 | TabStripPlacementProperty.AddOwner(typeof(TabControl), new FrameworkPropertyMetadata(Dock.Top, new PropertyChangedCallback(OnTabStripPlacementChanged))); 40 | } 41 | 42 | public TabControl() 43 | { 44 | Loaded += 45 | delegate 46 | { 47 | SetAddNewButtonVisibility(); 48 | SetTabItemsCloseButtonVisibility(); 49 | IsUsingItemsSource = BindingOperations.IsDataBound(this, ItemsSourceProperty); 50 | 51 | if (IsUsingItemsSource && IsFixedSize) 52 | AllowAddNew = AllowDelete = false; 53 | }; 54 | } 55 | 56 | #region Properties 57 | 58 | private bool IsFixedSize 59 | { 60 | get 61 | { 62 | IEnumerable items = GetItems(); 63 | return items as IList == null || (items as IList).IsFixedSize; 64 | } 65 | } 66 | 67 | #endregion 68 | 69 | #region Dependancy properties 70 | 71 | public bool IsUsingItemsSource 72 | { 73 | get { return (bool)GetValue(IsUsingItemsSourceProperty); } 74 | private set { SetValue(IsUsingItemsSourcePropertyKey, value); } 75 | } 76 | 77 | public static readonly DependencyPropertyKey IsUsingItemsSourcePropertyKey = 78 | DependencyProperty.RegisterReadOnly("IsUsingItemsSource", typeof(bool), typeof(TabControl), new UIPropertyMetadata(false)); 79 | 80 | public static readonly DependencyProperty IsUsingItemsSourceProperty = IsUsingItemsSourcePropertyKey.DependencyProperty; 81 | 82 | #region Brushes 83 | 84 | public Brush TabItemNormalBackground 85 | { 86 | get { return (Brush)GetValue(TabItemNormalBackgroundProperty); } 87 | set { SetValue(TabItemNormalBackgroundProperty, value); } 88 | } 89 | public static readonly DependencyProperty TabItemNormalBackgroundProperty = DependencyProperty.Register("TabItemNormalBackground", typeof(Brush), typeof(TabControl), new UIPropertyMetadata(null)); 90 | 91 | public Brush TabItemMouseOverBackground 92 | { 93 | get { return (Brush)GetValue(TabItemMouseOverBackgroundProperty); } 94 | set { SetValue(TabItemMouseOverBackgroundProperty, value); } 95 | } 96 | public static readonly DependencyProperty TabItemMouseOverBackgroundProperty = DependencyProperty.Register("TabItemMouseOverBackground", typeof(Brush), typeof(TabControl), new UIPropertyMetadata(null)); 97 | 98 | public Brush TabItemSelectedBackground 99 | { 100 | get { return (Brush)GetValue(TabItemSelectedBackgroundProperty); } 101 | set { SetValue(TabItemSelectedBackgroundProperty, value); } 102 | } 103 | public static readonly DependencyProperty TabItemSelectedBackgroundProperty = DependencyProperty.Register("TabItemSelectedBackground", typeof(Brush), typeof(TabControl), new UIPropertyMetadata(null)); 104 | 105 | 106 | 107 | 108 | #endregion 109 | 110 | /* 111 | * Based on the whether the ControlTemplate implements the NewTab button and Close Buttons determines the functionality of the AllowAddNew & AllowDelete properties 112 | * If they are in the control template, then the visibility of the AddNew & Header buttons are bound to these properties 113 | * 114 | */ 115 | /// 116 | /// Allow the User to Add New TabItems 117 | /// 118 | public bool AllowAddNew 119 | { 120 | get { return (bool)GetValue(AllowAddNewProperty); } 121 | set { SetValue(AllowAddNewProperty, value); } 122 | } 123 | public static readonly DependencyProperty AllowAddNewProperty = DependencyProperty.Register("AllowAddNew", typeof(bool), typeof(TabControl), 124 | new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnAllowAddNewChanged), OnCoerceAllowAddNewCallback)); 125 | 126 | private static void OnAllowAddNewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 127 | { 128 | ((TabControl)d).SetAddNewButtonVisibility(); 129 | } 130 | 131 | private static object OnCoerceAllowAddNewCallback(DependencyObject d, object basevalue) 132 | { 133 | return ((TabControl)d).OnCoerceAllowAddNewCallback(basevalue); 134 | } 135 | 136 | private object OnCoerceAllowAddNewCallback(object basevalue) 137 | { 138 | if (ItemsSource != null) 139 | { 140 | IList list = ItemsSource as IList; 141 | if (list != null) 142 | { 143 | if (list.IsFixedSize) 144 | return false; 145 | return basevalue; 146 | } 147 | return false; 148 | } 149 | return basevalue; 150 | } 151 | 152 | /// 153 | /// Allow the User to Delete TabItems 154 | /// 155 | public bool AllowDelete 156 | { 157 | get { return (bool)GetValue(AllowDeleteProperty); } 158 | set { SetValue(AllowDeleteProperty, value); } 159 | } 160 | public static readonly DependencyProperty AllowDeleteProperty = DependencyProperty.Register("AllowDelete", typeof(bool), typeof(TabControl), 161 | new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnAllowDeleteChanged), OnCoerceAllowDeleteNewCallback)); 162 | 163 | private static void OnAllowDeleteChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 164 | { 165 | TabControl tc = (TabControl)d; 166 | tc.SetTabItemsCloseButtonVisibility(); 167 | } 168 | 169 | private static object OnCoerceAllowDeleteNewCallback(DependencyObject d, object basevalue) 170 | { 171 | return ((TabControl)d).OnCoerceAllowDeleteCallback(basevalue); 172 | } 173 | private object OnCoerceAllowDeleteCallback(object basevalue) 174 | { 175 | if (ItemsSource != null) 176 | { 177 | IList list = ItemsSource as IList; 178 | if (list != null) 179 | { 180 | if (list.IsFixedSize) 181 | return false; 182 | return basevalue; 183 | } 184 | return false; 185 | } 186 | return basevalue; 187 | } 188 | 189 | /// 190 | /// Set new Header as the current selection 191 | /// 192 | public bool SelectNewTabOnCreate 193 | { 194 | get { return (bool)GetValue(SelectNewTabOnCreateProperty); } 195 | set { SetValue(SelectNewTabOnCreateProperty, value); } 196 | } 197 | public static readonly DependencyProperty SelectNewTabOnCreateProperty = DependencyProperty.Register("SelectNewTabOnCreate", typeof(bool), typeof(TabControl), new UIPropertyMetadata(true)); 198 | 199 | 200 | /// 201 | /// Determines where new TabItems are added to the TabControl 202 | /// 203 | /// 204 | /// Set to true (default) to add all new Tabs to the end of the TabControl 205 | /// Set to False to insert new tabs after the current selection 206 | /// 207 | public bool AddNewTabToEnd 208 | { 209 | get { return (bool)GetValue(AddNewTabToEndProperty); } 210 | set { SetValue(AddNewTabToEndProperty, value); } 211 | } 212 | public static readonly DependencyProperty AddNewTabToEndProperty = DependencyProperty.Register("AddNewTabToEnd", typeof(bool), typeof(TabControl), new UIPropertyMetadata(true)); 213 | 214 | /// 215 | /// defines the Minimum width of a Header 216 | /// 217 | [DefaultValue(20.0)] 218 | [Category("Layout")] 219 | [Description("Gets or Sets the minimum Width Constraint shared by all Items in the Control, individual child elements MinWidth property will overide this property")] 220 | public double TabItemMinWidth 221 | { 222 | get { return (double)GetValue(TabItemMinWidthProperty); } 223 | set { SetValue(TabItemMinWidthProperty, value); } 224 | } 225 | public static readonly DependencyProperty TabItemMinWidthProperty = DependencyProperty.Register("TabItemMinWidth", typeof(double), typeof(TabControl), 226 | new FrameworkPropertyMetadata(20.0, new PropertyChangedCallback(OnMinMaxChanged), CoerceMinWidth)); 227 | 228 | private static object CoerceMinWidth(DependencyObject d, object value) 229 | { 230 | TabControl tc = (TabControl)d; 231 | double newValue = (double)value; 232 | 233 | if (newValue > tc.TabItemMaxWidth) 234 | return tc.TabItemMaxWidth; 235 | 236 | return (newValue > 0 ? newValue : 0); 237 | } 238 | 239 | /// 240 | /// defines the Minimum height of a Header 241 | /// 242 | [DefaultValue(20.0)] 243 | [Category("Layout")] 244 | [Description("Gets or Sets the minimum Height Constraint shared by all Items in the Control, individual child elements MinHeight property will override this value")] 245 | public double TabItemMinHeight 246 | { 247 | get { return (double)GetValue(TabItemMinHeightProperty); } 248 | set { SetValue(TabItemMinHeightProperty, value); } 249 | } 250 | public static readonly DependencyProperty TabItemMinHeightProperty = DependencyProperty.Register("TabItemMinHeight", typeof(double), typeof(TabControl), 251 | new FrameworkPropertyMetadata(20.0, new PropertyChangedCallback(OnMinMaxChanged), CoerceMinHeight)); 252 | 253 | private static object CoerceMinHeight(DependencyObject d, object value) 254 | { 255 | TabControl tc = (TabControl)d; 256 | double newValue = (double)value; 257 | 258 | if (newValue > tc.TabItemMaxHeight) 259 | return tc.TabItemMaxHeight; 260 | 261 | return (newValue > 0 ? newValue : 0); 262 | } 263 | 264 | /// 265 | /// defines the Maximum width of a Header 266 | /// 267 | [DefaultValue(double.PositiveInfinity)] 268 | [Category("Layout")] 269 | [Description("Gets or Sets the maximum width Constraint shared by all Items in the Control, individual child elements MaxWidth property will override this value")] 270 | public double TabItemMaxWidth 271 | { 272 | get { return (double)GetValue(TabItemMaxWidthProperty); } 273 | set { SetValue(TabItemMaxWidthProperty, value); } 274 | } 275 | public static readonly DependencyProperty TabItemMaxWidthProperty = DependencyProperty.Register("TabItemMaxWidth", typeof(double), typeof(TabControl), 276 | new FrameworkPropertyMetadata(double.PositiveInfinity, new PropertyChangedCallback(OnMinMaxChanged), CoerceMaxWidth)); 277 | 278 | private static object CoerceMaxWidth(DependencyObject d, object value) 279 | { 280 | TabControl tc = (TabControl)d; 281 | double newValue = (double)value; 282 | 283 | if (newValue < tc.TabItemMinWidth) 284 | return tc.TabItemMinWidth; 285 | 286 | return newValue; 287 | } 288 | 289 | /// 290 | /// defines the Maximum width of a Header 291 | /// 292 | [DefaultValue(double.PositiveInfinity)] 293 | [Category("Layout")] 294 | [Description("Gets or Sets the maximum height Constraint shared by all Items in the Control, individual child elements MaxHeight property will override this value")] 295 | public double TabItemMaxHeight 296 | { 297 | get { return (double)GetValue(TabItemMaxHeightProperty); } 298 | set { SetValue(TabItemMaxHeightProperty, value); } 299 | } 300 | public static readonly DependencyProperty TabItemMaxHeightProperty = DependencyProperty.Register("TabItemMaxHeight", typeof(double), typeof(TabControl), 301 | new FrameworkPropertyMetadata(double.PositiveInfinity, new PropertyChangedCallback(OnMinMaxChanged), CoerceMaxHeight)); 302 | 303 | private static object CoerceMaxHeight(DependencyObject d, object value) 304 | { 305 | TabControl tc = (TabControl)d; 306 | double newValue = (double)value; 307 | 308 | if (newValue < tc.TabItemMinHeight) 309 | return tc.TabItemMinHeight; 310 | 311 | return newValue; 312 | } 313 | 314 | /// 315 | /// OnMinMaxChanged callback responds to any of the Min/Max dependancy properties changing 316 | /// 317 | /// 318 | /// 319 | private static void OnMinMaxChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 320 | { 321 | TabControl tc = (TabControl)d; 322 | if (tc.Template == null) return; 323 | 324 | foreach (TabItem child in tc.InternalChildren()) 325 | { 326 | if (child != null) 327 | child.Dimension = null; 328 | } 329 | 330 | 331 | // var tabsCount = tc.GetTabsCount(); 332 | // for (int i = 0; i < tabsCount; i++) 333 | // { 334 | // Header ti = tc.GetTabItem(i); 335 | // if (ti != null) 336 | // ti.Dimension = null; 337 | // } 338 | 339 | TabPanel tp = Helper.FindVirtualizingTabPanel(tc); 340 | if (tp != null) 341 | tp.InvalidateMeasure(); 342 | } 343 | 344 | /// 345 | /// OnTabStripPlacementChanged property callback 346 | /// 347 | /// 348 | /// We need to supplement the base implementation with this method as the base method does not work when 349 | /// we are using virtualization in the tabpanel, it only updates visible items 350 | /// 351 | private static void OnTabStripPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 352 | { 353 | TabControl tc = (TabControl)d; 354 | 355 | foreach (TabItem tabItem in tc.InternalChildren()) 356 | { 357 | if (tabItem != null) 358 | { 359 | tabItem.Dimension = null; 360 | tabItem.CoerceValue(System.Windows.Controls.TabItem.TabStripPlacementProperty); 361 | } 362 | } 363 | } 364 | 365 | #endregion 366 | 367 | /* 368 | * Protected override methods 369 | * 370 | */ 371 | 372 | #region Overrides 373 | 374 | /// 375 | /// OnApplyTemplate override 376 | /// 377 | public override void OnApplyTemplate() 378 | { 379 | base.OnApplyTemplate(); 380 | 381 | // set up the event handler for the template parts 382 | _toggleButton = this.Template.FindName("PART_DropDown", this) as ToggleButton; 383 | if (_toggleButton != null) 384 | { 385 | // create a context menu for the togglebutton 386 | ContextMenu cm = new ContextMenu { PlacementTarget = _toggleButton, Placement = PlacementMode.Bottom }; 387 | 388 | // create a binding between the togglebutton's IsChecked Property 389 | // and the Context Menu's IsOpen Property 390 | Binding b = new Binding 391 | { 392 | Source = _toggleButton, 393 | Mode = BindingMode.TwoWay, 394 | Path = new PropertyPath(ToggleButton.IsCheckedProperty) 395 | }; 396 | 397 | cm.SetBinding(ContextMenu.IsOpenProperty, b); 398 | 399 | _toggleButton.ContextMenu = cm; 400 | _toggleButton.Checked += DropdownButton_Checked; 401 | } 402 | 403 | ScrollViewer scrollViewer = this.Template.FindName("PART_ScrollViewer", this) as ScrollViewer; 404 | 405 | // set up event handlers for the RepeatButtons Click event 406 | RepeatButton repeatLeft = this.Template.FindName("PART_RepeatLeft", this) as RepeatButton; 407 | if (repeatLeft != null) 408 | { 409 | repeatLeft.Click += delegate 410 | { 411 | if (scrollViewer != null) 412 | scrollViewer.LineLeft(); 413 | }; 414 | } 415 | 416 | RepeatButton repeatRight = this.Template.FindName("PART_RepeatRight", this) as RepeatButton; 417 | if (repeatRight != null) 418 | { 419 | repeatRight.Click += delegate 420 | { 421 | if (scrollViewer != null) 422 | scrollViewer.LineRight(); 423 | }; 424 | } 425 | 426 | // set up the event handler for the 'New Tab' Button Click event 427 | _addNewButton = this.Template.FindName("PART_NewTabButton", this) as ButtonBase; 428 | if (_addNewButton != null) 429 | _addNewButton.Click += ((sender, routedEventArgs) => AddTabItem()); 430 | } 431 | 432 | /// 433 | /// IsItemItsOwnContainerOverride 434 | /// 435 | /// 436 | /// 437 | protected override bool IsItemItsOwnContainerOverride(object item) 438 | { 439 | return item is TabItem; 440 | } 441 | /// 442 | /// GetContainerForItemOverride 443 | /// 444 | /// 445 | protected override DependencyObject GetContainerForItemOverride() 446 | { 447 | return new TabItem(); 448 | } 449 | 450 | 451 | protected override void OnPreviewKeyDown(KeyEventArgs e) 452 | { 453 | var tabsCount = GetTabsCount(); 454 | if (tabsCount == 0) 455 | return; 456 | 457 | TabItem ti = null; 458 | 459 | switch (e.Key) 460 | { 461 | case Key.Home: 462 | ti = GetTabItem(0); 463 | break; 464 | 465 | case Key.End: 466 | ti = GetTabItem(tabsCount - 1); 467 | break; 468 | 469 | case Key.Tab: 470 | if (e.KeyboardDevice.Modifiers == ModifierKeys.Control) 471 | { 472 | var index = SelectedIndex; 473 | var direction = e.KeyboardDevice.Modifiers == ModifierKeys.Shift ? -1 : 1; 474 | 475 | while (true) 476 | { 477 | index += direction; 478 | if (index < 0) 479 | index = tabsCount - 1; 480 | else if (index > tabsCount - 1) 481 | index = 0; 482 | 483 | FrameworkElement ui = GetTabItem(index); 484 | if (ui != null) 485 | { 486 | if (ui.Visibility == Visibility.Visible && ui.IsEnabled) 487 | { 488 | ti = GetTabItem(index); 489 | break; 490 | } 491 | } 492 | } 493 | } 494 | break; 495 | } 496 | 497 | TabPanel panel = Helper.FindVirtualizingTabPanel(this); 498 | if (panel != null && ti != null) 499 | { 500 | panel.MakeVisible(ti, Rect.Empty); 501 | SelectedItem = ti; 502 | 503 | e.Handled = ti.Focus(); 504 | } 505 | base.OnPreviewKeyDown(e); 506 | } 507 | 508 | protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 509 | { 510 | base.OnItemsChanged(e); 511 | 512 | if (e.Action == NotifyCollectionChangedAction.Add && SelectNewTabOnCreate) 513 | { 514 | TabItem tabItem = (TabItem)this.ItemContainerGenerator.ContainerFromItem(e.NewItems[e.NewItems.Count - 1]); 515 | SelectedItem = tabItem; 516 | 517 | TabPanel itemsHost = Helper.FindVirtualizingTabPanel(this); 518 | if (itemsHost != null) 519 | itemsHost.MakeVisible(tabItem, Rect.Empty); 520 | 521 | tabItem.Focus(); 522 | } 523 | } 524 | 525 | protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) 526 | { 527 | base.OnItemsSourceChanged(oldValue, newValue); 528 | 529 | IsUsingItemsSource = newValue != null; 530 | if (IsFixedSize) 531 | AllowAddNew = AllowDelete = false; 532 | 533 | SetAddNewButtonVisibility(); 534 | SetTabItemsCloseButtonVisibility(); 535 | } 536 | 537 | #endregion 538 | 539 | /// 540 | /// Handle the ToggleButton Checked event that displays a context menu of Header Headers 541 | /// 542 | /// 543 | /// 544 | void DropdownButton_Checked(object sender, RoutedEventArgs e) 545 | { 546 | if (_toggleButton == null) return; 547 | 548 | _toggleButton.ContextMenu.Items.Clear(); 549 | _toggleButton.ContextMenu.Placement = TabStripPlacement == Dock.Bottom ? PlacementMode.Top : PlacementMode.Bottom; 550 | 551 | int index = 0; 552 | foreach (TabItem tabItem in this.InternalChildren()) 553 | { 554 | if (tabItem != null) 555 | { 556 | var header = Helper.CloneElement(tabItem.Header); 557 | var icon = tabItem.Icon == null ? null : Helper.CloneElement(tabItem.Icon); 558 | 559 | var mi = new MenuItem { Header = header, Icon = icon, Tag = index++.ToString() }; 560 | mi.Click += ContextMenuItem_Click; 561 | 562 | _toggleButton.ContextMenu.Items.Add(mi); 563 | } 564 | } 565 | } 566 | 567 | /// 568 | /// Handle the MenuItem's Click event 569 | /// 570 | /// 571 | /// 572 | void ContextMenuItem_Click(object sender, RoutedEventArgs e) 573 | { 574 | MenuItem mi = sender as MenuItem; 575 | if (mi == null) return; 576 | 577 | int index; 578 | // get the index of the Header from the manuitems Tag property 579 | bool b = int.TryParse(mi.Tag.ToString(), out index); 580 | 581 | if (b) 582 | { 583 | TabItem tabItem = GetTabItem(index); 584 | if (tabItem != null) 585 | { 586 | TabPanel itemsHost = Helper.FindVirtualizingTabPanel(this); 587 | if (itemsHost != null) 588 | itemsHost.MakeVisible(tabItem, Rect.Empty); 589 | 590 | tabItem.Focus(); 591 | } 592 | } 593 | } 594 | 595 | /// 596 | /// Add a new Header 597 | /// 598 | public void AddTabItem() 599 | { 600 | if (IsFixedSize) 601 | throw new InvalidOperationException("ItemsSource is Fixed Size"); 602 | 603 | int i = this.SelectedIndex; 604 | 605 | // give an opertunity to cancel the adding of the tabitem 606 | CancelEventArgs c = new CancelEventArgs(); 607 | if (TabItemAdding != null) 608 | TabItemAdding(this, c); 609 | 610 | if (c.Cancel) 611 | return; 612 | 613 | TabItem tabItem; 614 | 615 | // Using ItemsSource property 616 | if (ItemsSource != null) 617 | { 618 | IList list = (IList)ItemsSource; 619 | NewTabItemEventArgs n = new NewTabItemEventArgs(); 620 | if (NewTabItem == null) 621 | throw new InvalidOperationException("You must implement the NewTabItem event to supply the item to be added to the tab control."); 622 | 623 | NewTabItem(this, n); 624 | if (n.Content == null) 625 | return; 626 | 627 | if (i == -1 || i == list.Count - 1 || AddNewTabToEnd) 628 | list.Add(n.Content); 629 | else 630 | list.Insert(++i, n.Content); 631 | 632 | tabItem = (TabItem)this.ItemContainerGenerator.ContainerFromItem(n.Content); 633 | } 634 | else 635 | { 636 | // Using Items Property 637 | tabItem = new TabItem { Header = "New Tab" }; 638 | 639 | if (i == -1 || i == this.Items.Count - 1 || AddNewTabToEnd) 640 | this.Items.Add(tabItem); 641 | else 642 | this.Items.Insert(++i, tabItem); 643 | } 644 | 645 | if (TabItemAdded != null) 646 | TabItemAdded(this, new TabItemEventArgs(tabItem)); 647 | } 648 | 649 | /// 650 | /// Called by a child Header that wants to remove itself by clicking on the close button 651 | /// 652 | /// 653 | public void RemoveTabItem(TabItem tabItem) 654 | { 655 | if (IsFixedSize) 656 | throw new InvalidOperationException("ItemsSource is Fixed Size"); 657 | 658 | // gives an opertunity to cancel the removal of the tabitem 659 | var c = new TabItemCancelEventArgs(tabItem); 660 | if (TabItemClosing != null) 661 | TabItemClosing(tabItem, c); 662 | 663 | if (c.Cancel) 664 | return; 665 | 666 | if (ItemsSource != null) 667 | { 668 | var list = ItemsSource as IList; 669 | object listItem = ItemContainerGenerator.ItemFromContainer(tabItem); 670 | if (listItem != null && list != null) 671 | list.Remove(listItem); 672 | } 673 | else 674 | this.Items.Remove(tabItem); 675 | 676 | if (TabItemClosed != null) 677 | TabItemClosed(this, new TabItemEventArgs(tabItem)); 678 | } 679 | 680 | private void SetAddNewButtonVisibility() 681 | { 682 | if (this.Template == null) 683 | return; 684 | 685 | ButtonBase button = this.Template.FindName("PART_NewTabButton", this) as ButtonBase; 686 | if (button == null) return; 687 | 688 | if (IsFixedSize) 689 | button.Visibility = Visibility.Collapsed; 690 | else 691 | button.Visibility = AllowAddNew 692 | ? Visibility.Visible 693 | : Visibility.Collapsed; 694 | } 695 | 696 | private void SetTabItemsCloseButtonVisibility() 697 | { 698 | bool isFixedSize = IsFixedSize; 699 | 700 | var tabsCount = GetTabsCount(); 701 | for (int i = 0; i < tabsCount; i++) 702 | { 703 | TabItem ti = GetTabItem(i); 704 | if (ti != null) 705 | ti.AllowDelete = !isFixedSize && this.AllowDelete; 706 | } 707 | } 708 | 709 | internal IEnumerable GetItems() 710 | { 711 | if (IsUsingItemsSource) 712 | return ItemsSource; 713 | return Items; 714 | } 715 | 716 | internal int GetTabsCount() 717 | { 718 | if (BindingOperations.IsDataBound(this, ItemsSourceProperty)) 719 | { 720 | IList list = ItemsSource as IList; 721 | if (list != null) 722 | return list.Count; 723 | 724 | // ItemsSource is only an IEnumerable 725 | int i = 0; 726 | IEnumerator enumerator = ItemsSource.GetEnumerator(); 727 | while (enumerator.MoveNext()) 728 | i++; 729 | return i; 730 | } 731 | 732 | if (Items != null) 733 | return Items.Count; 734 | 735 | return 0; 736 | } 737 | 738 | internal TabItem GetTabItem(int index) 739 | { 740 | if (BindingOperations.IsDataBound(this, ItemsSourceProperty)) 741 | { 742 | IList list = ItemsSource as IList; 743 | if (list != null) 744 | return this.ItemContainerGenerator.ContainerFromItem(list[index]) as TabItem; 745 | 746 | // ItemsSource is at least an IEnumerable 747 | int i = 0; 748 | IEnumerator enumerator = ItemsSource.GetEnumerator(); 749 | while (enumerator.MoveNext()) 750 | { 751 | if (i == index) 752 | return this.ItemContainerGenerator.ContainerFromItem(enumerator.Current) as TabItem; 753 | i++; 754 | } 755 | return null; 756 | } 757 | return Items[index] as TabItem; 758 | } 759 | 760 | private IEnumerable InternalChildren() 761 | { 762 | IEnumerator enumerator = GetItems().GetEnumerator(); 763 | while (enumerator.MoveNext()) 764 | { 765 | if (enumerator.Current is TabItem) 766 | yield return enumerator.Current; 767 | else 768 | yield return this.ItemContainerGenerator.ContainerFromItem(enumerator.Current) as TabItem; 769 | } 770 | } 771 | } 772 | } 773 | -------------------------------------------------------------------------------- /TabControl/TabControl/TabEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.ComponentModel; 5 | 6 | namespace Wpf.Controls 7 | { 8 | public class TabItemEventArgs : EventArgs 9 | { 10 | public TabItem TabItem { get; private set; } 11 | 12 | public TabItemEventArgs(TabItem item) 13 | { 14 | TabItem = item; 15 | } 16 | } 17 | public class NewTabItemEventArgs : EventArgs 18 | { 19 | /// 20 | /// The object to be used as the Content for the new TabItem 21 | /// 22 | public object Content { get; set; } 23 | } 24 | 25 | public class TabItemCancelEventArgs : CancelEventArgs 26 | { 27 | public TabItem TabItem { get; private set; } 28 | 29 | public TabItemCancelEventArgs(TabItem item) 30 | { 31 | TabItem = item; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /TabControl/TabControl/TabItem.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls.Primitives; 3 | using System.Windows.Controls; 4 | 5 | namespace Wpf.Controls 6 | { 7 | [TemplatePart(Name = "PART_CloseButton", Type = typeof(ButtonBase))] 8 | public class TabItem : System.Windows.Controls.TabItem 9 | { 10 | static TabItem() 11 | { 12 | DefaultStyleKeyProperty.OverrideMetadata(typeof(Wpf.Controls.TabItem), new FrameworkPropertyMetadata(typeof(Wpf.Controls.TabItem))); 13 | } 14 | 15 | /// 16 | /// Provides a place to display an Icon on the Header and on the DropDown Context Menu 17 | /// 18 | public object Icon 19 | { 20 | get { return (object)GetValue(IconProperty); } 21 | set { SetValue(IconProperty, value); } 22 | } 23 | public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(object), typeof(TabItem), new UIPropertyMetadata(null)); 24 | 25 | /// 26 | /// Allow the Header to be Deleted by the end user 27 | /// 28 | public bool AllowDelete 29 | { 30 | get { return (bool)GetValue(AllowDeleteProperty); } 31 | set { SetValue(AllowDeleteProperty, value); } 32 | } 33 | public static readonly DependencyProperty AllowDeleteProperty = DependencyProperty.Register("AllowDelete", typeof(bool), typeof(TabItem), new UIPropertyMetadata(true)); 34 | 35 | /// 36 | /// OnApplyTemplate override 37 | /// 38 | public override void OnApplyTemplate() 39 | { 40 | base.OnApplyTemplate(); 41 | 42 | // wire up the CloseButton's Click event if the button exists 43 | ButtonBase button = this.Template.FindName("PART_CloseButton", this) as ButtonBase; 44 | if (button != null) 45 | { 46 | button.Click += delegate 47 | { 48 | // get the parent tabcontrol 49 | TabControl tc = Helper.FindParentControl(this); 50 | if (tc == null) return; 51 | 52 | // remove this tabitem from the parent tabcontrol 53 | tc.RemoveTabItem(this); 54 | }; 55 | } 56 | } 57 | 58 | /// 59 | /// Used by the TabPanel for sizing 60 | /// 61 | internal Dimension Dimension { get; set; } 62 | 63 | /// 64 | /// OnMouseEnter, Create and Display a Tooltip 65 | /// 66 | /// 67 | protected override void OnMouseEnter(System.Windows.Input.MouseEventArgs e) 68 | { 69 | base.OnMouseEnter(e); 70 | 71 | this.ToolTip = Helper.CloneElement(Header); 72 | e.Handled = true; 73 | } 74 | 75 | /// 76 | /// OnMouseLeave, remove the tooltip 77 | /// 78 | /// 79 | protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e) 80 | { 81 | base.OnMouseLeave(e); 82 | 83 | this.ToolTip = null; 84 | e.Handled = true; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /TabControl/TabControl/TabPanel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Windows.Controls; 5 | using System.Windows; 6 | using System.ComponentModel; 7 | using System.Windows.Controls.Primitives; 8 | using System.Windows.Media; 9 | 10 | namespace Wpf.Controls 11 | { 12 | /// 13 | /// TabPanel 14 | /// 15 | public class TabPanel : Panel, IScrollInfo 16 | { 17 | private int _maxVisibleItems; 18 | // private double _maxChildWidthOrHeight; 19 | private readonly List _childRects; 20 | private Dock _tabStripPlacement; 21 | 22 | // scrolling 23 | private readonly TranslateTransform _translateTransform = new TranslateTransform(); 24 | private Size _extent = new Size(0, 0); 25 | private Size _oldExtent = new Size(0, 0); 26 | private Size _viewPort = new Size(0, 0); 27 | private Size _lastSize = new Size(0, 0); 28 | private Point _offset = new Point(0, 0); 29 | private ScrollViewer _scrollOwner; 30 | 31 | private int _firstVisibleIndex; 32 | // private int _childCount; 33 | 34 | public TabPanel() 35 | { 36 | _childRects = new List(4); 37 | base.RenderTransform = _translateTransform; 38 | } 39 | 40 | #region CLR Properties 41 | private Dock TabStripPlacement 42 | { 43 | get 44 | { 45 | Dock dock = Dock.Top; 46 | TabControl templatedParent = base.TemplatedParent as TabControl; 47 | if (templatedParent != null) 48 | { 49 | dock = templatedParent.TabStripPlacement; 50 | 51 | } 52 | return dock; 53 | } 54 | } 55 | private double MinimumChildWidth 56 | { 57 | get 58 | { 59 | TabControl templatedParent = base.TemplatedParent as TabControl; 60 | if (templatedParent != null) 61 | return templatedParent.TabItemMinWidth; 62 | return 0; 63 | } 64 | } 65 | private double MinimumChildHeight 66 | { 67 | get 68 | { 69 | TabControl templatedParent = base.TemplatedParent as TabControl; 70 | if (templatedParent != null) 71 | return templatedParent.TabItemMinHeight; 72 | return 0; 73 | } 74 | } 75 | private double MaximumChildWidth 76 | { 77 | get 78 | { 79 | TabControl templatedParent = base.TemplatedParent as TabControl; 80 | if (templatedParent != null) 81 | return templatedParent.TabItemMaxWidth; 82 | return double.PositiveInfinity; 83 | } 84 | } 85 | private double MaximumChildHeight 86 | { 87 | get 88 | { 89 | TabControl templatedParent = base.TemplatedParent as TabControl; 90 | if (templatedParent != null) 91 | return templatedParent.TabItemMaxHeight; 92 | return double.PositiveInfinity; 93 | } 94 | } 95 | 96 | private int FirstVisibleIndex 97 | { 98 | get { return _firstVisibleIndex; } 99 | set 100 | { 101 | if (_firstVisibleIndex == value) 102 | return; 103 | 104 | if (value < 0) 105 | { 106 | _firstVisibleIndex = 0; 107 | return; 108 | } 109 | 110 | _firstVisibleIndex = value; 111 | if (LastVisibleIndex > InternalChildren.Count - 1) 112 | FirstVisibleIndex--; 113 | } 114 | } 115 | 116 | private int LastVisibleIndex 117 | { 118 | get { return _firstVisibleIndex + _maxVisibleItems - 1; } 119 | } 120 | #endregion 121 | 122 | #region Dependancy Properties 123 | 124 | /// 125 | /// CanScrollLeftOrUp Dependancy Property 126 | /// 127 | [Browsable(false)] 128 | internal bool CanScrollLeftOrUp 129 | { 130 | get { return (bool)GetValue(CanScrollLeftOrUpProperty); } 131 | set { SetValue(CanScrollLeftOrUpProperty, value); } 132 | } 133 | internal static readonly DependencyProperty CanScrollLeftOrUpProperty = DependencyProperty.Register("CanScrollLeftOrUp", typeof(bool), typeof(TabPanel), new UIPropertyMetadata(false)); 134 | 135 | /// 136 | /// CanScrollRightOrDown Dependancy Property 137 | /// 138 | [Browsable(false)] 139 | internal bool CanScrollRightOrDown 140 | { 141 | get { return (bool)GetValue(CanScrollRightOrDownProperty); } 142 | set { SetValue(CanScrollRightOrDownProperty, value); } 143 | } 144 | internal static readonly DependencyProperty CanScrollRightOrDownProperty = DependencyProperty.Register("CanScrollRightOrDown", typeof(bool), typeof(TabPanel), new UIPropertyMetadata(false)); 145 | 146 | #endregion 147 | 148 | #region MeasureOverride 149 | /// 150 | /// Measure Override 151 | /// 152 | protected override Size MeasureOverride(Size availableSize) 153 | { 154 | _viewPort = availableSize; 155 | _tabStripPlacement = TabStripPlacement; 156 | 157 | switch (_tabStripPlacement) 158 | { 159 | case Dock.Top: 160 | case Dock.Bottom: 161 | return MeasureHorizontal(availableSize); 162 | 163 | case Dock.Left: 164 | case Dock.Right: 165 | return MeasureVertical(availableSize); 166 | } 167 | 168 | return new Size(); 169 | } 170 | #endregion 171 | 172 | #region MeasureHorizontal 173 | /// 174 | /// Measure the tab items for docking at the top or bottom 175 | /// 176 | private Size MeasureHorizontal(Size availableSize) 177 | { 178 | double maxChildWidthOrHeight = 0d; 179 | int childCount = InternalChildren.Count; 180 | EnsureChildRects(); 181 | 182 | double extentWidth = 0d; 183 | double[] widths = new double[childCount]; // stores the widths of the items for use in the arrange pass 184 | 185 | for (int i = 0; i < childCount; i++) 186 | { 187 | TabItem tabItem = InternalChildren[i] as TabItem; 188 | if (tabItem == null) return new Size(); 189 | 190 | SetDimensions(tabItem); 191 | 192 | tabItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); 193 | 194 | ClearDimensions(tabItem); 195 | 196 | // calculate the maximum child height 197 | maxChildWidthOrHeight = Math.Max(maxChildWidthOrHeight, Math.Ceiling(tabItem.DesiredSize.Height)); 198 | 199 | // calculate the child width while respecting the Maximum & Minimum width constraints 200 | widths[i] = Math.Min(MaximumChildWidth, Math.Max(MinimumChildWidth, Math.Ceiling(tabItem.DesiredSize.Width))); 201 | 202 | // determines how much horizontal space we require 203 | extentWidth += widths[i]; 204 | } 205 | maxChildWidthOrHeight = Math.Max(MinimumChildHeight, Math.Min(MaximumChildHeight, maxChildWidthOrHeight)); // observe the constraints 206 | _extent = new Size(extentWidth, maxChildWidthOrHeight); 207 | 208 | bool flag = false; 209 | // 1). all the children fit into the available space using there desired widths 210 | if (extentWidth <= availableSize.Width) 211 | { 212 | _maxVisibleItems = childCount; 213 | FirstVisibleIndex = 0; 214 | 215 | double left = 0; 216 | for (int i = 0; i < childCount; i++) 217 | { 218 | _childRects[i] = new Rect(left, 0, widths[i], maxChildWidthOrHeight); 219 | left += widths[i]; 220 | 221 | FrameworkElement child = InternalChildren[i] as FrameworkElement; 222 | if (child != null) child.Measure(new Size(widths[i], maxChildWidthOrHeight)); 223 | } 224 | 225 | CanScrollLeftOrUp = false; 226 | CanScrollRightOrDown = false; 227 | 228 | flag = true; 229 | } 230 | 231 | // 2). all the children fit in the available space if we reduce their widths to a uniform value 232 | // while staying within the MinimumChildWidth and MaximumChildWidth constraints 233 | if (!flag) 234 | { 235 | // make sure the width is not greater than the MaximumChildWidth constraints 236 | double targetWidth = Math.Min(MaximumChildWidth, availableSize.Width / childCount); 237 | 238 | // target width applies now if whether we can fit all items in the available space or whether we are scrolling 239 | if (targetWidth >= MinimumChildWidth) 240 | { 241 | _maxVisibleItems = childCount; 242 | FirstVisibleIndex = 0; 243 | 244 | extentWidth = 0; 245 | double left = 0; 246 | 247 | for (int i = 0; i < childCount; i++) 248 | { 249 | extentWidth += targetWidth; 250 | widths[i] = targetWidth; 251 | _childRects[i] = new Rect(left, 0, widths[i], maxChildWidthOrHeight); 252 | left += widths[i]; 253 | 254 | FrameworkElement child = InternalChildren[i] as FrameworkElement; 255 | if (child != null) child.Measure(new Size(widths[i], maxChildWidthOrHeight)); 256 | } 257 | _extent = new Size(extentWidth, maxChildWidthOrHeight); 258 | 259 | flag = true; 260 | 261 | CanScrollLeftOrUp = false; 262 | CanScrollRightOrDown = false; 263 | } 264 | } 265 | 266 | // 3) we can not fit all the children in the viewport, so now we will enable scrolling/virtualizing items 267 | if (!flag) 268 | { 269 | _maxVisibleItems = (int)Math.Floor(_viewPort.Width / MinimumChildWidth); // calculate how many visible children we can show at once 270 | if (_maxVisibleItems == 0) 271 | _maxVisibleItems = 1; 272 | 273 | double targetWidth = availableSize.Width / _maxVisibleItems; // calculate the new target width 274 | FirstVisibleIndex = _firstVisibleIndex; 275 | 276 | extentWidth = 0; 277 | double left = 0; 278 | for (int i = 0; i < childCount; i++) 279 | { 280 | extentWidth += targetWidth; 281 | widths[i] = targetWidth; 282 | 283 | _childRects[i] = new Rect(left, 0, widths[i], maxChildWidthOrHeight); 284 | left += widths[i]; 285 | 286 | 287 | FrameworkElement child = InternalChildren[i] as FrameworkElement; 288 | if (child != null) child.Measure(new Size(widths[i], maxChildWidthOrHeight)); 289 | } 290 | _extent = new Size(extentWidth, maxChildWidthOrHeight); 291 | 292 | CanScrollLeftOrUp = LastVisibleIndex < childCount - 1; 293 | CanScrollRightOrDown = FirstVisibleIndex > 0; 294 | } 295 | 296 | return new Size(double.IsInfinity(availableSize.Width) ? _extent.Width : availableSize.Width, maxChildWidthOrHeight); 297 | } 298 | 299 | #endregion 300 | 301 | #region MeasureVertical 302 | 303 | /// 304 | /// Measure the tab items for docking at the left or right 305 | /// 306 | private Size MeasureVertical(Size availableSize) 307 | { 308 | int childCount = InternalChildren.Count; 309 | double maxChildWidthOrHeight = 0d; 310 | 311 | EnsureChildRects(); 312 | 313 | double extentHeight = 0d; 314 | double[] heights = new double[childCount]; 315 | 316 | // we will first measure all the children with unlimited space to get their desired sizes 317 | // this will also get us the height required for all TabItems 318 | for (int i = 0; i < childCount; i++) 319 | { 320 | TabItem tabItem = InternalChildren[i] as TabItem; 321 | if (tabItem == null) return new Size(); 322 | 323 | SetDimensions(tabItem); 324 | 325 | tabItem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); 326 | 327 | ClearDimensions(tabItem); 328 | 329 | // calculate the maximum child width 330 | maxChildWidthOrHeight = Math.Max(maxChildWidthOrHeight, Math.Ceiling(tabItem.DesiredSize.Width)); 331 | 332 | // calculate the child width while respecting the Maximum & Minimum width constraints 333 | heights[i] = Math.Min(MaximumChildHeight, Math.Max(MinimumChildHeight, Math.Ceiling(tabItem.DesiredSize.Height))); 334 | 335 | // determines how much horizontal space we require 336 | extentHeight += heights[i]; 337 | } 338 | maxChildWidthOrHeight = Math.Max(MinimumChildWidth, Math.Min(MaximumChildWidth, maxChildWidthOrHeight)); // observe the constraints 339 | _extent = new Size(maxChildWidthOrHeight, extentHeight); 340 | 341 | bool flag = false; 342 | // 1). all the children fit into the available space using there desired widths 343 | if (extentHeight <= availableSize.Height) 344 | { 345 | _maxVisibleItems = childCount; 346 | FirstVisibleIndex = 0; 347 | 348 | double top = 0; 349 | for (int i = 0; i < childCount; i++) 350 | { 351 | _childRects[i] = new Rect(0, top, maxChildWidthOrHeight, heights[i]); 352 | top += heights[i]; 353 | 354 | FrameworkElement child = InternalChildren[i] as FrameworkElement; 355 | if (child != null) child.Measure(new Size(maxChildWidthOrHeight, heights[i])); 356 | } 357 | 358 | CanScrollLeftOrUp = false; 359 | CanScrollRightOrDown = false; 360 | 361 | flag = true; 362 | } 363 | 364 | // 2). all the children fit in the available space if we reduce their widths to a uniform value 365 | // while staying within the MinimumChildWidth and MaximumChildWidth constraints 366 | if (!flag) 367 | { 368 | // make sure the width is not greater than the MaximumChildWidth constraints 369 | double targetHeight = Math.Min(MaximumChildHeight, availableSize.Height / childCount); 370 | 371 | // target width applies now if whether we can fit all items in the available space or whether we are scrolling 372 | if (targetHeight >= MinimumChildHeight) 373 | { 374 | _maxVisibleItems = childCount; 375 | FirstVisibleIndex = 0; 376 | 377 | extentHeight = 0; 378 | double top = 0; 379 | 380 | for (int i = 0; i < childCount; i++) 381 | { 382 | extentHeight += targetHeight; 383 | heights[i] = targetHeight; 384 | _childRects[i] = new Rect(0, top, maxChildWidthOrHeight, heights[i]); 385 | top += heights[i]; 386 | 387 | FrameworkElement child = InternalChildren[i] as FrameworkElement; 388 | if (child != null) child.Measure(new Size(maxChildWidthOrHeight, heights[i])); 389 | } 390 | _extent = new Size(maxChildWidthOrHeight, extentHeight); 391 | 392 | flag = true; 393 | 394 | CanScrollLeftOrUp = false; 395 | CanScrollRightOrDown = false; 396 | } 397 | } 398 | 399 | // 3) we can not fit all the children in the viewport, so now we will enable scrolling/virtualizing items 400 | if (!flag) 401 | { 402 | _maxVisibleItems = (int)Math.Floor(_viewPort.Height / MinimumChildHeight); // calculate how many visible children we can show at once 403 | double targetHeight = availableSize.Height / _maxVisibleItems; // calculate the new target width 404 | FirstVisibleIndex = _firstVisibleIndex; 405 | 406 | extentHeight = 0; 407 | double top = 0; 408 | for (int i = 0; i < childCount; i++) 409 | { 410 | extentHeight += targetHeight; 411 | heights[i] = targetHeight; 412 | _childRects[i] = new Rect(0, top, maxChildWidthOrHeight, heights[i]); 413 | top += heights[i]; 414 | 415 | FrameworkElement child = InternalChildren[i] as FrameworkElement; 416 | if (child != null) child.Measure(new Size(maxChildWidthOrHeight, heights[i])); 417 | } 418 | _extent = new Size(maxChildWidthOrHeight, extentHeight); 419 | 420 | CanScrollLeftOrUp = LastVisibleIndex < childCount - 1; 421 | CanScrollRightOrDown = FirstVisibleIndex > 0; 422 | } 423 | 424 | return new Size(maxChildWidthOrHeight, double.IsInfinity(availableSize.Height) ? _extent.Height : availableSize.Height); 425 | 426 | } 427 | #endregion 428 | 429 | #region ArrangeOverride 430 | 431 | /// 432 | /// Arrange Override 433 | /// 434 | protected override Size ArrangeOverride(Size finalSize) 435 | { 436 | // monitors changes to the ScrollViewer extent value 437 | if (_oldExtent != _extent) 438 | { 439 | _oldExtent = _extent; 440 | if (_scrollOwner != null) 441 | _scrollOwner.InvalidateScrollInfo(); 442 | } 443 | 444 | // monitors changes to the parent container size, (ie window resizes) 445 | if (finalSize != _lastSize) 446 | { 447 | _lastSize = finalSize; 448 | if (_scrollOwner != null) 449 | _scrollOwner.InvalidateScrollInfo(); 450 | } 451 | 452 | // monitor scrolling being removed 453 | bool invalidateMeasure = false; 454 | if (_extent.Width <= _viewPort.Width && _offset.X > 0) 455 | { 456 | _offset.X = 0; 457 | _translateTransform.X = 0; 458 | 459 | if (_scrollOwner != null) 460 | _scrollOwner.InvalidateScrollInfo(); 461 | 462 | invalidateMeasure = true; 463 | } 464 | if (_extent.Height <= _viewPort.Height && _offset.Y > 0) 465 | { 466 | _offset.Y = 0; 467 | _translateTransform.Y = 0; 468 | 469 | if (_scrollOwner != null) 470 | _scrollOwner.InvalidateScrollInfo(); 471 | 472 | invalidateMeasure = true; 473 | } 474 | if (invalidateMeasure) 475 | InvalidateMeasure(); 476 | 477 | 478 | 479 | // arrange the children 480 | for (var i = 0; i < InternalChildren.Count; i++) 481 | { 482 | InternalChildren[i].Arrange(_childRects[i]); 483 | } 484 | 485 | // we need these lines as when the Scroll Buttons get Shown/Hidden, 486 | // the _offset value gets out of line, this will ensure that our scroll position stays in line 487 | if (InternalChildren.Count > 0) 488 | { 489 | _offset = _childRects[FirstVisibleIndex].TopLeft; 490 | _translateTransform.X = -_offset.X; 491 | _translateTransform.Y = -_offset.Y; 492 | } 493 | 494 | return finalSize; 495 | } 496 | #endregion 497 | 498 | private void EnsureChildRects() 499 | { 500 | while (InternalChildren.Count > _childRects.Count) 501 | _childRects.Add(new Rect()); 502 | } 503 | 504 | #region IScrollInfo Members 505 | 506 | public bool CanHorizontallyScroll { get; set; } 507 | 508 | public bool CanVerticallyScroll { get; set; } 509 | 510 | public double ExtentHeight 511 | { 512 | get { return _extent.Height; } 513 | } 514 | 515 | public double ExtentWidth 516 | { 517 | get { return _extent.Width; } 518 | } 519 | 520 | public double HorizontalOffset 521 | { 522 | get { return _offset.X; } 523 | } 524 | 525 | public void LineDown() 526 | { 527 | LineRight(); 528 | } 529 | 530 | public void LineLeft() 531 | { 532 | // this works because we can guarantee that when we are in scroll mode, 533 | // there will be children, and they will all be of equal size 534 | FirstVisibleIndex++; 535 | 536 | if (_tabStripPlacement == Dock.Top || _tabStripPlacement == Dock.Bottom) 537 | SetHorizontalOffset(HorizontalOffset + _childRects[0].Width); 538 | else 539 | SetVerticalOffset(HorizontalOffset + _childRects[0].Height); 540 | } 541 | 542 | public void LineRight() 543 | { 544 | FirstVisibleIndex--; 545 | 546 | if (_tabStripPlacement == Dock.Top || _tabStripPlacement == Dock.Bottom) 547 | SetHorizontalOffset(HorizontalOffset - _childRects[0].Width); 548 | else 549 | SetVerticalOffset(HorizontalOffset - _childRects[0].Height); 550 | } 551 | 552 | public void LineUp() 553 | { 554 | LineLeft(); 555 | } 556 | 557 | public Rect MakeVisible(Visual visual, Rect rectangle) 558 | { 559 | InvalidateMeasure(); 560 | UpdateLayout(); 561 | 562 | TabControl ic = ItemsControl.GetItemsOwner(this) as TabControl; 563 | if (ic == null) return Rect.Empty; 564 | 565 | int index = -1; 566 | var tabsCount = ic.GetTabsCount(); 567 | for (int i = 0; i < tabsCount; i++) 568 | { 569 | if (visual.Equals(ic.GetTabItem(i))) 570 | { 571 | index = i; 572 | break; 573 | } 574 | } 575 | if (index > -1) 576 | { 577 | if (index < FirstVisibleIndex) 578 | FirstVisibleIndex = index; 579 | else if (index > LastVisibleIndex) 580 | { 581 | while (index > LastVisibleIndex) 582 | FirstVisibleIndex++; 583 | } 584 | 585 | InvalidateArrange(); 586 | } 587 | 588 | return Rect.Empty; 589 | } 590 | 591 | public void MouseWheelDown() 592 | { 593 | LineDown(); 594 | } 595 | 596 | public void MouseWheelLeft() 597 | { 598 | LineLeft(); 599 | } 600 | 601 | public void MouseWheelRight() 602 | { 603 | LineRight(); 604 | } 605 | 606 | public void MouseWheelUp() 607 | { 608 | LineUp(); 609 | } 610 | 611 | public void PageDown() 612 | { 613 | throw new NotImplementedException(); 614 | } 615 | 616 | public void PageLeft() 617 | { 618 | throw new NotImplementedException(); 619 | } 620 | 621 | public void PageRight() 622 | { 623 | throw new NotImplementedException(); 624 | } 625 | 626 | public void PageUp() 627 | { 628 | throw new NotImplementedException(); 629 | } 630 | 631 | public ScrollViewer ScrollOwner 632 | { 633 | get { return _scrollOwner; } 634 | set { _scrollOwner = value; } 635 | } 636 | 637 | public void SetHorizontalOffset(double offset) 638 | { 639 | if (offset < 0 || _viewPort.Width >= _extent.Width) 640 | offset = 0; 641 | else 642 | { 643 | if (offset + _viewPort.Width > _extent.Width) 644 | offset = _extent.Width - _viewPort.Width; 645 | } 646 | 647 | _offset.X = offset; 648 | if (_scrollOwner != null) 649 | _scrollOwner.InvalidateScrollInfo(); 650 | 651 | _translateTransform.X = -offset; 652 | 653 | InvalidateMeasure(); 654 | } 655 | 656 | public void SetVerticalOffset(double offset) 657 | { 658 | if (offset < 0 || _viewPort.Height >= _extent.Height) 659 | offset = 0; 660 | else 661 | { 662 | if (offset + _viewPort.Height > _extent.Height) 663 | offset = _extent.Height - _viewPort.Height; 664 | } 665 | 666 | _offset.Y = offset; 667 | if (_scrollOwner != null) 668 | _scrollOwner.InvalidateScrollInfo(); 669 | 670 | _translateTransform.Y = -offset; 671 | 672 | InvalidateMeasure(); 673 | } 674 | 675 | public double VerticalOffset 676 | { 677 | get { return _offset.Y; } 678 | } 679 | 680 | public double ViewportHeight 681 | { 682 | get { return _viewPort.Height; } 683 | } 684 | 685 | public double ViewportWidth 686 | { 687 | get { return _viewPort.Width; } 688 | } 689 | 690 | #endregion 691 | 692 | #region Helpers 693 | 694 | private static void SetDimensions(TabItem tabItem) 695 | { 696 | if (tabItem.Dimension == null) 697 | { 698 | // store the original size specifications of the tab 699 | tabItem.Dimension = 700 | new Dimension 701 | { 702 | Height = tabItem.Height, 703 | Width = tabItem.Width, 704 | MaxHeight = tabItem.MaxHeight, 705 | MaxWidth = tabItem.MaxWidth, 706 | MinHeight = tabItem.MinHeight, 707 | MinWidth = tabItem.MinWidth 708 | }; 709 | } 710 | else 711 | { 712 | // restore the original values for the tab 713 | tabItem.BeginInit(); 714 | tabItem.Height = tabItem.Dimension.Height; 715 | tabItem.Width = tabItem.Dimension.Width; 716 | tabItem.MaxHeight = tabItem.Dimension.MaxHeight; 717 | tabItem.MaxWidth = tabItem.Dimension.MaxWidth; 718 | tabItem.MinHeight = tabItem.Dimension.MinHeight; 719 | tabItem.MinWidth = tabItem.Dimension.MinWidth; 720 | tabItem.EndInit(); 721 | } 722 | } 723 | 724 | private static void ClearDimensions(FrameworkElement tabItem) 725 | { 726 | // remove any size restrictions from the Header, 727 | // this is because the TabControl's size restriction properties takes precedence over 728 | // the individual tab items 729 | // eg, if the TabControl sets the TabItemMaxWidth property to 300, but the Header 730 | // has a minWidth of 400, the TabControls value of 300 should be used 731 | tabItem.BeginInit(); 732 | tabItem.Height = double.NaN; 733 | tabItem.Width = double.NaN; 734 | tabItem.MaxHeight = double.PositiveInfinity; 735 | tabItem.MaxWidth = double.PositiveInfinity; 736 | tabItem.MinHeight = 0; 737 | tabItem.MinWidth = 0; 738 | tabItem.EndInit(); 739 | } 740 | 741 | #endregion 742 | } 743 | } 744 | -------------------------------------------------------------------------------- /TabControl/TabControl/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 78 | 79 | 80 | 136 | 137 | 138 | 177 | 178 | 179 | 218 | 219 | 220 | 261 | 262 | 263 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 427 | 428 | 442 | 443 | 444 | 455 | 456 | 457 | 458 | 459 | 460 | 470 | 471 | 472 | 480 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 517 | 518 | 532 | 533 | 534 | 545 | 546 | 547 | 548 | 549 | 550 | 561 | 562 | 563 | 572 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 613 | 614 | 615 | 617 | 618 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 637 | 638 | 639 | 647 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 688 | 689 | 690 | 692 | 693 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 713 | 714 | 715 | 723 | 729 | 730 | 731 | 732 | 733 | 775 | 776 | -------------------------------------------------------------------------------- /TabControl/TabControl/Wpf.TabControl.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {509D06BD-AEB6-4E92-8E5B-21188EBDBD9C} 9 | library 10 | Properties 11 | Wpf.Controls 12 | Wpf.TabControl 13 | v3.0 14 | 512 15 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 16 | 4 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 3.0 38 | 39 | 40 | 3.0 41 | 42 | 43 | 3.0 44 | 45 | 46 | 3.0 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Code 60 | 61 | 62 | True 63 | True 64 | Resources.resx 65 | 66 | 67 | True 68 | Settings.settings 69 | True 70 | 71 | 72 | 73 | 74 | 75 | 76 | ResXFileCodeGenerator 77 | Resources.Designer.cs 78 | Designer 79 | 80 | 81 | SettingsSingleFileGenerator 82 | Settings.Designer.cs 83 | 84 | 85 | 86 | 87 | 88 | MSBuild:Compile 89 | Designer 90 | 91 | 92 | 93 | 94 | 95 | 96 | 103 | -------------------------------------------------------------------------------- /TabControl/Test/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TabControl/Test/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Windows; 7 | 8 | namespace Test 9 | { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TabControl/Test/Images/ie.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FluentNotepad/TabControl/438271851a4bfbc0a57f8b16ba2212b0ceb59f2e/TabControl/Test/Images/ie.ico -------------------------------------------------------------------------------- /TabControl/Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("Test")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("Test")] 15 | [assembly: AssemblyCopyright("Copyright © 2007")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /TabControl/Test/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:2.0.50727.1378 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Test.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Test.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /TabControl/Test/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /TabControl/Test/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:2.0.50727.1378 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Test.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /TabControl/Test/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /TabControl/Test/Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {25344CF3-B7AA-4C75-B0EE-7C92ACFF3A9F} 9 | WinExe 10 | Properties 11 | Test 12 | Test 13 | v3.5 14 | 512 15 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 16 | 4 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 3.5 39 | 40 | 41 | 42 | 3.5 43 | 44 | 45 | 3.5 46 | 47 | 48 | 49 | 50 | 3.0 51 | 52 | 53 | 3.0 54 | 55 | 56 | 3.0 57 | 58 | 59 | 3.0 60 | 61 | 62 | 3.0 63 | 64 | 65 | 66 | 67 | MSBuild:Compile 68 | Designer 69 | 70 | 71 | Designer 72 | MSBuild:Compile 73 | 74 | 75 | MSBuild:Compile 76 | Designer 77 | 78 | 79 | MSBuild:Compile 80 | Designer 81 | 82 | 83 | App.xaml 84 | Code 85 | 86 | 87 | WindowUsingItemsProperty.xaml 88 | Code 89 | 90 | 91 | 92 | 93 | Code 94 | 95 | 96 | True 97 | True 98 | Resources.resx 99 | 100 | 101 | True 102 | Settings.settings 103 | True 104 | 105 | 106 | Window1.xaml 107 | 108 | 109 | WindowUsingItemsSourceProperty.xaml 110 | 111 | 112 | ResXFileCodeGenerator 113 | Resources.Designer.cs 114 | Designer 115 | 116 | 117 | SettingsSingleFileGenerator 118 | Settings.Designer.cs 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | {509D06BD-AEB6-4E92-8E5B-21188EBDBD9C} 128 | Wpf.TabControl 129 | 130 | 131 | 132 | 139 | -------------------------------------------------------------------------------- /TabControl/Test/Window1.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 |