שתף באמצעות


How to Anchor a button to Datagridview column header

Question

Saturday, March 3, 2018 10:51 AM

Hi 

I have a Datagridview and I add a buttom to a column header

I would like to anchor this buttom to the right of the column as the column is  resized

This is my code

Public Sub Addbuttom(ByRef DGV As DataGridView)
        Dim btn As New Button
        btn.BackColor = Color.Red
        btn.Text = "F"
        btn.Height = 20
        btn.Width = 20
        Dim r As Rectangle = DGV.GetCellDisplayRectangle(3, -1, False)
        Dim btnx As Integer = r.X + r.Width - 30
        Dim btny As Integer = (r.Y / 2) + 8

        Dim btnloc As New Point(btnx, btny)
        btn.Location = btnloc
        DGV.Controls.Add(btn)
    End Sub

How to do that ?

Thanks

Claudio

All replies (23)

Saturday, March 3, 2018 11:58 AM | 1 vote

Hi

Using the same code as previous thread, but altered to anchor button1 to right of column header.

  Dim started As Boolean = False

  Private Sub Form1_Resize(sender As Object, e As EventArgs) Handles MyBase.Resize
    If Not started Then Exit Sub
    Dim r As Rectangle = DataGridView1.GetCellDisplayRectangle(3, -1, False)
    Button1.Location = New Point(r.Right - Button1.Width, r.Top)
  End Sub

  Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
    started = True
  End Sub

Regards Les, Livingston, Scotland


Saturday, March 3, 2018 12:16 PM

ah ok,

one more think

What to do to delete the button for each column header ?

I have a Button (filter) on the form 

First time i click the Filter button ,  I show several buttons for each column header

If I click again the Filter button I want to delete the buttons from each column header 


Saturday, March 3, 2018 12:28 PM

Hi Leshay

The anchor does not work if the user resize form or Column

The button stay in the same position and do not follow the column header right margin


Saturday, March 3, 2018 12:31 PM

Hi

You say you want to delete the buttons, maybe, rather than delete you could set the Button.Visible to False instead.

You could have a Boolean Flag which you toggle in the Filter and in the Form.Resize (or where ever you are setting the Button Locations), set the Button.Visible = Flag (show or not show).

Regards Les, Livingston, Scotland


Saturday, March 3, 2018 12:46 PM

What are the buttons for?

Please remember to mark the replies as answers if they help and unmark them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.
VB Forums - moderator


Saturday, March 3, 2018 1:35 PM

Thanks Leshay, buttom Visible problem solved !


Saturday, March 3, 2018 1:48 PM

Hi Karen

I will use each button on column header to filer trows, as the image below

Now I still have the proble how to anchor the  button to the right of each column header


Saturday, March 3, 2018 2:21 PM

Hi

EDIT: ignore my posts, I misunderstood your aims.

Here is a different approach. Try this as a stand alone new test project and see if it works as you need. It uses 3 buttons for Header cell display (1.2 and3) and Button22 to toggle Button2 on/off. Each of the buttons for header display need their .Tag property set to "hc".

Designer

Code

Option Strict On
Option Explicit On
Public Class Form1
  Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    With DataGridView1
      .Location = New Point(10, 10)
      .Size = New Size(ClientSize.Width - 60, ClientSize.Height - 50)
      .Anchor = AnchorStyles.Bottom Or AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top
    End With
  End Sub
  Private Sub DataGridView1_CellPainting(ByVal sender As System.Object, ByVal e As DataGridViewCellPaintingEventArgs) Handles DataGridView1.CellPainting
    If e.RowIndex = -1 AndAlso e.ColumnIndex = 3 Then
      Dim x As Integer = 0
      For Each c As Control In Controls
        If c.Visible AndAlso c.Tag Is "hc" Then
          c.Location = New Point(DataGridView1.Left + e.CellBounds.Right - x - c.Width, DataGridView1.Top + e.CellBounds.Top + 2)
          x += c.Width
        End If
      Next
    End If
  End Sub
  Private Sub Button22_Click(sender As Object, e As EventArgs) Handles Button22.Click

    ' here, I toggle Button2 only - you can
    'toggle any/all as needed here.
    Button2.Visible = Not Button2.Visible
    DataGridView1.Invalidate()
  End Sub
