question

nachoshaw-9496 avatar image
0 Votes"
nachoshaw-9496 asked nachoshaw-9496 commented

referencing class in multiple forms

Hi

thought it would be easy enough, maybe im missing something logically...

As i select a product, i load that specific product form. in the form, a reference a class instance (Info) that has already been instantiated on startup in ApplicationEvents-

 Dim Inf As New Info

as i load my product form, i have set it like that to access the class without creating a new instance of it

 Private _Inf as Info

In my New class, i do this

 _Inf = My.Application.Inf

this seems to work but i dont think its the best method. however, when i do the exact same in a sub UserControl on the product form, i get an exception error on a control EditEvent. I understand that this is because the _Inf has completely been qualified yet as the form isnt 100% instantiated.

So

what is a good method for multiple forms to access the same class containing data?

i could use My.Application.Inf for all access but is there a better way?


Thanks



dotnet-standard
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

nachoshaw-9496 avatar image
0 Votes"
nachoshaw-9496 answered

in addition, i did try it as a shared member of ApplicationEvents

 Public Shared Inf As New Info


but couldnt access it using the above methods or a direct call

 My.Application.Inf.Something


5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

nachoshaw-9496 avatar image
0 Votes"
nachoshaw-9496 answered

well, i created a new Class called AppGlobals and added the instance in there like this

 Public Class AppGlobals
   Friend Shared Inf As New Info
    ' add others here required
 End Class

which works by typing

 AppGlobal.Inf.Something

in any class without needing to create an instance of it. Is this methods acceptable or is there a correct way that im not doing?



Thanks



5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

cooldadtx avatar image
0 Votes"
cooldadtx answered

In general you're asking to use global variables and that is almost always the wrong approach. The only time, I believe, global variables are appropriate is when you are going to store something that is immutable for the life of the application. Examples might include the product information or a cached copy of a lookup table loaded from the database. In all other cases I don't believe global variables are a good idea. They should almost never be mutable as this can cause problems if they are updated while someone is trying to use them (especially for lists of items).

Nevertheless in the cases where global variables make sense I prefer to create a standalone shared class that exposes the data. The data is generally loaded upon first use (using Lazy when possible) and then remains unchanged for the life of the application. Following the single responsibility principle I tend to use a separate shared class for unrelated data but single shared class for related data. Going back to the product information, this is probably in a class by itself simply because nothing else makes sense.

Note: My VB is rusty so expect issues.

Public Class ApplicationInfo
   ' Singleton stuff, globally shared data
   Public Property Shared Default() As ApplicationInfo
      Get 
         Return _application
      End Get
   End Property

   Private Shared _default As New ApplicationInfo

   ' Instance data
   Public Property Name As String
      Get
           'Do work to get name of application
      End Get
   End Property
End Class


In this example I'm using the singleton pattern. I prefer a singleton object over a shared class because it gives me flexibility to move away from singleton later without having to update a lot of code. The net effect is the same though.

To use it.

Sub Foo
    Dim appName = ApplicationInfo.Default.Name 
End Sub


In the case of commonly needed data then I prefer to use methods on a shared class that can then cache the data if desired.

Public Shared Class GeographicService
   Public Shared Function GetCountries () As IEnumerable(Of Country)
       ' Get country data from DB if not cached
   End Function

   Public Shared Function GetStates ( ) As IEnumerable(Of State)
      ' Get states from DB if not cached
   End Function
End Class


Usage.

Sub Foo
    Dim countries = GeographicService.GetCountries
End Sub


But I generally recommend you not going this route just to share data between forms. In most cases expose properties on the form(s) (or create a constructor that has parameters) and allow the caller to provide the data (even if across forms). Ideally you are using some sort of servicing layer that exposes services that you can pass to your forms.

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

nachoshaw-9496 avatar image
0 Votes"
nachoshaw-9496 answered

Hi

Thanks for the detailed reply. I originally had the variable members in my specific Product form because nothing except the form itself and its sub forms required access. I was passing the data down to the sub forms using a Constructor with parameters but was advised that the method should be avoided and instead, Delegate from the sub form to the parent form which is what i have done now.

The data is updatable but locally per user. Each sub form requires access to get / set the data and the parent form requires access to update a model. It is then processed from the parent model as a complete data set. So, there are different datasets per different products and i only need to load the specific product elements required.

In this image, i try to demonstrate what im thinking which is-

  • Create the ProductForm on demand by Product

  • Create the Subforms required for the ProductForm

  • Create the Data Class specific to the product

  • Subforms Get / Set data as required through UI to / from DataClass

  • Invoke an update on the ProductForm to graphically display the DataClass Update

  • Process the data in a final method to its destination

  • Dispose of the Product Form and all of the objects created for the product

88346-object.png

Apologies for incorrect naming here, this was done for a non code team meeting..

It looks like i need to instantiate the DataClass either as the productForm is loaded or Directly after on the ProductFormCreate method then Dispose of it after while not creating a direct link between the form & subforms..

Hope that make sense :S



