Code Red => .NET => Topic started by: LS67 on January 08, 2020, 02:48:56 AM

Title: UserControl containing autodesk object references issue
Post by: LS67 on January 08, 2020, 02:48:56 AM
This is my first post at this forum. Usually i find a solution but this time i can not.
This issue is not for forums like stackoverflow because it strongly relates to autodesk api.
It could be a post at autodesk autocad/civil 3d developer forums but autodesk personnel no longer participate and i think this issue is quite unique for other members to deal with.
I did a research using the 'net user control issue' but...
So here i am.

Each time i make a new program i have to do all the UI from scratch. So i decided to create UserControls.
For example a user control to get the corridors of a dwg and put them in a dropdown list in a combo box.

So i created one. Next to compiling the component is automatically added at the toolbox.
Trying to add it to a form i get a failure message and it is automatically removed from the toolbox.

I found out that if i add the lines below everything works fine
Code - Visual Basic: [Select]
  1. Public currentCRDRobj As Autodesk.Civil.DatabaseServices.Corridor
  2. Public ListOfCorridorNames As New List(Of String)
I also tried entering an Alignment object and it worked fine.

But if i add one of the lines below i have the issue i mentioned
Code - Visual Basic: [Select]
  1. Public currentCRDRid As Autodesk.AutoCAD.DatabaseServices.ObjectId = Autodesk.AutoCAD.DatabaseServices.ObjectId.Null
  2. Public ListOfCorridorObjects As new List(Of Autodesk.Civil.DatabaseServices.Corridor)

So there is no issue with civil 3d custom objects such as corridor or alignment but there is an issue when they are set in a ListOf.
Also the foundation of each program, assigning an autodesk custom object ID to a variable, leads to an issue.

I attach a simple VisualStudio 2017-Civil 3D 2018 solution containing both UserControl and Form to test zipped files.
The base solution file is at 'CorridorBaselineStationsAddByFile' folder.
Load this solution.
The paths are related to my pc folders so project reloading is a must.

Project : autoCAD_Civil3D_2018_r2_UserControls
contains the user control

Project : TKV_CorridorBaselineStationsAddByFile
contains the form to drop the user control

Project build order is not an issue.
Recompile solution. It will succeed.
Set the focus at the 'frmCorridorBaselineStationsAddByFile.vb [Design] tab.
A new folder named 'autoCAD_Civil3D_2018_r2_UserControls Components' will be created at the toolbox.
It will contain the 'UserControl1' component.
Now drag and drop this component at the form.
Everything works fine.

Now set focus to 'UserControl1.vb'.
The lines that cause the issues are set to comment state.
Uncomment 'Public ListOfCorridorObjects by removing the '.
Recompile solution and set focus to 'frmCorridorBaselineStationsAddByFile.vb [Design] tab.
Voila !
Select Ignore and Continue, then Yes, and...UserControl1 is gone although it exists at the 'UserControl1.Designer.vb'.
This message should not show. Whose fault is it ? Mine ? Autodesk's ? Microsoft's ? Mix of ?

Now reset focus to 'UserControl1.vb'.
Uncomment 'Public currentCRDRid.
Recompile solution and set focus to 'frmCorridorBaselineStationsAddByFile.vb [Design] tab.
Select Ignore and Continue, then Yes.
A different error message shows up.
Now go to Toolbox. The component still exists. Try drag and drop it.
Oops. It is gone.

Any ideas ? Am i doing something wrong ?
Should i try posting also at the autodesk autocad/civil 3D dev forums ?
Title: Re: UserControl containing autodesk object references issue
Post by: n.yuan on January 08, 2020, 11:06:07 AM
This is because of how Form Designer works in Visual Studio: when the UI (UserControl or Form) is to be shown in the Form Designer, an actual instance of the UI class must be created, hence anything your code makes to be created within the default constructor would also be instantiated, and anything declared by "... As New ...". In your case, this line

Public ListOfCorridorObjects As new List(Of Autodesk.Civil.DatabaseServices.Corridor)

always runs when the form instance is created (within VS' form designer). Because the inst instantiation needs to know the memory needed for Civil3D's Corridor, which can only be known inside a running Civil3D session, thus the Form designer crashes.

There are a few ways to deal with this. The best/recommended ways to apply "Separate Concerns" principle in software develop practice. In your case, you separate the UI, which is mainly for presenting data and gather user interaction, with actual/specialized data that is specialized (Civil3D/AutoCAD).

I did not download/review your code, but I can see you have the data and UI pretty mingled together. This is not good practice, especially when you are doing an UI component. You may want to follow some kind of well-developed code pattern, such as View-Model-Controller/Presenter, to separate AutoCAD/Civil3D data with UI.

I'd strongly recommend to use WPF UI with MVVM pattern. This way, the UI part (View) never need to know the actual data type of Acad/Civil3D at design time. Only Model/ViewModel deals with AutoCAD/Civil3D data. So, you would not run into this issue at all.

However, if you are not up to speed with WPF UI, you might want to try following (not sure if it works, but worth trying):

Change the public filed member:

Public ListOfCorridorObjects As new List(Of Autodesk.Civil.DatabaseServices.Corridor)

To public property:

Private _corrdors As List(of Corridor) = Nothing
Public ReadOnly Property ListOfCorridors () As List(Of Corridor)
    Get ....
End Property

Then you only assign private member _corridors to an actual instance of List somewhere down the line during code execution. You may also assign it in a constructor other then the default one (the one without parameter input, because VS form designer uses it), like:

Partial Class MyUserControl
Inherit UserControl

  '' default constructor
  Public Sub New()
  End Sub

  '' Your customized constructor
  Public Sub New([Some input parameters])
    _corridors = New List(of Corridor)()
  End Sub

End Class

This way, the form designer in VS can instantiate your form/usercontrol with default constructor without need to figure out how to allocate memories to AutoCAD/Civil3D types, as long as the variables for these data are Nothing/Null.

Again, using WPF would be much better way to go, IMO.