Using WinForms in Journals

We've looked at various graphical interface elements to interact with the user:

However, sometimes you need something customized to the task at hand; it's time to create our own input form.


Prerequisites

This tutorial assumes you are fairly comfortable working with forms and controls and now want to know how to use them with your journals. If you don't know a radio button from a combo box, you will want to check out one of the VB books from your local library (check out some suggestions on the resource page) and spend some time noodling around in an integrated development environment (IDE). Speaking of which, you will need an IDE to create forms for user interaction; well, technically that's untrue, our resulting journal file will be plain text - you only really need notepad, a lot of know-how, and the patience of Job to create userforms. If, like me, you only have one of those - and don't like using notepad, get an IDE. I'm using (and recommend) VB.net express, but there are other options out there (see the resource page for some links). It will also be helpful to know some object oriented programming (OOP) concepts such as classes, properties, methods, etc., but we'll explore the necessary bits as we go.



Goal

The journal that we'll build will check the work part for the existence of a particular attribute. It will then display a form showing the value of that attribute (if any) in a textbox; if the user enters a new value and presses OK, the part attribute will be updated. Nothing too exciting in and of itself, but it will demonstrate how to display a user form that you have created and pass information between the form object and the journal module.



The Project

After installing VB.net express and configuring the NX code wizards, I started a new NXOpen project. The NX code wizard asked a few questions after which it presented me some starting code based on my answers. I next added a form object to my new project and added: a label, a textbox, and two buttons. The buttons I named btnOk and btnCancel to help keep them straight (Button1 and Button2 were not descriptive enough for my taste). I didn't spend too much time on the layout, as evidenced below:




journal form




When the journal starts, the Main subroutine will be the first to execute (as usual). What we want to do is:

  • check for the existence of a particular attribute
  • display a form showing the attribute (if it exists) and allow the user to type in a new value
  • and finally, assign the entered value to the part attribute

Reading the value of an attribute is fairly easy; the questions that remain are: how do we show the form that we've created, how do we pass information from the Main subroutine to the form, and how do we pass information from the form back to the journal?



The Code

To pass information to the journal, we could use some module level public variables. While this approach would work and would be convenient in a project of this size; we'll use properties to manage this information. Using properties rather than public variables gives us two advantages: we can more easily do data validation and it avoids the use of global variables. We can write code that ensures the value passed into the property is usable and this code will run every time the property value is changed. It is a programming 'best practice' to avoid the use of global variables where possible. By their nature, global variables can be changed by any line of code in the project. As your code projects grow larger and more complex, it will take considerably more effort on your part to track down bugs in the code. By limiting the scope of your variables and by limiting access to those variables, you can greatly reduce the time spent debugging.



The Module Code


Option Strict Off
Imports System
Imports NXOpen

Module Module1

Private Const _attributeTitle As String = "PROJECT"
Public ReadOnly Property AttributeTitle() As String
Get
Return _attributeTitle
End Get
End Property

Private _attributeValue As String = ""
Public Property AttributeValue() As String
Get
Return _attributeValue
End Get
Set(ByVal value As String)
_attributeValue = value
End Set
End Property

Sub Main()

Dim theSession As Session = Session.GetSession()
If IsNothing(theSession.Parts.Work) Then
'active part required
Return
End If

Dim workPart As Part = theSession.Parts.Work
Dim lw As ListingWindow = theSession.ListingWindow
lw.Open()

Const undoMarkName As String = "NXJ form demo journal"
Dim markId1 As Session.UndoMarkId
markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, undoMarkName)

Dim myAttributeInfo As NXObject.AttributeInformation

Try
myAttributeInfo = workPart.GetUserAttribute(_attributeTitle, NXObject.AttributeType.String, -1)
_attributeValue = myAttributeInfo.StringValue

Catch ex As NXException
If ex.ErrorCode = 512008 Then
'attribute not found
Else
'unexpected error: show error message, undo, and exit journal
MsgBox(ex.ErrorCode & ": " & ex.Message)
theSession.UndoToMark(markId1, undoMarkName)
Return
End If

Finally

End Try