object.png (21.2 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

cooldadtx avatar image
0 Votes"
cooldadtx answered cooldadtx commented

I'm not sure what the original conversation was around why you should avoid constructor parameters as this is the correct approach in most cases. There is a fine line between required and optional data however. If the data is required then a constructor parameter ensures the compiler will prevent someone from opening the form without a product (of course it could still be Nothing). However if it is optional then a property is the better route. In either case the data should be passed from the parent to the child form. This eliminates the need for any additional support types.

Furthermore it would allow you, in the future if desired, to support opening multiple product forms at the same time with each of their sub forms and there would be no chance that any confusion would occur.

I do agree that the product form (parent) should be responsible for getting the initial data and probably saving that data but I could also see each sub form loading and saving its portion of the data. But none of this is really related to getting the data into the form to begin with. Using a shared data class to avoid a constructor parameter or property assignment is introducing unneeded complexity to a simple problem. There may be other business reasons that I'm not aware of though.

' Required data is passed as parameters
Dim child as New ChildForm(product)

' Optional data can be set on properties
child.OptionalStuff = optionalData
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Meant to include a more concrete example.

Class Product
   Property Manufacturer As ManufacturerInfo
      ...
   End Property

   Property Classification As ClassificationInfo
   ...
   End Property
End Class

` Inside ProductForm
Dim product As Product = GetAProduct

' Create child forms
Dim manufacturerForm As New ManufacturerForm(product.Manufacturer)
Dim classForm As New ClassificationForm(product.Classification)

' Show forms and do work

' All done, save
SaveProduct(product)
0 Votes 0 ·
nachoshaw-9496 avatar image
0 Votes"
nachoshaw-9496 answered nachoshaw-9496 commented

Thanks for being thorough. I did prefer the method of passing parameters to the Constructor but i can also see the benefits of the delegates. Its probably too time consuming now to revert back but we'll see..

There arent any business reasons withheld.

  • there is 100% guarantee that only 1 product will be open at a time, its the way we need to operate.

  • The application is a plugin to a 3rd party software

  • each product is a simple left to right process to each ChildForm with a function at the end to process the data to a dataset

i have had various versions, all working in their own way. At one point, i did have all subs containing their own data properties but as the complexity grew, it became more difficult passing those property values between subforms without creating a bunch of extra handling code (as the process moves left to right, some property values are required in the next subforms etc). In my mind, if

  • each subform only had to look at 1 dataclass to get / set property values

  • then invoke a ControlChangeEvent in the ParentForm to update

  • ParentForm collects all properties from DataClass and makes the graphical update.

I dont want to get into bad coding habits so I might revert to something like this-

  • ParentForm contains all ProductProperties (no Shared class, members or new instance)

  • ChildForms contain all relevant variable members specific to that ChildForm

  • on a control update, invoke a data collector from the ParentForm to update the Properties

  • ParentForm graphically updates what it needs to

  • On a ChildForm NavigationEvent, collect the required data and pass to the req. ChildForm

  • ParentForm processes final data to a dataset


The graphical update on the parent form is a realtime result of the updated data. Is mainly materials & sizes in a representation of the product.

Is this something that would more inline with a typical construction?



Thanks for your help :)





· 5
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

If you are using WPF for the UI I would recommend using the MVVM pattern. This will make easier to separate the code from the UI

0 Votes 0 ·

Hi

Im working in Winforms at present, havent looked at WPF yet


Thanks

0 Votes 0 ·

I'm not sure what you mean by a data collector nor am I 100% sure about what you mean by product properties but if you want to go the partial route then keep all your product data in a data class Product which may have other classes containing subsets of the data that are exposed as properties on the Product. Then just pass the Product instance to any child forms. Let the child forms decide what pieces of the data they need.

Going this route eliminate any sort of parent-child communication because a child can update the product by setting the property on the instance it was given. The parent form can still do the actual saving of the modified product after your work is done as normal. I don't see any need for data collectors or navigation events but I don't fully see your problem you're trying to solve. However you mentioned this fits into a 3rd party product so perhaps it is tied to that.

0 Votes 0 ·

Remember that a Product class in VB is a reference type and therefore any variables that point to the same instance (such as by passing to the constructor or by assigning to a property). Hence the child forms don't need to do anything to update the "product" other than set the properties. Because it is a reference type the original product instance that the parent form is using points to the same place in memory so the parent form will "see" the changes .

Dim product As New Product

product.Title = "Abc"

Dim child As New ChildForm(product)

'Child form code...
product.Title = "123"

' Back in parent form...
' Parent and child are using the same product instance so parent's version has changes made in child version
Dim newTitle As String = product.TItle  ' 123
0 Votes 0 ·

Apologies, my descriptive could be better. Since that post i went with passing the Class instance to the Child forms as you had suggested.

To clarify what i meant-

Data collector - would have been a Method called DataCollector() to collect all data from ChildForms properties into the ParentForms Property collection.
Product Properties - the collection of Properties with data relating to the product and being Get / Set in the ChildForms.

sorry for any confusion and the help is greatly appreciated :)



Thanks






0 Votes 0 ·
nachoshaw-9496 avatar image
0 Votes"
nachoshaw-9496 answered

Hey

to fully qualify and correct my earlier comments, the Delegate advice i had was related to my method of passing the Form to the constructor as a form which is different in this case like this-

 Dim UC1 As New UserControl1 = UserControl1(me)

then in my usercontrol

 Private _UC1 as UserControl1
 Public Sub New(ByVal UC As UserControl1)
    _UC1 = UC
 End Sub

I have since added in the delegate classes required and its better.

I will look at this method which is similar to your approach by separating the properties into a single class-

In ParentForm

 Private Inf As New Info = InfoClass
    
 Friend Sub LoadChilds()
    Dim C1 As New Child1 = Child1(Inf)
    Dim C2 As New Child2 = Child2(Inf)
 End Sub

In ChildForm

 Private _Inf As InfoClass
 Public Sub New(ByRef Inf As InfoClass)
    _Inf = Inf
 End Sub
    
 Private Sub Dosomething()
    Dim GetVal1 As String = Inf.GetSomething
 End Sub






5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.