23

I have two C# winform (.NET 4.0) forms that each run separate but similar automated tasks continuously. Separate in that they are distinct processes/workflows, but similar enough in how they operate to share the same resources (methods, data models, assemblies, etc) in the project.

Both forms are complete, but now I'm not sure how to run the program so that each window opens on launch and runs independently. The program will be "always-on" when deployed.

This might seem a little basic, but most of my development experience has been web applications. Threading/etc is still a little foreign to me. I've researched but most of the answers I've found relate to user interaction and sequential use cases -- this will just be one system continuously running two distinct processes, which will need to interact with the world independently.

Potential solutions I've found might involve multi-threading, or maybe some kind of MDI, or a few folks have suggested the DockPanelSuite (although being in a super-corporate environment, downloading third party files is easier said than done).

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        // Rather than specifying frmOne or frmTwo,
        // load both winforms and keep them running.
        Application.Run(new frmOne());
    }
}
RJB
  • 2,063
  • 5
  • 29
  • 34
  • 3
    Why you cannot just create two forms and show them? – wRAR Mar 08 '13 at 18:37
  • @wRAR Well, the one thing that comes to mind is that the program probably shouldn't stop until both forms are closed, rather than just one. – Servy Mar 08 '13 at 18:39
  • 1
    If you mean.... frmOne one = new frmOne(); frmTwo two = new frmTwo(); one.Show(); two.Show(); ... I tried that, but it doesn't keep the program running. The program shows both forms for a second and then ends. I'm sure there's every chance that I'm doing it wrong. – RJB Mar 08 '13 at 18:43
  • Application.Run waits until the form exits. If you just instantiate 2 forms the application will exist immediately – bas Mar 08 '13 at 18:44
  • Does C# not have the equivalent of VB's `Me.ShutdownStyle = Global.Microsoft.VisualBasic.ApplicationServices.ShutdownMode.AfterAllFormsClose`?? – hometoast Mar 08 '13 at 18:50
  • "Distinct Processes" - do you mean two processes viewable in task manager, or two separate business processes? – StingyJack Mar 08 '13 at 18:56
  • @StingyJack Good question. I meant unique business workflows. – RJB Mar 08 '13 at 18:59
  • 1
    You may want to consider splitting the code into two separate windows forms projects and putting all the common code into a third class library project. – StingyJack Mar 08 '13 at 19:03
  • @RJB i pitched another idea, if you have some more specific question let me know. If it's in the wrong direction: let me know :) – bas Mar 08 '13 at 19:09

3 Answers3

44

You can create a new ApplicationContext to represent multiple forms:

public class MultiFormContext : ApplicationContext
{
    private int openForms;
    public MultiFormContext(params Form[] forms)
    {
        openForms = forms.Length;

        foreach (var form in forms)
        {
            form.FormClosed += (s, args) =>
            {
                //When we have closed the last of the "starting" forms, 
                //end the program.
                if (Interlocked.Decrement(ref openForms) == 0)
                    ExitThread();
            };

            form.Show();
        }
    }
}

