Click here to Skip to main content
15,884,425 members
Articles / Desktop Programming / Windows Forms

i00 Spell Check and Control Extensions - No Third Party Components Required!

Rate me:
Please Sign up or sign in to vote.
4.95/5 (117 votes)
11 Jan 2014Ms-PL16 min read 1.3M   21   266  
Simple to use, open source Spell Checker for .NET
'i00 BindingList with DataGridView
'©i00 Productions All rights reserved
'Created by Kris Bennett
'----------------------------------------------------------------------------------------------------
'All property in this file is and remains the property of i00 Productions, regardless of its usage,
'unless stated otherwise in writing from i00 Productions.
'
'i00 is not and shall not be held accountable for any damages directly or indirectly caused by the
'use or miss-use of this product.  This product is only a component and thus is intended to be used 
'as part of other software, it is not a complete software package, thus i00 Productions is not 
'responsible for any legal ramifications that software using this product breaches.

<System.ComponentModel.DesignerCategory("")> _
Public Class DataGridView
    Inherits System.Windows.Forms.DataGridView

#Region "Properties"

    Dim mc_DrawUIEditorCells As Boolean = True
    <System.ComponentModel.DefaultValue(True)> _
    Public Property DrawUIEditorCells() As Boolean
        Get
            Return mc_DrawUIEditorCells
        End Get
        Set(ByVal value As Boolean)
            If mc_DrawUIEditorCells <> value Then
                mc_DrawUIEditorCells = value
                MyBase.Invalidate()
            End If
        End Set
    End Property

    Dim mc_AllowUIEditorCells As Boolean = True
    <System.ComponentModel.DefaultValue(True)> _
    Public Property AllowUIEditorCells() As Boolean
        Get
            Return mc_AllowUIEditorCells
        End Get
        Set(ByVal value As Boolean)
            mc_AllowUIEditorCells = value
        End Set
    End Property

    Dim mc_NoImageText As String = "No Image"
    <System.ComponentModel.DefaultValue("No Image")> _
    Public Property NoImageText() As String
        Get
            Return mc_NoImageText
        End Get
        Set(ByVal value As String)
            mc_NoImageText = value
        End Set
    End Property

    Dim mc_NoImageImage As Image = Nothing
    <System.ComponentModel.DefaultValue(GetType(Image), Nothing)> _
    Public Property NoImageImage() As Image
        Get
            Return mc_NoImageImage
        End Get
        Set(ByVal value As Image)
            mc_NoImageImage = value
        End Set
    End Property

#End Region

#Region "Events"

    Public Event AddingFilterPlugin(ByVal sender As Object, ByVal e As FilterEventArgs)

    Public Class FilterEventArgs
        Inherits EventArgs
        Public Column As DataGridViewColumn
        Public Plugin As Plugins.iFilterPlugin
        Public Cancel As Boolean
    End Class

    Protected Overridable Sub OnAddingFilterPlugin(ByVal e As FilterEventArgs)
        RaiseEvent AddingFilterPlugin(Me, e)
    End Sub

#End Region

