Author Topic: Return Filepath & Filename to Calling Form's Textbox  (Read 4242 times)

Starblue7

  • Full Member
  • ***
  • Posts: 131
Return Filepath & Filename to Calling Form's Textbox
« on: June 06, 2023, 04:21:36 AM »
In doing a lot of searching and looking at some examples, I'm still quite in the dark about what's going wrong with this code, and appreciate any Gurus assistance.

I have an inline function, modeling off of @Exergist 's Alternate 'Get User Input' Method (buttons!) example here---> https://forum.voiceattack.com/smf/index.php?topic=2395.msg11022#msg11022 example.

I'm calling a form, and from within that form I have a button that calls the Windows OpenFileDialog in order to choose a filepath and filename and return this to a variable.  Subsequently to return this value to the base form's textbox.

I have no problem getting the filepath and filename and dumping it to the eventlog, but I get a System.NullReferenceException when I try to apply this value to the textbox of the form.

Offending Line:   LauncherBox.Text = Global.LauncherPathFile;  //THIS WON'T UPDATE ABOVE LAUNCHERBOX and causes a System.NullReferenceException  <---------




Here is a sample of the code I have:
Code: [Select]
using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Text.RegularExpressions;

public class Global
{
public static string LauncherPathFile = ""; 
}

public class VAInline
{
[STAThread]
public void main()
{
     // Enable (operating system) visual styles for the application
     Application.EnableVisualStyles();

     // Create an instance of Form1 (while passing the dynamic VA for use by the form)
             // and an associated message loop
     Application.Run(new MY_FORM(VA));
}
}

public class MY_FORM : Form
{
     // Initialize string variable for storing user input
     public string result = null;

     // Create and display MY_FORM
     public MY_FORM(dynamic VA)
     {
          // SETTINGS FORM
          {
                this.ClientSize = new Size(1452, 577);
                this.Font = new Font("Arial", 12F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
        this.Margin = new Padding(2, 4, 2, 4);
        this.FormBorderStyle = FormBorderStyle.FixedDialog; // Define the border style of the form to a dialog box
        this.MaximizeBox = false; // Set the MaximizeBox to false to remove the maximize box
        this.MinimizeBox = false; // Set the MinimizeBox to false to remove the minimize box
        this.StartPosition = FormStartPosition.CenterScreen; // Set the start position of the form to the center of the screen
        this.MinimumSize = new Size(this.Width, this.Height); // Set the form window to be no smaller than design time size
        this.MaximumSize = new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); // Set the form window to be no larger than screen size
        int FontSize = 12; // Set the font size used for buttons
        int ControlSpacing = 15; // Set the spacing between form controls
        this.FormClosing += new FormClosingEventHandler((sender, e) => MY_FORM_FormClosing(sender, e, VA)); // Subscribe to event for the form closing and pass in dynamic VA
        //this.toolTip = new ToolTip();  //  TRYING TO CREATE MOUSEOVER TOOLTIPS for a later time...
        this.Name = "MY_FORM";
        this.Text = "Settings"; // Set the caption bar text of the form
           }

           // Launcher Button
   {
        Button Launcher_Button = new Button(); // Create the instance of the button
        Launcher_Button.TabIndex = 1;
        Launcher_Button.Location = new Point(12, 19);
        Launcher_Button.Text = "Launcher Location"; // Set the text of the button
        Launcher_Button.Size = new Size(262, 43);
        Launcher_Button.Font =  new Font("Arial", 12F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
        this.Controls.Add(Launcher_Button); // Add button to the form
        Launcher_Button.Click += new EventHandler((sender, e) => Launcher_Button_Click(sender, e, VA)); // Subscribe to button click events and pass in dynamic VA
   }

           // Launcher Box
   {
        RichTextBox LauncherBox = new RichTextBox();
        LauncherBox.BackColor = SystemColors.ButtonFace;
LauncherBox.BorderStyle = BorderStyle.FixedSingle;
LauncherBox.Location = new Point(12, 68);
LauncherBox.Name = "LauncherBox";
LauncherBox.ReadOnly = true;
LauncherBox.ScrollBars = RichTextBoxScrollBars.Vertical;
LauncherBox.ShortcutsEnabled = false;
LauncherBox.Size = new Size(938, 56);
LauncherBox.TabIndex = 0;
LauncherBox.TabStop = false;
LauncherBox.Text = Global.LauncherPathFile;  // WORKS WITH CODE THAT READS A TEXT FILE AND IS UPDATED (Code not supplied for this example) 
this.Controls.Add(LauncherBox);
//LauncherBox.TextChanged += new EventHandler(this.LauncherBox_TextChanged);
   }

   // Call function for bringing form into focus
   Functions.ShowForm(this);
   // Output info to event log
   VA.WriteToLog("Form initialization complete");
      }

             // Function run when Launcher_Button is clicked
             private void Launcher_Button_Click(object sender, EventArgs e, dynamic VA)
     {
   OpenFileDialog openFileDialog1 = new OpenFileDialog();
   openFileDialog1.Filter = "Executable files|*.exe";
   openFileDialog1.Title = "Select a File";
   if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
   {
Global.LauncherPathFile = openFileDialog1.FileName;
VA.WriteToLog("[[ INLINE ]] " + Global.LauncherPathFile, "purple");  // THIS WORKS FINE
   }
   openFileDialog1 = null;
   LauncherBox.Text = Global.LauncherPathFile;  //THIS WON'T UPDATE ABOVE LAUNCHERBOX and causes a System.NullReferenceException  <---------
     }

private System.Windows.Forms.RichTextBox LauncherBox;
private System.Windows.Forms.Button Launcher_Button;
}

public class Functions
{
// Function for brining form into focus
public static void ShowForm(Form f)
{
// Retrieve the window handle for the form and give the form focus
IntPtr myHandle = f.Handle;
SetForegroundWindow(myHandle.ToInt32());
}

// External function for bringing window into focus using its handle
[DllImport("User32.dll")]
public static extern Int32 SetForegroundWindow(int hWnd);
}


Pfeil

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 4782
  • RTFM
Re: Return Filepath & Filename to Calling Form's Textbox
« Reply #1 on: June 06, 2023, 07:04:06 AM »
The exception being thrown has nothing to do with the variable.

Looking up the control by name in the Controls collection of the form can work:
Code: [Select]
Controls["LauncherBox"].Text = Global.LauncherPathFile;

However, note that learning how to write and debug C# is outside the scope of support for of VoiceAttack itself.

Starblue7

