To display the sprite thumbnails in my level editor application, I wrote a couple of simple custom controls.
The first control extends from the .NET PictureBox control, and just has an additional property to track whether it’s been selected. Then, in the OnPaint method, if the SelectablePictureBox is selected, then I draw a border over the top of it.
The second control extends from the .NET FlowLayoutPanel control, and has an event that is raised when any child SelectablePictureBox is clicked, and then some code to handle Ctrl-clicking and Shift-clicking to select and deselect individual SelectablePictureBox child controls.
The source code for both controls is below. Feel free to use it if you want to, but note that it’s pretty simple and inflexible, so the SelectableImageFlowLayoutPanel will only work with the SelectablePictureBox.
Let me know if you have any questions about it.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace Editor
{
public class SelectablePictureBox : PictureBox
{
private bool _selected = false;
public bool Selected
{
get { return _selected; }
set
{
_selected = value;
}
}
public SelectablePictureBox()
: base()
{
this.SizeMode = PictureBoxSizeMode.Zoom;
this.BorderStyle = BorderStyle.FixedSingle;
}
protected override void OnPaint(PaintEventArgs pe)
{
if (_selected)
{
this.BorderStyle = BorderStyle.None;
}
else
{
this.BorderStyle = BorderStyle.FixedSingle;
}
base.OnPaint(pe);
if (_selected)
{
this.BorderStyle = BorderStyle.None;
Pen pen = new Pen(System.Drawing.Color.CornflowerBlue, 6.0f);
int penWidth = (int)pen.Width;
Point[] points = new Point[5] { new Point(pe.ClipRectangle.Left, pe.ClipRectangle.Top),
new Point(pe.ClipRectangle.Right, pe.ClipRectangle.Top),
new Point(pe.ClipRectangle.Right, pe.ClipRectangle.Bottom),
new Point(pe.ClipRectangle.Left, pe.ClipRectangle.Bottom),
new Point(pe.ClipRectangle.Left, pe.ClipRectangle.Top )};
pe.Graphics.DrawLines(pen, points);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace Editor
{
public class SelectableImageFlowLayoutPanel : FlowLayoutPanel
{
public delegate void OnSelectedIndexChanged();
public event OnSelectedIndexChanged SelectedIndexChanged;
public SelectableImageFlowLayoutPanel()
: base()
{
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
if (e.Control.GetType() == typeof(SelectablePictureBox))
{
e.Control.Click += new EventHandler(SelectablePictureBox_Click);
}
}
public int SelectedIndex
{
get
{
int i = 0;
bool found = false; ;
foreach (Control c in Controls)
{
if (((SelectablePictureBox)c).Selected)
{
found = true;
break;
}
i++;
}
if (found)
{
return i;
}
else
{
return -1;
}
}
set
{
int i = 0;
foreach (Control c in Controls)
{
if (i == value)
{
// select controls
((SelectablePictureBox)c).Selected = true;
// force a redraw
c.Invalidate();
break;
}
else
{
// deselect controls
((SelectablePictureBox)c).Selected = false;
// force a redraw
c.Invalidate();
}
i++;
}
}
}
public List<SelectablePictureBox> SelectedItems
{
get
{
List<SelectablePictureBox> selectedItems = new List<SelectablePictureBox>();
foreach (Control c in Controls)
{
if (((SelectablePictureBox)c).Selected)
{
selectedItems.Add(((SelectablePictureBox)c));
}
}
return selectedItems;
}
}
public List<SelectablePictureBox> Items
{
get
{
List<SelectablePictureBox> items = new List<SelectablePictureBox>();
foreach (Control c in Controls)
{
items.Add(((SelectablePictureBox)c));
}
return items;
}
}
void SelectablePictureBox_Click(object sender, EventArgs e)
{
if (InputHelper.IsKeyDown(Keys.ControlKey))
{
((SelectablePictureBox)sender).Selected = true;
// force a redraw
((SelectablePictureBox)sender).Invalidate();
}
else if (InputHelper.IsKeyDown(Keys.ShiftKey))
{
int startIdx = 0, endIdx = 0;
bool foundStart = false;
endIdx = Controls.IndexOf(((SelectablePictureBox)sender));
foreach (Control c in Controls)
{
if (!foundStart && ((SelectablePictureBox)c).Selected)
{
foundStart = true;
startIdx = Controls.IndexOf(c);
}
else
{
// deselect controls
((SelectablePictureBox)c).Selected = false;
// force a redraw
c.Invalidate();
}
}
if (!foundStart)
{
startIdx = endIdx;
}
for (int i = startIdx; i <= endIdx; i++)
{
// select the controls in range
((SelectablePictureBox)Controls[i]).Selected = true;
// force a redraw
Controls[i].Invalidate();
}
}
else
{
foreach (Control c in Controls)
{
if (c == sender)
{
((SelectablePictureBox)c).Selected = true;
}
else
{
((SelectablePictureBox)c).Selected = false;
}
// force a redraw
c.Invalidate();
}
}
if (SelectedIndexChanged != null)
{
SelectedIndexChanged();
}
}
}
}
Hi
interesting stuff – what control did you use for placing the items on the main grid? I imagine it wouldn’t be too hard to modify that so that you could build up a collection of those grids, and then use XNA to scroll over that map?
Kind regards
Tom.
Hi Tom,
The main grid is rendered via XNA, based on the samples on the Creators Club website:
http://creators.xna.com/en-US/sample/winforms_series1
http://creators.xna.com/en-US/sample/winforms_series2
And the grid itself is drawn using code based on the Primitives sample:
http://creators.xna.com/en-US/sample/primitives
I’ve just got a bit of additional state-handling code in there to handle interactions with the thumbnail control, toolbar buttons etc.
So it’s possible to set the level to any size, and use scrollbars or a custom camera to move around the map.
Dylan.