WΛЯИING's blog

The deepest secrets of Visual Basic ...

C#: Adding many SortField to a CrystalReportViewer using reflection

April 9, 2009 22:16 by WΛЯИІNG

I'm currently working on a project for a client which consists in translating an important management application initially developed in VB6, into C# 3.5, by using decompilation since the client lost his source code. This is my first .Net related post.

The application uses many external components, including the powerful Crystal Report component.

During the development of that new version I faced a problem with the .Net implementation of Crystal Report.

Actually, with Visual Basic 6.0 it was an easy task to add one or many sort fields to the report document in order to sort the result by field (for example by "Lastname", "Firstname", and "ID").

It's not the case with .Net implementation of Crystal Report Document, on which you cannot dynamically add sort field to document. You must add the sort field when you're editing the document. It's not really convenient when you don't know how many fields must be sorted when creating the document.

The reason of this missing is not really clear since it's an existing feature on Visual Basic 6.0. We will learn here how to add many SortField to the ReportDocument of a CrystalReportViewer, using reflection.

Imagine you have to sort the request's result by "Firstname", "Lastname", and "ID" fields, using the Crystal report document with the variable "Filename" as the report document path, and the variable "srcDataSet" dataset as the data source:

string[] sortFieldNames = new string[] { "Firstname", "Lastname", "ID" };

ReportDocument
reportDocument = new ReportDocument();

reportDocument.FileName = String.Format("rassdk://{0}", Filename);

reportDocument.SetDataSource(srcDataSet.Tables[0]);

SortFields targetSortField = reportDocument.DataDefinition.SortFields;

Here we can modify the sort field (only if existing in the document):

DatabaseFieldDefinition fieldDef = reportDocument.Database.Tables["REPORTS"].Fields["FIRSTNAME"];

targetSortField[0].Field = fieldDef;

targetSortField[0].SortDirection = CrystalDecisions.Shared.SortDirection.AscendingOrder;

But impossible to add directly a new field to that SortFields collection because no method allows performing action:

pic1

Actually, it's possible to add a new sort field using the mother inherited class of each used component.

First we get the "SortFields" mother's class of type (CrystalDecisions.ReportAppServer.DataDefModel.SortsClass ) by using the RasSorts private accessor of the SortFields object:

MethodInfo getRasSorts = targetSortField.GetType().GetMethod("get_RasSorts", BindingFlags.NonPublic | BindingFlags.Instance);

object rasSorts = getRasSorts.Invoke(targetSortField, System.Type.EmptyTypes);

It return an object of type CrystalDecisions.ReportAppServer.DataDefModel.SortsClass.

Now we can access to the private "Add" method of the mother's class:

MethodInfo addSort = rasSorts.GetType().GetMethod("Add");

The description of that method show that it accept one parameter of type CrystalDecisions.ReportAppServer.DataDefModel.ISCRSort.

pic2 

ISCRSort is an interface, so we have to create a new instance of a class that implement the ISCRSort interface. That class is called "SortClass" and come from the "CrystalDecisions.ReportAppServer.DataDefModel" assembly.

Assembly rasAssembly = getRasSorts.ReturnType.Assembly;

ConstructorInfo ciRasSort = rasAssembly.GetType("CrystalDecisions.ReportAppServer.DataDefModel.SortClass").GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, System.Type.EmptyTypes, null);

object rasSort = ciRasSort.Invoke(System.Type.EmptyTypes);

Our "rasSort" object now instantiate the SortClass class, implementing ISCRSort.
Once the SortClass object is created, we must complete it with valid field, else it will return an error on execution.

The SortClass class uses the "RasField" accessor to define the field which will be sorted.

MethodInfo setSortField = rasSort.GetType().GetMethod("set_SortField", BindingFlags.Public | BindingFlags.Instance);

But this method only accepts an object implementing the ISCRField interface:
Void set_SortField(CrystalDecisions.ReportAppServer.DataDefModel.ISCRField);

So we have to extract the object of our field object (called fieldDef) that implement the ISCRField interface, it can be done with the "RasField" accessor of our DatabaseFieldDefinition object called "fieldDef":

MethodInfo getRasField = fieldDef.GetType().GetMethod("get_RasField", BindingFlags.NonPublic | BindingFlags.Instance);

object rasField = getRasField.Invoke(fieldDef, System.Type.EmptyTypes);

And now, the final touch, we add it to the collection:

addSort.Invoke(rasSorts, new object[] { rasSort });

Your new sort field has been adding to the SortFields collection, and you can now modify it:

int n = reportDocument.DataDefinition.SortFields.Count - 1;

reportDocument.DataDefinition.SortFields[n].SortDirection = CrystalDecisions.Shared.SortDirection.AscendingOrder;
reportDocument.DataDefinition.SortFields[n].Field = fieldDef;

That's all.


Comments (11) -

June 26. 2009 07:32

rajesh singh


        I try to use it in VS2008 using VB, in WPF, it generates the report without sorting but before generation of report gives information error
        Titled:"Crystal Report Windows Form Viewer"
        Info: "Invalid Input"
        and the sorting dosn't work.

        My implementation code is using wpf and xaml. I am hosting the crystal report viewer control under "WindowFormHost".
        The code snippet :
        Dim reportDocument As New ReportDocument()
        Dim assemblyFilePath As String = System.Reflection.Assembly.GetExecutingAssembly.Location
        Dim MainStart As Integer = assemblyFilePath.IndexOf("Main")
        Dim Filename As String = assemblyFilePath.Substring(0, MainStart) & "Reports\ReportFiles\crStudenHistory.rpt"
        reportDocument.FileName = [String].Format("rassdk://{0}", Filename)
        reportDocument.SetDataSource(_ahDataset.Tables(0))
        Dim targetSortField As SortFields = reportDocument.DataDefinition.SortFields
        Dim fieldDef As DatabaseFieldDefinition = reportDocument.Database.Tables(0).Fields(2)
        Dim getRasSorts As MethodInfo = targetSortField.[GetType]().GetMethod("get_RasSorts", BindingFlags.NonPublic Or BindingFlags.Instance)
        Dim rasSorts As Object = getRasSorts.Invoke(targetSortField, System.Type.EmptyTypes)
        Dim addSort As MethodInfo = rasSorts.[GetType]().GetMethod("Add")
        Dim rasAssembly As Assembly = getRasSorts.ReturnType.Assembly
        Dim ciRasSort As ConstructorInfo = rasAssembly.[GetType]("CrystalDecisions.ReportAppServer.DataDefModel.SortClass").GetConstructor(BindingFlags.[Public] Or BindingFlags.Instance, Nothing, System.Type.EmptyTypes, Nothing)
        Dim rasSort As Object = ciRasSort.Invoke(System.Type.EmptyTypes)
        Dim setSortField As MethodInfo = rasSort.[GetType]().GetMethod("set_SortField", BindingFlags.[Public] Or BindingFlags.Instance)
        Dim getRasField As MethodInfo = fieldDef.[GetType]().GetMethod("get_RasField", BindingFlags.NonPublic Or BindingFlags.Instance)
        Dim rasField As Object = getRasField.Invoke(fieldDef, System.Type.EmptyTypes)
        addSort.Invoke(rasSorts, New Object() {rasSort})
        Dim n As Integer = reportDocument.DataDefinition.SortFields.Count - 1
        reportDocument.DataDefinition.SortFields(n).Field = fieldDef
        reportDocument.DataDefinition.SortFields(n).SortDirection = CrystalDecisions.[Shared].SortDirection.AscendingOrder
        MyCrystalReportViewer.ReportSource = reportDocument
      

rajesh singh

September 26. 2009 00:35

VB Guest


        I strongly believe the limitation might be from Crystal Reports.
        BTW we can always sort either from the database or else can be sorted in the dataView and trasfer to datatable before passing to Crystal Reports
      

VB Guest

October 29. 2009 06:36

