Building a GUI Editor in C#

In this article I'll show you how to build a simple GUI Editor using C# in XNA.


In this article I'll show you how to build a simple "GUI Editor". First of all lets talk about what GUI Editor means.

What's GUI Editor?

"GUI Editor" or "GUI Builder" is a Software Development Tool. It has a kind of WYSIWYG structure and helps the user to build a structure without coding or less coding. Without GUI's we had to write code for everything.

Lets Start...

STEP BY STEP BUILDING A "GUI EDITOR"

First of all, we are creating a new XNA 3.1 Game Project...

1.gif

Here it is..

2.gif

Now lets talk about everything we are going to do:

  1. Add a resource file that includes Skin images for controls.
  2. A Structure that will help us export the control List as an XML File.
  3. A "Screens" Folder that will help us to read this controls.
  4. For Communication of Windows & XNA creating a class where we will declare "public static" variables
  5. A "Properties Panel" where we can change the properties of selected controls.
  6. A "Toolbox Panel" where we can add controls.
  7. Our Custom Controls

1. Add a resource file that includes Skin images for controls

We are adding 3-4 skin images for our Button control...

First of all we're creating a Resource File that will help us storing the skins:

3.gif

After that open the resource file and "Add Resource->Add Existing File..."

4.gif

Then add 4 sample skins(you can create your own skins named 'disabled','down','over' and 'up'):

5.gif

Here they are added at Resources...

6.gif

2. A Structure that will help us export the control List as an XML File

First creating a new XML File:

7.gif

Then make it similar to the codes below:

<?xml version="1.0" encoding="utf-8" ?>
<
GUI>
  <
Control Name="">
    <
Type></Type>
    <
AllowDrop></AllowDrop>
    <
Enabled></Enabled>
    <
ForeColor></ForeColor>
    <
LocationX></LocationX>
    <
LocationY></LocationY>
    <
SizeW></SizeW>
    <
SizeH></SizeH>
    <
Text></Text>
  </
Control>
</
GUI>

We are taking advantage of XML for the structure we will be building on GUI Editor & as you can see we added elements as they are already properties of the controls.Actually it would be a big mistake not to use XML in this kind of applications.

3. A "Screens" Folder that will help us to read this controls.

Just create a new Folder in the project and call it Screens.

8.gif

9.gif

4. For Communication of Windows & XNA, creating a class where we will declare "public static" variables

Create a new Class & name it Infos.cs...

10.gif

It seems just like this:

11.gif

In this point we aren't writing any code.

5. A "Properties Panel" where we can change the properties of selected controls

We need to add a Properties Panel. First we need to create a Windows Forms:

12.gif

Then set its properties just as below...

Backcolor =White
Size
Width=219
Height=600
Text=Properties Panel

Create a new Folder and UserControl named Control_Properties

13.gif

Set its Properties as below:

BackColor = White
Size
Width=175
Height=400

14.gif

It should look like above + add a timer control on the user control

Set the controls above as given properties:

            // label1
            Text = "Name";

            // textBox1
            Name = "textBox1";
            // Label2
            Name = "Label2";
            Text = "AllowDrop";
            // comboBox1
            Items = "True","False"
            Name = "comboBox1";           
            // label3
            Name = "label3";
            Text = "Enabled";

            // comboBox2
            Items = "True","False"
            Name = "comboBox2";
            // label4
            Name = "label4";
            Text = "ForeColor";
            // comboBox3
            Name = "comboBox3";
            // label5
            Name = "label5";
            Text = "LocationX";
            // textBox2
            Name = "textBox2";
            // label6
            Name = "label6";
            Text = "LocationY";
            // textBox3
            Name = "textBox3";
            // label7
            Name = "label7";
            Text = "SizeW";
            // textBox4
            Name = "textBox4";
            // label8
            Name = "label8";
            Text = "SizeH";
            // textBox5
            Name = "textBox5";
            // textBox6
            Name = "textBox6";
            // label9
            Name = "label9";
            Text = "Text";
            // timer1
            Interval = 2;

Add a GroupBox then set its Text property as "Properties".

Add this UserControl inside your previously built "Properties_Panel" Windows Form

Your Properties_Panel shall look like this:

15.gif

6. A "Toolbox Panel" where we can add controls.

Now were going to create a Windows Form for "ToolBox Panel" and then add the images being used in this toolbox.

Add a New Form and name it Toolbox.cs

16.gif

Set the Properties below:

BackColor= White
Size
Width=190
Height=562

