Monday, December 12, 2011

Graphics in console application

A long, long time ago when programming for Windows you had to make a choice - your application would have to be either console, or GUI, but not both. If you liked your app for the console subsystem, you could not create windows or dialog boxes, and the application did not have a message loop. If you were a GUI app, you could only use console if you created it yourself, and your app could not inherit its parent's console.

At some point down the line this got fixed, so a console application today can have UI elements - for example, it can call MessageBox(). Despite the weirdness - I am sure HCI purists/Apple would never approve of it - it can actually come quite handy. I, for one, quite often find myself in need of a graphic in the middle of a simple application (sometimes to just visualize something as part of a debug code path) - which I don't want to convert to fully-fledged GUI.

Unfortunately, there is precious little information on how to do do mixed mode console/GUI programming on the Internet, so I figured I'd fill the void :-).

First add references to System.Windows.Forms and System.Drawing (only if you are going to be drawing of course) to your app, as well as the corresponding "using"s.

Then you can create a dialog box that derives from Form:

    using System.Windows.Forms;
    class MyForm : Form
    {
    }

...and display it as follows:

    MyForm f = new MyForm();
    f.ShowDialog();

ShowDialog function is blocking - your console thread will not get control until user closes the window. Of course, standard console functions all work, you can print to the screen like so:


    class MyForm : Form
    {
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            Console.WriteLine("Paint called!");
        }
    }

As an example, here is a very simple application that allows a user to plot simple functions from Math library:

//-----------------------------------------------------------------------
// 
// Copyright (C) Sergey Solyanik.
//
// This file is subject to the terms and conditions of the Microsoft Public License (MS-PL).
// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL for more details.
// 
//----------------------------------------------------------------------- 
using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;

namespace Graph
{
    class Graph : Form
    {
        public delegate double Function(double x);
        public Function F;
        public double X1;
        public double X2;
        public double Y1;
        public double Y2;

        private double stretchX;
        private double stretchY;

        private int ToScreenX(double x)
        {
            return ClientRectangle.Left + (int)((x - X1) * stretchX);
        }

        private int ToScreenY(double y)
        {
            return ClientRectangle.Bottom + (int)((y - Y1) * stretchY);
        }

        private double ToPlaneX(int x)
        {
            return X1 + ((double)(x - ClientRectangle.Left)) / stretchX;
        }

        private double ToPlaneY(int y)
        {
            return Y1 + ((double)(y - ClientRectangle.Bottom)) / stretchY;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
         base.OnPaint(e);

            stretchX = (double)(ClientRectangle.Right - ClientRectangle.Left) / 
                (X2 - X1);
            stretchY = (double)(ClientRectangle.Top - ClientRectangle.Bottom) /
                (Y2 - Y1);

            if (Math.Sign(X1) != Math.Sign(X2))
            {
                e.Graphics.DrawLine(
                    Pens.Black,
                    ToScreenX(0),
                    ClientRectangle.Bottom,
                    ToScreenX(0),
                    ClientRectangle.Top);
            }

            if (Math.Sign(Y1) != Math.Sign(Y2))
            {
                e.Graphics.DrawLine(
                    Pens.Black,
                    ClientRectangle.Left,
                    ToScreenY(0),
                    ClientRectangle.Right,
                    ToScreenY(0));
            }

            for (int x = ClientRectangle.Left; x < ClientRectangle.Right - 1; ++x)
            {
                e.Graphics.DrawLine(
                    Pens.Blue,
                    x,
                    ToScreenY(F(ToPlaneX(x))),
                    x + 1,
                    ToScreenY(F(ToPlaneX(x + 1))));
            }
        }
    }

    class Program
    {
        private static Graph.Function SelectFunction()
        {
            Console.WriteLine("Available functions:");
            Type t = typeof(Math);
            MethodInfo[] m = t.GetMethods();
            for (int i = 0; i < m.Length; ++i)
            {
                if (m[i].IsPublic && m[i].IsStatic && m[i].ReturnType == typeof(double))
                {
                    ParameterInfo[] p = m[i].GetParameters();
                    if (p.Length == 1 && p[0].ParameterType == typeof(double))
                        Console.WriteLine("    " + m[i].Name);
                }
            }

            while (true)
            {
                Console.Write("Select a function to plot: ");
                string response = Console.ReadLine();
                for (int i = 0; i < m.Length; ++i)
                {
                    if (m[i].IsPublic && m[i].IsStatic && m[i].ReturnType == typeof(double))
                    {
                        ParameterInfo[] p = m[i].GetParameters();
                        if (p.Length == 1 && p[0].ParameterType == typeof(double))
                        {
                            if (m[i].Name.Equals(response))
                            {
                                return (Graph.Function)
                                    Delegate.CreateDelegate(typeof(Graph.Function), m[i]);
                            }
                        }
                    }
                }
            }
        }

        private static double GetNumber(string prompt)
        {
            for (; ; )
            {
                Console.Write(prompt);
                string response = Console.ReadLine();
                double result;
                if (double.TryParse(response, out result))
                    return result;
            }
        }

        static void Main(string[] args)
        {
            Console.CancelKeyPress += delegate
            {
                Console.WriteLine("Cancelled!");
                Environment.Exit(1);
            };

            Console.WriteLine("Press Ctrl-C to quit.");
            Console.WriteLine();

            Graph g = new Graph();

            g.F = SelectFunction();
            g.X1 = GetNumber("Abscissa lower boundary: ");
            g.X2 = GetNumber("Abscissa upper boundary: ");
            g.Y1 = GetNumber("Ordinate lower boundary: ");
            g.Y2 = GetNumber("Ordinate upper boundary: ");

            g.ShowDialog();
        }
    }
}

1 comment:

Duplication said...

It's good about Graphic in Cosole Process. It's best console related idea.