What's the right way to handle multiple Windows Forms in a C# program?

5/23/2015 10:16:00 PM
Tweetable
Ok, no one really makes Windows Forms anymore--it's all web this web that these days--but I still think there's something deeply satisfying about the level of control that an actual formal desktop program offers over a web-based "app" or webpage. If Windows Forms don't interest you, maybe jump ahead to the bonus material about Object-Oriented design for handling database operations. If that doesn't interest you, here's puppets singing Mozart. Look, I really tried to keep your attention, ok?

There's one issue with Windows Forms that seems needlessly complicated: how to handle Form.Close versus Application.Exit so that it does the right one. Consider this form--it's just the default form with a menustrip added with the default elements:
The default windows form with default menustrip using Visual Studio 2010.
Ok, so consider the first and last menu item shown. When the user selects Exit, we obviously want to completely exit the program, releasing all of the resources back to the operating system--that is, we want to call Application.Exit();--while when the user selects New we want to close this form and open a completely new form. So we put in the event handlers for code something like this:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace FormClosingExample
{
    public partial class Example : FormMaster
    {
        public Example()
        {
            InitializeComponent();
        }

        private void exit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void new_Click(object sender, EventArgs e)
        {
            Example newform = new Example();
            newform.Show();
            this.Close();
        }
    }
}
Now, there are multiple problems with this. The first, most obvious problem is that because we are creating the new instance of the Example form as a child inside of the original one, when we call this.Close(); the program will terminate all dependencies of this form (update: seems that wasn't true. C# can run a form instanced from a different form even after that other term has been closed. But that still doesn't work here for the reason described below: once the original form is closed, control reverts not to the child form but to the owner of the original form, the Program class, which will terminate the application anyway.), including the new form we wanted to open. Ok, so we can substitute instead this.Hide(); so that the old form will no longer be open, but the new form won't be terminated. And when, on the new form, the user selects File->Exit from the menu the method will call Application.Exit(); which will close both the new form and the original hidden one. Everything works.

Well, everything works right up until the moment the user clicks that X button in the top right corner instead of File->Exit. Clicking that button will trigger this.Close(); on which ever form it is clicked, so if the user clicks it on the new form it will close the new form but won't close the original instance of the form that we had hidden. As a result the application won't exit, and that original form will continue to consume memory and resources in the background until the user shuts the computer down. I see this error committed by inexperienced programmers with surprising frequency. Unfortunately, you can't handle the X button directly--the only handlers that attach to it are the form close and closing events generally, which are also triggered by this.Close();. Hence, inserting Application.Exit(); in those handlers will cause the application to exit in situations where we want it to stay running. Also unfortunately, the sender and EventArgs objects don't provide us much information at all--you could discern which form is calling the event, but not whether it was triggered by the X button specifically--so writing a method here doesn't really work.

This is just annoying. To further understand the problem, let's turn to a somewhat obscure little file that Visual Studio inserted along with our Example form: the file called Program.cs. Every executable program in C# has a program Main method somewhere, which serves as the entry point for the system to start executing code. This is, in fact, what distinguishes an exe program from a dll library which can have all the same code, but lacks a defined starting point. The default contents of the Program.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace FormClosingExample
{
    static class Program
    {
        /// 
        /// The main entry point for the application.
        /// 
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Example());
        }
    }
}
So here's what's happening. You have a mass of compiled code; when you instruct the operating system to run this code, it looks for the Program.Main() and starts executing statements there, in order from top to bottom, closing the program and returning all resources to the operating system once the end of the Main is reached. And this gets at the bigger problem with our original code above: even if we divined a way to open a new form that wasn't a dependent of the original form, when we Close(); the original form control will be returned not to the new form but to Program.Main where, finding nothing left to do, the system will exit the entire application. That is, this.Close(); in the original form is exactly equivalent to Application.Exit();.

I have not found any great solution to this--I would imagine commonplace--problem. But here's what I've come up with so far. We'll create two static fields in the Program that contain two states, the first indicating whether the application should exit when control reverts to Program.Main, and the second storing an instance of the next form to open if the application is not intended to exit. Our Program.cs file now looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace FormClosingExample
{
    static class Program
    {
        static internal bool close = true;
        static internal Form open;
        /// 
        /// The main entry point for the application.
        /// 
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Example());
            if (!close)
            {
                Application.Run(open);
            }
        }
    }
}
So instead of what we did originally, this is how we handle the New option on the Example form:
private void new_Click(object sender, EventArgs e)
{
    Program.open = new Example();
    Program.close = false;
    this.Close();
}
When the user select File->New, instead of creating a new form as a child of the original one, we've passed the new form to Program and set Program.close to false so that, when we call this.Close(); and control returns to Program.Main, rather than exit the application it will run the new instance of Example we sent it.