'create new form object
Dim myForm As New Form1
'set form object properties (current part attribute title and value)
myForm.AttributeTitle = _attributeTitle
myForm.AttributeValue = _attributeValue
'display our form
myForm.ShowDialog()

If myForm.Canceled Then
'user pressed cancel, exit journal
Return
Else
'user pressed OK, assign value from form to part attribute
_attributeValue = myForm.AttributeValue
workPart.SetUserAttribute(_attributeTitle, -1, _attributeValue, Update.Option.Later)

End If

lw.Close()

End Sub

Public Function GetUnloadOption(ByVal dummy As String) As Integer

'Unloads the image when the NX session terminates
'GetUnloadOption = NXOpen.Session.LibraryUnloadOption.AtTermination

'----Other unload options-------
'Unloads the image immediately after execution within NX
GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Immediately

'Unloads the image explicitly, via an unload dialog
'GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Explicitly
'-------------------------------

End Function

End Module



The module's properties consist of private variables to hold the property values and an interface for other objects/code to access those values. Properties can be read/write, read only, or the more rarely used write only.
The AttributeTitle property is defined as ReadOnly because we don't want any other code changing its value. The AttributeValue property is defined as read/write since we do want other code to change its value. I've also defined AttributeTitle and AttributeValue properties for the form to pass this information between the journal module and the form object. The module and form properties do NOT have to share the same name, it just made sense in this case to do so.




Once we specify the part attribute title and retrieve the value, we create our form object, set its properties accordingly, and display it. The code that does this is:



'create new form object
Dim myForm As New Form1
'set form object properties (current part attribute title and value)
myForm.AttributeTitle = _attributeTitle
myForm.AttributeValue = _attributeValue
'display our form
myForm.ShowDialog()

The form object, which we'll look at in detail momentarily, has three properties defined: AttributeTitle, AttributeValue, and Canceled. The Canceled property will hold a value of True if the user pressed the cancel button, in which case we'll want the journal to end and take no action. If the user pressed OK, we'll assign the value from the form to the part's attribute.




The Form Code


Public Class Form1

Private _frmAttributeTitle As String
Public Property AttributeTitle() As String
Get
Return _frmAttributeTitle
End Get
Set(ByVal value As String)
_frmAttributeTitle = value
End Set
End Property

Private _frmAttributeValue As String
Public Property AttributeValue() As String
Get
Return _frmAttributeValue
End Get
Set(ByVal value As String)
_frmAttributeValue = value
End Set
End Property

Private _canceled As Boolean = False
Public ReadOnly Property Canceled() As Boolean
Get
Return _canceled
End Get
End Property

Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Label1.Text = _frmAttributeTitle
TextBox1.Text = _frmAttributeValue
End Sub

Private Sub btnCancel_Click(sender As System.Object, e As System.EventArgs) Handles btnCancel.Click
_canceled = True
Me.Close()
End Sub

Private Sub btnOK_Click(sender As System.Object, e As System.EventArgs) Handles btnOK.Click
_frmAttributeValue = TextBox1.Text.ToUpper
Me.Close()
End Sub

End Class



The form object has its own properties defined. Unlike the module's AttributeTitle property, the form's AttributeTitle property is defined as read/write. This helps make the form more reusable; if you want to customize the code to display the value of a different part attribute, you only need to change it in the module code.




As you can see, the form code is pretty simple and straightforward (helped by the fact that it does no error checking or input validation). When the form loads, the property values are transferred to the label and textbox. The user is free to change the value in the text box and then press OK or Cancel. Once one of the buttons are pressed, the form closes and execution resumes in the journal module. In our case Sub Main displayed the form so it is Sub Main that resumes execution when the form is closed.




If the Cancel button is pressed, the private variable _canceled is updated to True before the form closes. As Sub Main resumes execution, it checks the form's Canceled property (which returns the _canceled value) and simply ends the journal. If the user presses OK, the current value in the text box is copied to the _frmAttributeValue variable before the form is closed. Sub Main takes over, checks the form's Canceled property, sees that the user pressed OK, and assigns the form's AttributeValue property to the part attribute of interest and finally ends the journal as seen in the code below (copied from the journal module code above).





