Report Viewer - Object With Nested List Objects - vb.net

I have an existing class structure in place and want/need to use that as a data source for a series of reports using vb and 2005, (though we are almost ready to move to 2010, so if that will solve this ill move today!)
Using gotreportviewer sample for nested objects ive added a reportmanager class exposing a getdata method which ive populated with all my data and returned a list(of object). the data is there and correct at the point of databinding, i can add and reference top level properties, however not matter what syntax i try i cant reference the fields in nested classes/lists. I get various messages ranging from "#Error" in the ouput field to nothing, to wont compile.
my class structure is roughly this in short form:
Assembly0
Class ReportManager
TheData as List(Of Object)
New() 'that populates TheData from the class structure below
GetData() as List(of Object)
Assembly1
Class Test
aProperty1 as String
aProperty2 as Int
aProperty3 as String
aProperty4 as String
aProperty4 as List(of aType1)
Assembly2
Class AaType1
aProperty1 as String
aProperty2 as Int
aProperty3 as String
aProperty4 as String
aProperty4 as List(of aType2)
aProperty4 as List(of aType3)
aProperty4 as String
Assembly3
Class aType2
aProperty1 as Boolean
aProperty1 as String
you get the idea
and so on.....
in my main app
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' Create an instance of our ReportManager Class
Try
' trust assemblies used in get data
ReportViewer1.LocalReport.ExecuteReportInCurrentAppDomain(Assembly.GetExecutingAssembly().Evidence)
ReportViewer1.LocalReport.AddTrustedCodeModuleInCurrentAppDomain("assy1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1234")
ReportViewer1.LocalReport.AddTrustedCodeModuleInCurrentAppDomain("assy2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1234")
' etc through ALL dependant assemblies
' create datamanager, that will populate its TheData property
Dim reportMan As Data.Reporting.Manager = New Data.Reporting.Manager(18) ' test id sent
' this is the method from the gotreportviewer sample, which only allows you to reference top level properties, regardless of syntax used. i.e. =Fields!Prop.Value.SubProp
' doesnt work
'ReportViewer1.LocalReport.DataSources.Add(New ReportDataSource("DummyDataSource", reportMan.GetData))
'Me.ReportingDataBindingSource.DataSource = reportMan.GetData
' this is the only method I have found that allows me to reference an objects nested property and its fields.....?
Data = reportMan.GetData()
Me.ReportViewer1.ProcessingMode = Microsoft.Reporting.WinForms.ProcessingMode.Local
Me.ReportViewer1.LocalReport.DataSources.Add(New ReportDataSource("Data_Reporting_ReportingData", Data))
' fortnatley there is only ever one test in the list, HOWEVER there will be 4 specimens and n stages below that and so on..
Dim SpecimenData As SpecimenList = Data(0).Specimens
Me.ReportViewer1.LocalReport.DataSources.Add(New ReportDataSource("Tests_Specimen", SpecimenData))
' so this method is no good either. currently only a test its just returning the first specimen.
'Dim StageData As Tests.Stages = Data(0).Specimens(0).Stages
'Me.ReportViewer1.LocalReport.DataSources.Add(New ReportDataSource("Tests_Specimen", SpecimenData))
' render report
Me.ReportViewer1.RefreshReport()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Fixes i found online/googling:
You must add "ExecuteReportInCurrentAppDomain",
done that no difference.
You must add
Assembly: AllowPartiallyTrustedCallers() to AssemblyInfo.vb, No difference.
You must strongly name you dependent assemblies, done that and it did get rid of
an error regarding a call being made in the "Code" property of the report
(for localization).
have tried the =Fields!Property.Value.SubProperty syntax and it DOESN'T work! no matter what variation I try.
' in the rdlc - this syntax works for a top level properties
=Sum(Fields!TestVersion.Value, "Data_Reporting_ReportingData")
' using the alternate method list in the above code this works
=First(Fields!Index.Value, "Tests_Specimen")
' but these dont for its child properties
=First(Fields!Specimens.Value.Index, "Data_Reporting_ReportingData")
=Fields!Specimens.Value.Index
=Fields!Specimens.Value.Index.Value
So does that mean I have no choice but to create something like
Dim SpecimenData As Tests.SpecimenList = Data(0).Specimens for every single nested object? Also for obvious reasons I'd rather not have to flatten the entire datastructure as it's massive.
I have tried everything I can find on this, not much out there and everything points back to the same three four articles/blog posts that just aren't working for me, their samples unmodified work, but none of them work when applied to nested lists or nested objects of inherited list types.
Does anyone have any sample code of using objects with nested lists that actually works? as none of the ones I could find online work with anything but the simplest of scenarios. i.e. one assembly, or one code file or no nested lists or simple/native types.
I've been at this the best part of A WEEK! I'm now bald and stressed please help.
Failing that can anyone suggest a thrid party vendor that does support this sort of thing?
does crystal? pebble?
Apologies for the wall of text...
Matma

