' Follow steps 1a or 1b and then 2 to use this custom control in a XAML file. ' ' Step 1a) Using this custom control in a XAML file that exists in the current project. ' Add this XmlNamespace attribute to the root element of the markup file where it is ' to be used: ' ' xmlns:MyNamespace="clr-namespace:EgtWPFLib5" ' ' ' Step 1b) Using this custom control in a XAML file that exists in a different project. ' Add this XmlNamespace attribute to the root element of the markup file where it is ' to be used: ' ' xmlns:MyNamespace="clr-namespace:EgtWPFLib5;assembly=EgtWPFLib5" ' ' You will also need to add a project reference from the project where the XAML file lives ' to this project and Rebuild to avoid compilation errors: ' ' Right click on the target project in the Solution Explorer and ' "Add Reference"->"Projects"->[Browse to and select this project] ' ' ' Step 2) ' Go ahead and use your control in the XAML file. Note that Intellisense in the ' XML editor does not currently work on custom controls and its child elements. ' ' ' Imports System.Windows.Controls.Primitives Imports System.Collections.Specialized Imports System.Windows.Threading ' Extended TabControl which saves the displayed item so you don't get the performance hit of ' unloading and reloading the VisualTree when switching tabs ' Made some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations _ Public Class EgtTabControl Inherits System.Windows.Controls.TabControl ' Holds all items, but only marks the current tab's item as visible Private _itemsHolder As Panel = Nothing ' Temporaily holds deleted item in case this was a drag/drop operation Private _deletedObject As Object = Nothing Public Sub New() MyBase.New() ' this is necessary so that we get the initial databound selected item AddHandler Me.ItemContainerGenerator.StatusChanged, AddressOf ItemContainerGenerator_StatusChanged End Sub ''' ''' if containers are done, generate the selected item ''' ''' ''' Private Sub ItemContainerGenerator_StatusChanged(sender As Object, e As EventArgs) If Me.ItemContainerGenerator.Status = GeneratorStatus.ContainersGenerated Then RemoveHandler Me.ItemContainerGenerator.StatusChanged, AddressOf ItemContainerGenerator_StatusChanged UpdateSelectedItem() End If End Sub ''' ''' get the ItemsHolder and generate any children ''' Public Overrides Sub OnApplyTemplate() MyBase.OnApplyTemplate() _itemsHolder = TryCast(MyBase.GetTemplateChild("PART_ItemsHolder"), Panel) UpdateSelectedItem() End Sub ''' ''' when the items change we remove any generated panel children and add any new ones as necessary ''' ''' Protected Overrides Sub OnItemsChanged(e As NotifyCollectionChangedEventArgs) MyBase.OnItemsChanged(e) If _itemsHolder Is Nothing Then Return End If Select Case e.Action Case NotifyCollectionChangedAction.Reset _itemsHolder.Children.Clear() If MyBase.Items.Count > 0 Then MyBase.SelectedItem = MyBase.Items(0) UpdateSelectedItem() End If Exit Select Case NotifyCollectionChangedAction.Add, NotifyCollectionChangedAction.Remove ' Search for recently deleted items caused by a Drag/Drop operation If e.NewItems IsNot Nothing AndAlso _deletedObject IsNot Nothing Then For Each item As Object In e.NewItems If _deletedObject Is item Then ' If the new item is the same as the recently deleted one (i.e. a drag/drop event) ' then cancel the deletion and reuse the ContentPresenter so it doesn't have to be ' redrawn. We do need to link the presenter to the new item though (using the Tag) Dim cp As ContentPresenter = FindChildContentPresenter(_deletedObject) If cp IsNot Nothing Then Dim index As Integer = _itemsHolder.Children.IndexOf(cp) TryCast(_itemsHolder.Children(index), ContentPresenter).Tag = If((TypeOf item Is TabItem), item, (Me.ItemContainerGenerator.ContainerFromItem(item))) End If _deletedObject = Nothing End If Next End If If e.OldItems IsNot Nothing Then For Each item As Object In e.OldItems _deletedObject = item ' We want to run this at a slightly later priority in case this ' is a drag/drop operation so that we can reuse the template Me.Dispatcher.BeginInvoke(DispatcherPriority.DataBind, (Sub() If _deletedObject IsNot Nothing Then Dim cp As ContentPresenter = FindChildContentPresenter(_deletedObject) If cp IsNot Nothing Then Me._itemsHolder.Children.Remove(cp) End If End If End Sub)) Next End If UpdateSelectedItem() Exit Select Case NotifyCollectionChangedAction.Replace Throw New NotImplementedException("Replace not implemented yet") End Select End Sub ''' ''' update the visible child in the ItemsHolder ''' ''' Protected Overrides Sub OnSelectionChanged(e As SelectionChangedEventArgs) MyBase.OnSelectionChanged(e) UpdateSelectedItem() End Sub ''' ''' generate a ContentPresenter for the selected item ''' Private Sub UpdateSelectedItem() If _itemsHolder Is Nothing Then Return End If ' generate a ContentPresenter if necessary Dim item As TabItem = GetSelectedTabItem() If item IsNot Nothing Then CreateChildContentPresenter(item) End If ' show the right child For Each child As ContentPresenter In _itemsHolder.Children child.Visibility = If((TryCast(child.Tag, TabItem).IsSelected), Visibility.Visible, Visibility.Collapsed) Next End Sub ''' ''' create the child ContentPresenter for the given item (could be data or a TabItem) ''' ''' ''' Private Function CreateChildContentPresenter(item As Object) As ContentPresenter If item Is Nothing Then Return Nothing End If Dim cp As ContentPresenter = FindChildContentPresenter(item) If cp IsNot Nothing Then Return cp End If ' the actual child to be added. cp.Tag is a reference to the TabItem cp = New ContentPresenter() With { .Content = If((TypeOf item Is TabItem), TryCast(item, TabItem).Content, item), .ContentTemplate = Me.SelectedContentTemplate, .ContentTemplateSelector = Me.SelectedContentTemplateSelector, .ContentStringFormat = Me.SelectedContentStringFormat, .Visibility = Visibility.Collapsed, .Tag = If((TypeOf item Is TabItem), item, (Me.ItemContainerGenerator.ContainerFromItem(item))) } _itemsHolder.Children.Add(cp) Return cp End Function ''' ''' Find the CP for the given object. data could be a TabItem or a piece of data ''' ''' ''' Private Function FindChildContentPresenter(data As Object) As ContentPresenter If TypeOf data Is TabItem Then data = TryCast(data, TabItem).Content End If If data Is Nothing Then Return Nothing End If If _itemsHolder Is Nothing Then Return Nothing End If For Each cp As ContentPresenter In _itemsHolder.Children If cp.Content Is data Then Return cp End If Next Return Nothing End Function ''' ''' copied from TabControl; wish it were protected in that class instead of private ''' ''' Protected Function GetSelectedTabItem() As TabItem Dim selectedItem As Object = MyBase.SelectedItem If selectedItem Is Nothing Then Return Nothing End If If _deletedObject Is selectedItem Then End If Dim item As TabItem = TryCast(selectedItem, TabItem) If item Is Nothing Then item = TryCast(MyBase.ItemContainerGenerator.ContainerFromIndex(MyBase.SelectedIndex), TabItem) End If Return item End Function End Class