If myForm.Canceled Then
'user pressed cancel, exit journal
Return
Else
'user pressed OK, assign value from form to part attribute
_attributeValue = myForm.AttributeValue
workPart.SetUserAttribute(_attributeTitle, -1, _attributeValue, Update.Option.Later)

End If




Putting it all together

Ok, so we've created a form and written code to pass information between the form and journal code. How do we get all of this to work together as a journal? One of the limitations of journaling is that all the code must be contained in a single file. One way to accomplish this is to fire up notepad and copy & paste all the code into a new text file. Open a windows explorer and browse to your code project folder. There are three files that you will need to copy into a new file:

  • the module code
  • the form code
  • the form designer code

The module code and form code are fairly obvious, that's the code that we've been directly editing - but what's the 'form designer code'?


The 'form designer code' is a text file description of your form and its controls that the IDE creates as you create and edit your form. While it's possible to edit your form by editing this file, it is a bad idea to do so. Hand editing this file for form changes is a slow, error prone process. It would be better to let the IDE's form designer do what it does best.




Below is the full code listing combining the module, form, and form designer code:


Option Strict Off
Imports System
Imports NXOpen

Module Module1

Private Const _attributeTitle As String = "PROJECT"
Public ReadOnly Property AttributeTitle() As String
Get
Return _attributeTitle
End Get
End Property

Private _attributeValue As String = ""
Public Property AttributeValue() As String
Get
Return _attributeValue
End Get
Set(ByVal value As String)
_attributeValue = value
End Set
End Property

Sub Main()

Dim theSession As Session = Session.GetSession()
If IsNothing(theSession.Parts.Work) Then
'active part required
Return
End If

Dim workPart As Part = theSession.Parts.Work
Dim lw As ListingWindow = theSession.ListingWindow
lw.Open()

Const undoMarkName As String = "NXJ form demo journal"
Dim markId1 As Session.UndoMarkId
markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, undoMarkName)

Dim myAttributeInfo As NXObject.AttributeInformation

Try
myAttributeInfo = workPart.GetUserAttribute(_attributeTitle, NXObject.AttributeType.String, -1)
_attributeValue = myAttributeInfo.StringValue

Catch ex As NXException
If ex.ErrorCode = 512008 Then
'attribute not found
Else
'unexpected error: show error message, undo, and exit journal
MsgBox(ex.ErrorCode & ": " & ex.Message)
theSession.UndoToMark(markId1, undoMarkName)
Return
End If

Finally

End Try

'create new form object
Dim myForm As New Form1
'set form object properties (current part attribute title and value)
myForm.AttributeTitle = _attributeTitle
myForm.AttributeValue = _attributeValue
'display our form
myForm.ShowDialog()

If myForm.Canceled Then
'user pressed cancel, exit journal
Return
Else
'user pressed OK, assign value from form to part attribute
_attributeValue = myForm.AttributeValue
workPart.SetUserAttribute(_attributeTitle, -1, _attributeValue, Update.Option.Later)

End If

lw.Close()

End Sub

Public Function GetUnloadOption(ByVal dummy As String) As Integer

'Unloads the image when the NX session terminates
'GetUnloadOption = NXOpen.Session.LibraryUnloadOption.AtTermination

'----Other unload options-------
'Unloads the image immediately after execution within NX
GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Immediately

'Unloads the image explicitly, via an unload dialog
'GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Explicitly
'-------------------------------

End Function

End Module

Public Class Form1

Private _frmAttributeTitle As String
Public Property AttributeTitle() As String
Get
Return _frmAttributeTitle
End Get
Set(ByVal value As String)
_frmAttributeTitle = value
End Set
End Property

Private _frmAttributeValue As String
Public Property AttributeValue() As String
Get
Return _frmAttributeValue
End Get
Set(ByVal value As String)
_frmAttributeValue = value
End Set
End Property

Private _canceled As Boolean = False
Public ReadOnly Property Canceled() As Boolean
Get
Return _canceled
End Get
End Property

Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Label1.Text = _frmAttributeTitle
TextBox1.Text = _frmAttributeValue
End Sub

Private Sub btnCancel_Click(sender As System.Object, e As System.EventArgs) Handles btnCancel.Click
_canceled = True
Me.Close()
End Sub

