' 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