I´m Looking for almost the same, except, that I have objects that have as property other objects, no List of objects, any way, you asked if Crystal Reports do this kind of thing, YES IT DOES, it´s a little bit difficult to do it, but it does.
I don´t know why it´s so difficult to work with that kind of think on now days. Because we are aways working with persistance Frameworks, like Entity Framework, and others, so, you do a hell of a job with a Persistance, and when you go to reports, you need to back to your DataBase model if you want easy work! So waste of time!
I Just have found, it´s possible to do it in report viewer, But it had a problem in visual studio 2010, it´s fixed in SP1 but you need to set all your classes that are used as nested objects as Serializable
Please read : http://wraithnath.blogspot.com.br/2011/04/reportviewer-object-datasource-nested.html

Related

How to Build a List of Form Controls Before the Form is Loaded

I’m new to OOP and VB.NET, so please bear with me.
In VB.Net I currently have working code that defines, for each form, significant information about selected controls on the form.
I have defined in a public class:
Public Class FormFld
Public Property ScrField As Control ' A control on the form
Public Property DbField As String ' Its corresponding field name in the database
… ' Other info about the control or its database field
End Class
When each form loads, I create a list of FormFlds for the form’s selected controls:
At the start of each form
ReadOnly FormFlds As New List(Of FormFld)()
and in the form’s Load routine
FormFlds.Add(New FormFld With {.ScrField = Control1Name, .DbField = "Field1Name", …})
FormFlds.Add(New FormFld With {.ScrField = Control2Name, .DbField = "Field2Name", …})
…
This technique has worked well to easily loop through the selected fields and, on input, populate those fields from the database, or, on output, write those field values to the database.
With this implementation, however, the list must be built every single time the form is loaded. I’m wondering if the setup of the list can be done only once, during program initialization, before the forms are loaded.
Here’s the latest that I have tried.
In Class1:
Public Class indiv
Public FormFlds As List(Of FormFld)() ' The list for the frmIndividual form
End Class
In Module1, I attempt defining the FormFlds for the eventual form frmIndividual, to be saved as Indiv.FormFlds. I’d like the form name (f) and the list owner (owner) to be defineable so I can easily change those for each form.
Dim owner As New indiv
Dim f As FrmIndividual
owner.FormFlds.Add(New FormFld With {.ScrField = f.TxtKey, .DbField = "Sort_Key", …})
In Visual Studio, the third line shows error “BC30456: 'Add' is not a member of 'List(Of FormFld)()”.
Wondering if the problem might be due to not having an actual form FrmIndividual created yet, I tried changing the second line to
Dim f As New FrmIndividual
but it didn’t change anything.
I’m using VS 2022, v17.2.1. If there’s more info you need, please let me know.
The reason that you're told there's no Add method is because arrays have no Add method and you have an array. Here:
ReadOnly FormFlds As New List(Of FormFld)()
You are using the New keyword to invoke a constructor, so the parentheses at the end are for the argument list for that method call. Here:
Public FormFlds As List(Of FormFld)()
there's no New keyword so there's no constructor, so the parentheses at the end indicate that the field is an array type. That code is functionally equivalent to this:
Public FormFlds() As List(Of FormFld)
When you get that field you're getting a reference to an array of List(Of FormFld), not just a single List(Of FormFld) object. Of course, the field is initially Nothing anyway, so you'd have to assign something to it first to be able to use it.
By the way, the error message was already telling you what the issue was:
'Add' is not a member of 'List(Of FormFld)()`
It is telling you that Add is not a member of array of List(Of FormFld), which it's obviously not.