Add a GroupBox,set its Text value "Toolbox",Dock value "Fill" deyin...

Now add the Control images were going to use in Toolbox Panel:

"Add Resource -> Add Existing Item";

17.gif

18.gif

Added the controls' images.

Add 9 picturebox and assign the images that we currently added on Resource and it will look like below:

19.gif

7. Our Custom Controls

Now were going to create our own controls just like listed below:

XButton
XCheckBox
XComboBox
XLabel
XListBox
XPictureBox
XProgressBar
XRadioButton
XTextBox

20.gif

Create a new Class & name it "XButton.cs"

Update the class like that:

XButton

using System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Windows.Forms;
namespace
GuiEditor
{
    class XButton:Button
    {
        public XButton()
        {
        }
    }

}

Create classes for other controls with the same process we made above.

Now lets talk about our INFOS Class...

This class stores all the information's needed for interaction between XNA & Windows Forms(Properties & Toolbox)

Allright lets look at the codes & try to explain them:

public static Control kontrool;

This static control variable is the selected control on XNA Window.When you select a control,this variable will store its name...

public static List<Control> kontroller = new List<Control>();

This stores all of the controls(repeating) on XNA Window.

public static List<Control> strunq = new List<Control>();

This stores all of the controls(unique) on XNA Window.

public static Control selectedcontrol;

This static control variable is the selected control on XNA Window.When you select a control,this variable will store its name...

public static bool xmlbuild;

If true we will be creating an XML File.

        //Button Variables
        public static bool varpub;    
        public static int a;
        public static bool close;       
        public static bool atleastone;

"varpub" controls whether the specified control is added or not."a" makes control's name & text property +1. "close" variable prevents the specified control added more than once.You can think it as a lock. "atleastonce" controls the specified control added for once.

//Button Events
        public void Button_Added(bool varb)
        {
           varpub = varb;
           close = true;          
           ButtonAdd_Num();        
        }     
        public int ButtonAdd_Num()
        {
            a++;
            return a;
        }

"Button_Added" function is the key where controls a Button added on Game Window.ButtonAdd_Num helps to increase "a" variable.

        public static void CreateUniqueList(List<Control> oDup, ref List<Control> oUniq)
        {
            for (int x = 0; x < oDup.Count; x++)
            {
                bool bDuplicate = false;
                foreach (object oItem in oUniq)
                {
                    if (oItem == oDup[x])
                    {
                        bDuplicate = true;
                        oDup.RemoveAt(x);
                        break;
                    }
                }
                if (!bDuplicate)
                {
                    oUniq.Add(oDup[x]);
                }
            }
        }

This function takes all the repeating controls and makes them unique in strunq control list by reference.

You can call it by coding: CreateUniqueList(Infos.kontroller,ref Infos.strunq);

public static void XMLBuild()
{
    FileStream fs = new FileStream("controls.xml", FileMode.Create);
    XmlWriter w = XmlWriter.Create(fs);
    w.WriteStartDocument();
    w.WriteStartElement("GUI");
    foreach (Control v in Infos.strunq)
    {
        w.WriteStartElement("Control");
        w.WriteAttributeString("Name", v.Name);
        w.WriteElementString("Type", v.GetType().ToString());
        w.WriteElementString("AllowDrop", v.AllowDrop.ToString());
        w.WriteElementString("Enabled", v.Enabled.ToString());
        w.WriteElementString("ForeColor", v.ForeColor.ToKnownColor().ToString());
        w.WriteElementString("LocationX", v.Location.X.ToString());
        w.WriteElementString("LocationY", v.Location.Y.ToString());
        w.WriteElementString("SizeW", v.Size.Width.ToString());
        w.WriteElementString("SizeH", v.Size.Height.ToString());
        w.WriteElementString("Text", v.Text);
        w.WriteEndElement();
    }
    w.WriteEndElement();
    w.WriteEndDocument();
    w.Flush();
    fs.Close();
}

In this XML Creation function we query all the unique controls inside strunq and write them on a XML File.

CONTROLS' Structure

Now lets talk about Controls' Code Structures and try to explain them :

As I said above we have created 9 controls Starts With "X", now we are going to explain XButton controls codes step by step:

private Boolean dragInProgress = false;
int MouseDownX = 0;
int MouseDownY = 0;

"dragInProgress" gives us the value whether the Drag process continues or not.

"MouseDownX" ve "MouseDownY" stores X & Y coordinates of the selected control.