#Region "Drawing"

    'give some space for the UIEditors..
    Protected Overrides Sub OnColumnAdded(ByVal e As System.Windows.Forms.DataGridViewColumnEventArgs)
        If mc_DrawUIEditorCells AndAlso e.Column.ValueType IsNot Nothing Then
            If e.Column.ValueType IsNot GetType(System.Drawing.Image) AndAlso PropertyEditor.PaintProperty(e.Column.ValueType, Nothing, Nothing, GetColumnOverrideTypeEditor(e.Column)) = True Then
                If e.Column.CellTemplate.Style.Padding.Left < 22 Then
                    e.Column.CellTemplate.Style.Padding = New Padding(22, e.Column.CellTemplate.Style.Padding.Top, e.Column.CellTemplate.Style.Padding.Right, e.Column.CellTemplate.Style.Padding.Bottom)
                End If
            End If
        End If
        MyBase.OnColumnAdded(e)
    End Sub

    'redraw the column header when the mouse leaves the control
    Protected Overrides Sub OnMouseLeave(ByVal e As System.EventArgs)
        MyBase.OnMouseLeave(e)

        If HeaderMenu.Visible Then Exit Sub 'don't redraw if we have the menu open :)
        If Me.OverColumnIndex <> -1 Then
            Me.InvalidateCell(Me.OverColumnIndex, -1)
        End If
        OverColumnIndex = -1
    End Sub

    'redraw the column header when the menu is closed
    Private Sub HeaderMenu_Closed(ByVal sender As Object, ByVal e As System.Windows.Forms.ToolStripDropDownClosedEventArgs) Handles HeaderMenu.Closed
        OnMouseLeave(EventArgs.Empty)
    End Sub

    Public Sub New()
        MyBase.DoubleBuffered = True
    End Sub

    'draw the column headers
    Protected Overrides Sub OnCellPainting(ByVal e As System.Windows.Forms.DataGridViewCellPaintingEventArgs)
        MyBase.OnCellPainting(e)
        If e.Handled = False Then
            e.Graphics.SetClip(Me.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, True))
            If e.RowIndex = -1 Then
                Static FilterImage As Image = My.Resources.Filter 'cache for filter image.. faster than looking up the image each from Resources time...
                If e.ColumnIndex = -1 Then
                    If Me.GetFilter <> "" Then
                        'draw filter
                        e.PaintBackground(e.CellBounds, True)
                        e.Graphics.DrawImage(FilterImage, New Rectangle(e.CellBounds.Left + (e.CellBounds.Width - FilterImage.Width - 2), e.CellBounds.Top + CInt((e.CellBounds.Height - FilterImage.Height) / 2), FilterImage.Width, FilterImage.Height))
                        e.Handled = True
                    End If
                Else
                    Dim Column = Me.Columns(e.ColumnIndex)

                    Dim HStyle = Column.HeaderCell.InheritedStyle
                    If Column IsNot Nothing Then

                        Dim CellBounds = New Rectangle(e.CellBounds.X, e.CellBounds.Y + 1, e.CellBounds.Width, e.CellBounds.Height - 2)
                        Dim WidgetButtonBounds = New Rectangle(CellBounds.Right - 16, CellBounds.Y, 16, CellBounds.Height)

                        If OverColumnIndex = e.ColumnIndex Then
                            e.PaintBackground(e.CellBounds, True)
                            If System.Windows.Forms.VisualStyles.VisualStyleRenderer.IsSupported Then
                                Dim r = New System.Windows.Forms.VisualStyles.VisualStyleRenderer("Header", 1, 1)
                                r.DrawBackground(e.Graphics, CellBounds)
                            End If
                            'e.Graphics.FillRectangle(Brushes.White, e.CellBounds)

                            Dim HighlightBounds As Rectangle
                            If isOverWidget Then
                                HighlightBounds = WidgetButtonBounds
                            Else
                                HighlightBounds = CellBounds
                            End If
                            If System.Windows.Forms.VisualStyles.VisualStyleRenderer.IsSupported Then
                                Dim r = New System.Windows.Forms.VisualStyles.VisualStyleRenderer("Header", 1, If((Control.MouseButtons And Windows.Forms.MouseButtons.Left) = Windows.Forms.MouseButtons.Left, 3, 2))
                                r.DrawBackground(e.Graphics, HighlightBounds)
                            Else
                                If isOverWidget Then
                                    Using p As New Pen(Color.FromKnownColor(KnownColor.ControlLightLight))
                                        e.Graphics.DrawRectangle(p, HighlightBounds)
                                    End Using
                                    Using p As New Pen(Color.FromKnownColor(KnownColor.ControlDark))
                                        e.Graphics.DrawLine(p, HighlightBounds.Left, HighlightBounds.Bottom, HighlightBounds.Right, HighlightBounds.Bottom)
                                        e.Graphics.DrawLine(p, HighlightBounds.Right, HighlightBounds.Top, HighlightBounds.Right, HighlightBounds.Bottom)
                                    End Using
                                End If
                            End If
                            'e.Graphics.FillRectangle(Brushes.Blue, HighlightBounds)
                            'e.PaintBackground(HighlightBounds, True)
                            If Column.IsDataBound AndAlso (ColumnSupportsSorting(Column) OrElse Me.SupportsFiltering <> FilteringMethods.None) AndAlso Column.ValueType IsNot GetType(System.Drawing.Image) Then
                                Dim ShowDropDownArrow = True
                                Select Case Me.SupportsFiltering()
                                    Case FilteringMethods.Basic
                                        'qwertyuiop
                                        'NOT IMPLEMENTED
                                    Case FilteringMethods.Advanced
                                        Dim bs = DirectCast(Me.DataSource, AdvancedBindingSource)
                                        Dim ThisFilterItem = (From xItem In bs.BasicFilters Where xItem.Field = Column.DataPropertyName).FirstOrDefault
                                        If ThisFilterItem IsNot Nothing Then
                                            e.Graphics.DrawImage(FilterImage, New Rectangle(e.CellBounds.Left + (e.CellBounds.Width - FilterImage.Width), e.CellBounds.Top + CInt((e.CellBounds.Height - FilterImage.Height) / 2), FilterImage.Width, FilterImage.Height))
                                            ShowDropDownArrow = False
                                        End If
                                    Case Else
                                End Select

                                If ShowDropDownArrow Then
                                    'show the widget dropdown
                                    If System.Windows.Forms.VisualStyles.VisualStyleRenderer.IsSupported Then
                                        Dim r = New System.Windows.Forms.VisualStyles.VisualStyleRenderer("Toolbar", 4, If((Control.MouseButtons And Windows.Forms.MouseButtons.Left) = Windows.Forms.MouseButtons.Left, 1, 4))
                                        r.DrawBackground(e.Graphics, WidgetButtonBounds)
                                    Else
                                        'drop down arrow
                                        Dim tsr As New ToolStripProfessionalRenderer
                                        Using tsmi As New ToolStripMenuItem
                                            tsr.DrawArrow(New ToolStripArrowRenderEventArgs(e.Graphics, tsmi, WidgetButtonBounds, HStyle.ForeColor, ArrowDirection.Down))
                                        End Using
                                    End If
                                End If
                                WidgetButtonBounds.X -= WidgetButtonBounds.Width

                            End If
                        Else
                            e.PaintBackground(e.CellBounds, True)
                        End If

                        If Column.SortMode <> DataGridViewColumnSortMode.NotSortable Then
                            Select Case Column.HeaderCell.SortGlyphDirection
                                Case Windows.Forms.SortOrder.Ascending
                                    If System.Windows.Forms.VisualStyles.VisualStyleRenderer.IsSupported Then
                                        Dim r = New System.Windows.Forms.VisualStyles.VisualStyleRenderer("Header", 4, 1)
                                        r.DrawBackground(e.Graphics, WidgetButtonBounds)
                                    Else
                                        'drop down arrow
                                        Dim tsr As New ToolStripProfessionalRenderer
                                        Using tsmi As New ToolStripMenuItem
                                            tsr.DrawArrow(New ToolStripArrowRenderEventArgs(e.Graphics, tsmi, WidgetButtonBounds, HStyle.ForeColor, ArrowDirection.Up))
                                        End Using
                                    End If
                                Case Windows.Forms.SortOrder.Descending
                                    If System.Windows.Forms.VisualStyles.VisualStyleRenderer.IsSupported Then
                                        Dim r = New System.Windows.Forms.VisualStyles.VisualStyleRenderer("Header", 4, 2)
                                        r.DrawBackground(e.Graphics, WidgetButtonBounds)
                                    Else
                                        'drop down arrow
                                        Dim tsr As New ToolStripProfessionalRenderer
                                        Using tsmi As New ToolStripMenuItem
                                            tsr.DrawArrow(New ToolStripArrowRenderEventArgs(e.Graphics, tsmi, WidgetButtonBounds, HStyle.ForeColor, ArrowDirection.Down))
                                        End Using
                                    End If
                                Case Else
                                    WidgetButtonBounds.X += WidgetButtonBounds.Width
                            End Select
                        Else
                            WidgetButtonBounds.X += WidgetButtonBounds.Width
                        End If

                        Dim Padding = HStyle.Padding
                        If Padding = Nothing Then
                            Padding = New Padding(4, 0, 0, 0)
                        End If

                        Dim ContentBounds = e.CellBounds 'Me.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, True)
                        ContentBounds = New Rectangle(ContentBounds.X + Padding.Left, ContentBounds.Y + Padding.Top, ContentBounds.Width - (Padding.Left + Padding.Right), ContentBounds.Height - (Padding.Top + Padding.Bottom))
                        If ContentBounds.Right > WidgetButtonBounds.X Then
                            ContentBounds.Width = WidgetButtonBounds.X - ContentBounds.Left
                        End If
                        Dim TextStyle = DrawingText.TextRendererMeasure.TextFlagsFrom(HStyle)
                        If (TextStyle And TextFormatFlags.WordBreak) = TextFormatFlags.WordBreak Then
                            TextStyle = TextStyle Xor TextFormatFlags.WordBreak
                        End If
                        TextRenderer.DrawText(e.Graphics, Column.HeaderText, HStyle.Font, ContentBounds, HStyle.ForeColor, TextStyle)
                        e.Handled = True
                    End If
                    End If
            Else

                    'data
                    '
                    If e.ColumnIndex = -1 Then

                    Else
                        Dim Column = Me.Columns(e.ColumnIndex)
                        If Column IsNot Nothing Then

                            e.Handled = True
                            If Column.ValueType Is GetType(System.Drawing.Image) Then
                                e.PaintBackground(e.ClipBounds, True)
                                If e.Value Is Nothing Then
                                    If mc_NoImageImage IsNot Nothing Then
                                        'draw the image bg... for no image...
                                        Dim Image = NoImageImage
                                        Dim ImageBounds = New Rectangle(e.CellBounds.X, e.CellBounds.Y, e.CellBounds.Width - 1, e.CellBounds.Height - 1)
                                        Dim BestFitRect = DrawingFunctions.GetBestFitRect(ImageBounds, New RectangleF(0, 0, Image.Width, Image.Height), DrawingFunctions.BestFitStyle.Stretch Or DrawingFunctions.BestFitStyle.DoNotAllowEnlarge)
                                        Using b As New Bitmap(CInt(BestFitRect.Width), CInt(BestFitRect.Height))
                                            Using g = Graphics.FromImage(b)
                                                g.InterpolationMode = Drawing2D.InterpolationMode.High
                                                g.TranslateTransform(-BestFitRect.X, -BestFitRect.Y)
                                                g.DrawImage(Image, BestFitRect)
                                            End Using
                                            b.Filters.Alpha() 'with alpha
                                            e.Graphics.DrawImageUnscaled(b, New Point(CInt(BestFitRect.Location.X), CInt(BestFitRect.Location.Y)))
                                        End Using
                                    End If
                                    TextRenderer.DrawText(e.Graphics, NoImageText, e.CellStyle.Font, e.CellBounds, DrawingFunctions.BlendColor(e.CellStyle.ForeColor, e.CellStyle.BackColor), TextFormatFlags.HorizontalCenter Or TextFormatFlags.VerticalCenter Or TextFormatFlags.NoPrefix Or TextFormatFlags.PreserveGraphicsClipping Or TextFormatFlags.PreserveGraphicsTranslateTransform)
                                Else
                                    Dim Image = DirectCast(e.Value, Image)
                                    e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.High
                                    Dim ImageBounds = New Rectangle(e.CellBounds.X, e.CellBounds.Y, e.CellBounds.Width - 1, e.CellBounds.Height - 1)
                                    DrawingFunctions.DrawImageBestFit(e.Graphics, ImageBounds, Image, DrawingFunctions.BestFitStyle.Stretch Or DrawingFunctions.BestFitStyle.DoNotAllowEnlarge)
                                End If
                            Else

                                e.PaintBackground(e.ClipBounds, True)

                                If mc_DrawUIEditorCells Then

                                    If e.Value IsNot Nothing Then
                                        Dim PaintPropertyRect = New Rectangle(e.CellBounds.X + 2, CInt(Int(e.CellBounds.Y + ((e.CellBounds.Height - 15) / 2))), 19, 15)
                                        'Dim PaintPropertyRect = New Rectangle(e.CellBounds.X + 2, e.CellBounds.Y + 2, 19, e.CellBounds.Height - 6)
                                        If PropertyEditor.PaintProperty(e.Value, e.Graphics, PaintPropertyRect, GetColumnOverrideTypeEditor(Column)) = True Then
                                            'Dim NiceText = System.ComponentModel.TypeDescriptor.GetConverter(e.Value).ConvertToString(e.Value)
                                            e.Graphics.DrawRectangle(System.Drawing.SystemPens.WindowText, PaintPropertyRect)
                                        End If
                                    End If
                                End If

                                Select Case Me.SupportsFiltering()
                                    Case FilteringMethods.Basic
                                        'qwertyuiop
                                        'NOT IMPLEMENTED
                                    Case FilteringMethods.Advanced
                                        Dim bs = DirectCast(Me.DataSource, AdvancedBindingSource)
                                        Dim ThisFilterItem = (From xItem In bs.BasicFilters.OfType(Of AdvancedBindingSource.BasicFilter)() Where xItem.Field = Column.DataPropertyName).FirstOrDefault
                                        If ThisFilterItem IsNot Nothing Then
                                            'filtered...

                                            Dim RegExString = System.Text.RegularExpressions.Regex.Escape(ThisFilterItem.Filter)
                                            RegExString = Replace(RegExString, "\?", ".")
                                            RegExString = Replace(RegExString, "\*", ".{0,}")
                                            Dim FindResult = System.Text.RegularExpressions.Regex.Match(e.FormattedValue.ToString, RegExString, System.Text.RegularExpressions.RegexOptions.IgnoreCase)
                                            If FindResult.Success Then
                                                Dim CellBounds = New Rectangle(e.CellBounds.X, e.CellBounds.Y, e.CellBounds.Width - 1, e.CellBounds.Height - 1)

                                                Dim CellSize = CellBounds.Size
                                                CellSize.Width -= e.CellStyle.Padding.Left + e.CellStyle.Padding.Right
                                                CellSize.Height -= e.CellStyle.Padding.Top + e.CellStyle.Padding.Bottom

                                                Dim WordBounds = DrawingText.TextRendererMeasure.Measure(e.FormattedValue.ToString, e.CellStyle.Font, CellSize, DrawingText.TextRendererMeasure.TextFlagsFrom(e.CellStyle), , False)

                                                Dim StartIndex = (From xItem In WordBounds Where xItem.LetterIndex = FindResult.Index).FirstOrDefault
                                                Dim EndIndex = (From xItem In WordBounds Where xItem.LetterIndex = FindResult.Index + (FindResult.Length - 1)).FirstOrDefault

                                                If StartIndex IsNot Nothing AndAlso EndIndex IsNot Nothing Then
                                                    Dim HighlightRect = New Rectangle(StartIndex.Bounds.X + CellBounds.X + e.CellStyle.Padding.Left, StartIndex.Bounds.Y + CellBounds.Y + e.CellStyle.Padding.Top, EndIndex.Bounds.Right - StartIndex.Bounds.X, EndIndex.Bounds.Bottom - StartIndex.Bounds.Y)

                                                    Using sb As New SolidBrush(Color.FromArgb(127, e.CellStyle.BackColor))
                                                        e.Graphics.FillRectangle(sb, HighlightRect)
                                                    End Using

                                                    Using sb As New SolidBrush(Color.FromArgb(63, Color.FromKnownColor(KnownColor.Highlight)))
                                                        e.Graphics.FillRectangle(sb, HighlightRect)
                                                        'Using p As New Pen(sb)
                                                        '    e.Graphics.DrawRectangle(p, HighlightRect)
                                                        'End Using
                                                    End Using
                                                End If
                                            End If
                                        End If
                                    Case Else
                                End Select
                                e.PaintContent(e.ClipBounds)
                            End If
                        End If
                    End If
            End If
            e.Graphics.ResetClip()
        End If
    End Sub