End Class

Regards Les, Livingston, Scotland


Saturday, March 3, 2018 3:02 PM

An alternate (which I have used in a app I created in 2003 and still in use) is the following which requires two (or is it three) lines of code.

Here is vb.net my demo back from 2013. The demo does some basic and advance filtering.

Please remember to mark the replies as answers if they help and unmark them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.
VB Forums - moderator


Saturday, March 3, 2018 3:49 PM

Karen I know this solution and i will use it later.

The difference is that it use a Mouse right click to open the filter Panels

I would like to use a button on the column header instead of If I use the the Mouse right click

So now I dont know which event to use to re-locate the buttons

If I use the ColumnWidthChanged i cannot get the header width and hight that I need to re-calculate the button position


Saturday, March 3, 2018 4:13 PM

HI Les

The problem is that the CellPainting event is raised for each cell of dgv.

So with 10.000 rows and 20 columns I get 200.000 calls to the event

mmmmmm not too good ! ;.(


Saturday, March 3, 2018 4:35 PM

HI Les

The problem is that the CellPainting event is raised for each cell of dgv.

So with 10.000 rows and 20 columns I get 200.000 calls to the event

mmmmmm not too good ! ;.(

Hi

Well, the cell paint event is called anyway as the system paints the cells (visible cells),and, the code I posted only intercepts the column header row, and only on selected column indexes.

So, not so bad after all.

Regards Les, Livingston, Scotland


Saturday, March 3, 2018 5:50 PM

Okay. In the mean time I tinkered with a custom DataGridView as shown below, the buttons are added in the custom DataGridView. Press the filter button (at this time) displays a text box to enter a filter. 

Not worth sharing especially since this is in C#. If you don't get a solution I will look at this in a week as I'm heading to Microsoft all next week and will not have much time to play with this.

Please remember to mark the replies as answers if they help and unmark them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.
VB Forums - moderator


Saturday, March 3, 2018 7:11 PM

You can create a custom header cell which contains a button.  This will allow you to maintain the sort glyph and will handle things like horizontal scrolling when the columns exceed the display area.  Here is an example of how you can go about doing this.

Public Class DataGridViewButtonHeaderCell
    Inherits DataGridViewColumnHeaderCell

    Public Event HeaderButtonClick As EventHandler

    ' button size will be column height minus this amount, square
    Public Property ButtonOffset As Integer = 2

    Private buttonBounds As Rectangle 'store button bounds
    Private isMouseDown As Boolean 'track mouse down over button for drawing button state
    Private lastSize As Size 'track column size to ignore size changes
    Private sortDirection As ComponentModel.ListSortDirection 'track sort direction for displaying sort gylph

    ' helper method to determine if button is clicked
    Private Function IsPointInButtonBounds(value As Point, columnIndex As Integer) As Boolean
        Return buttonBounds.Contains(value + Me.DataGridView.GetColumnDisplayRectangle(columnIndex, True).Location - New Point(Me.DataGridView.HorizontalScrollingOffset, 0))
    End Function

    Protected Overrides Sub OnDataGridViewChanged()
        MyBase.OnDataGridViewChanged()
        ' need to programmaticaly control sorting so that clicking the button doesn't cause a sort
        If Me.DataGridView IsNot Nothing Then
            Me.DataGridView.Columns.Item(Me.ColumnIndex).SortMode = DataGridViewColumnSortMode.Programmatic
            AddHandler Me.DataGridView.Sorted, Sub(sender As Object, e As EventArgs)
                                                   If sender Is Me.DataGridView Then
                                                       If Me.DataGridView.SortedColumn IsNot Me.DataGridView.Columns.Item(Me.ColumnIndex) Then
                                                           Me.SortGlyphDirection = SortOrder.None
                                                       End If
                                                   End If
                                               End Sub
            lastSize = Me.Size
        End If
    End Sub

    ' provide an event to handle for the custom button click
    Protected Overridable Sub OnHeaderButtonClick(e As EventArgs)
        RaiseEvent HeaderButtonClick(Me, e)
    End Sub

    ' determine if button is being clicked and redraw cell
    Protected Overrides Sub OnMouseDown(e As DataGridViewCellMouseEventArgs)
        MyBase.OnMouseDown(e)
        isMouseDown = IsPointInButtonBounds(e.Location, e.ColumnIndex)
        Me.DataGridView.InvalidateCell(Me)
    End Sub

    Protected Overrides Sub OnMouseUp(e As DataGridViewCellMouseEventArgs)
        MyBase.OnMouseUp(e)
        If Size = lastSize Then 'ignore column size changes
            ' if button is being clicked, raise the click event
            If IsPointInButtonBounds(e.Location, e.ColumnIndex) Then
                OnHeaderButtonClick(EventArgs.Empty)
            Else
                ' otherwise sort the column
                Dim column = Me.DataGridView.Columns.Item(e.ColumnIndex)
                Dim direction As ComponentModel.ListSortDirection = System.ComponentModel.ListSortDirection.Ascending
                If Me.DataGridView.SortedColumn Is column Then direction = If(sortDirection = System.ComponentModel.ListSortDirection.Ascending, ComponentModel.ListSortDirection.Descending, ComponentModel.ListSortDirection.Ascending)
                Me.DataGridView.Sort(column, direction)
                Me.SortGlyphDirection = If(direction = System.ComponentModel.ListSortDirection.Ascending, SortOrder.Ascending, SortOrder.Descending)
                sortDirection = direction
            End If
        End If

        isMouseDown = False
        Me.DataGridView.InvalidateCell(Me)
    End Sub

    Protected Overrides Sub Paint(graphics As Graphics, clipBounds As Rectangle, cellBounds As Rectangle, rowIndex As Integer, elementState As DataGridViewElementStates, value As Object, formattedValue As Object, errorText As String, cellStyle As DataGridViewCellStyle, advancedBorderStyle As DataGridViewAdvancedBorderStyle, paintParts As DataGridViewPaintParts)
        ' store the current button size
        lastSize = Size

        ' calculate the button bounds
        buttonBounds = New Rectangle(cellBounds.Right - cellBounds.Height, cellBounds.Top + ButtonOffset, cellBounds.Height - ButtonOffset * 2, cellBounds.Height - ButtonOffset * 2)

        ' paint background and border to erase previous contents
        MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, DataGridViewPaintParts.Background Or DataGridViewPaintParts.Border)

        ' paint custom button
        ControlPaint.DrawButton(graphics, buttonBounds, If(isMouseDown, ButtonState.Pushed, ButtonState.Normal))
        'TODO: draw something in the button if desired

        'resize cell to exclude button area
        cellBounds.Width -= buttonBounds.Width + ButtonOffset
        'perform default painting of the header cell within the modified bounds
        MyBase.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts)
    End Sub
