Search code examples
c#.netwinformsuser-controls

C# Get variable from popup form that will be closed


I have a form populated with an array of userControls that is created from the main form. I need to be able to access this array of userControls from the main form once the popup has been closed when a button is pressed. If I fill out the forms and then press the button on the main form without closing the popup, the values are present. However, if I close the popup window, the values are not present. My main form is static so I can use it's variables in other forms.

Code for the popup:

 public ScanChannel[] controls;
    public ScanListSetup()
    {
        InitializeComponent();
        int numChans = Convert.ToInt32(Form1.Self.numChannels.Text);
        controls = new ScanChannel[numChans];

        // Create the UserControls
        for(int i = 0; i < numChans; i++)
        {
            controls[i] = new ScanChannel();
        }

        // Place them
        for (int i = 0; i < numChans; i++)
        {
            controls[i].Location = new Point(13,(35+25*(i)));
            this.Controls.Add(controls[i]);
        }

        doneButton.Location = new Point(82, 35 + (25 * (numChans + 1)));
        this.Size =new Size(280, 110 + (25 * (numChans + 1)));
    }

    private void doneButton_Click(object sender, EventArgs e)
        {
            Form1.Self.setChannelsToScan(controls);
        }

I need to access the controls array in the main form. The code for the main form is as follows:

private ScanChannel[] channelsToScan;


private void configureScanListButton_Click(object sender, EventArgs e)
    {

        var form = new ScanListSetup();
        form.Show(this);
        scanListConfigured = true;
        this.channelsToScan = new ScanChannel[Convert.ToInt32(numChannels.Text)];

    }

public void setChannelsToScan(ScanChannel[] arr)
    {
        for (int i = 0; i < arr.Length; i++)
        {
            this.channelsToScan[i] = arr[i];
        }
    }

 private void scanButton_Click(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Test: " + this.channelsToScan[0].getDeviceType());
        // THIS PRINTS AN EMPTY STRING
    }

So, the Debug writeLine outputs the correct value if I click the scanButton while the popup form is still open. However, if I close the form after clicking the doneButton on the popup form, the Debug writeLine outputs Test: with nothing else.

Any help with this would be greatly appreciated. Thanks.


Solution

  • Your problem essentially boils down to sending data from a secondary window (your 'pop-up' window) to the main window from where it was created. It doesn't matter whether you're working with Windows Control objects or simple data types like string, so I'm going to use a simple example to illustrate how to handle such a situation.

    Let's assume you have a Main form that looks like this. It has an OPEN button and a TextBox.

    enter image description here

    When you click OPEN, it opens up this secondary input window (your pop-up) which looks like this:

    enter image description here

    Now the idea is this. You click OPEN and opens the Input form, and lets the user enter some text into the TextBox there. Once you click the OK button, it should close the Input window, and display the text entered by the user in the Main window. Remember that at this point the Input window is closed, which is equivalent to your situation.

    So I'd make use of Delegates to accomplish this goal. A delegate lets you transfer data between windows which is what you want.

    In my Main I'd declare a public delegate with a signature like this:

    public delegate void DataTransfer(string data);
    

    That is, this delegate represents a method that takes in a single string parameter, and has void return type. The idea is to let the secondary Input window 'call' a method in the Main, and that method takes in a string parameter. So, if there was a way for us to call a method that resides in the Main from Input, and pass a string, we can then take the user input text in the Input window, and pass it to the Main window. With me so far?

    Now, if I write a method like this in the Main, and let it be called from Input, that should accomplish our goal. Here, txtDisplay is the TextBox in the Main form.

    public void ReceiveInput(string data)
    {
        txtDisplay.Text = data;
    }
    

    To accomplish this, I would define a delegate of type DataTransfer in the Main form like below, and register the ReceiveInput() method to it. Your Main form code behind should look like this:

    public delegate void DataTransfer(string data);
    
    public partial class MainForm : Form
    {
        public DataTransfer transferDelegate;
    
        InputForm inputForm = null;
    
        public MainForm()
        {
            InitializeComponent();
            transferDelegate += new DataTransfer(ReceiveInput);
        }
    
        public void ReceiveInput(string data)
        {
            txtDisplay.Text = data;
        }
    
        private void BtnOpen_Click(object sender, EventArgs e)
        {
            inputForm = new InputForm(transferDelegate);
            inputForm.Show();
        }
    }
    

    BtnOpen is the OPEN button in the Main form, and when it's clicked, it passes the delegate to the Input form, then opens it. So, accordingly, we need to now modify our Input form:

    public partial class InputForm : Form
    {
        DataTransfer transferDel;
    
        public InputForm(DataTransfer del)
        {
            InitializeComponent();
            transferDel = del;
        }
    
        private void BtnOK_Click(object sender, EventArgs e)
        {
            string data = txtInput.Text;
            transferDel.Invoke(data);
            Close();
        }
    }
    

    Here, we modify the constructor so that it takes in a delegate of type DataTransfer, and sets it to the local instance of the same type. Then, at the click of BtnOK on the Input form, we take in the text input by user, and pass that text to the said delegate and invoke it. 'Invoking' is the same as calling the method in the Main form. At this point, you can Clsoe() the Input window as shown above, and you'd still have access to the user input string data from your Main form.

    You can use this same approach, and instead of strings you can pass around Controls. However, it's not the best approach to pass around a bunch of controls back and forth, so ideally you would extract the data you need from those controls in your pop-up, and pass only the said data instead of the whole controls.


    EDIT: After OP posted the erroneous code.

    OK, so here's your issue. The testUserControl class is not a regular class but a control element derived from UserControl. In otherwise, a GUI element. You shouldn't use GUI elements to pass data around. Because, when you do your controlArr[i].getText();, it tries to get the text from the textItem, but textItem is a TextBox Control which doesn't exist at this point because you closed your window. Remember, you do the delegate.Invoke() only once, and at that point *you must send ALL the data back to your main window*.

    What you should do is, simply define a class to hold ALL the data you want to pass to your main. For example something like this:

    public class DataToPass
    {
        public string TextBoxText { get; set; }
    
        public string SomeOtherData { get; set; }
    
        // Other stuff you want...
    }
    

    Now, instead of passing an array of testUserControl, pass an array of DataToPass. That way, at the Main form you don't have to do the following:

    controlArr[i].getText();
    

    Instead you'd simply do something like:

    controlArr[i].TextBoxText;
    

    where controlArr now is an array of type DataToPass.

    Simply, passing a control derived from UserControl is not a good idea. Just create one class that is capable of holding ALL the data you want to pass and pass it back to the main once.