#End Region

#Region "Property Editor"

    Private WithEvents OwnerForm As Form

    Dim DropDownCancelled As Boolean

    Private Sub CancelDropDown()
        DropDownCancelled = True
        PropertyEditor.CloseDropDown()
    End Sub

    Private Sub OwnerForm_Deactivate(ByVal sender As Object, ByVal e As System.EventArgs) Handles OwnerForm.Deactivate
        CancelDropDown()
    End Sub

    Dim AllowOnCellBeginEdit As Boolean = True

    Protected Overrides Sub OnCellBeginEdit(ByVal e As System.Windows.Forms.DataGridViewCellCancelEventArgs)
        If e.ColumnIndex <> -1 AndAlso AllowUIEditorCells AndAlso AllowOnCellBeginEdit Then
            Dim Column = Me.Columns(e.ColumnIndex)
            Dim CellBounds = Me.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, True)
            OwnerForm = Me.FindForm
            DropDownCancelled = False

            Dim t As New System.Threading.Thread(AddressOf OpenPropWindow)
            t.IsBackground = True
            t.Name = "Property Editor"
            t.Start(New PropEditPrams With {.Value = Me.CurrentCell.Value, .DataType = Me.CurrentCell.ValueType, .Location = Me.PointToScreen(New Point(CellBounds.X, CellBounds.Bottom)), .OverrideTypeEditor = GetColumnOverrideTypeEditor(Column)})

        End If

        MyBase.OnCellBeginEdit(e)
    End Sub

    Private WithEvents PropertyEditor As New PropertyEditor

    Private Class PropEditPrams
        Public Value As Object
        Public Location As Point
        Public DataType As Type
        Public OverrideTypeEditor As System.Reflection.MemberInfo
    End Class

    Private Sub OpenPropWindow(ByVal oPropEditPrams As Object)
        Dim PropEditPrams = TryCast(oPropEditPrams, PropEditPrams)
        If PropEditPrams IsNot Nothing Then
            'PropertyEditor.TopMost = True
            Try
                Dim Result = PropertyEditor.ShowPropertyEditor(If(PropEditPrams.Value Is Nothing, PropEditPrams.DataType, PropEditPrams.Value), PropEditPrams.Location, PropEditPrams.OverrideTypeEditor)
                If Me.IsCurrentCellInEditMode AndAlso DropDownCancelled = False Then
                    'send the result back to the cell.. we selected something :)
                    SetCurrentCellData(Result)
                End If
            Catch ex As NotSupportedException

            End Try
        End If
        OwnerForm = Nothing
    End Sub

    Delegate Sub SetCurrentCellData_cb(ByVal Data As Object)
    Private Sub SetCurrentCellData(ByVal Data As Object)
        If Me.InvokeRequired Then
            Dim SetCurrentCellData_cb As New SetCurrentCellData_cb(AddressOf SetCurrentCellData)
            Me.Invoke(SetCurrentCellData_cb, Data)
        Else
            'should just be able to call:
            'Me.EndEdit()
            'Me.CurrentCell.Value = Data
            '...but the above doesn't work properly ... so had to use this dodge method:
            AllowOnCellBeginEdit = False
            Me.EndEdit()
            Me.CurrentCell.Value = Data
            Me.BeginEdit(False)
            Me.NotifyCurrentCellDirty(True)
            Me.EndEdit()
            AllowOnCellBeginEdit = True
        End If
    End Sub

    Protected Overrides Sub OnCellDoubleClick(ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)
        MyBase.OnCellDoubleClick(e)
        If e.ColumnIndex <> -1 AndAlso e.RowIndex <> -1 Then
            Dim Column = Me.Columns(e.ColumnIndex)
            If Column.ValueType Is GetType(System.Drawing.Image) AndAlso AllowUIEditorCells Then
                'show editor...
                Try
                    Dim toBeValue = PropertyEditor.ShowPropertyEditor(If(Me.CurrentCell.Value Is Nothing, Column.ValueType, Me.CurrentCell.Value), Point.Empty, GetColumnOverrideTypeEditor(Column))
                    If TypeOf toBeValue Is Image Then
                        Me.Rows(e.RowIndex).Cells(e.ColumnIndex).Value = toBeValue
                    End If
                Catch ex As Exception
                    MsgBox("Error setting image: " & ex.Message, MsgBoxStyle.Exclamation)
                End Try
            End If
        End If
    End Sub

    Protected Overrides Sub OnCellEndEdit(ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)
        CancelDropDown()
        MyBase.OnCellEndEdit(e)
    End Sub

    Private Function GetColumnOverrideTypeEditor(ByVal Column As DataGridViewColumn) As System.Reflection.MemberInfo
        'for generics ... eg... list(of T) ... all of this is to use the datasources UIEditor instead of the default one...
        Dim BaseType = If(DataSource IsNot Nothing, Me.DataSource.GetType.GetGenericArguments.FirstOrDefault, Nothing)
        Dim OverrideTypeEditor As System.Reflection.MemberInfo = Nothing
        If BaseType Is Nothing Then
            'we could also be an AdvancedBindingSource :)
            If Me.SupportsFiltering = FilteringMethods.Advanced Then
                Dim AdvancedBindingSource = DirectCast(Me.DataSource, AdvancedBindingSource)
                BaseType = AdvancedBindingSource.BaseType
            End If
        End If
        If BaseType IsNot Nothing Then
            OverrideTypeEditor = BaseType.GetProperty(Column.DataPropertyName)
        End If
        Return OverrideTypeEditor
    End Function