End Class

When the form loads you can assign an instance of this custom header cell to whatever columns you choose:

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    ' choose a column
    Dim column = DataGridView1.Columns.Item(0)
    ' create a new custom button header cell
    Dim headerCell As New DataGridViewButtonHeaderCell
    ' handle the custom button's click event
    AddHandler headerCell.HeaderButtonClick, Sub(_sender As Object, _e As EventArgs)
                                                 ' do something when the button is clicked
                                                 MessageBox.Show("button clicked")
                                             End Sub
    ' set the custom header cell as the column header
    column.HeaderCell = headerCell
End Sub

Reed Kimble - "When you do things right, people won't be sure you've done anything at all"


Saturday, March 3, 2018 7:16 PM

Hi Les

you are right.

I changed a little bit the code as follows. It is supposed that every button has a numeric Tag = column index (that can be set in the DGV1.Controls.Add(btn) )

Private Sub DGV1_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles DGV1.CellPainting
        If e.RowIndex = -1 And e.ColumnIndex = 0 Then
            For Each c As Control In DGV1.Controls
                If c.GetType = GetType(Button) Then
                    Dim index As Integer = c.Tag
                    Dim r As Rectangle = DGV1.GetCellDisplayRectangle(index, -1, False)
                    Dim btnx As Integer = r.Right - c.Width
                    Dim btny As Integer = (r.Y / 2) + 8
                    Dim btnloc As New Point(btnx, btny)
                    c.Location = btnloc
                End If
            Next
        End If
    End Sub