  • Full Member
  • ***
  • Posts: 131
Re: Return Filepath & Filename to Calling Form's Textbox
« Reply #2 on: June 06, 2023, 07:10:26 AM »
Thanks Pfeil.  That works.  Sure appreciate any voluntary path steering.  Much thanks.

SemlerPDX

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 291
  • Upstanding Lunatic
    • My AVCS Homepage
Re: Return Filepath & Filename to Calling Form's Textbox
« Reply #3 on: June 06, 2023, 10:11:40 PM »
1. Your code is difficult to read because you have needlessly nested code blocks and mixed indentation of spaces and tabs
2. Addressing a button by name is not a big deal, but passing a reference of LauncherBox in the event handler is more ambiguous
3. Pfeil as always is correct, learning C# itself is beyond scope here, but you should read up on static classes and properties as well
4. Avoid spaghetti code in general, keep it simple - not sure if you redacted code for brevity, but you have an event leading to a method which doesn't exist, and a mix of tabs and spaces for indents

Keep tinkering and learning, but don't negate study at places like w3schools, etc.  Don't be afraid to refactor code after you learn something new, helps cement the proper ways of doing things once you learn best practices and general standards including use of variables, properties, methods, classes, and even curly brackets and indentation.

Starblue7

  • Full Member
  • ***
  • Posts: 131
Re: Return Filepath & Filename to Calling Form's Textbox
« Reply #4 on: June 06, 2023, 10:50:55 PM »
1. Your code is difficult to read because you have needlessly nested code blocks and mixed indentation of spaces and tabs

Yes, I have lots of form elements to make settings for.  Thus it's a ton of time to scroll thru it all.  Thus I've put in place { }'s in order to be able to condense those blocks so I can focus on what I'm working on without wearing out my mouse scroll wheel.
Yes, I'm sure I can reduce redundancy by refactoring the code.  At some point I may do that.

4. Avoid spaghetti code in general, keep it simple - not sure if you redacted code for brevity, but you have an event leading to a method which doesn't exist, and a mix of tabs and spaces for indents

Various code was cut out and modified by hand to just try to focus mostly on what I was doing with the classes and the issue I had at hand.  It's not the FULL code.

SemlerPDX

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 291
  • Upstanding Lunatic
    • My AVCS Homepage
Re: Return Filepath & Filename to Calling Form's Textbox
« Reply #5 on: June 07, 2023, 11:21:59 AM »
Yes, I have lots of form elements to make settings for.  Thus it's a ton of time to scroll thru it all.  Thus I've put in place { }'s in order to be able to condense those blocks so I can focus on what I'm working on without wearing out my mouse scroll wheel.

Use comments to visually break up sections, not needlessly nested code blocks. If you need to force a foldable section around a block of code, use regions. The GUI inline function which I built for my AVCS SENS profile is nearly 5000 lines long, including somewhere around 15 form pages (classes), and I use comments to help visually break up these items, and folding to be able to scroll through these various pages easily, expanding just the section I am working on by clicking the little "+" symbol next to that folded section.  If you're interested, you can check out this extremely long inline function on my pastebin - it was a challenge to myself to make a complex WinForm in a single inline, and while I wouldn't do it like this again, I met and achieved that challenge & have a working GUI that would be a pain in the neck to edit or refactor so much that I'd likely redesign it as a plugin if I ever had to return to it.
https://pastebin.com/8a1NFRDp
Just because we can do something doesn't necessarily mean it is a good way to do it.



It may not be possible for you yet, but as you learn you should begin to be aware of nesting and begin to attempt keeping it to 3 or less. This means entering new method, and you add an "if" statement, the contents of it will be the first nested level - adding yet another if-statement inside that would mean its contents would be at the second nested level, and so on to the third. At this point, you will have an end of this method which may look like this:

Code: (csharp) [Select]
      }
    }
  }
}