#End Region

#Region "Nice Data Errors"

    Protected Overrides Sub OnDataError(ByVal displayErrorDialogIfNoHandler As Boolean, ByVal e As System.Windows.Forms.DataGridViewDataErrorEventArgs)
        e.ThrowException = True
        MyBase.OnDataError(displayErrorDialogIfNoHandler, e)
        If e.ThrowException Then
            If e.Exception IsNot Nothing Then
                Dim ErrorText As String = e.Exception.Message

                If e.Exception.InnerException IsNot Nothing Then
                    If TypeOf (e.Exception.InnerException) Is FormatException Then
                        ErrorText = "The value you have entered is not valid valid for this field."
                    End If
                End If

                'If MsgBox(ErrorText & vbCrLf & vbCrLf & "Do you want to revert the changes?", MsgBoxStyle.Exclamation Or MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then
                '    Me.CancelEdit()
                'End If
                MsgBox(ErrorText, MsgBoxStyle.Exclamation)

            End If
        End If
        e.ThrowException = False
    End Sub

    Private Sub DataGridView_DataError(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewDataErrorEventArgs) Handles Me.DataError
        'required for nice errors :)
    End Sub

#End Region

#Region "Cell sizing"

    ''These are to make cells size nicely to the image size...

    'Protected Overrides Sub OnColumnDividerDoubleClick(ByVal e As System.Windows.Forms.DataGridViewColumnDividerDoubleClickEventArgs)

    '    Dim ImageCells = (From xItem In MyBase.Rows.OfType(Of DataGridViewRow)() Where xItem.Cells(e.ColumnIndex).ValueType Is GetType(System.Drawing.Image) Select xItem.Cells(e.ColumnIndex)).ToList
    '    Dim MaxCellWidth = 22
    '    Dim CellWidths = (From xItem In MyBase.Rows.OfType(Of DataGridViewRow)() Where xItem.Cells(e.ColumnIndex).RowIndex <> -1 AndAlso ImageCells.Contains(xItem.Cells(e.ColumnIndex)) = False Select xItem.Cells(e.ColumnIndex).ContentBounds.Width + xItem.Cells(e.ColumnIndex).InheritedStyle.Padding.Left + xItem.Cells(e.ColumnIndex).InheritedStyle.Padding.Right)
    '    If CellWidths.Count > 0 Then MaxCellWidth = CellWidths.Max
    '    'work out the ideal image width..
    '    For Each iItem In ImageCells
    '        Dim image = TryCast(iItem.Value, Image)
    '        If image IsNot Nothing Then
    '            Dim BestFitRect = DrawingFunctions.GetBestFitRect(New RectangleF(0, 0, Single.MaxValue, iItem.Size.Height), New RectangleF(0, 0, image.Width, image.Height), DrawingFunctions.BestFitStyle.Stretch Or DrawingFunctions.BestFitStyle.DoNotAllowEnlarge)
    '            If MaxCellWidth <= CInt(BestFitRect.Width) Then MaxCellWidth = CInt(BestFitRect.Width)
    '        End If
    '    Next

    '    MyBase.Columns(e.ColumnIndex).Width = MaxCellWidth + 4
    '    'MyBase.OnColumnDividerDoubleClick(e)
    'End Sub

    'Protected Overrides Sub OnRowDividerDoubleClick(ByVal e As System.Windows.Forms.DataGridViewRowDividerDoubleClickEventArgs)

    '    Dim ImageCells = (From xItem In MyBase.Rows(e.RowIndex).Cells.OfType(Of DataGridViewCell)() Where xItem.ValueType Is GetType(System.Drawing.Image)).ToList
    '    Dim MaxCellHeight = 16
    '    Dim CellHeights = (From xItem In MyBase.Rows(e.RowIndex).Cells.OfType(Of DataGridViewCell)() Where xItem.ColumnIndex <> -1 AndAlso ImageCells.Contains(xItem) = False Select xItem.ContentBounds.Height + xItem.InheritedStyle.Padding.Top + xItem.InheritedStyle.Padding.Bottom)
    '    If CellHeights.Count > 0 Then MaxCellHeight = CellHeights.Max
    '    'work out the ideal image height..
    '    For Each iItem In ImageCells
    '        Dim image = TryCast(iItem.Value, Image)
    '        If image IsNot Nothing Then
    '            Dim BestFitRect = DrawingFunctions.GetBestFitRect(New RectangleF(0, 0, iItem.Size.Width, Single.MaxValue), New RectangleF(0, 0, image.Width, image.Height), DrawingFunctions.BestFitStyle.Stretch Or DrawingFunctions.BestFitStyle.DoNotAllowEnlarge)
    '            If MaxCellHeight <= CInt(BestFitRect.Height) Then MaxCellHeight = CInt(BestFitRect.Height)
    '        End If
    '    Next

    '    MyBase.Rows(e.RowIndex).Height = MaxCellHeight + 4
    '    'MyBase.OnRowDividerDoubleClick(e)
    'End Sub

