Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Building Code-Only Forms in Visual Studio

0.00/5 (No votes)
3 Feb 2009 1  
How to create a code-only form in a Visual Studio project, and prevent the VS-Designer from messing with your code

Introduction

My brother asked me to send him a tiny C# program to blank his primary screen while he watched movies on his big monitor. A few minutes later, I sent him the following BlackScreen.cs.

class BlackScreen : System.Windows.Forms.Form
{   static void Main() { System.Windows.Forms.Application.Run(new BlackScreen()); }
    protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e){Close();}
    protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e) { Close(); }
    BlackScreen()
    {   FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
        StartPosition = System.Windows.Forms.FormStartPosition.Manual;
        TopMost = true; BackColor = System.Drawing.Color.Black;
        Bounds = System.Windows.Forms.Screen.PrimaryScreen.Bounds;
}   }

... and told him how to find and compile it with the CSC.exe on his machine. He did ask for a tiny program ... and yes, I was having some fun.

Of course, I did follow up with a properly formatted (and modified) BlackScreen.cs embedded in a Visual Studio(VS) project... given that part of his intention was to become familiar with C# and VS. But when he opened the project, the form did not display in a VS-Designer window.

Background

When I saw my first VB 1.0 designer window, I was blown away. WYSIWYG development had arrived... at last!

But then, in my VB3 era, in a really big project, I needed several modals. They were not simple modals, but they all looked so much alike, I was sure they could be made into a single form.

So I made a single form and generated all the necessary modals ... sized, positioned, populated ... in the Load event. When I needed another modal, I could just tweak a few lines in that Load event and, presto, another modal. It was my first form generator.

I'm always trying to write less code ... and so, when I applied that technique to the application level forms, this is what I discovered:

  1. The generated forms looked far better than the manually edited forms. The buttons, labels, and text boxes were always properly aligned, separated, and sized, with the correct font and font size. No bug reports came back at me regarding those issues.
  2. The common production logic made it very easy to code common operational logic. For example: All click events were handled by one routine that could disable/enable other controls based on what was being clicked, as well as preventing the operator from clicking another operation before the previous one was done. If you double clicked on a control to get to the WYSIWYG click event, it would tell you nothing about the control. You had to go to the central routine. I would have liked an alternate method for directing click events to that function.
  3. Because of the common production and operation code, if there was a bug, most of the time everything would break. It was instantly obvious and instantly fixed. Form code became stable and trustworthy very quickly.
  4. For the reoccurring design "change it this way" and "change it back" requests, I soon learned to simply add a few lines within the form code, and a switch (initially secret) to toggle the old or new code. Often, when the design committee could not agree on what they wanted, I would just say "Ok, let's make it a user option."
    Note: This technique was extremely useful in a future prototyping project.
  5. The EXEs were smaller and the forms loaded faster.
  6. Overall, the project seemed to accelerate to completion.

I was totally sold on form production rather than form editing. The only reason for the WYSIWYG editor was to bind control arrays to the form because there was no other way to do so.

If VB6 married C++ and they had a child, it would be C#. C# would totally respect his father, Charlie, but would always dearly love his mother (maiden name Veronica Baker) for her elegance and compassion.

Imagine my delight when I created my first C# windows app with VS-Designer, and looked at the code under. It was completely code. Gee whiz, all those hours of trying to turn VB3 and VB6 into C# ... and there it was... at last!

BlankScreens.cs

When I sent BlackScreen.cs to a friend, for the laugh, he remarked that if the back color was white instead of black, he could use it when cleaning his monitors. Monitors? Colors? A few minutes later, I had the following BlankScreens.cs:

