I need to access a form control from another class (C#)

On my form, I have one Panel container, named "panelShowList".

On my project, i added a new class, which look like this:

class myNewClass
{
    private int newPanelPos = 30;
    private const int spaceBetweenElements = 30;
    private const int panelWidth = 90;
    private const int panelHeight = 40;
    private int elementPos = 0;

    private ArrayList myPanels = new ArrayList() { };

    // some irelevant methods

    public void addElementPanels(Panel dataPanel, Panel nextPanel)
    {
        myPanels.Add(dataPanel);
        myPanels.Add(nextPanel);
    }

    public void displayPanels()
    {
        foreach (Panel tmp in myPanels)
        {
            // here i'm stuck

            // i need to do something like this :
            // myMainForm.panelShowList.Controls.Add(tmp);
            // of course this is wrong! but i need a method to acces that control
        }
    }
}

Basically, I need a way to add all Panels from my ArrayList on "panelShowList" control from my form.

I tried something like this:

public void displayPanels()
    {
        frmMain f = new frmMain();
        foreach (Panel tmp in myPanels)
        {

            f.display(tmp);
            // where display(Panel tmp) is a function in my Form, who access
            // "panelShowList" control and add a new Panel
        }
    }

But it only works if i do this:

f.ShowDialog();

and another form is open.

Any suggestions will be appreciated.


Maybe a bit late, but by all means, here is another approach, that's still more clean than David's approach:

You should add an EventHandler in your MyNewClass. Then you can subscribe to that event from within your form.

public partial class Form1 : Form
{
    private readonly MyNewClass _myNewClass;

    public Form1()
    {
        InitializeComponent();
        _myNewClass = new MyNewClass();
        _myNewClass.DisplayPanelsInvoked += DisplayPanelsInvoked;
    }

    private void DisplayPanelsInvoked(object sender, DisplayPanelsEventArgs e)
    {
        var panels = e.Panels; // Add the panels somewhere on the UI ;)
    }
}

internal class MyNewClass
{
    private IList<Panel> _panels = new List<Panel>();

    public void AddPanel(Panel panel)
    {
        _panels.Add(panel);
    }

    public void DisplayPanels()
    {
        OnDisplayPanels(new DisplayPanelsEventArgs(_panels));
    }

    protected virtual void OnDisplayPanels(DisplayPanelsEventArgs e)
    {
        EventHandler<DisplayPanelsEventArgs> handler = DisplayPanelsInvoked;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    public event EventHandler<DisplayPanelsEventArgs> DisplayPanelsInvoked;
}

internal class DisplayPanelsEventArgs : EventArgs
{
    public DisplayPanelsEventArgs(IList<Panel> panels)
    {
        Panels = panels;
    }

    public IList<Panel> Panels { get; private set; }
}

In my opinion it's a better solution, because you don't need to provide a reference of the form to the MyNewClass instance. So this approach reduces coupling, because only the form has a dependency to the MyNewClass.

If you always want to "update" the form whenever a panel is added, you could remove the DisplayPanels-method and shorten the code to this:

public partial class Form1 : Form
{
    private readonly MyNewClass _myNewClass;

    public Form1()
    {
        InitializeComponent();
        _myNewClass = new MyNewClass();
        _myNewClass.PanelAdded += PanelAdded;
    }

    private void PanelAdded(object sender, DisplayPanelsEventArgs e)
    {
        var panels = e.AllPanels; // Add the panels somewhere on the UI ;)
    }
}

internal class MyNewClass
{
    private IList<Panel> _panels = new List<Panel>();

    public void AddPanel(Panel panel)
    {
        _panels.Add(panel);
        OnPanelAdded(new DisplayPanelsEventArgs(_panels, panel)); // raise event, everytime a panel is added
    }

    protected virtual void OnPanelAdded(DisplayPanelsEventArgs e)
    {
        EventHandler<DisplayPanelsEventArgs> handler = PanelAdded;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    public event EventHandler<DisplayPanelsEventArgs> PanelAdded;
}

internal class DisplayPanelsEventArgs : EventArgs
{
    public DisplayPanelsEventArgs(IList<Panel> allPanels, Panel panelAddedLast)
    {
        AllPanels = allPanels;
        PanelAddedLast = panelAddedLast;
    }

    public IList<Panel> AllPanels { get; private set; }
    public Panel PanelAddedLast { get; private set; }
}

and another form is open

That's because you're creating an entirely new form:

frmMain f = new frmMain();

If you want to modify the state of an existing form, that code will need a reference to that form. There are a number of ways to do this. One could be to simply pass a reference to that method:

public void displayPanels(frmMain myMainForm)
{
    foreach (Panel tmp in myPanels)
    {
        // myMainForm.panelShowList.Controls.Add(tmp);
        // etc.
    }
}

Then when your main form invokes that method, it supplies a reference to itself:

instanceOfNewClass.displayPanels(this);

Though, to be honest, it's not really clear what sort of structure you're going for here. If code is modifying a form then I imagine that code should be on that form. It can certainly be organized into a class, but perhaps that can be an inner class of that form since nothing else needs to know about it.

I'm also concerned that your implementation of myNewClass requires methods to be invoked in a specific order. Any given operation on an object should fully encapsulate the logic to complete that operation. Some of that initialization logic may belong in the constructor if the object isn't in a valid state until that logic is completed.

This is all a bit conjecture though, since the object structure isn't clear here.