Private Sub btnOK_Click(sender As System.Object, e As System.EventArgs) Handles btnOK.Click
_frmAttributeValue = TextBox1.Text.ToUpper
Me.Close()
End Sub

End Class

_
Partial Class Form1
Inherits System.Windows.Forms.Form

'Form overrides dispose to clean up the component list.
_
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub

'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.
_
Private Sub InitializeComponent()
Me.btnCancel = New System.Windows.Forms.Button()
Me.btnOK = New System.Windows.Forms.Button()
Me.Label1 = New System.Windows.Forms.Label()
Me.TextBox1 = New System.Windows.Forms.TextBox()
Me.SuspendLayout()
'
'btnCancel
'
Me.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel
Me.btnCancel.Location = New System.Drawing.Point(178, 107)
Me.btnCancel.Name = "btnCancel"
Me.btnCancel.Size = New System.Drawing.Size(85, 50)
Me.btnCancel.TabIndex = 0
Me.btnCancel.Text = "Cancel"
Me.btnCancel.UseVisualStyleBackColor = True
'
'btnOK
'
Me.btnOK.Location = New System.Drawing.Point(66, 107)
Me.btnOK.Name = "btnOK"
Me.btnOK.Size = New System.Drawing.Size(85, 50)
Me.btnOK.TabIndex = 1
Me.btnOK.Text = "Ok"
Me.btnOK.UseVisualStyleBackColor = True
'
'Label1
'
Me.Label1.Location = New System.Drawing.Point(12, 54)
Me.Label1.Name = "Label1"
Me.Label1.Size = New System.Drawing.Size(79, 13)
Me.Label1.TabIndex = 2
Me.Label1.Text = "Label1"
Me.Label1.TextAlign = System.Drawing.ContentAlignment.MiddleRight
'
'TextBox1
'
Me.TextBox1.Location = New System.Drawing.Point(97, 51)
Me.TextBox1.Name = "TextBox1"
Me.TextBox1.Size = New System.Drawing.Size(166, 20)
Me.TextBox1.TabIndex = 3
'
'Form1
'
Me.AcceptButton = Me.btnOK
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.CancelButton = Me.btnCancel
Me.ClientSize = New System.Drawing.Size(284, 176)
Me.Controls.Add(Me.TextBox1)
Me.Controls.Add(Me.Label1)
Me.Controls.Add(Me.btnOK)
Me.Controls.Add(Me.btnCancel)
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
Me.MaximizeBox = False
Me.MinimizeBox = False
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
Me.PerformLayout()

End Sub
Friend WithEvents btnCancel As System.Windows.Forms.Button
Friend WithEvents btnOK As System.Windows.Forms.Button
Friend WithEvents Label1 As System.Windows.Forms.Label
Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
End Class


Conclusion

We've looked at creating a form and passing information between the form and journal code. The guiding principle is to only use global variables when absolutely necessary; in keeping with this prinicple, we used object properties to pass the information and limit variable scope. As your project complexity grows, it will be beneficial to create your own classes and forms to 'divide and conquer' the programming task at hand.



Next Steps

Modify this journal to edit a part attribute that you often use. Try adding some validation to the user's input. The simple code presented here would allow some strange input, even some characters which may not be allowed in a part attribute! If you have a small number of allowable input values, try using a list box instead of a text box; the user could simply pick the desired value from the list rather than entering their own (probably misspelled) value. Edit this journal to make it your own. Build a more complex input form for your specialized task.


Happy coding!

Comments

How to have 2 FORMS. A button on FORM1 which opens FORM2?

Carlo Tony Daristotile

private void button1_Click(object sender, EventArgs e)
{
Form1 f1 = new Form1();
Form2 f2 = new Form2();
this.Hide();
f2.ShowDialog();
this.Close();
}
Santosh

Do you have a simple example similar to the code above "Putting it all together" , but with the 2 forms ?

Carlo Tony Daristotile

