Tuesday, March 13, 2012

TabItem Header and Content as UserControls in ObservableCollection

Here is a little example project I made to allow the tabitem header to be of a usercontrol and tabitem content to be of another usercontrol. It allows for adding and removing tabitems in the same way internet explorer functions. I have also included a flag button next to the close button on each tab item that you can toggle.

This is a full MVVM application written in VB.Net.




 




Imports System.Collections.ObjectModel
Imports System.ComponentModel

Namespace ViewModel
    Public Class TabControlVM
        Implements INotifyPropertyChanged

#Region "Properties"

        ''Holds collection of tab items
        Private _myTab As ObservableCollection(Of Model.MyTab) = Nothing
        Public Property MyTabCollection As ObservableCollection(Of Model.MyTab)
            Get
                Return _myTab
            End Get
            Set(ByVal value As ObservableCollection(Of Model.MyTab))
                _myTab = value
                OnPropertyChanged("MyTabCollection")
            End Set
        End Property

        ''Selected tab item index
        Private _selectedIndex As Integer = 0
        Public Property SelectedIndex As Integer
            Get
                Return _selectedIndex
            End Get
            Set(ByVal value As Integer)
                _selectedIndex = value
                OnPropertyChanged("SelectedIndex")
            End Set
        End Property

#End Region

#Region "Relay Commands"

        Private _addTab As RelayCommand = Nothing
        Public ReadOnly Property AddTabCommand() As ICommand
            Get
                If _addTab Is Nothing Then
                    Dim commandAction As New Action(Of Object)(AddressOf Me.AddTab)
                    _addTab = New RelayCommand(commandAction)
                End If
                Return _addTab
            End Get
        End Property

        Private _removeTab As RelayCommand = Nothing
        Public ReadOnly Property RemoveTabCommand() As ICommand
            Get
                If _removeTab Is Nothing Then
                    Dim commandAction As New Action(Of Object)(AddressOf Me.RemoveTab)
                    _removeTab = New RelayCommand(commandAction)
                End If
                Return _removeTab
            End Get
        End Property

#End Region

#Region "Constructor"

        ''' <summary>
        ''' <para>Creates a new obeservable collection with tab items for tab control.
        ''' Uses usercontrol for tab item header and content.</para>
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub New()

            ''Create a MyTab collection (tabitems)
            _myTab = New ObservableCollection(Of Model.MyTab)

            ''Create tabitem header object
            Dim tabHeaderObj = New TabHeaderAddUC
            tabHeaderObj.DataContext = Me

            ''Create the Add Tab content object
            Dim tabContentObj As New TabContentAddUC
            tabContentObj.DataContext = Me

            ''Add to collection
            _myTab.Add(New Model.MyTab(tabHeaderObj, tabContentObj, 0))

        End Sub

#End Region

#Region "Add Tab"

        ''' <summary>
        ''' <para>Method which creates a new tabitem object and adds it to MyTab collection.</para>
        ''' </summary>
        ''' <param name="obj">Object of command parameter from view.</param>
        ''' <remarks></remarks>
        Private Sub AddTab(ByVal obj As Object)

            ''Checks the highest MyTab.Index which will be used when assigingin
            ''a new tab item (index + 1)
            Dim i As Integer = 0
            For Each tb As Model.MyTab In MyTabCollection
                If tb.Index > i Then i = tb.Index
            Next

            ''Create tabitem header object
            Dim tabHeaderObj = New TabHeaderUC
            ''Enable if you want to explicitly set view model class here instead of in the view
            ''-----------------------------------------------------------------------------
            tabHeaderObj.DataContext = New ViewModel.TabHeaderVM With {.HeaderText = "Tab item " & i}
            ''-----------------------------------------------------------------------------

            ''Create tabitem content object
            Dim tabContentObj As New TabContentMyContentUC
            tabContentObj.DataContext = Me

            ''Have the add tab on the left side
            ''-----------------------------------------------------------------------------
            ''Add to collection
            'MyTabCollection.Add(New Model.MyTab(tabHeaderObj, tabContentObj, i + 1))
            ''-----------------------------------------------------------------------------

            ''Have the add tab on the right side
            ''-----------------------------------------------------------------------------
            ''Insert to collection
            MyTabCollection.Insert(i, New Model.MyTab(tabHeaderObj, tabContentObj, i + 1))
            ''-----------------------------------------------------------------------------

            ''Set the highest tab item to visible
            SelectedIndex = i

        End Sub

#End Region