Sender.Gettype from multiple handles

I have code that handles multiple events. FYI - I use Devexpress Components. I have two items, a Lookupedit and a GridLookupEdit, that are handled by the same code. I am trying to do something like the following:
Dim type = sender.GetType()
Select Case DirectCast(sender, Type).Name
Case "mgrLUE"
log("View metrics for manager: " & mgrLUE.Properties.GetDisplayText(mgrLUE.EditValue), Me.Name)
Case "sectLUE"
log("View metrics for section: " & sectGLUE.Properties.GetDisplayText(sectGLUE.EditValue), Me.Name)
End Select
I am getting errors at the select case line. I cant figure out how to dynamically get the type to be able to direct cast to it. The types will be DevExpress.XtraEditors.GridLookUpEdit and DevExpress.XtraEditors.LookUpEdit in this case. I have tried searching for a solution, but everything I have tried is failing.
Thank you for the help.
Casting can't be done dynamically because its sole purpose is to let the compiler know that you expect an object to be of a certain type. This is necessary so that the compiler knows what members the object contains when you try to access it.
I should mention that VB.NET has a feature called late binding, which allows you to access members of a type wrapped in an Object by looking up if the member you're trying to access exists at runtime. Using late binding, however, is not recommended as it is very easy to make mistakes and break your code.
Now, as for your problem: Casting can be done if an object is of a certain type, or if it inherits from that type. Since I'm guessing what you're using are controls (that you've placed on your form) they all should inherit from System.Windows.Forms.Control, thus you can cast them to that which contains the base property Name:
Select Case DirectCast(sender, Control).Name

Late Binding to a form's public variables

I have 20+ MDI forms with consistently named Public variables. When the child form closes a method on the MDI Parent is called passing Me as a generic form type. How can I access the public variables by name via the Form reference? I only need to read the variables. Of course the Variables() method does not exist...
Public Sub CleanupForm(ByVal frm As Form)
Dim sTable_Name As String = frm.Variables("TABLE_NAME") ' Public at form level
Dim cLock As clsRecLocks
cLock = frm.Variables("Rec_Lock")
cLock.DeleteThisLock()
'..
I've seen some posts on similar requests but most start out with "don't do it that way..." then go off in the weeds not answering the question. I concede it is poor design. I can't change all the calling forms in the short term so I need to use this approach.
VS2010, VB.Net, Win Forms, .Net 2.0
I was able to get to a simple variable using CallByName:
Try
Dim s As String = CallByName(frm, "TABLE_NAME", CallType.Get)
Stop
Catch ex As Exception
MsgBox(ex.Message)
End Try
On to the class object. Perhaps I can add a default Get for the class that returns the ID I need.
Default property won't work as the Locks object was not declared Public - at least for the CallByName() approach.
Can Reflection get to form level variables not declared Public? Seems like a security issue, but...
Can I get a "Parent" reference in the instantiated Locks class? i.e. A reference to the form that established the Locks object? I can change the clsRecLocks() class.
I found a property I could get to that told me the form was "read-only" and I can use that tidbit to delete the correct (or more correct - still not 100%) lock record. So the bug is 90% fixed. I think I need update all the forms with code that records the info I need to get to 100%.
Thanks to all!
Poor design but you can do this:
Public Sub CleanupForm(ByVal frm As Form)
Dim frmTest as object = frm
? = frmTest.TABLE_NAME
? = frmTest.Rec_Lock
End Sub
This will compile and if the variables exist, it will return them but if not, you get an error.
Converting to an interface after the fact is not that hard, you should do it now rather than later.

Why is it not necessary to indicate ByVal/ByRef anymore?

I just installed Visual Studio 2010 Service pack (proposed on Windows Update), and I can see a new feature on the "intellisense" that means when I write a Function or Sub in VB.NET it doesn't auto-complete parameters with ByRef or ByVal...
1) Is there anyway that I can configure this option back to how it was before?
2) If I don't specify ByX, which one is used by default? (it seems like it is always ByRef)
It seems that this post covers your question:
http://msmvps.com/blogs/carlosq/archive/2011/03/15/vs-2010-sp1-changing-quot-byval-quot-vb-net-code-editor-experience.aspx
So no, there is no way to get the old behaviour. From now on ByVal is the default (what it was before) and it won't get added automatically to the method parameters.
In my opinion this is a good decision since it's making VB.NET a bit more consistent with C# and avoids unnecessary "noises"(it's already verbose enough).
Old behaviour:
Private Sub test(ByVal test As String)
End Sub
New behaviour
Private Sub test(test As String)
End Sub
Tim covered what you asked directly, but something else to keep in mind is that any reference type variable, like a user defined class even if passed by value will allow you to make changes to that instances properties etc that stay. It won't however allow you to change the entire object. Which may be why it seemed to you to be defaulting to by reference
Public Sub (Something As WhateverClass)
Something = New WhateverClass 'will result in no changes when outside this method
Something.Property1 = "Test" 'will result in an updated property when outside this method
End Sub
From MSDN:
The value of a reference type is a pointer to the data elsewhere in memory.
This means that when you pass a reference type by value,
the procedure code has a pointer to the underlying element's data,
even though it cannot access the underlying element itself. For
example, if the element is an array variable, the procedure code does
not have access to the variable itself, but it can access the array
members.
Beware when transferring routines to VBA, where the default is ByRef (see, e.g., "The Default Method Of Passing Parameters" at the bottom of this page, by the great Chip Pearson).
That can be messy.

