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...
Here it is..
Now lets talk about everything we are going to
do:
- Add a resource file that includes Skin
images for controls.
- A Structure that will help us export the
control List as an XML File.
- A "Screens" Folder that will help us to
read this controls.
- For Communication of Windows & XNA
creating a class where we will declare "public static" variables
- A "Properties Panel" where we can change
the properties of selected controls.
- A "Toolbox Panel" where we can add
controls.
- 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:
After that open the resource file and "Add
Resource->Add Existing File..."
Then add 4 sample skins(you can create your own
skins named 'disabled','down','over' and 'up'):
Here they are added at Resources...
2. A Structure that will help us export the
control List as an XML File
First creating a new XML File:
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.
4. For Communication of Windows & XNA, creating
a class where we will declare "public static" variables
Create a new Class & name it Infos.cs...
It seems just like this:
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:
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
Set its Properties as below:
BackColor = White
Size
Width=175
Height=400
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:
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
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";
Added the controls' images.
Add 9 picturebox and assign the images that we
currently added on Resource and it will look like below:
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
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:
Lets create an Interface like this below:
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!