public XButton()
{
    this.BackgroundImage = Resource1.Button_overSkin;
    this.FlatStyle = FlatStyle.Flat;
    this.BackColor = Color.CornflowerBlue;
    this.FlatAppearance.BorderColor = Color.CornflowerBlue;
    this.FlatAppearance.BorderSize = 0;
    this.FlatAppearance.CheckedBackColor = Color.CornflowerBlue;
    this.FlatAppearance.MouseDownBackColor = Color.CornflowerBlue;
    this.FlatAppearance.MouseOverBackColor = Color.CornflowerBlue;
    this.BackgroundImageLayout = ImageLayout.Stretch;
    this.MouseDown += new MouseEventHandler(MDown);
    this.MouseUp += new MouseEventHandler(MUp);
    this.MouseMove += new MouseEventHandler(MMove);
    this.Click += new EventHandler(BClick);
    this.MouseEnter += new EventHandler(MEnter);
    this.MouseLeave += new EventHandler(MLeave);
}

When we are creating a new XButton Control this property will be the default values...

protected override bool ShowFocusCues
{
    get
    {
        return false;
    }
}

This bool property is readonly by default but we overriding it.When we select a control it automatically draws a rectangle with lines.We are setting its value false to prevent it.

private void MUp(Object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        this.dragInProgress = false;
        this.BackgroundImage = Resource1.Button_downSkin;              
    }
    else if (e.Button == MouseButtons.Right)
    {               
        Infos.kontroller.Clear();
        foreach (Control c in Infos.strunq)
        {
            Infos.kontroller.Add(c);
        }
        Infos.strunq.Remove(Infos.secilikontrol);
        this.Dispose(true);               
    }          
    return;
}

This function processes when we are on the control.

if (e.Button == MouseButtons.Left)
{

    this
.dragInProgress = false;
    this
.BackgroundImage = Resource1.Button_downSkin;              
}

If we Left Click Mouse then it changes background & because of no "drag" process make dragInProgress false.