#End Region

#Region "Widget Hit Test"

    Dim isOverWidget As Boolean = False
    Dim OverColumnIndex As Integer = -1

    Private Function ColumnSupportsSorting(ByVal Column As DataGridViewColumn) As Boolean
        If Column.SortMode = DataGridViewColumnSortMode.NotSortable Then Return False
        If Me.DataSource Is Nothing Then Return False
        If Not TypeOf Me.DataSource Is System.ComponentModel.IBindingList Then Return False
        Return True
    End Function

    'find out if we are over the widget?
    Protected Overrides Sub OnCellMouseMove(ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs)
        MyBase.OnCellMouseMove(e)
        If e.RowIndex = -1 AndAlso e.ColumnIndex <> -1 Then
            Dim Column = Me.Columns(e.ColumnIndex)
            If Column IsNot Nothing Then
                If Column.IsDataBound AndAlso (ColumnSupportsSorting(Column) OrElse Me.SupportsFiltering <> FilteringMethods.None) AndAlso Column.ValueType IsNot GetType(System.Drawing.Image) Then
                    'show the widget dropdown highlight
                    Dim isOverWidget = False
                    If e.X >= Column.Width - 16 Then
                        isOverWidget = True
                    End If
                    If Me.isOverWidget <> isOverWidget Then
                        Me.isOverWidget = isOverWidget
                        Me.InvalidateCell(Column.Index, -1)
                    End If
                Else
                    Me.isOverWidget = False
                End If
            End If
        End If
        If e.RowIndex <> -1 Then
            If Me.OverColumnIndex <> -1 Then
                Me.InvalidateCell(Me.OverColumnIndex, -1)
            End If
            OverColumnIndex = -1
        Else
            If OverColumnIndex <> e.ColumnIndex Then
                If Me.OverColumnIndex <> -1 Then
                    Me.InvalidateCell(Me.OverColumnIndex, -1)
                End If
                OverColumnIndex = e.ColumnIndex
            End If
        End If
    End Sub