Suppose i have taken two forms:
Fom1 contains one label(username) & one button
Form2 contains one textbox, & one button
after clicking button1 of form1 , form2 will popup whatever you are going to type in textbox of form2 will reflect in label of form1
for this go in form2designer2.cs change this (public System.Windows.Forms.TextBox textBox1;) in public
then click button of form2 & write this.close();
now click button event of form1 & write below code
private void button1_Click(object sender, EventArgs e)
{
Form2 fr = new Form2();
fr.ShowDialog();
label1 .Text =fr.textBox1.Text;
}

Santpsh

I noticed that when running this journal, the attribute value updates but the drawing note (that links to that attribute) doesn't update until I update the drawing. If, however, I go to File > Properties and edit the value, it updates immediately. 1) why the difference? 2) can the journal be edited to update the drawing upon completion?

Matt

If I want to hand over a 3 x 3 Array from Form1 to the journal and reverse. What is the best solution? Public Array? Thank you and best regards

A public variable would certainly work in this case.

The programming "best practice" would be to limit the variable scope as much as possible. One way to do this is to use private variables and use properties to get/set the values as demonstrated in the code in the article. Using properties provides for some safeguards but is probably overkill if your journal is small and simple.

Hello,

I compressed the array into one variable like you suggested and splitted it after handover. This works perfectly. Thank you for your help so far.

May I ask you an other question? I would like to pimp my form with a background image. Is this possible? A reference to resources in the form designer is created automatically:

Me.BackgroundImage = CType(resources.GetObject("$this.BackgroundImage"), System.Drawing.Image)

What data and where do I have to include this data after "Putting it all together" that the image will work?

Best regards

I think that you can only use the "resource file" if you compile your code (only possible if you have an NX author license).

However, even if you are running your code as a journal, you should be able to change the background image of your form. Save the image in a convenient location and in your form load routine, load the image into the form background.

This article was super helpful in making a simple journal that uses a form with radio buttons to collect decisions from the user. However, (and I'm being super picky here) the radio buttons don't look the same as they do in Visual Studio when I actually run the journal in NX. They are the older, uglier, more pixelated looking radio buttons. I'm trying to get my form to look like any other form in NX. Any idea why these are displaying differently I run the journal? I'm running NX 11 on Windows 7.

If you are using the standard visual studio tools for creating the form, I can't think of any good reason why it would look different when run with NX. My only real guess is that maybe NX is referencing an older version of the .net framework than what your IDE is using?

There are other form design tools available (WPF, Blend, XAML). I am not familiar with these tools and cannot comment on how well they work with NX. If you are using one of these designers, it may not be fully compatible with NX.

Any idea how to check what .NET framework NX is referencing? I had 4.6.1 installed. The release notes for NX 11.0.2 say to install .NET 4.5. Maybe it's referencing an older version since it can't find 4.5? I downloaded 4.7.1 but I'm still having the issue. Also, I'm using VS 2017 Community.

The .net framework version that NX uses will be listed in the programming help files or somewhere in the what's new section (I don't remember the exact location from memory). I'm not saying that this is definitely the cause of your issue, it may have nothing to do with the difference that you are seeing. It is just the only thing that I could think of offhand that may play a part...

Hi NXJournaling,

I created a windows form with a button according to Getting Started with NX Open.
I wish I can click "start", select a face and then everything goes like my journal here: https://www.nxjournaling.com/content/get-area-face-created-face-curves
(You can also refer to the mail which I sent you on Jan. 10th.)

I created a property for the face I want to get "dispObj1", and met the error "'dispObj1' is not declared. It may be inaccessible due to its protection level.".
After debugging with ChatGPT, there is no error, but I can't select a face, either (there is no the small window for selection)...
https://chat.openai.com/c/b62ee264-8ea9-4658-864b-ff13e1d4aa5d

What's going wrong in my codes?
Besides, should I create properties for every functions such as TraceARay, reading matrix... and so on?

Code excerpt:

Module GetFace
'Variablen für Teiledimensionen / Variable for part dimentions
Dim theSession As Session = Session.GetSession()
Dim workPart As NXOpen.Part = theSession.Parts.Work
Dim theUI As UI = UI.GetUI()
Dim theUfSession As UFSession = UFSession.GetUFSession()
Dim lw As ListingWindow = theSession.ListingWindow
Friend dispObj1 As Face = Nothing
Dim minPt As Point3d = Nothing
Dim allLengthInnerEdge As Double = Nothing
Dim allLengthOuterEdge As Double = Nothing