#Region "Remove Tab"

        ''' <summary>
        ''' <para>Removes the tab item at specified index.</para>
        ''' </summary>
        ''' <param name="obj">Object of command parameter from view containing the TabItem.</param>
        ''' <remarks></remarks>
        Private Sub RemoveTab(ByVal obj As Object)

            ''Gets the tabitem MyTab.Index from tabitem Tag and
            ''removes the item at this location from the collection
            Dim tabItemObj = DirectCast(obj, TabItem)

            ''Have the add tab on the left side
            ''-----------------------------------------------------------------------------
            'If Not tabItemObj.Tag = 0 Then MyTabCollection.RemoveAt(tabItemObj.Tag)
            ''-----------------------------------------------------------------------------

            ''Have the add tab on the right side
            ''-----------------------------------------------------------------------------
            If Not tabItemObj.Tag = 0 Then MyTabCollection.RemoveAt(tabItemObj.Tag - 1)           
            ''-----------------------------------------------------------------------------

            ''Rebuild the MyTab.Index to match tabitem index
            Dim j As Integer = 1
            For Each item As Model.MyTab In _myTab
                If Not item.Index = 0 Then
                    item.Index = j
                    j += 1
                End If
            Next

            ''Set the highest tab item to visible
            SelectedIndex = j
        End Sub

#End Region

#Region "INotifyPropertyChanged"

        Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

        Protected Overridable Sub OnPropertyChanged(ByVal PropertyName As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))

        End Sub

#End Region

    End Class

End Namespace

Imports System.ComponentModel

Namespace Model
    Public Class MyTab
        Implements INotifyPropertyChanged

#Region "Properties"

        ''Tab item header usercontrol
        Private _header As UserControl = Nothing
        Property Header As UserControl
            Get
                Return _header
            End Get
            Set(ByVal value As UserControl)
                _header = value
                OnPropertyChanged("Header")
            End Set
        End Property

        ''Tab content user control
        Private _content As UserControl = Nothing
        Property Content As UserControl
            Get
                Return _content
            End Get
            Set(ByVal value As UserControl)
                _content = value
                OnPropertyChanged("Content")
            End Set
        End Property

        ''Tab item index in tabcontrol
        Private _indexObj As Integer = 0
        Property Index As Integer
            Get
                Return _indexObj
            End Get
            Set(ByVal value As Integer)
                _indexObj = value
                OnPropertyChanged("Index")
            End Set
        End Property

#End Region

#Region "Constructor"

        ''' <summary>
        ''' <para>Constructor accepting two user controls as parameters for header and content.</para>
        ''' </summary>
        ''' <param name="h">UserControl containing header information.</param>
        ''' <param name="c">UserControl containing content information.</param>
        ''' <param name="i">Integer representing an artificial index of the tabitems.</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal h As UserControl, ByVal c As UserControl, ByVal i As Integer)
            Try
                Header = h
                Content = c
                Index = i
            Catch ex As Exception
                Throw New Exception("Error in MyTab constructor.")
            End Try
        End Sub

#End Region

#Region "INotifyPropertyChanged"

        Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

        Protected Overridable Sub OnPropertyChanged(ByVal PropertyName As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
        End Sub

#End Region

    End Class
End Namespace

Imports System.Collections.ObjectModel
Imports System.ComponentModel

Namespace ViewModel
    Public Class TabHeaderVM
        Implements INotifyPropertyChanged

#Region "Properties"

        ''Set flag image
        Private _imageSource As String = ""
        Public Property ImageSource As String
            Get
                Return _imageSource
            End Get
            Set(ByVal value As String)
                _imageSource = value
                OnPropertyChanged("ImageSource")
            End Set
        End Property

        ''Header text
        Private _headerText As String = ""
        Public Property HeaderText As String
            Get
                Return _headerText
            End Get
            Set(ByVal value As String)
                _headerText = value
                OnPropertyChanged("HeaderText")
            End Set
        End Property

#End Region

#Region "Relay Commands"

        Private _SetTabItemFlagImageCommand As RelayCommand = Nothing
        Public ReadOnly Property SetTabItemFlagImageCommand() As ICommand
            Get
                If _SetTabItemFlagImageCommand Is Nothing Then
                    Dim commandAction As New Action(Of Object)(AddressOf Me.SetTabItemFlagImage)
                    _SetTabItemFlagImageCommand = New RelayCommand(commandAction)
                End If
                Return _SetTabItemFlagImageCommand
            End Get
        End Property

#End Region

#Region "Constructor"

        ''' <summary>
        ''' <para></para>
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub New()

        End Sub

#End Region

#Region "Set Flag Visibility"

        Private Sub SetTabItemFlagImage()
            If ImageSource = "" Then
                ImageSource = "/Images/16x16/Flag-red.png"
            ElseIf ImageSource = "/Images/16x16/Flag-red.png" Then
                ImageSource = "/Images/16x16/Flag-gray.png"
            ElseIf ImageSource = "/Images/16x16/Flag-gray.png" Then
                ImageSource = ""
            End If

        End Sub

#End Region

#Region "INotifyPropertyChanged"

        Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

        Protected Overridable Sub OnPropertyChanged(ByVal PropertyName As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))

        End Sub

#End Region

    End Class

End Namespace


Thursday, March 1, 2012

DispatcherUnhandledExceptionEventArgs with DataView RowFilter

If you are performing a RowFilter operation from a mulithreaded application on a property that is bound to a grid in the view in you application, you might get an unhandled exception.

You can handle the exception manually with the DispatcherUnhandledExceptionEventArgs.

What happens is that while performing the RowFilter from a separate thread than the main one, if it is quick enough to update the property before the thread has completed, it will throw an exception.

A better place to perform the rowfilter is after the thread has completed. Using the background worker, you can perform the filtering in the RunWorkerCompleted()

        Private _ItemsTable As DataView = Nothing
        Public Property ItemsTable As DataView
            Get
                Return _ItemsTable            End Get
            Set(ByVal value As DataView)
                _ItemsTable = value
                OnPropertyChanged("ItemsTable")            End Set
        End Property

 Private Sub PickThread_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles PickThread.DoWork

                ItemsTable = connString.FillDataTable("SPName", Nothing).DefaultView                       
   
        End Sub

        Private Sub PickThread_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles PickThread.RunWorkerCompleted            

                If Not ItemsTable Is Nothing OrElse Not ItemsTable.Count < 1 Then                   
                            ItemsTable.RowFilter = " Pick = 'T' "                       
                End If

        End Sub