Using that you can now write:

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MultiFormContext(new Form1(), new Form2()));
Servy
  • 202,030
  • 26
  • 332
  • 449
  • 1
    +1 Interesting solution that allows the forms to be independent of each other (i.e. one form does not have to spawn and control the life of the other.) – Paul Sasik Mar 08 '13 at 19:01
  • I'm trying to test both answers right now, but this code doesn't actually open either form. Sorry, I've written it just as I see here (with the right params of course), is there something that I might be missing? – RJB Mar 08 '13 at 19:08
  • @RJB Oh, it creates the forms but doesn't show them; it's a trivial line to add. – Servy Mar 08 '13 at 19:09
  • @Servy you might have the best solution. MSDN provides something very similar (but less generic, matter of taste I guess). Here is the link to MSDN http://msdn.microsoft.com/en-us/library/system.windows.forms.application.applicationexit.aspx. +1 for best answer – bas Mar 08 '13 at 19:29
  • @bas Yep. I in fact looked over that solution before writing mine; mine was written as a more simple and slightly more generic version of that class. Note [this page](http://msdn.microsoft.com/en-us/library/system.windows.forms.applicationcontext.aspx) has the full example; the page you linked is a limited portion of the code. – Servy Mar 08 '13 at 19:31
  • Fair enough, thank you for the edit, I just didn't immediately see exactly where to place that line. – RJB Mar 08 '13 at 19:47
  • Unfortunately I've tested and this doesn't work for my need. It does a great job of opening both forms and having them both work on one synchronous process, but in order for the forms to both work independently, it looks like separate threads are needed as implemented in Paul's answer. But a definite +1 and I'll remember this answer for the future, thank you! – RJB Mar 08 '13 at 19:51
  • @RJB What doesn't work about them? In what way to do you want them to be independent in which they aren't? Odds are if you're relying on multiple UI threads you're going to cause yourself way more problems than you'll solve in the long run. – Servy Mar 08 '13 at 19:53
  • @Servy I don't see how, which is to say I'm curious how it would. frmOne queries a database every X seconds, takes the results, and drives them through a mainframe host. frmTwo essentially does the same but the mainframe workflow is significantly longer. An end user (on a web app) waits on the results from frmOne to review/confirm. Confirmed entries in the DB initiate the frmTwo process, which the end user is not part of. So speed is critical for frmOne, but the process isn't complete until after frmTwo. Does that make sense? The forms don't interact with each other, just the database. – RJB Mar 08 '13 at 20:04
  • @RJB And essentially all of what you're describing should be taking place in background threads, so there should be no performance costs to sharing a UI thread. – Servy Mar 08 '13 at 20:18
  • @Servy Just for me to understand: Do I really need `Interlocked.Decrement()`? I've only 1 UI thread, don't I? – Alexander Aleksandrovič Klimov Nov 23 '16 at 16:30
  • 1
    @AlexanderAleksandrovičKlimov Correct, if you're not exposing the application context through another thread it'd only be accessed through the UI thread. – Servy Nov 23 '16 at 16:32
10

If you really need two windows/forms to run on two separate UI threads, you could do something like this:

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        var thread = new Thread(ThreadStart);
        // allow UI with ApartmentState.STA though [STAThread] above should give that to you
        thread.TrySetApartmentState(ApartmentState.STA); 
        thread.Start(); 

        Application.Run(new frmOne());
    }

    private static void ThreadStart()
    {
        Application.Run(new frmTwo()); // <-- other form started on its own UI thread
    }
}
Paul Sasik
  • 79,492
  • 20
  • 149
  • 189
  • does that really works? it looks like trouble. that means that you have "2 main threads" or something? Would you use this yourself? (really just asking, not trying to put your answer in a negative light) – bas Mar 08 '13 at 18:46
  • Having multiple UI threads should really be avoided whenever possible. Particularly given that these forms are likely going to need to interact with each other since they have shared resources. – Servy Mar 08 '13 at 18:47
  • @bas: Yes, this does work. I would only use it very rare and specific cases. I could not tell from the OP's question if a separate UI thread is really needed. – Paul Sasik Mar 08 '13 at 18:50
  • @Servy: I completely agree about your threading statement. I'm just offering an option. If possible, two separate forms in one thread are much preferable. – Paul Sasik Mar 08 '13 at 18:51
  • I've selected this as the answer. Due to an unrelated problem with a grumpy ActiveX control on my "frmOne", I needed to move the ThreadStart function to just after loading frmOne. Other than that, this appears to work very well. Separate UI threads were necessary in order for the forms to asynchronously interact with a database and drive separate mainframe controls, as I describe more in Servy's answer. Thank you Paul and everyone for your time! You guys rock. – RJB Mar 08 '13 at 20:09
1

Assumption

You do not need the two different processes, you are only using the 2 processes because you want to have the two different forms and want to be able to keep the application running until both forms are exited.

Another solution

Rely on the Form.Closed event mechanism. You can add an eventhandler which allows you to specify what to do when a form closes. E.g. exit the application when both forms are closed.