else if (e.Button == MouseButtons.Right)
{

    Infos
.kontroller.Clear();
    foreach (Control c in Infos.strunq)
    {
        Infos.kontroller.Add(c);
    }
    Infos.strunq.Remove(Infos.selectedcontrol);
    this.Dispose(true);               

If we Right Click control while we are on it we will delete it and reload Control List.

private void MMove(Object sender, MouseEventArgs e)
{
    if (dragInProgress)
    {
        Point temp = new Point();
        temp.X = this.Location.X + (e.X - MouseDownX);
        temp.Y = this.Location.Y + (e.Y - MouseDownY);
        this.Location = temp;
        this.BackgroundImage = Resource1.Button_upSkin;
    }
    return;
}

This is the function where we "Drag" Control...

private void MEnter(Object sender, EventArgs e)
{

    this
.BackgroundImage = Resource1.Button_upSkin;
}

private
void MLeave(Object sender, EventArgs e)
{

    this
.BackgroundImage = Resource1.Button_overSkin;
}

This functions change the skin of button control when its over or leaving the button control

USER CONTROL'S Structure

Now lets talk about Control_Properties User Control:

string[] colorNames = System.Enum.GetNames(typeof(KnownColor));

Were getting all the system colors into a String Array...

private void Control_Properties_Load(object sender, EventArgs e)
{           
    comboBox3.Items.AddRange(colorNames);
    timer1.Enabled = true;
}

We are adding the system colors stored in colorNames into a combobox.

private void timer1_Tick(object sender, EventArgs e)
{
    if (Infos.kontrool == null)
    {
    }
    else
    {
        textBox1.Text = Infos.kontrool.Name;
        comboBox1.Text = Infos.kontrool.AllowDrop.ToString();
        comboBox2.Text = Infos.kontrool.Enabled.ToString();
        comboBox3.Text = Infos.kontrool.ForeColor.ToKnownColor().ToString();
        textBox2.Text = Infos.kontrool.Location.X.ToString();
        textBox3.Text = Infos.kontrool.Location.Y.ToString();
        textBox4.Text = Infos.kontrool.Size.Width.ToString();
        textBox5.Text = Infos.kontrool.Size.Height.ToString();
        textBox6.Text = Infos.kontrool.Text;                             
    }
}

Timer checks if a control is selected or not.If selected,then it assigns the control to our control variable "kontrool"

private void textBox1_TextChanged(object sender, EventArgs e)
{

    Infos
.kontrool.Name = textBox1.Text;
}

This helps us to change the name of the selected control.

private void comboBox1_SelectedValueChanged(object sender, EventArgs e)
{
    if (comboBox1.SelectedIndex == 0)
    {
        Infos.kontrool.AllowDrop = true;
    }
    else
    {
        Infos.kontrool.AllowDrop = false;
    }          
}

This helps us changing the AllowDrop property of the selected control.

private void comboBox2_SelectedValueChanged(object sender, EventArgs e)
{
    if (comboBox2.SelectedIndex == 0)
    {
        Infos.kontrool.Enabled  = true;
    }
    else
    {
        Infos.kontrool.Enabled = false;
    }
}

With this code you will be able to enable or disable the specified control.

private void comboBox3_SelectedValueChanged(object sender, EventArgs e)
{
    if (comboBox3.Text == null)
    {
    }
    else
    {
        Infos.kontrool.ForeColor = Color.FromName(comboBox3.Text);
    }
}

Combobox3 stores the system colors we have added above.you can change the forecolor of the control by selecting a value from combobox3

private void textBox2_TextChanged(object sender, EventArgs e)
{
    if (textBox2.Text == null)
    {
    }
    else
    {
        int xdeger = Infos.kontrool.Location.X;
        int ydeger = Infos.kontrool.Location.Y;             
        xdeger = Int32.Parse(textBox2.Text);
        Infos.kontrool.Location = new Point(xdeger, ydeger);
     }          
}

This code helps us to change the Location.X value of the selected control.

private void textBox3_TextChanged(object sender, EventArgs e)
{
    if (textBox3.Text == null)
    {
    }
    else
    {
        int xdeger = Infos.kontrool.Location.X;
        int ydeger = Infos.kontrool.Location.Y;
        ydeger = Int32.Parse(textBox3.Text);
        Infos.kontrool.Location = new Point(xdeger, ydeger);
    }           
}

This code helps us to change the Location.Y value of the selected control.

private void textBox4_TextChanged(object sender, EventArgs e)
{
   if (textBox4.Text == null)
   {
   }
   else
   {
       int wdeger = Infos.kontrool.Size.Width;
       int hdeger = Infos.kontrool.Size.Height;
       wdeger = Int32.Parse(textBox4.Text);
       Infos.kontrool.Size = new Size(wdeger, hdeger);
   }
}

This code helps us to change the Size.Width value of the selected control.

private void textBox5_TextChanged(object sender, EventArgs e)
{
    if (textBox5.Text == null)
    {
    }
    else
    {
        int wdeger = Infos.kontrool.Size.Width;
        int hdeger = Infos.kontrool.Size.Height;
        hdeger = Int32.Parse(textBox5.Text);
        Infos.kontrool.Size = new Size(wdeger, hdeger);
    }
}

This code helps us to change the Size.Height value of the selected control.

private void textBox6_TextChanged(object sender, EventArgs e)
{

    Infos
.kontrool.Text = textBox6.Text;
}

This code helps us to change the Text value of the selected control.But some controls dont have Text value as you well know.

Now lets talk about Properties Panel:

Properties Panel

private const int SC_CLOSE = 0xF060;
private
const int MF_GRAYED = 0x1;
[DllImport("user32.dll")]

private
static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]

private
static extern int EnableMenuItem(IntPtr hMenu, int wIDEnableItem, int wEnable);

These codes are Win32 API Calls.It only disables Close Button of Forms...

private void Ozellikler_Load(object sender, EventArgs e)
{
    this.ShowInTaskbar = false;
    EnableMenuItem(GetSystemMenu(this.Handle, false), SC_CLOSE, MF_GRAYED);
}

And we are calling it,making close button disabled.

private void Ozellikler_Resize(object sender, EventArgs e)
{

    this
.Size = new Size(219, 600);
}

By default we mustnt be able to resize the forms.Thats why we have given these values.

private void Ozellikler_Move(object sender, EventArgs e)
{

    this
.DesktopLocation = new System.Drawing.Point(949, 109);
}

By default we mustnt be able to move the forms.Thats why we have given these values.

private void button1_Click(object sender, EventArgs e)
{

    Infos
.xmlbuild = true;
}  

We are sending a bool value to Infos class where we can build XML File...

TOOLBOX

private void pictureBox1_MouseLeave(object sender, EventArgs e)
{
    pictureBox1.BorderStyle = BorderStyle.None;
}

We are changing the BorderStyle to make it feel like we are leaving it.

private void pictureBox1_MouseEnter(object sender, EventArgs e)
{
    pictureBox1.BorderStyle = BorderStyle.FixedSingle;
}