'Variablen für die Matrizen / Variable for the matrizes
Dim matrixWerk As String(,)
Dim matrixMasch As String(,)

' Expose dispObj1 as a property
Public Property SelectedFace As Face
Get
Return dispObj1
End Get
Set(value As Face)
dispObj1 = value
End Set
End Property

Public Sub Main()

Dim form As New NXOpenWinForm
form.ShowDialog()
If form.Canceled Then
'user pressed cancel, exit journal
Return
Else

End If

lw.Open()

If IsNothing(theSession.Parts.Work) Then
lw.WriteLine("No work part for processing")
Return
End If

Dim partBodiesArray() As Body = workPart.Bodies.ToArray()
Dim theBody As Body = Nothing
Dim myMeasure As MeasureManager = theSession.Parts.Display.MeasureManager()
Dim displayPart As NXOpen.Part = theSession.Parts.Display

dispObj1 = SelectFace()

lw.Close()

End Sub

Public Function GetUnloadOption(ByVal dummy As String) As Integer

'Unloads the image immediately after execution within NX
GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Immediately
End Function

'unterscheide die äußeren und inneren Kanten durch Loop

Public Function SelectFace() As Face
Dim dispObj1 As Face = Nothing
' Variablen fuer Auswahl-Dialoge (Selection Dialogs)

Dim selManager = theUI.SelectionManager
Dim selectedObject As TaggedObject
Dim cursor As Point3d
Dim cue As String = "Bitte wähle die Fläche, auf der gelasert wird, aus."
Dim title As String = "Kosten- und CO2-Rechner"
Dim scope As Selection.SelectionScope = Selection.SelectionScope.WorkPart
Dim action = Selection.SelectionAction.ClearAndEnableSpecific
Dim includeFeatures As Boolean = False
Dim keepHighlighted As Boolean = False
Dim faceMask = New Selection.MaskTriple(NXOpen.UF.UFConstants.UF_face_type, 0, 0)
Dim maskArray As Selection.MaskTriple() = {faceMask}
Dim response As Selection.Response = selManager.SelectTaggedObject(cue, title, scope, action, includeFeatures, keepHighlighted, maskArray, selectedObject, cursor)

If response = Selection.Response.ObjectSelected OrElse response = Selection.Response.ObjectSelectedByName Then

If TypeOf selectedObject Is Face Then
dispObj1 = CType(selectedObject, Face)
End If
Else

End If

Return dispObj1

End Function

Public Function PointOnSelectedFace(dispObj1 As Face) As Point3d
'Greife den Punkt mit minU und minV einer Fläche zu / Get the point with minU and minV on a face
Dim selectedFace As Face = dispObj1
Dim uvMin(3) As Double
theUfSession.Modl.AskFaceUvMinmax(selectedFace.Tag, uvMin)
Dim ptMin(2) As Double
Dim ptMax(2) As Double

'get the point of given face
'Dim param() As Double = {(uvMin(0) + uvMin(1)) / 2, (uvMin(2) + uvMin(3)) / 2} 'center point Of a face
Dim param() As Double = {uvMin(0), uvMin(2)} '[0] - umin[1] - umax [2] - vmin [3] - vmax point with minU and minV (Parameter (u,v) on face)
Dim pt(2) As Double 'Point at parameter
Dim u1(2) As Double 'First derivative in U
Dim v1(2) As Double 'First derivative in V
Dim u2(2) As Double 'Second derivative in U
Dim v2(2) As Double 'Second derivative in V
Dim unitNormal(2) As Double 'Normal vector of the face
Dim radii(1) As Double ' Principal radii of curvature
theUfSession.Modl.AskFaceProps(selectedFace.Tag, param, pt, u1, v1, u2, v2, unitNormal, radii)

Dim minPt As New Point3d(pt(0), pt(1), pt(2))

Return minPt

End Function

End Module

Public Class NXOpenWinForm
' Add a property to track if the form is canceled
Public Property Canceled As Boolean

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' Call the property to get the selected face
dispObj1 = GetFace.dispObj1
If dispObj1 Is Nothing Then
'face selection cancelled
Return
End If
lw.Closed()
End Sub
End Class