using System; using System.Reflection;
using System.Windows.Forms; using System.Drawing;
[assembly: AssemblyCompany("Appso Associates")]
[assembly: AssemblyCopyright("2006 Appso Associates")]
[assembly: AssemblyProduct("BlankScreens")]
[assembly: AssemblyVersion("1.0.0.*")]
static class BlankScreens
{   //************************************************************************
    // Copyright 2006 Appso Associates (associates@appso.net>
    // CPOL Licensed http://www.codeproject.com/info/cpol10.aspx
    //************************************************************************
    [STAThread]
    static void Main() { Application.Run(new Display(0)); }
    class Display : Form
    {
        static Display[] displays = new Display[Screen.AllScreens.Length];
        static void disposeAllDisplays()
        {   for (int i = displays.Length - 1; i >= 0; i--)
                displays[i].Dispose();
        }
        static int currentColor = 0;
        static void displayNextColor()
        {   if (++currentColor >= possibleColors.Length) currentColor = 0;
            for (int i = 0; i < displays.Length; i++)
                displays[i].BackColor = possibleColors[currentColor];
        }
        static Color[] possibleColors
        = { Color.Black, Color.White
          , Color.Red, Color.Green, Color.Blue
          , Color.FromArgb(0, 255, 255) // G+B not Red
          , Color.FromArgb(255, 0, 255) // R+B not Green
          , Color.FromArgb(255, 255, 0) // R+G not Blue
          };

        public Display(int index)
        {   Text = "BlankScreens[" + index.ToString() + "]";
            FormBorderStyle = FormBorderStyle.None;
            StartPosition = FormStartPosition.Manual;
            BackColor = possibleColors[currentColor];
            Cursor.Dispose();
            TopMost = true;
            ShowInTaskbar = false;
            Bounds = Screen.AllScreens[index].Bounds;
            if (System.Diagnostics.Debugger.IsAttached)
                Size = new Size(11, 11);
            Show();
            if (index > 0) return; // if it's not the 0 form
            displays[0] = this; // 0 form loads the rest
            for (int i = 1; i < displays.Length; i++)
                displays[i] = new Display(i);
        }
        protected override void OnMouseDown(MouseEventArgs e)
        {   if (e.Button == MouseButtons.Right) displayNextColor();
            else disposeAllDisplays();
        }
        protected override void OnKeyDown(KeyEventArgs e)
        {   if (e.KeyCode == Keys.ControlKey) displayNextColor();
            else disposeAllDisplays();
}   }   }

Points of Interest

  1. BlankScreens.cs looks more like a ".bat" or ".cmd" or ".js" than a "VS project". Yet as far as the OS is concerned, it is a System.Windows.Forms application with all the associated benefits. It even embeds manifest information and an icon for the EXE assembly to identify itself to the system, as a proper application should do.
  2. It does not use events. No event delegate arrays are created. There is no need to "manage" events or create and += or -= event handlers. And yet it has complete access to the keyboard and the mouse.
  3. It's a code-only form that does not depend on VS Forms Designer. It is a plain text file that can be edited by NotePad.exe, to be compiled by any available .NET Framework CSC.exe.
  4. To edit the code with VS as easily as with NotePad.exe, the form class must be an "inner class". VS does not care about "inner class forms" (more about this below).
  5. VS Forms Designer would NOT be very helpful in creating this particular Windows application. It would be difficult to develop with Designer and would involve more code and stuff to manage.
  6. Compiled by VS, it's a 16KB EXE. Compiled by CSC, it's 7KB ... and 2KB of that is the icon.
  7. It's trivial, but quite useful. Even now, the black moon sits in my Quick Launch toolbar.
    • When I need to blank the monitors quickly... I click on the black moon.
    • When I'm cleaning the monitors, a 2nd click (to white) shows up any smudges to be cleaned, or dead pixels to be frustrated at.
    • When I want to compare monitors, successive clicks show me how the monitors display basic colors.

Code-Only Forms

All C# VS-Designer designed forms are, in fact, only code. Therefore, let's use the term "code-only form" to mean a form whose code is NOT dependent on or generated by a WYSIWYG editor. If you are creating a "code-only form", you are completely responsible for coding the instantiation, binding, and layout of all form elements and controls. You cannot view the form's layout at design time. A code-only form is only visible when it is running.

This may seem like a daunting task with no payback, but if you make the effort, it has huge payback:

  1. Much (if not all) of the form's initial properties can be set directly from persistence streams or embedded tables ... immediately, during instantiation, arriving fully formed without the need to re-initialize in the Load event.
  2. A single form can be designed to do various and different duties, using the same code base to create multiple and different forms ... just like MessageBox does.
  3. It's an easy way to make a code-only and abstract form encapsulating common application properties and behaviors. Application forms that inherit that base form would only need additional coding for that application's specific properties and behavior. If a common property (example: font) needs to be changed, only a single line in the abstract class needs to be changed. In fact, it would be trivial coding to allow the user to select the font of his choice.
  4. Code-only forms are a means to do more functionality, with more options, with more flexibility, and faster execution ... with less code and headaches to manage or worry about.