...any more than this, and the task of visually reviewing code and attempting to expand or even troubleshoot various blocks of code and various methods can become quite cumbersome and unwieldy.  Avoid excessive nesting with guard clauses and early returns, use private helper methods, and generally limit the structures of any single method to avoid going much farther than 3 levels of nesting in a method.

For example (this is a nonsensical code bit just to demo the concept above, using TABs only to better illustrate it):
Code: (csharp) [Select]
private void MyMethod(bool? myBool)
{
if (myBool != null)
{
VA.SetBoolean("MyBoolVar", myBool);
if (VA.GetText("SomeVar") != null)
{
int index = 0;
string[] someVars = VA.GetText("SomeVar").Split(',');
foreach (string vars in someVars)
{
if (!String.IsNullOrEmpty(vars))
{
index++;
VA.SetText("ExampleVar" + index.ToString(), vars);
if (index >= 23)
{
break;
}
}
}
}
}
}



By using guard clauses and early returns, and a slightly different structure, we can make it much easier to read and follow, eliminating deeply nested code for greater maintainability in development, testing, and troubleshooting:
Code: (csharp) [Select]
private void MyMethod(bool? myBool)
{
if (myBool == null)
{
return;
}

VA.SetBoolean("MyBoolVar", myBool);

if (VA.GetText("SomeVar") == null)
{
return;
}

int index = 0;
string[] someVars = VA.GetText("SomeVar").Split(',');
foreach (string vars in someVars)
{
if (String.IsNullOrEmpty(vars))
{
continue;
}

index++;
VA.SetText("ExampleVar" + index.ToString(), vars);
if (index >= 23)
{
break;
}
}
}


Notice how much easier it is to read and potentially modify the second example versus the first. Try to be aware of these things as you study, learn, and practice, and you'll save yourself a lot of eye-strain and headaches when reviewing and developing your code.  When you run into barriers, remember to ask yourself, "Do I really need to do this?" -OR- "Do I really need to do this in THIS way?" -OR- "Can I do this in a more straightforward and simple manner?"

In summary, nested is to be avoided where possible to make code easier to read, maintain, modify, test, and/or troubleshoot.

Best wishes and keep up the hard work - it's totally worth it!!

SemlerPDX

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 291
  • Upstanding Lunatic
    • My AVCS Homepage
Re: Return Filepath & Filename to Calling Form's Textbox
« Reply #6 on: June 07, 2023, 11:37:00 AM »
Just to demonstrate the other concept, of using a helper method to eliminate further nesting, we could also flatten that method like this:

Code: (csharp) [Select]
private void SetVars(string[] someVarArray)
{
int index = 0;
foreach (string vars in someVarArray)
{
if (String.IsNullOrEmpty(vars))
{
continue;
}

index++;
VA.SetText("ExampleVar" + index.ToString(), vars);
if (index >= 23)
{
break;
}
}
}

private void MyMethod(bool? myBool)
{
if (myBool == null)
{
return;
}

VA.SetBoolean("MyBoolVar", myBool);

if (VA.GetText("SomeVar") == null)
{
return;
}

string[] someVars = VA.GetText("SomeVar").Split(',');
SetVars(someVars);
}


...when you create structures such as this, by placing the "helper" method ABOVE the method which uses it, while reading a code page from top down, users will be aware of what "SetVars()" does before they read it's use within "MyMethod()", eliminating any mystery or need to jump around and search for more information elsewhere in this class.

NOTE: The example above is not ideal, as we were not already excessively nested, and so there is no need to create a helper method to further flatten the example method above - but for situations where you are already well nested and wish to clean things up a bit, this would be a good example for how to do that. We should not create additional methods unless their code is also used by other methods or they add excessive nesting when part of an existing method.

Cheers!