Partial Class NXOpenWinForm
Inherits System.Windows.Forms.Form

Public Sub New()
InitializeComponent()
NXOpenUI.FormUtilities.SetApplicationIcon(Me)
NXOpenUI.FormUtilities.ReparentForm(Me)
End Sub

'Form overrides dispose to clean up the component list.

Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub

'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.

Private Sub InitializeComponent()
Me.Button1 = New System.Windows.Forms.Button()
Me.SuspendLayout()
'
'Button1
'
Me.Button1.BackColor = System.Drawing.SystemColors.ActiveCaption
Me.Button1.Location = New System.Drawing.Point(117, 62)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(179, 47)
Me.Button1.TabIndex = 0
Me.Button1.Text = "Start"
Me.Button1.UseVisualStyleBackColor = False
'
'NXOpenWinForm
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(9.0!, 18.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(412, 197)
Me.Controls.Add(Me.Button1)
Me.Margin = New System.Windows.Forms.Padding(4, 4, 4, 4)
Me.Name = "NXOpenWinForm"
Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent
Me.Text = "Kosten- und CO2-Emissionen Rechner"
Me.ResumeLayout(False)

End Sub

Friend WithEvents Button1 As System.Windows.Forms.Button
End Class

Best regards,
Ray

I found that after I closed the window, the selection window popped up. How to make the starting window automatically disappear and show the selection window?

Do you happen to have a blockstyler license? If so, the blockstyler is the preferred way to make dialogs that interact with NX. Winforms are more limited in what you can do.

On to your question. When NX executes your journal, it starts with Sub Main - that will be the first thing executed. If you want the user to select the face before the winform is shown, try moving the face selection step before calling the form's .showdialog method.

The properties in the example code are only there to pass information between the journal code and the form code. I suggest only creating the properties that you need.

Ok I'll try blockstyler in the meantime. I used WinForm at first because Getting Started with NX Open said "WinForm dialogs are very rich and flexible, so there may be times when they are appropriate. On the other hand, block-based dialogs are rigid and highly structured, because they enforce NX user interface standards."

I wish the plug-in to work like this:
User click a button to start the Sub Main() -> select a face -> plug-in analyses the part and calculate the info -> info is displayed in a graph (e.g. pie chart)
Or maybe this tutorial below is better than a window with a start button?
https://www.nxjournaling.com/content/start-journal-custom-button

I tried to modify my codes this morning before I found you reply on 26.01. After I clicked start, I could select face, but my button window didn’t close as Me.Close() implies. What’s worse, I couldn’t close my button window owing to the exception (it crashed):
NXOpen.NXException: GUI can not be closed in the status.
bei Microsoft.VisualBasic.CompilerServices.Symbols.Container.InvokeMethod(Method TargetProcedure, Object[] Arguments, Boolean[] CopyBack, BindingFlags Flags)
bei Microsoft.VisualBasic.CompilerServices.NewLateBinding.ObjectLateGet(Object Instance, Type Type, String MemberName, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, Boolean[] CopyBack)

Could you please help me find out what’s going wrong?
Thanks in advance!

Here are my code snippets:

Module GetFace
'Variablen für Teiledimensionen / Variable for part dimentions
Dim theSession As Session = Session.GetSession()
Dim workPart As NXOpen.Part = theSession.Parts.Work

' Expose dispObj1 as a property
Public Property SelectedFace() As Face
Get
Return dispObj1
End Get
Set(ByVal value As Face)
dispObj1 = value
End Set
End Property

Public Sub Main()

lw.Open()

If IsNothing(theSession.Parts.Work) Then
lw.WriteLine("No work part for processing")
Return
End If

Dim partBodiesArray() As Body = workPart.Bodies.ToArray()
Dim theBody As Body = Nothing
Dim myMeasure As MeasureManager = theSession.Parts.Display.MeasureManager()
Dim displayPart As NXOpen.Part = theSession.Parts.Display

Dim form As New NXOpenWinForm
'set form object properties
form.ShowDialog()
If form.Canceled Then
'user pressed cancel, exit journal
Return
Else

End If

'dispObj1 = SelectFace()
If dispObj1 Is Nothing Then
'face selection cancelled
Return
End If
End Sub

Public Function GetUnloadOption(ByVal dummy As String) As Integer

'Unloads the image immediately after execution within NX
GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Immediately

End Function
Public Function SelectFace() As Face
Dim dispObj1 As Face = Nothing
' Variablen fuer Auswahl-Dialoge (Selection Dialogs)

Dim selManager = theUI.SelectionManager
Dim selectedObject As TaggedObject
Dim cursor As Point3d
Dim cue As String = "Bitte wähle die Fläche, auf der gelasert wird, aus."
Dim title As String = "Kosten- und CO2-Rechner"
Dim scope As Selection.SelectionScope = Selection.SelectionScope.WorkPart
Dim action = Selection.SelectionAction.ClearAndEnableSpecific
Dim includeFeatures As Boolean = False
Dim keepHighlighted As Boolean = False
Dim faceMask = New Selection.MaskTriple(NXOpen.UF.UFConstants.UF_face_type, 0, 0)
Dim maskArray As Selection.MaskTriple() = {faceMask}
Dim response As Selection.Response = selManager.SelectTaggedObject(cue, title, scope, action, includeFeatures, keepHighlighted, maskArray, selectedObject, cursor)

If response = Selection.Response.ObjectSelected OrElse response = Selection.Response.ObjectSelectedByName Then

If TypeOf selectedObject Is Face Then
dispObj1 = CType(selectedObject, Face)
End If
Else

End If

Return dispObj1

End Function

Public Class NXOpenWinForm
' Add a property to track if the form is canceled
Public Property Canceled As Boolean
Private formSelectedFace As Face
Public Property SelectedFace() As Face
Get
Return formSelectedFace
End Get
Set(ByVal value As Face)
formSelectedFace = value
End Set
End Property

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' Call the property to get the selected face
formSelectedFace = SelectFace()

'dispObj1 = GetFace.dispObj1
If formSelectedFace Is Nothing Then
'face selection cancelled
Return
End If

Me.Close()
End Sub
End Class

Partial Class NXOpenWinForm
Inherits System.Windows.Forms.Form

Public Sub New()
InitializeComponent()
NXOpenUI.FormUtilities.SetApplicationIcon(Me)
NXOpenUI.FormUtilities.ReparentForm(Me)
End Sub

'Form overrides dispose to clean up the component list.

Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub

'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.

Private Sub InitializeComponent()
Me.Button1 = New System.Windows.Forms.Button()
Me.SuspendLayout()
'
'Button1
'
Me.Button1.BackColor = System.Drawing.SystemColors.ActiveCaption
Me.Button1.Location = New System.Drawing.Point(117, 62)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(179, 47)
Me.Button1.TabIndex = 0
Me.Button1.Text = "Start"
Me.Button1.UseVisualStyleBackColor = False
'
'NXOpenWinForm
'
Me.AcceptButton = Me.Button1
Me.AutoScaleDimensions = New System.Drawing.SizeF(9.0!, 18.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(412, 197)
Me.Controls.Add(Me.Button1)
Me.Margin = New System.Windows.Forms.Padding(4, 4, 4, 4)
Me.Name = "NXOpenWinForm"
Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent
Me.Text = "Kosten- und CO2-Emissionen Rechner"
Me.ResumeLayout(False)
Me.PerformLayout()

End Sub

Friend WithEvents Button1 As System.Windows.Forms.Button
End Class

In the code that you have posted, "dispObj1" is declared in the "SelectFace" function; as such, the scope of the dispObj1 variable is limited to the SelectFace function. Any attempts to use the variable outside of that function will fail.

Also, the code is still attempting to open the form before the user has selected the face. I think it will work as described in your workflow if you prompt the user for the face selection before calling form.showdialog.

Hello NXJournaling,

I just moved face selection before form.showdialog. When I select face in the 1st step, then the window pops up. When I click start button in the window, it crashes like you mentioned.

I wonder how to define the return in property SelectedFace(), so that the button works like a "switch", which calls the selecting function and the rest procedures?