In terms of some code

    public Form1()
    {
        InitializeComponent();

        _form2 = new Form2();
        _form2.Show(this);

        this.Closed += Form1Closed;
        _form2.Closed += Form2Closed;
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        e.Cancel = true;
        Hide();
        Form1Closed(this, new EventArgs());
        base.OnFormClosing(e);
    }

    private void Form1Closed(object sender, EventArgs eventArgs)
    {
        form1IsClosed = true;

        TryExitApplication();
    }

    private void Form2Closed(object sender, EventArgs eventArgs)
    {
        _form2IsClosed = true;

        TryExitApplication();
    }

    private void TryExitApplication()
    {
        if (form1IsClosed && _form2IsClosed)
        {
            Dispose();
            Application.Exit();
        }
    }

Note that this should be refactored to make it a better solution.


UPDATE

The comments provided by Servy made my revise this "supposed to be simple solution", which pointed out that his solution is way better then this solution. Since I am supported to leave the answer I will use this answer I will also address the issues that start arising when going for this solution:

  • cancelling close events
  • rerouting from one event to another
  • force calling Dispose.
  • as Servy pointed out: maintenance unfriendly (state to check which form is closed)
bas
  • 13,550
  • 20
  • 69
  • 146
  • 1) this is actually more code than just using an application context. 2) It forces the logic to be embedded throughout the forms, rather than located in a single logical location. 3) It doesn't currently work; you need to stop closing of the main form for this to work, which means it can't be disposed, it's resources collected, etc. 4) Nothing about this is re-usable or scalable. You need to re-do all of the work, from scratch, for each project. If you write a custom app context you write it once and you're done, forever. 5) This is rather bug prone; it's easy to leave some little bit out. – Servy Mar 08 '13 at 19:11
  • @Servy I am a bit surprised by a few of your remarks. 1) more code is irrelevant as long as it's easy to maintain and easy to understand. 2) You can refactor this out **easily** in a single generic piece of code. 3) you got a point there. That can be fixed. 4) Again, write working sane code first, then start to optimize and refactor. Reuse code when you need it somewhere else instead of writing generic code that people don't understand. 5) It's simplistic code, how can that be error prone. – bas Mar 08 '13 at 19:24
  • 1) But it's not easy to maintain; it's harder to maintain. To add a new startup form you need to add a new custom handler, a new boolean field, and edit the check in `TryExitApplication`. 2) You mean like what's done in my answer? Yes, you can, my point is you choose not to. 4) You provided this as an alternative to another solution that is already sane, working, and optimized. If there were no other solutions presented, then sure, it's better than nothing. I know I used this model once upon a time. 5) Considering you have an error in the code, and still haven't fixed it...see point #3. – Servy Mar 08 '13 at 19:29
  • @Servy yeah I'll remove my answer. I thought yours was "over doing it" but after visiting MSDN I see that you have the better solution. Will go feed the baby and remove it so that I am sure you read this comment :) – bas Mar 08 '13 at 19:31
  • As I said, I used this solution once upon the time. It does work, it's just not quite as good. It's why I wouldn't downvote it (once you fix the minor error), it just has room for improvement. That's nothing to be ashamed of. Most *every* answer has some way in which it can be improved on. – Servy Mar 08 '13 at 19:33
  • @bas Please feel free to keep it up, I appreciate your time and the logic here might help someone in the future. :-) – RJB Mar 08 '13 at 20:21
  • Okay then, then I'll spend some more time on it to at least to correct the issue Servy pointed out. Took me a while to get back here :) – bas Mar 08 '13 at 20:26
  • @Servy I am glad I started the debate. Once I started solving the issue you addressed it became even more obvious that this is getting messy. Retested this somewhat doubtful solution and it does work... but knowing all this I'd definitely go for your solution (which is the best of the three imho). Anyway, apologies. On the positive side I learned something here as well. – bas Mar 08 '13 at 20:50
  • 2
    @bas That's why I make these comments. I'm glad you [and I] learned something. – Servy Mar 08 '13 at 20:51