LR


        Hi, thank you for your document.
        I have similar problem (add SortField by .NET, in CR2008 reports) but in a VB.NET program (a porting from VB6 program).

        My VB.NET code (from your code):

        Public Function AddSortFiled(ByRef reportdoc As ReportDocument, ByRef fd As FieldDefinition) As Boolean
        Dim result As Boolean
        Dim sfs As SortFields
        Dim rassort As Object
        Dim rassorts As Object
        Dim rasfield As Object
        Dim getrassorts As MethodInfo
        Dim addsort As MethodInfo
        Dim setsortfield As MethodInfo
        Dim getrasfield As MethodInfo
        Dim cirassort As ConstructorInfo
        Dim rasassembly As Assembly
        result = False
        If Not reportdoc Is Nothing OrElse Not fd Is Nothing Then
        sfs = reportdoc.DataDefinition.SortFields
        getrassorts = sfs.GetType().GetMethod("get_RasSorts", BindingFlags.NonPublic Or BindingFlags.Instance)
        rassorts = getrassorts.Invoke(sfs, System.Type.EmptyTypes)
        addsort = rassorts.GetType().GetMethod("Add")
        rasassembly = getrassorts.ReturnType.Assembly
        cirassort = rasassembly.GetType("CrystalDecisions.ReportAppServer.DataDefModel.SortClass").GetConstructor(BindingFlags.Public Or BindingFlags.Instance, Nothing, System.Type.EmptyTypes, Nothing)
        rassort = cirassort.Invoke(System.Type.EmptyTypes)
        setsortfield = rassort.GetType().GetMethod("set_SortField", BindingFlags.Public Or BindingFlags.Instance)
        getrasfield = fd.GetType().GetMethod("get_RasField", BindingFlags.NonPublic Or BindingFlags.Instance)
        rasfield = getrasfield.Invoke(fd, System.Type.EmptyTypes)
        ' setsortfield.Invoke(getrasfield.Invoke(fd, System.Type.EmptyTypes), Nothing)
        addsort.Invoke(rassorts, New Object() {rassort})
        result = True
        End If
        Return result
        End Function

        I've commented out this bad statement:

        ' setsortfield.Invoke(getrasfield.Invoke(fd, System.Type.EmptyTypes), Nothing)

        Here's the code whichi uses AddSortField:

        f = reportdoc.Database.Tables.Item(t).Fields.Item(c)
        If AddSortFiled(reportdoc, f) Then
        lastindex = reportdoc.DataDefinition.SortFields.Count - 1
        sf = reportdoc.DataDefinition.SortFields.Item(lastindex)
        sf.SortDirection = CrystalDecisions.Shared.SortDirection.AscendingOrder
        sf.Field = f
        End If

        But when run

        ? sf
        {CrystalDecisions.CrystalReports.Engine.SortField}
        Field: {"Operazione o metodo non implementato."}
        (Operation or method is not implemented)
        SortDirection: AscendingOrder {1}
        SortType: RecordSortField {1}

        and in sf.SortDirection = ...:

        ? ex.Message
        "Riferimento a un oggetto non impostato su un'istanza di oggetto."
        (Object reference not set to an instance of object)

        This is because I've commented out setsortfield.Invoke(...).
        How I can write right code to do this? (vb.net or c#...)

        Thank you again.


      

LR

November 3. 2009 13:31

Manuel


        I think is missing a step.

        before
        addSort.Invoke(rasSorts, new object[] { rasSort });
        i think is missing
        setSortField.Invoke(rasSort, New object[] {rasField});
      

Manuel

January 26. 2010 07:57

Abdul Hussain


        Hi,

        I found your article really helpful.  I am now able to remove the existing sortfields from the report and add new ones to it programatically in VS2005 (I am using VB.net).

        When I step through the code I can see the new fields in the SortFields collection and they seem to be set up correctly.

        However rather then the report being displayed in the viewer I get an 'Invalid Pointer' error message.

        All my code seems to run through ok but I get this error at the end.

        I have tried searching the web but have not had any success.  I would really appreciate it if you could help as I am not sure where else to look.

        Thanks

        Abdul
      

Abdul Hussain

January 28. 2010 04:37

abdul


        Hi,
        Crystal 2008 does not make it easy to add extra sortfields to a report programmatically. I found an article which showed that you could do it using reflection. Following the procedure in the article I am now able to remove the existing sortfields from the report and add new ones to it programatically in VS2005 (I am using VB.net).
        When I step through the code I can see the new fields in the SortFields collection and they seem to be set up correctly.
        However, rather then the report being displayed in the viewer I get an 'Invalid Pointer' error message.
        All my code seems to run through ok but I get this error at the end.
        I have tried searching the web but have not had any success. I would really appreciate it if you could help as I am not sure where else to look. My code is below:
        Dim bCompare As Integer = 0
        Dim iUBound As Integer = 0
        Dim asSortFields() As FieldDefinition
        Dim mySortFields As SortFields
        Dim getRasSorts As MethodInfo
        Dim rasSorts As Object
        Dim removeSort As MethodInfo
        Dim addSort As MethodInfo
        Dim rasAssembly As Assembly
        Dim ciRasSort As ConstructorInfo
        Dim rasSort As Object
        Dim setSortField As MethodInfo
        Dim aiParam(0) As Object
        Dim fieldDef As DatabaseFieldDefinition
        Dim iCount as Integer = 0
        'Clear existing sorts
        For iCount1 = 0 To rdReportDoc.DataDefinition.SortFields.Count - 1
        mySortFields = rdReportDoc.DataDefinition.SortFields
        getRasSorts = mySortFields.GetType().GetMethod("get_RasSorts", BindingFlags.NonPublic Or BindingFlags.Instance)
        rasSorts = getRasSorts.Invoke(mySortFields, System.Type.EmptyTypes)
        removeSort = rasSorts.GetType().GetMethod("Remove")
        rasAssembly = getRasSorts.ReturnType.Assembly
        ciRasSort = rasAssembly.GetType("CrystalDecisions.ReportAppServ er.DataDefModel.SortClass").GetConstructor(Binding Flags.Public Or BindingFlags.Instance, Nothing, System.Type.EmptyTypes, Nothing)
        rasSort = ciRasSort.Invoke(System.Type.EmptyTypes)
        setSortField = rasSort.GetType().GetMethod("set_SortField", BindingFlags.Public Or BindingFlags.Instance)
        'remove the first field in the sortfields collection, this way one by one we will delete them all
        aiParam(0) = 0
        removeSort.Invoke(rasSorts, aiParam)
        Next
        'Now set up the sort
        For iCount1 = 0 To iUBound
        mySortFields = rdReportDoc.DataDefinition.SortFields
        'fieldDef = rdReportDoc.Database.Tables(0).Fields("Cust_Num")
        'fieldDef = asSortFields(1)
        fieldDef = asSortFields(iCount1)
        getRasSorts = mySortFields.GetType().GetMethod("get_RasSorts", BindingFlags.NonPublic Or BindingFlags.Instance)
        rasSorts = getRasSorts.Invoke(mySortFields, System.Type.EmptyTypes)
        addSort = rasSorts.GetType().GetMethod("Add")
        rasAssembly = getRasSorts.ReturnType.Assembly
        ciRasSort = rasAssembly.GetType("CrystalDecisions.ReportAppServ er.DataDefModel.SortClass").GetConstructor(Binding Flags.Public Or BindingFlags.Instance, Nothing, System.Type.EmptyTypes, Nothing)
        rasSort = ciRasSort.Invoke(System.Type.EmptyTypes)
        setSortField = rasSort.GetType().GetMethod("set_SortField", BindingFlags.Public Or BindingFlags.Instance)
        addSort.Invoke(rasSorts, New Object() {rasSort})
        mySortFields(iCount1).Field = fieldDef
        If sAscend = "Y" Then
        rdReportDoc.DataDefinition.SortFields.Item(iCount1) .SortDirection = SortDirection.AscendingOrder
        Else 'descending
        rdReportDoc.DataDefinition.SortFields.Item(iCount1) .SortDirection = SortDirection.DescendingOrder
        End If
        Next

        Thanks

        Abdul
      

abdul

February 5. 2010 08:48

Marcelo


        This code is amazing. Helped me a lot !!
        I think that you have forgotten in your instructions to copy something from your code.
        Object rasField  has to be passed to rasSort using setSortField method:
        setSortField.Invoke(rasSort, new object[] { rasField });
      

Marcelo

March 5. 2010 14:56

Pete


        I had to flip these lines of code:
        reportDocument.DataDefinition.SortFields[n].SortDirection = CrystalDecisions.Shared.SortDirection.AscendingOrder;
        reportDocument.DataDefinition.SortFields[n].Field = fieldDef;


        When I try to preview the report I get "Invalid Pointer" error.

      

Pete

May 19. 2010 12:01

Natasha


        Hi, Thanks for the instruction on how to sort on multiple fields, I am trying to implement it on VB.net and getting error.
        "Unable to cast COM Object of type 'CrystalDecisions.ReportAppServer.DataDefModel.SortClass' to class type 'System.Object[]

        Does anybody have idea? Any help is appreciated.

        Regards
      

Natasha

May 19. 2010 12:42

Natasha


        Hi,

        I am trying to implement the above sorting code in Vb.net but getting error
        "Unable to cast COM object of type 'CrystalDecisions.ReportAppServer.DataDefModel.SortClass' to class type 'System.Object[]"

        Does anybody have idea? I appreciate any help.

        Thanks
      

Natasha

June 9. 2010 13:34

Matt


        This is a great post and very useful, however it is missing one line of code in the example that makes it work.

        This line of code:
        setSortField.Invoke(rasSort, new [] { rasField });

        needs to go before:
        ddSort.Invoke(rasSorts, new object[] { rasSort });
      

Matt

Comments are closed
 
PageRank Actuel