We are changing the BorderStyle to make it feel like we are on it.

private void pictureBox1_Click(object sender, EventArgs e)
{
   Infos inf = new Infos();
   inf.Button_Added(true);
   Infos.close = false;
   Infos.atleastonce = true;
}

By using Button_Added we are adding a button kontrol to our project.

GAME1.CS

First of all we are adding reference to Windows.Forms in System...

using System.Windows.Forms;

Creating instances of Properties_Panel and Toolbox Forms:

Properties_Panel pp = new Properties_Panel();
Toolbox
tool = new Toolbox();

In Game1 constructor:

graphics.PreferredBackBufferWidth = 600;  //Width  of XNA Window
graphics.PreferredBackBufferHeight = 600; //Height of XNA Window

And setting Title of XNA Window and enabling MouseCursor:

Window.Title = "Simple GUI Editor";
this
.IsMouseVisible = true;

After that we are setting XNA Windows,Properties_Panel's and Toolbox's Locations as we want to.

Control.FromHandle(Window.Handle).Location = new System.Drawing.Point(337, 86);

pp.Show();
pp.DesktopLocation = new System.Drawing.Point(949, 109);
tool.Show();
tool.DesktopLocation = new System.Drawing.Point(141, 109);

We are creating a timer that will help us show the controls on the form.And dynamically change the values from Properties_Panel

Timer tim = new Timer();
int
oldvar;
Infos
inf = new Infos();

In Game1() Constructor function;

tim.Enabled = true;

Enable the timer to work immediately.

Inside Initialize() function add;

//Timer Object
tim.Interval = 1;
tim.Tick += new EventHandler(tim_Tick);

Which has a 1 interval value(very fast) and adding its event we will be discussing right now.

private void tim_Tick(object sender, EventArgs e)
{
    //Adding XBUTTON
    if (Infos.varpub == true)
    {
        if (Infos.close == true)
        {
        }
        else if (Infos.close == false)
        {
            XButton btn = new XButton();
            btn.Name = "button" + Infos.a;
            btn.Text = "button" + Infos.a;
            btn.Location = new System.Drawing.Point(50, 50);
            Control.FromHandle(Window.Handle).Controls.Add(btn);
            Infos.strunq.Add(btn);
            Infos.close = true;
        }
    }
    foreach (Control v in Control.FromHandle(Window.Handle).Controls)
    {
        Infos.kontroller.Add(v);
        if (v.Focused)
        {
            Infos.selectedcontrol = v;
            Infos.kontrool = v;
        }
    }
    if (Infos.xmlbuild == true)
    {
       Infos.XMLBuild();
       Infos.xmlbuild = false;
    }           

Lets talk about after adding Button control:

foreach (Control v in Control.FromHandle(Window.Handle).Controls)
{
     Infos.kontroller.Add(v);
     if (v.Focused)
    {
        Infos.selectedcontrol = v;
        Infos.kontrool = v;
    }
}

if
(Infos.xmlbuild == true)
{

    Infos
.XMLBuild();
    Infos.xmlbuild = false;
}

We are querying dynamically if any control added and if added,is it active or not.

And if "Save" button on Properties_Panel clicked,we are calling XMLBuild() function from Infos class...

Besides In Game1() Constructor:

Control.FromHandle(Window.Handle).Move += new EventHandler(XNAWindowsMove);

We are creating an eventhandler that will disable moving XNA Window...

public void XNAWindowsMove(object sender, EventArgs e)
{

    Control
.FromHandle(Window.Handle).Location = new System.Drawing.Point(337, 86);
}

Lets run our application and try something:

21.gif

Lets create an Interface like this below:

22.gif

After that Click Save button lets take a closer look at controls.xml in x86->bin->debug

<?xml version="1.0" encoding="utf-8"?>
<
GUI>
  <
Control Name="button1">
    <
Type>GUIEditor.XButton</Type>
    <
AllowDrop>False</AllowDrop>
    <
Enabled>True</Enabled>
    <
ForeColor>ControlText</ForeColor>
    <
LocationX>324</LocationX>
    <
LocationY>42</LocationY>
    <
SizeW>75</SizeW>
    <
SizeH>23</SizeH>
    <
Text>New Game</Text>
  </
Control>
  ......
</
GUI>

Yes that means we have successfully get what we wanted. A List of Controls that can be used in our Screens.

In the second part, I will be telling about How To Load this XML file, adding events and creating screens. After that we will be successfully finishing our GUI Editor Creation Process...

See you in our following articles!