#End Region

#Region "Widget Menu"

#Region "Menu"

    Private WithEvents HeaderMenu As New WidgetContextMenuStrip(Me)

    Private Class WidgetContextMenuStrip
        Inherits ContextMenuStrip

#Region "Constructor"

        Private mc_DataGridView As DataGridView
        Private mc_Column As DataGridViewColumn

        Public Sub New(ByVal DataGridView As DataGridView)
            Me.mc_DataGridView = DataGridView
            Me.Items.Add(SortHeader)
            Me.Items.Add(SortAscending)
            Me.Items.Add(SortDescending)
            Me.Items.Add(FilterHeader)
            Me.Items.Add(FilterBasic)
            Me.Items.Add(tsiFilterRemoveSeperator)
            Me.Items.Add(FilteredFields)
            Me.Items.Add(FilterRemove)
        End Sub

#End Region

#Region "Built In Items"

        Protected Overrides Sub Dispose(ByVal disposing As Boolean)
            If disposing Then
                tmrRemoveFilterRemove.Dispose()
                SortHeader.Dispose()
                SortAscending.Dispose()
                SortDescending.Dispose()
                FilterHeader.Dispose()
                FilterBasic.Dispose()
                FilterRemove.Dispose()
            End If
        End Sub

        Dim SortHeader As New MenuTextSeperator("Sort")

        Private WithEvents SortAscending As New ToolStripMenuItem("Ascending")
        Private Sub SortAscending_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SortAscending.Click
            mc_DataGridView.Sort(mc_Column, System.ComponentModel.ListSortDirection.Ascending)
        End Sub

        Private WithEvents SortDescending As New ToolStripMenuItem("Descending")
        Private Sub SortDescending_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SortDescending.Click
            mc_DataGridView.Sort(mc_Column, System.ComponentModel.ListSortDirection.Descending)
        End Sub

        Dim FilterHeader As New MenuTextSeperator("Filter")

        Private WithEvents FilterBasic As New ToolStripTextBox()
        Dim RaiseFilterBasic_TextChanged As Boolean = True
        Private Sub FilterBasic_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles FilterBasic.TextChanged
            If RaiseFilterBasic_TextChanged Then
                Select Case mc_DataGridView.SupportsFiltering()
                    Case FilteringMethods.Basic
                        'qwertyuiop
                        'NOT IMPLEMENTED
                    Case FilteringMethods.Advanced
                        Dim bs = DirectCast(mc_DataGridView.DataSource, AdvancedBindingSource)

                        'remove existing filtering on the column...
                        Dim ThisFilterItems = (From xItem In bs.BasicFilters Where xItem.Field = mc_Column.DataPropertyName).ToList
                        For Each FilterToRemove In ThisFilterItems
                            bs.BasicFilters.Remove(FilterToRemove)
                        Next

                        'add new filtering...
                        If FilterBasic.Text <> "" Then
                            bs.BasicFilters.Add(New AdvancedBindingSource.BasicFilter(mc_Column.DataPropertyName, FilterBasic.Text))
                            FilterRemove.Visible = True
                        Else
                            FilterRemove.Visible = False
                        End If
                        bs.RunBasicFilter()
                End Select
                For Each Plugin In LoadedPlugins
                    Plugin.LoadFromFilter(Nothing)
                Next
            End If
        End Sub

        Dim tsiFilterRemoveSeperator As New ToolStripSeparator

        Dim FilteredFields As New ToolStripMenuItem("") With {.Enabled = False}

        Private WithEvents FilterRemove As New PersistentToolStripMenuItem With {.Text = "Remove Filter"}
        Private Sub FilterRemove_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles FilterRemove.Click
            If RaiseFilterBasic_TextChanged Then
                Select Case mc_DataGridView.SupportsFiltering()
                    Case FilteringMethods.Basic
                        'qwertyuiop
                        'NOT IMPLEMENTED
                    Case FilteringMethods.Advanced
                        Dim bs = DirectCast(mc_DataGridView.DataSource, AdvancedBindingSource)
                        'remove existing filtering on the column...
                        If mc_Column Is Nothing Then
                            'remove all
                            bs.BasicFilters.Clear()
                        Else
                            'remove existing filtering on the column...
                            Dim ThisFilterItems = (From xItem In bs.BasicFilters Where xItem.Field = mc_Column.DataPropertyName).ToList
                            For Each FilterToRemove In ThisFilterItems
                                bs.BasicFilters.Remove(FilterToRemove)
                            Next
                        End If
                        bs.RunBasicFilter()
                End Select
                For Each Plugin In LoadedPlugins
                    Plugin.LoadFromFilter(Nothing)
                Next
                RaiseFilterBasic_TextChanged = False
                FilterBasic.Text = ""
                RaiseFilterBasic_TextChanged = True
                tmrRemoveFilterRemove.Enabled = True
            End If
        End Sub

        'this timer is used to remove the FilterRemove item
        'this is due to an issue that when clicking on a percistant menu item,
        'if the menu is no longer under the menu after the click, the menu
        'will close
        Private WithEvents tmrRemoveFilterRemove As New Timer With {.Interval = 1}

        Private Sub tmrRemoveFilterRemove_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles tmrRemoveFilterRemove.Tick
            tmrRemoveFilterRemove.Enabled = False
            FilterRemove.Visible = False
        End Sub

