Question

I ran into a problem I don't know how to solve.

I've created an application containing 3 forms. frm1 is the main form where all the heavy work will be done. frm2 is just a settings form. frm3 is a status window which will likely show some logging received from frm1 and frm2.

On startup both forms (frm2 and frm3) will be initialized, but not shown. To show one of these forms you need to open it seperately (notify icon).

Each form (frm2 and frm3) are running as STA thread, because there will be heavy load on main form and sometimes it causes the forms to show with huge delays.

When I run frm3 (containing a DataGridView) without starting settings first, everything is fine. frm3 keeps logging all the informations needed from frm2 and frm1. Even if I run frm2 only, it keeps logging into the DataGridView. But starting frm2 first (it starts logging into DataGridView from frm3) and running frm3 afterwards results in InvalidOperationException because frm2 holds access to DataGridView from frm3. I really can't pass the data via Invoke because both forms (frm2 and frm3) are mostly not shown.

Any ideas how to solve this?

Here are a few snippets from my code you can have a look at:

frm1 - Main

public partial class Tray : Form
{
    private Settings s = new Settings();
    public static LogWindow log = new LogWindow();

    public Tray()
    {
        InitializeComponent();

        Data.ni_tray = ni_tray;

        ctm.MenuItems.Add("Start", StartStop);
        ctm.MenuItems.Add("Einstellungen", OpenSettings);
        ctm.MenuItems.Add("Log", OpenLog);
        ctm.MenuItems.Add("Exit", CloseApp);
        ni_tray.ContextMenu = ctm;
    }

    private void OpenSettings(object sender, EventArgs e)
    {
        if (!s.Visible)
        {
            Thread thread = new Thread(() => s.ShowDialog());
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
        }
        else
            s.Invoke(new MethodInvoker(() => { s.locate(); }));
    }
    private void OpenLog(object sender, EventArgs e)
    {
        if (!log.Visible)
        {
            Thread thread = new Thread(() => log.ShowDialog());
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
        }
        else
            log.Invoke(new MethodInvoker(() => { log.locate(); }));
    }

[ ... }
}

frm2 - Settings

public partial class Settings : Form
{
    private readonly string[] info = { String.Empty, "Füge Programm hinzu ...", "Füge Szenario hinzu ...", "Sichere Konfiguration ...", "Übernehme Änderungen ohne Sicherung ...", "Verwerfe Konfiguration ...", "Konfiguration erfolgreich geladen ...", "Konfiguration erfolgreich gespeichert ..." };
[ ... ]

    private void WriteConf()
    {
        writeSection(conf.Root.Element("Applications"), dgv_apps);
        writeSection(conf.Root.Element("Scenarios"), dgv_scen);
        writeSection(conf.Root.Element("Misc"));
        conf.Save(ConfFile);
        Tray.log.writeLogEntry("Settings", info[7]);
        showInfo(info[7]);
    }

[ ... ]
}

frm3 - LogWindow

public partial class LogWindow : Form
{
    public LogWindow()
    {
        InitializeComponent();
    }

    private void btn_close_Click(object sender, EventArgs e)
    {
        this.Hide();
    }

private void dgv_log_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
    {
        btn_clear.Enabled = true;
        dgv_log.ClearSelection();
        dgv_log.FirstDisplayedScrollingRowIndex = dgv_log.Rows.Count - 1;
        dgv_log.Rows[dgv_log.Rows.Count - 1].Selected = true;
    }

public void writeLogEntry(string application, string info)
    {
        if (dgv_log.InvokeRequired)
            this.Invoke((MethodInvoker)delegate { dgv_log.Rows.Add(DateTime.Now.ToShortTimeString(), application, info); });
        else
            dgv_log.Rows.Add(DateTime.Now.ToShortTimeString(), application, info);
    }

    private void btn_clear_Click(object sender, EventArgs e)
    {
        dgv_log.Rows.Clear();
        btn_clear.Enabled = false;
    }
}

This one will cause the exception:

if (!log.Visible)
{
    Thread thread = new Thread(() => log.ShowDialog());
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
}
Was it helpful?

Solution

It is possible to have multiple threads, but please, use only one UI thread. There is main window anyway, so all windows can use its instance to invoke.

Basically:

public partial class FormTray : Form
{
    private static _instance;
    public static Instance { get { return _instance; } } // to get from anywhere

    public FormTray()
    {
        InitializeComponents();
        _instance = this; // store instance
    }

    private void OpenSettings(object sender, EventArgs e)
    {
        FormSettings.Show(); // call static method 
    }

    private void OpenLog(object sender, EventArgs e)
    {
        FormLog.Show(); // call static method
    }

    // ...
}

public partial class FormSettings
{
    private static FormSettings _instance; // to be used from static methods

    public FormSettings()
    {
        InitializeComponents();
        _instance = this;
    }

    public static Show()
    {
        if(_instance == null) // not yet created - create and show
        {
            _instance = new FormSettings();
            _instance.Show();
        }
        else
            _instance.Visible = true; // was created and hidden - un-hide
    }

    void FormSettings_Closing(object sender, FormClosingEventArgs e)
    {
        e.Cancel = true; // disable closing
        Visible = false; // hide instead
    }

    // ...
}

public partial class FormLog
{
    // ... same as settings

    // static method for log messages
    public static AddMessage(string message)
    {
        if(FormTray.Instance != null && FormTray.Instance.IsHandleCreated) // avoid errors if attempting to log before main form is created
        {
             if(FormTray.Instance.InvokeRequired)
                 FormTray.Instance.BeginInvoke(() = { AddMessage(message); } // need invoke
             else
             {
                 // ... logging here
             }
        }
    }
}

It's not a complete solution, just ideas.

  • Naming.
  • Instead of form opening/closing, use Visible=true/false.
  • Sort of singleton for each form.
  • Invoke log messages through main form instance.
  • Thread-safe FormLog.AddMessage().
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top