Results 1 to 11 of 11

Thread: programatically click column header of a Datagrid

  1. #1
    Holly Max is offline VB.NET Forum Newbie
    .NET Framework
    .NET 1.1 (VS 2003)
    Join Date
    Nov 2006
    Posts
    6
    Reputation
    0

    programatically click column header of a Datagrid

    I need to sort a datagrid control by programatically clicking the column header of a Datagrid. I have seen code in C# that uses the datagrid's private method ColumnHeaderClicked, but this is beyond me.
    My problem is that if I use the .DefaultView.Sort I loose the binding I need to text boxes for updating records ect.
    If I manually click the header of the column everything is fine.
    Any help would be appreciated or if you need more info I will do this.

    Holly Max

  2. #2
    vis781's Avatar
    vis781 is offline VB.NET Forum All-Mighty
    .NET Framework
    .NET 4.0
    Join Date
    Aug 2005
    Location
    Cambridge, UK
    Posts
    2,015
    Reputation
    261
    have seen code in C# that uses the datagrid's private method ColumnHeaderClicked, but this is beyond me.
    I'm a little confused by the above statement. How could anyone directly use a private method? Fact is you can't because it's private. Have you got this c# code to hand and i'll translate it for you. Also why are there a couple of links to the experts exchange in your post???
    GDI+ Code Generation Utility : GDI+ Architect

    A Must : FxCop .Net Code analyzer : FxCop

  3. #3
    Holly Max is offline VB.NET Forum Newbie
    .NET Framework
    .NET 1.1 (VS 2003)
    Join Date
    Nov 2006
    Posts
    6
    Reputation
    0
    This is the whole article. Hope this helps you.
    "This code sorts a Windows.Forms.DataGrid programmatically, "emulating" a header click"
    Introduction
    This article shows, how to programmatically sort a System.Windows.Forms.DataGrid. In other words, how to emulate a "click" on a column header.
    Typically, to sort a DataGrid, we call the DataView.Sort method of an underlying DataView. But what if our DataGrid is bound to a custom datasource? In this case, there's no DataView under our DataGrid!
    Background
    I was writing an application and the task was to "remember" the way DataGrids are sorted. So I needed to save my sortings to the Windows registry and restore it each time my application starts.
    The problem was: my DataGrid was bound to a custom collection, no DataViews or DataTables.
    Sorting Basics
    If your DataGrid is bound to some IList collection and you want your DataGrid to support sorting - your collection must implement IBindingList interface. This interface contains a method ApplySort() among others. When a user clicks a column header, DataGrid calls the datasource's method ApplySort().
    ApplySort() method takes two arguments: property and direction.
    void IBindingList.ApplySort(PropertyDescriptor property,
    ListSortDirection direction)
    Each column of your DataGrid represents some property of an underlying object. And you have to pass this property to ApplySort() method.
    Emulate column header click
    I've discovered that simply calling ApplySort() doesn't work. Calling DataGrid's OnMouseDown() protected method with MouseEventArgs pointed to a column header also doesn't work!
    So, I used System.Reflection to look deep inside the DataGrid class and see what happens when a user clicks a header. In the list of DataGrid's private members, I've found a private method ColumnHeaderClicked(). See what I mean? Bingo.
    This method is defined as follows:
    private void ColumnHeaderClicked(PropertyDescriptor prop)
    So, if we want to sort column number 5, we have to determine the underlying PropertyDescriptor for this column and pass this PropertyDescriptor to ColumnHeaderClicked method. This can be done in two ways.
    * Calling for a GridColumnStyle.PropertyDescriptor property of a grid column. But this property sometimes returns null if our grid is bound to a custom collection through MappingNames.
    * If our DataGrid is bound to a custom collection MyCollection, we typically create a TableStyle and some GridColumnStyles and assign its MappingNames. TableStyle's MappingName would be "MyCollection" and ColumnStyle's MappingName would contain the name of some property this column displays. So, we can get the PropertyDescriptor object by seeking for the property with the name, equal to the column's MappingName.
    After we have a PropertyDescriptor object, we simply invoke a private method using System.Reflection.
    Now the code:
    Collapse
    public class MyDataGrid : DataGrid
    {
    //sort a column by its index
    public void SortColumn(int columnIndex)
    {
    if(this.DataSource!=null &&
    ((System.Collections.IList)this.DataSource).Count> 0)
    {
    //discover the TYPE of underlying objects
    Type sourceType = ((System.Collections.IList)this.DataSource)[0].GetType();
    //get the PropertyDescriptor for a sorted column
    //assume TableStyles[0] is used for our datagrid... (change it if necessary)
    System.ComponentModel.PropertyDescriptor pd =
    this.TableStyles[0].GridColumnStyles[columnIndex].PropertyDescriptor;
    //if the above line of code didn't work try to get a propertydescriptor
    // via MappingName
    if(pd == null)
    {
    System.ComponentModel.PropertyDescriptorCollection pdc =
    System.ComponentModel.TypeDescriptor.GetProperties (sourceType);
    pd =
    pdc.Find( this.TableStyles[0].GridColumnStyles[columnIndex].MappingName,
    false);
    }
    //now invoke ColumnHeaderClicked method using system.reflection tools
    System.Reflection.MethodInfo mi =
    typeof(System.Windows.Forms.DataGrid).GetMethod("C olumnHeaderClicked",
    System.Reflection.BindingFlags.Instance |
    System.Reflection.BindingFlags.NonPublic);
    mi.Invoke(this, new object[] { pd });
    }
    }
    }
    That's it.

  4. #4
    JohnH's Avatar
    JohnH is offline VB.NET Forum Moderator
    .NET Framework
    .NET 4.0
    Join Date
    Dec 2005
    Location
    Norway
    Posts
    14,457
    Reputation
    2727
    Running that code through Developer Fusions converter give this VB.Net code (in box). I haven't tested it, but it 'looks' right It's an inherited control from DataGrid, to be used instead of the regular DataGrid control. This 'MyDataGrid' control then expose the public SortColumn method that should work as if the column header was clicked (according to the description).
    Code:
    Public Class MyDataGrid 
    Inherits DataGrid 
     
    Public Sub SortColumn(ByVal columnIndex As Integer) 
    If Not (Me.DataSource Is Nothing) AndAlso CType(Me.DataSource, System.Collections.IList).Count > 0 Then 
     Dim sourceType As Type = CType(Me.DataSource, System.Collections.IList)(0).GetType 
     Dim pd As System.ComponentModel.PropertyDescriptor = _
       Me.TableStyles(0).GridColumnStyles(columnIndex).PropertyDescriptor 
     If pd Is Nothing Then 
       Dim pdc As System.ComponentModel.PropertyDescriptorCollection = _
         System.ComponentModel.TypeDescriptor.GetProperties(sourceType) 
       pd = pdc.Find(Me.TableStyles(0).GridColumnStyles(columnIndex).MappingName, False) 
     End If 
     Dim mi As System.Reflection.MethodInfo = _
       GetType(System.Windows.Forms.DataGrid).GetMethod("ColumnHeaderClicked", _
       System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic) 
     mi.Invoke(Me, New Object() {pd}) 
    End If 
    End Sub 
    End Class

  5. #5
    vis781's Avatar
    vis781 is offline VB.NET Forum All-Mighty
    .NET Framework
    .NET 4.0
    Join Date
    Aug 2005
    Location
    Cambridge, UK
    Posts
    2,015
    Reputation
    261
    Code:
     
    Public Class MyDataGrid : Inherits DataGrid
    'sort a column by its index
    Public Sub SortColumn(ByVal columnIndex As Integer)
    If Not Me.DataSource Is Nothing AndAlso (CType(Me.DataSource, System.Collections.IList)).Count> 0 Then
    'discover the TYPE of underlying objects
    Dim sourceType As Type = (CType(Me.DataSource, System.Collections.IList))(0).GetType()
    'get the PropertyDescriptor for a sorted column
    'assume TableStyles[0] is used for our datagrid... (change it if necessary)
    Dim pd As System.ComponentModel.PropertyDescriptor = Me.TableStyles(0).GridColumnStyles(columnIndex).PropertyDescriptor
    'if the above line of code didn't work try to get a propertydescriptor
    ' via MappingName
    If pd Is Nothing Then
    Dim pdc As System.ComponentModel.PropertyDescriptorCollection = System.ComponentModel.TypeDescriptor.GetProperties (sourceType)
    pd = pdc.Find(Me.TableStyles(0).GridColumnStyles(columnIndex).MappingName, False)
    End If
    'now invoke ColumnHeaderClicked method using system.reflection tools
    Dim mi As System.Reflection.MethodInfo = GetType(System.Windows.Forms.DataGrid).GetMethod("C olumnHeaderClicked", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)
    mi.Invoke(Me, New Object() { pd })
    End If
    End Sub
    End Class
    Ok, here's that in vb.net. So basically it's just a class that inherits from the datagrid. So rather than using the standard datagrid in your app you would use this one instead which has the new method that you need. If you are using 2005 then you may be able to accomplish all this with a partial class. but it will work just fine as it is.
    GDI+ Code Generation Utility : GDI+ Architect

    A Must : FxCop .Net Code analyzer : FxCop

  6. #6
    Holly Max is offline VB.NET Forum Newbie
    .NET Framework
    .NET 1.1 (VS 2003)
    Join Date
    Nov 2006
    Posts
    6
    Reputation
    0
    Thanks for the help so far. Now is this code used as a User Control, or how do I replace the standard datagrid with MyDataGrid (or make the reference to this new control)?

    I know you haven't tested it but can you see any problems using this type of method to acheive what I am trying to do.
    I basically need for the datagrid columns to be able to be sorted without using the .DefaultView.Sort method which looses the binding to the text boxes I need. Having the Columns clicked (or clicking the columns) maintains the binding.

    Holly Max

  7. #7
    Holly Max is offline VB.NET Forum Newbie
    .NET Framework
    .NET 1.1 (VS 2003)
    Join Date
    Nov 2006
    Posts
    6
    Reputation
    0
    Also I didn't post the C# code laid out in case I missed part of the code in amongst the article. That's why I posted the whole thing as it was. I've never done any C# coding. I have read some and can sometimes follow what the vb.net equivalent is or should be.

  8. #8
    Holly Max is offline VB.NET Forum Newbie
    .NET Framework
    .NET 1.1 (VS 2003)
    Join Date
    Nov 2006
    Posts
    6
    Reputation
    0
    OK this is the Custom Control I created.
    Using the line
    MyDataGrid.SortColumn(1)
    I get the following error

    An unhandled exception of type 'System.InvalidCastException' occurred in mydatagrid.dll
    Additional information: Specified cast is not valid.
    Custom Control code
    Code:
    PublicClass MyDataGrid
    Inherits DataGrid
    'Inherits System.Windows.Forms.DataGrid
    #Region " Windows Form Designer generated code "
    PublicSubNew()
    MyBase.New()
    'This call is required by the Windows Form Designer.
    InitializeComponent()
    'Add any initialization after the InitializeComponent() call
    EndSub
    'UserControl1 overrides dispose to clean up the component list.
    ProtectedOverloadsOverridesSub Dispose(ByVal disposing AsBoolean)
    If disposing Then
    IfNot (components IsNothing) Then
    components.Dispose()
    EndIf
    EndIf
    MyBase.Dispose(disposing)
    EndSub
    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer
    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer. 
    'Do not modify it using the code editor.
    FriendWithEvents DataGrid As System.Windows.Forms.DataGrid
    <System.Diagnostics.DebuggerStepThrough()> PrivateSub InitializeComponent()
    Me.DataGrid = New System.Windows.Forms.DataGrid
    CType(Me.DataGrid, System.ComponentModel.ISupportInitialize).BeginInit()
    CType(Me, System.ComponentModel.ISupportInitialize).BeginInit()
    '
    'DataGrid
    '
    Me.DataGrid.DataMember = ""
    Me.DataGrid.HeaderForeColor = System.Drawing.SystemColors.ControlText
    Me.DataGrid.Location = New System.Drawing.Point(17, 17)
    Me.DataGrid.Name = "DataGrid"
    Me.DataGrid.TabIndex = 0
    CType(Me.DataGrid, System.ComponentModel.ISupportInitialize).EndInit()
    CType(Me, System.ComponentModel.ISupportInitialize).EndInit()
    EndSub
    #EndRegion
    'sort a column by its index
    PublicSub SortColumn(ByVal columnIndex AsInteger)
    IfNotMe.DataSource IsNothingAndAlso (CType(Me.DataSource, System.Collections.IList)).Count > 0 Then
    'discover the TYPE of underlying objects
    Dim sourceType As Type = (CType(Me.DataSource, System.Collections.IList))(0).GetType()
    'get the PropertyDescriptor for a sorted column
    'assume TableStyles[0] is used for our datagrid... (change it if necessary)
    Dim pd As System.ComponentModel.PropertyDescriptor = Me.TableStyles(0).GridColumnStyles(columnIndex).PropertyDescriptor
    'if the above line of code didn't work try to get a propertydescriptor
    ' via MappingName
    If pd IsNothingThen
    Dim pdc As System.ComponentModel.PropertyDescriptorCollection = System.ComponentModel.TypeDescriptor.GetProperties(sourceType)
    pd = pdc.Find(Me.TableStyles(0).GridColumnStyles(columnIndex).MappingName, False)
    EndIf
    'now invoke ColumnHeaderClicked method using system.reflection tools
    Dim mi As System.Reflection.MethodInfo = GetType(System.Windows.Forms.DataGrid).GetMethod("ColumnHeaderClicked", _
    System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)
    mi.Invoke(Me, NewObject() {pd})
    EndIf
    EndSub
    EndClass

  9. #9
    JohnH's Avatar
    JohnH is offline VB.NET Forum Moderator
    .NET Framework
    .NET 4.0
    Join Date
    Dec 2005
    Location
    Norway
    Posts
    14,457
    Reputation
    2727
    The code you posted in the #Region doesn't belong there, but it don't affect this request.

    Here is a modification of the inherited Datagrid class, I have tested it and it works for sorting programmatically.
    Code:
    Public Class MyDataGrid
        Inherits DataGrid
     
        Public Sub SortColumn(ByVal columnIndex As Integer)
            If Not Me.DataSource Is Nothing Then 
                Dim pd As System.ComponentModel.PropertyDescriptor
                pd = Me.TableStyles(0).GridColumnStyles(columnIndex).PropertyDescriptor
                Dim mi As System.Reflection.MethodInfo
                mi = GetType(DataGrid).GetMethod("ColumnHeaderClicked", _
                    System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)
                mi.Invoke(Me, New Object() {pd})
            End If
        End Sub
    End Class
    Here is example of usage, do take note that the databound Datagrid doesn't have a TableStyles by default so you must create one and add it if you don't already have one - the sort method use this to get the PropertyDescriptor which is passed to the private ColumnHeaderClicked method.
    Code:
    Private WithEvents x As New MyDataGrid
     
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
    Handles MyBase.Load
        Me.Controls.Add(x)
        Dim ds As New DataSet
        ds.ReadXml("somedata.xml")
        Dim ts As New DataGridTableStyle
        ts.MappingName = ds.Tables(2).TableName
        x.TableStyles.Add(ts)
        x.DataSource = ds.Tables(2)
        x.Dock = DockStyle.Fill
    End Sub
     
    Private Sub MenuItem1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
    Handles MenuItem1.Click
        x.SortColumn(2)
    End Sub

  10. #10
    Holly Max is offline VB.NET Forum Newbie
    .NET Framework
    .NET 1.1 (VS 2003)
    Join Date
    Nov 2006
    Posts
    6
    Reputation
    0
    Thank you JohnH. Finally got it working along with the rest of my code.
    The last code I posted was for a Custom User Control, which I realise I don't need now you have given me an example to explain it.

    Thank you again for the help and also vis781

    Holly Max

  11. #11
    cjard's Avatar
    cjard is offline VB.NET Forum All-Mighty
    .NET Framework
    .NET 4.0
    Join Date
    Apr 2006
    Posts
    7,036
    Reputation
    1716
    Quote Originally Posted by Holly Max View Post
    Also I didn't post the C# code laid out in case I missed part of the code in amongst the article. That's why I posted the whole thing as it was. I've never done any C# coding. I have read some and can sometimes follow what the vb.net equivalent is or should be.

    Just compile the c# project and add a reference to the compiled result, to your project - your VB code can use C# components because when compiled they all use the same low level language

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Harvest time tracking