But programming code-only forms does require a different mind set.

MessageBox is an example of a code-only form. When you MessageBox.Show(..), there is no statically defined shape and size and text stored somewhere. All of the elements of the message box (including icons, buttons, labels) are created directly from the parameters you supply. I'm pretty sure this also applies to OpenFileDialog, FontDialog, ColorDialog, etc.

I love VS. There is no doubt in my mind that it is the best programming tool I have ever used. Period! And I say that boldly.

Coding with VS is mostly a process of hitting a few keystrokes against the "IntelliSense" database of all nouns and verbs that are proper to enter in the context of what was just typed, then navigating to a choice and hitting a return key.

And, well ... right-mouse -> Refactor -> Rename ... I can't live without it.

I just don't want VS to mess with my code-only forms.

BlackScreen.cs as a VS Project

Try this experiment with the BlackScreen root class. Open a new "empty" project (empty like using CSC.exe). Add as an "existing file" BlackScreen.cs and build it. Note the following:

  1. You need to make it a Windows project (Project Properties -> Output type -> change from Console to Windows Application.
  2. You need to add .NET DLL references:
    • System.Windows.Forms - because you are accessing Windows Forms
    • System.Drawing - because you are accessing "Color"
    • System - because VS (not you) wants access to System.ComponentModel.Component
  3. Bring up Solution Explorer and click on "BlackScreen.cs". Up comes "BlackScreen [Design]" window (not the code) ... as VS attempts to help you "design" your window.
  4. Double click on that "design window", and note that a BlackScreen.resx file has been created ... something that you did not really need or want.
  5. Look at the code of BlackScreen.cs. VS has edited in some new code to "InitializeComponent" ... even though you are not using "components".

Build the project. Compare the size of the VS built EXE (16KB) to the size of the CSC built EXE (4KB).

Run each of them. Notice any difference in operation? I did not.

Inner Class Forms

Now open another new "empty" project. "Add existing file" BlankScreens.cs, and build it.

You will still have to change the project to a Windows project and add the first two .NET DLL references specified above. But when you click on the BlankScreens.cs (in Solution Explorer) ... only the code comes up. No additional modules are created, no additional code is added to BlankScreens.cs. The BlankScreens.Display form is an inner class of the BlankScreens static class.

Please note that you now have all the benefits of IntelliSense text editing, but VS will not impose VS-Designer upon your code-only form.

And one more thing ... working through IntelliSense to set form properties is much faster than working with a "Property Box". In any given method, type in "this" ... and a complete list of properties and methods of the form will appear in a menu. As you navigate down, it even pops up tool tips explaining stuff. Remember, IntelliSense is a text editing (i.e. code-only) feature.

Using BlankScreens.exe

  1. Move the BlankScreens.exe to wherever you want.
  2. Create a shortcut(s) of the EXE, wherever you want.
  3. Click the "black moon" BlankScreens.exe or shortcut. All your screens should go black.
  4. Press the Ctrl key, or the right mouse button to toggle through the colors.
  5. Press any other key, or the left mouse button to exit.

Summary

WYSIWYG editors do have their place. There, I've said it ... so now we don't have to talk about it. Please, hold your email!

Consider code-only forms:

  1. for dialog boxes and other "utility forms" within a WYSIWYG project.
  2. for applications of very dynamic presentations, such as kiosks.
  3. for any small tool or utility applications, especially your personal ones.
  4. whenever minimum code size, maximum speed, or flexible and context sensitive presentation are required.

But if, like me, you get the hang of doing that initialization coding, you might even wonder, from time to time ... why WYSIWYG?

A Beginner's Tour of BlankScreens.cs

using System; using System.Reflection;
using System.Windows.Forms; using System.Drawing;

Using using allows you to simplify the amount of text you need to enter. For example: using System.Windows.Forms allows you type in "Form" rather than having to type in "System.Windows.Forms.Form". You really don't have to use using statements. They just make code a lot easier to read and write.

However, using System.Reflection is required to enter the following:

[assembly: AssemblyCompany("Appso Associates")]
[assembly: AssemblyCopyright("2006 Appso Associates")]
[assembly: AssemblyProduct("BlankScreens")]
[assembly: AssemblyVersion("1.0.0.*")]

This is assembly/EXE manifest information. Just like a package manifest indicates where a package comes from and goes to, and what the contents are (for proper import/export permits and taxation), this code identifies the program contents and where it came from.

The OS uses this information in many ways, for example: explorer may show you a tooltip describing the Company and Version when you wave the mouse over the file "BlankScreens.exe".

static class BlankScreens
{   [STAThread]
    static void Main()
    {
        Application.Run(new Display(0));
    }
    class Display : Form
    {
        static Display[] displays = new Display[Screen.AllScreens.Length];

"static class BlankScreens" contains the entry point Main() where the OS assigns a computer processor and memory to the program so it can run. The program then does this:

  1. Creates a static Display array to manage the forms soon to be created.
  2. Instantiates (Creates) the 1st window (form) ... i.e. new Display(0).
  3. Passes that 1st window and the currently assigned computer processor to the built-in window application manager, which starts the window's interaction with the keyboard, mouse, and the display.

When all of the forms close (or dispose), the program will continue to the line following the Application.Run statement. There is nothing there, so, in this case, the program will exit.

BlankScreens.cs class Display is an "inner class" because it is being defined inside the class BlankScreens. But it's also a form, as specified by " : Form". Thus, it is an inner class form. VS treats inner class forms differently than it treats root class forms. BlackScreen.cs, by contrast, is a root class form.

        static void disposeAllDisplays()
        {   for (int i = displays.Length - 1; i >= 0; i--)
                displays[i].Dispose();
        }
        static int currentColor = 0;
        static void displayNextColor()
        {   if (++currentColor >= possibleColors.Length) currentColor = 0;
            for (int i = 0; i < displays.Length; i++)
                displays[i].BackColor = possibleColors[currentColor];
        }
        static Color[] possibleColors
        = { Color.Black, Color.White
          , Color.Red, Color.Green, Color.Blue
          , Color.FromArgb(0, 255, 255) // G+B not Red
          , Color.FromArgb(255, 0, 255) // R+B not Green
          , Color.FromArgb(255, 255, 0) // R+G not Blue
          };

Any one of the windows created can call displayNextColor() to change the color of every window, or disposeAllDisplays() to close every window ... which will then exit the window application manager, and subsequently exit the EXE.

        public Display(int index)
        {   Text = "BlankScreens[" + index.ToString() + "]";
            FormBorderStyle = FormBorderStyle.None;
            StartPosition = FormStartPosition.Manual;
            BackColor = possibleColors[currentColor];
            Cursor.Dispose();
            TopMost = true;
            ShowInTaskbar = false;
            Bounds = Screen.AllScreens[index].Bounds;
            if (System.Diagnostics.Debugger.IsAttached)
                Size = new Size(11, 11);
            Show();
            if (index > 0) return; // if it's not the 0 form

            displays[0] = this; // 0 form loads the rest
            for (int i = 1; i < displays.Length; i++)
                displays[i] = new Display(i);
        }

Here, the window has a chance to make itself presentable before going out in public ... i.e. set itself up to display on the proper screen in the proper way. When this instantiation method exits, the window will have full citizenship as an object and will be interacting with the display, keyboard, and mouse.

When the Debugger.IsAttached == true, the windows will appear as tiny windows in the upper left of each screen, allowing you to see and single step through the code within VS.

The first window (i.e. index 0), has the additional responsibility of instantiating all the other windows (if any).

Note: instantiate (in the class sense) - to make an instance of.

        protected override void OnMouseDown(MouseEventArgs e)
        {   if (e.Button == MouseButtons.Right) displayNextColor();
            else disposeAllDisplays();
        }
        protected override void OnKeyDown(KeyEventArgs e)
        {   if (e.KeyCode == Keys.ControlKey) displayNextColor();
            else disposeAllDisplays();
}   }   }

This is the code that allows each of the windows to examine the mouse clicks and keyboard keystrokes without having to use events. Any window "clicked" or "pressed" will trigger displayNextColor on a right-mouse button down, or a CtrlKey down. Otherwise, it will disposeAllDisplays, causing the EXE to exit.

History

  • 2008-07-25 - Initial article - Hello Dapple! (A C# Code Puzzle for Beginners)
  • 2009-02-01 - Revised article - Building Code-Only Forms in Visual Studio

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here