By itself, this seems to work though I'm not totally sure if it should. When we call Application.Run(open); it seems that the Program.open and Program.close reset to their original states. I'm at a loss to explain why that's true, so I'll just add that inserting Program.close=true; into the constructors in all our forms--or perhaps into our base FormMaster class from which all the forms inherit--we can ensure that the state resets. And that takes us to the bonus material:

Bonus Material: A method for database operations

You may have noticed all my forms inherit from FormMaster instead of the default Form. Here's why.

Whether you are writing a forms application or a web application, chances are it will make heavy use of databases. Everything has a database these days--if it isn't interfacing with a full-fledged SQL database service, then it has it's own local database for app data. Here's the C# code to retrieve a (salted hash of a) user's password from a database:

SqlConnection con=new SqlConnection(@"Server=000.000.000.000;Database=exampleDB;User ID=username; Password=pass; Trusted_Connection=False");
string query=@"SELECT hashedPassword FROM Credentials WHERE username=@user;";
SqlDataAdapter adapter=new SqLDataAdapter();
DataSet ds=new DataSet();
con.Open();
adapter.SelectCommand=new SqlCommand(query, con);
adapter.SelectCommand.Parameters.AddWithValue("@user",username);
adapter.Fill(ds);
con.Close();
string password=ds.Tables[0].Rows[0]["hashedPassword"].ToString();
Holy cow that's a lot of crap just to look up a password for a username! What's even more frustrating, though, is that the code for basically every dataretrieval operation is the same. In every case you are just inputting a query and parameters [note: always parameterize any user inputs. DO NOT CONCATENATE QUERIES. Parameters protect against injection attacks that could compromise your data. Despite being 100 percent preventable, SQL injection remains the most common and most deadly type of data security breach.] and outputting a datatable.

Now, one approach is to create a class specifically for doing database operations and create an instance of this database utility class whenever we want to do any database operations in any of our other classes. This seems to be fairly popular. But really, creating an instance of our database utility class every time we want to do a database operation is kind of a waste because our database queries never actually change the internal state of our utility object. If the internal state can't change, then don't instance--that's what I always say. So we could make our database utility class static with the SqlConnection as a static field and a static Select method that accepts the query and parameters as arguments.

To be honest, this seems like bad class-oriented programming to me. We don't want spaghetti classes where all of the classes are calling methods in other classes all the time. Moreover, we don't necessarily want to grant all of our classes the ability to use our database--it's best to keep scope as narrow as possible in general, and restriction of access may be necessary for security reasons. I think the better approach is to put our database utilities into a base class from which the classes that need to access the database inherit. So, for example, our forms which need to retrieve data from the database can inherit from this FormMaster class:
using System;
using System.Data.SqlClient;
using System.Windows.Forms;
namespace FormClosingExample
{
    class FormMaster:Form
    {
        SqlConnection con=new SqlConnection(@"Server=000.000.000.000;Database=exampleDB;User ID=username; Password=pass;Trusted_Connection=False");
        protected void Select(string query, ref dt, params object[] sqlParameters)
        {
            SqlDataAdapter adapter=new SqLDataAdapter();
            DataSet ds=new DataSet();
            con.Open();
            adapter.SelectCommand=new SqlCommand(query, con);
            for(int i=0;i < sqlParameters.Length;i+=2)
            {
                adapter.SelectCommand.Parameters.AddWithValue(sqlParameters[i].ToString(),sqlParameters[i+1]);
            }
            adapter.Fill(ds);
            con.Close();
            dt=ds.Tables[0]
        }
    }
}
Thus, in any of our other forms we can generically query the database merely by specifying the query, the output datatable, and parameters:

string query=@"SELECT hashedPassword FROM Credentials WHERE username=@user;";
DataTable dt=new DataTable();
Select(query,ref dt,"@user",username);
string password=dt.Rows[0]["hashedPassword"].ToString();
I really like C#'s params declaration. This way, the method will work with any number of parameters, including no parameters, and we can enter them each directly in the method's arguments, or we can make an array of parameters and then pass the array as an argument to the method.

Obviously we can make more methods in the base class to do other SQL operations, or make them more specific to the things we want the descendant classes to do.