InvokeMember using GetField. Field not found in VB.NET

This is probably a simple one but I can't seem to figure it out.
I have a bunch of form items created by the form designer declared as (in frmAquRun.Designer.vb)
Public WithEvents btnAquRunEvent1 As VisibiltyButtonLib.VisibilityButton
Public WithEvents btnAquRunEvent2 As VisibiltyButtonLib.VisibilityButton
... etc
And I basically want to be able to supply a number to a function access each of these fields. So I wrote this function. (in frmAquRun.vb)
Const EVENT_BUTTON_PREFIX As String = "btnAquRunEvent"
Public Function getEventButton(ByVal id As Integer) As Windows.Forms.Button
Dim returnButton As Windows.Forms.Button = Nothing
Try
returnButton = DirectCast(Me.GetType().InvokeMember(eventButtonName, Reflection.BindingFlags.GetField Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.Instance, Nothing, Me, Nothing), Windows.Forms.Button)
Catch ex As Exception
End Try
Return returnButton
End Function
But it always seems to be generating field not found exceptions.
The message in the exception is "Field 'ATSIS_ControlProgram.frmAquRun.btnAquRunEvent1' not found.".
The namespace and form name in the message are correct. Any idea what i'm doing wrong?
The problem is that for WithEvents fields, VB actually creates a property that does the necessary event handler attaching and detaching. The generated property has the name of the field. The actual backing field gets renamed to _ + original name.1)
So in order for your code to work just prefix the button name by _ or use the BindingFlag that corresponds to the property getter (instead of GetField).
Alternatively, you can do this a lot easier by using the Controls collection of the form:
returnButton = DirectCast(Me.Controls(eventButtonName), Windows.Forms.Button)
But beware that this only works if the button is top-level, i.e. not nested within a container control on the form.
1) This is an implementation detail of the VB compiler but it’s portable (especially to Mono’s vbnc compiler) since the handling for WithEvents fields is described in great detail in the VB language specifications.
The problem is that the event handlers aren't really fields. As compiled they're really properties that implement add_btnAquRunEventX, remove_btnAquRunEventX and fire_btnAquRunEventX methods. There are ways of using reflection to get around this, but that's probably not the best way to approach the problem. Instead you can simply create a List<> and populate it with the event handlers, then index into that list.
I'm a little rusty in VB syntax but it should look something like this:
Dim events = New List<EventHandler>()
events.Add( btnAquRunEvent1 )
events.Add( btnAquRunEvent2 )
....
events( 0 )( null, EventArgs.Empty )
Take a step back though and evaluate why you're invoking by index. There may be a simpler way of abstracting the whole thing that doesn't involve all this indirection.