#End Region

        Protected Overrides Sub OnClosed(ByVal e As System.Windows.Forms.ToolStripDropDownClosedEventArgs)
            For Each CustomMenuItem In CustomMenuItems.ToArray
                Me.Items.Remove(CustomMenuItem)
            Next
            CustomMenuItems.Clear()
            MyBase.OnClosed(e)
        End Sub

        Public LoadedPlugins As New List(Of Plugins.iFilterPlugin)

        Public Overloads Sub Show(ByVal ColIndex As Integer)
            FilterRemove.Persistent = True
            FilteredFields.Visible = False
            If ColIndex = -1 Then
                For Each MenuItem In Me.Items.OfType(Of ToolStripItem)()
                    MenuItem.Visible = False
                Next
                FilterRemove.Visible = True
                mc_Column = Nothing

                FilterHeader.Visible = True

                'RaiseFilterBasic_TextChanged = False
                'If mc_DataGridView.SupportsFiltering <> FilteringMethods.None Then
                '    FilterBasic.Text = mc_DataGridView.GetFilter
                '    FilterBasic.Visible = True
                'End If
                'RaiseFilterBasic_TextChanged = True
                Select Case mc_DataGridView.SupportsFiltering
                    Case FilteringMethods.Basic
                        'qwertyuiop
                        'NOT IMPLEMENTED
                    Case FilteringMethods.Advanced
                        Dim bs = DirectCast(mc_DataGridView.DataSource, AdvancedBindingSource)

                        Dim Fields As New List(Of String)
                        For Each BasicFilter In bs.BasicFilters
                            Dim Display = BasicFilter.Field
                            Dim Prop = bs.BaseType.GetProperty(BasicFilter.Field)
                            Dim ActivePluginAttrib = Prop.GetCustomAttributes(False).OfType(Of System.ComponentModel.DisplayNameAttribute)().FirstOrDefault
                            If ActivePluginAttrib IsNot Nothing Then
                                Display = ActivePluginAttrib.DisplayName
                            End If
                            Fields.Add(Display)
                        Next
                        If Fields.Count > 0 Then
                            FilteredFields.Text = "Filtered on: " & Join(Fields.ToArray, ", ")
                            FilteredFields.Visible = True
                        End If
                    Case Else

                End Select
                FilterRemove.Persistent = False
                FilterRemove.Text = "Remove All Filters"
            Else
                mc_Column = mc_DataGridView.Columns(ColIndex)
                If mc_Column.SortMode <> DataGridViewColumnSortMode.NotSortable Then
                    SortHeader.Visible = True
                    Select Case mc_Column.HeaderCell.SortGlyphDirection
                        Case Windows.Forms.SortOrder.Ascending
                            SortAscending.Visible = False
                            SortDescending.Visible = True
                        Case Windows.Forms.SortOrder.Descending
                            SortAscending.Visible = True
                            SortDescending.Visible = False
                        Case Windows.Forms.SortOrder.None
                            SortAscending.Visible = True
                            SortDescending.Visible = True
                    End Select
                Else
                    SortHeader.Visible = False
                    SortAscending.Visible = False
                    SortDescending.Visible = False
                End If

                RaiseFilterBasic_TextChanged = False
                FilterRemove.Visible = False
                Select Case mc_DataGridView.SupportsFiltering
                    Case FilteringMethods.Basic
                        'qwertyuiop
                        'NOT IMPLEMENTED
                    Case FilteringMethods.Advanced
                        FilterHeader.Visible = True
                        FilterBasic.Visible = True
                        Dim bs = DirectCast(mc_DataGridView.DataSource, AdvancedBindingSource)
                        Dim ThisFilterItem = (From xItem In bs.BasicFilters Where xItem.Field = mc_Column.DataPropertyName).FirstOrDefault
                        If ThisFilterItem IsNot Nothing Then
                            Dim ThisBasicFilterItem = TryCast(ThisFilterItem, AdvancedBindingSource.BasicFilter)
                            If ThisBasicFilterItem IsNot Nothing Then
                                FilterBasic.Text = ThisBasicFilterItem.Filter
                            Else
                                FilterBasic.Text = ""
                            End If
                            FilterRemove.Text = "Remove Filter"
                            FilterRemove.Visible = True
                            tsiFilterRemoveSeperator.Visible = True 'need to set this as event doesn't get thrown to show it when the menu has yet to be loaded
                        Else
                            FilterBasic.Text = ""
                        End If
                        Dim UnderlyingListObjectType = bs.DataSource.GetType.GetGenericArguments().First()
                        Dim Prop = UnderlyingListObjectType.GetProperty(mc_DataGridView.Columns(ColIndex).DataPropertyName)
                        Dim Datatype = Prop.PropertyType
                        Dim AvaliablePlugins = (From xItem In Plugins.Setup.FilterPlugins Where xItem.DataTypes.Contains(Datatype))


                        Dim ActivePluginAttrib = Prop.GetCustomAttributes(False).OfType(Of i00BindingList.Plugins.ActiveFilterPluginsAttribute)()
                        Dim ActivePlugins = If(ActivePluginAttrib.Count > 0, New List(Of Type), Nothing)
                        For Each ActivePlugin In ActivePluginAttrib
                            ActivePlugins.AddRange(ActivePlugin.ActivePlugins)
                        Next

                        LoadedPlugins.Clear()
                        For Each plugin In (From xItem In AvaliablePlugins Where xItem.Dispaly = Plugins.DisplayMethods.Always OrElse _
                                                                                 (xItem.Dispaly = Plugins.DisplayMethods.DefaultShow AndAlso (ActivePlugins Is Nothing OrElse ActivePlugins.Contains(xItem.GetType))) OrElse _
                                                                                 (xItem.Dispaly = Plugins.DisplayMethods.DefaultHide AndAlso (ActivePlugins IsNot Nothing AndAlso ActivePlugins.Contains(xItem.GetType))))
                            plugin.LoadFromFilter(ThisFilterItem)
                            Dim FilterEventArgs As New FilterEventArgs With {.Cancel = False, .Column = mc_Column, .Plugin = plugin}
                            mc_DataGridView.OnAddingFilterPlugin(FilterEventArgs)
                            If FilterEventArgs.Cancel = False Then
                                RemoveHandler plugin.UpdateFilter, AddressOf Plugin_UpdateFilter
                                AddHandler plugin.UpdateFilter, AddressOf Plugin_UpdateFilter
                                LoadedPlugins.Add(plugin)
                                CustomMenuItems.Add(New ToolStripSeparator)
                                For Each MenuItemToAdd In plugin.MenuItems
                                    CustomMenuItems.Add(MenuItemToAdd)
                                Next
                            End If
                        Next
                        Me.Items.AddRange(CustomMenuItems.ToArray)

                        Me.Items.Remove(tsiFilterRemoveSeperator)
                        Me.Items.Add(tsiFilterRemoveSeperator)
                        Me.Items.Remove(FilterRemove)
                        Me.Items.Add(FilterRemove)

                    Case Else
                        FilterHeader.Visible = False
                        FilterBasic.Visible = False
                End Select
                RaiseFilterBasic_TextChanged = True


            End If
            Me.Show(mc_DataGridView, New Point(mc_DataGridView.GetCellDisplayRectangle(ColIndex, -1, True).Right - 16, mc_DataGridView.ColumnHeadersHeight))
        End Sub

        Private Sub Plugin_UpdateFilter(ByVal sender As Object, ByVal e As Plugins.UpdateFilterPluginEventArgs)
            If RaiseFilterBasic_TextChanged Then
                'clear any other plugins 1st
                For Each plugin In LoadedPlugins
                    If plugin IsNot sender Then
                        plugin.LoadFromFilter(Nothing)
                    End If
                Next
                Select Case mc_DataGridView.SupportsFiltering()
                    Case FilteringMethods.Basic
                        'qwertyuiop
                        'NOT IMPLEMENTED
                    Case FilteringMethods.Advanced
                        Dim bs = DirectCast(mc_DataGridView.DataSource, AdvancedBindingSource)

                        'remove existing filtering on the column...
                        Dim ThisFilterItems = (From xItem In bs.BasicFilters Where xItem.Field = mc_Column.DataPropertyName).ToList
                        For Each FilterToRemove In ThisFilterItems
                            bs.BasicFilters.Remove(FilterToRemove)
                        Next

                        'add new filtering...
                        If e.FilterBase IsNot Nothing Then
                            bs.BasicFilters.Add(e.FilterBase)
                            e.FilterBase.Field = mc_Column.DataPropertyName
                            FilterRemove.Visible = True
                        Else
                            FilterRemove.Visible = False
                        End If
                        bs.RunBasicFilter()
                End Select
                RaiseFilterBasic_TextChanged = False
                FilterBasic.Text = ""
                RaiseFilterBasic_TextChanged = True
            End If

        End Sub

        Dim CustomMenuItems As New List(Of ToolStripItem)

        Private Sub FilterRemove_VisibleChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles FilterRemove.VisibleChanged
            tsiFilterRemoveSeperator.Visible = FilterRemove.Visible
        End Sub
    End Class

#End Region

    Protected Overrides Sub OnColumnHeaderMouseClick(ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs)
        If isOverWidget AndAlso e.ColumnIndex <> -1 Then
            HeaderMenu.Show(e.ColumnIndex)
        Else
            MyBase.OnColumnHeaderMouseClick(e)
        End If
    End Sub


    Protected Overrides Sub OnCellMouseClick(ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs)
        If e.ColumnIndex = -1 AndAlso e.RowIndex = -1 AndAlso e.Button = Windows.Forms.MouseButtons.Right AndAlso Me.GetFilter <> "" Then
            'remove filter
            HeaderMenu.Show(e.ColumnIndex)
        Else
            MyBase.OnCellMouseClick(e)
        End If
    End Sub

#End Region

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            HeaderMenu.Dispose()
        End If
        MyBase.Dispose(disposing)
    End Sub

    Private Sub DataGridView_RowValidating(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellCancelEventArgs) Handles Me.RowValidating
        If Me.IsCurrentRowDirty Then
            'e.Cancel = True
        End If
    End Sub

End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
i00
Software Developer (Senior) i00 Productions
Australia Australia
I hope you enjoy my code. It's yours to use for free, but if you do wish to say thank you then a donation is always appreciated.
You can donate here.

Comments and Discussions