One more thing. Do you know which event is raised when two columns are splitted ?

Thanks for your help anyway !


Saturday, March 3, 2018 7:39 PM

Hi Karen

I will be glad if you will send me the  sample of Custom Datagridview with the buttoms  when you will have time to do it

Thanks a Lot

Claudio


Saturday, March 3, 2018 7:53 PM

One more thing. Do you know which event is raised when two columns are splitted ?

Thanks for your help anyway !

Hi

Do you mean when column(s) are Frozen or not?

If that is what you mean, then you must have (say) a button, or perhaps a ContextMenu item that you use to Toggle the Column.Frozen state - in which case, the state can be examined and code called for either Froze/UnFrozen state.

Can't help thinking you mean something else though.

Regards Les, Livingston, Scotland


Saturday, March 3, 2018 8:08 PM

I mean when I move a column before another (of after)

if the AllowsUerToOrderColumns = true

What event is raised when this appens ?


Saturday, March 3, 2018 8:32 PM

I mean when I move a column before another (of after)

if the AllowsUerToOrderColumns = true

What event is raised when this appens ?

Hi

I was correct - you did mean something different :)

Private Sub DataGridView1_ColumnDisplayIndexChanged(sender As Object, e As DataGridViewColumnEventArgs) Handles DataGridView1.ColumnDisplayIndexChanged
    Stop
  End Sub

Regards Les, Livingston, Scotland


Saturday, March 3, 2018 10:01 PM

The column index does not change if the user exchange the position do two columns

ColumnDisplayIndexChanged

is not the  right event to check :-(((

thank you Les


Saturday, March 3, 2018 11:15 PM

The column index does not change if the user exchange the position do two columns

ColumnDisplayIndexChanged

is not the  right event to check :-(((

thank you Les

Hi

Pardon me! You said nothing about what you wanted other than an event that is raised 'when I move a column before another (of after)'

Do  you really think that we are mind readers in this forum?

Using that event, even though the Column Index remains the same, you get the new DisplayIndex.

For example:

Column(1)      Text = "Column1"      DisplayIndex=1

after move one column to right,

Column(1)      Text = "Column1"      DisplayIndex=2

Isn't that what you need?

Furthermore, just think of the consequences of changing the ColumnIndex based on users shifting columns all over the place - how do you propose that the cell addresses of the data is tracked?

Regards Les, Livingston, Scotland


Sunday, March 4, 2018 4:24 PM

Hi Reed

It seems a good solution

I have to study it and test it

Thank you so much

Claudio


Sunday, March 4, 2018 6:06 PM

Hi Reed

one more thing

I set the DataGridViewButtonHeaderCell  for each column by a cliking a Button in the Form (Let us call it Btn1) and then loop through all columns in the dgv

I use your code in this way and everything is ok.

For Each col As DataGridViewColumn In DGV.Columns 

    Dim HeaderCell = New DataGridViewButtonHeaderCell
        AddHandler HeaderCell.HeaderButtonClick,
        Sub(_sender As Object, _e As EventArgs)
                                        
                MessageBox.Show("button clicked" & _e.ToString & " " & _sender.ToString)

            End Sub

                
    col.HeaderCell = HeaderCell
Next   

Now clicking BTN1 again I would like to go back to standard

DataGridViewColumnHeaderCell ( The headercell without the buttons) and I use this code

For Each col As DataGridViewColumn In DGV.Columns
                
    col.HeaderCell = New DataGridViewColumnHeaderCell

Next

I get just one problem

When I pass from ColumnHeaderCell to ButtonHeaderCell the  Column.HeaderTexts I set by designer in the DGV, change to DesignName and this of course is a problem

How to keep the Column.HeaderText ?

Thank you for help 

Claudio