I am trying to figure out a method in which the user can drag a grid over a image and then resize the columns and rows to fit the image. How would i go about creating someth
Here is a grid class that can be overlaid over any Control
and will draw an N x M
grid of lines.
You can move the lines with the mouse and move the grid with the right mouse button. You can access the current x- and y- values in the two List<int> Xs
and Ys
.
It is a Panel
subclass and you should make sure it has the correct size and number of rows and columns.
Let's see it in action:
To set it up use the Init
function..
Here is the code:
public partial class Grid : Panel
{
public Grid()
{
InitializeComponent();
GridColor = Color.DarkMagenta;
HandleSize = 4;
BackColor = Color.Transparent;
DoubleBuffered = true;
}
int RowCount { get; set; }
int ColCount { get; set; }
Color GridColor { get; set; }
int HandleSize { get; set; }
List<int> Xs { get; set; }
List<int> Ys { get; set; }
public void Init(int cols, int rows)
{
RowCount = rows;
ColCount = cols;
Xs = new List<int>();
Ys = new List<int>();
float w = 1f * Width / cols;
float h = 1f * Height / rows;
for (int i = 0; i <= cols; i++) Xs.Add((int)(i * w));
for (int i = 0; i <= rows; i++) Ys.Add((int)(i * h));
// draw inside the panel only
if (Xs[cols] == Width) Xs[cols]--;
if (Ys[rows] == Height) Ys[cols]--;
}
public void Init(int cols, int rows, Size sz)
{
Size = sz;
Init(cols, rows);
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
using (Pen pen = new Pen(GridColor))
{
foreach (int x in Xs) pe.Graphics.DrawLine(pen, x, 0, x, Height);
foreach (int y in Ys) pe.Graphics.DrawLine(pen, 0, y, Width, y);
}
}
private Point mDown = Point.Empty;
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (Cursor != Cursors.Default) mDown = e.Location;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
// distances
var dx = Xs.Select(x => Math.Abs(x - e.X));
var dy = Ys.Select(y => Math.Abs(y - e.Y));
// smallest distance
int mx = dx.Min();
int my = dy.Min();
// grid index
int ix = dx.ToList().IndexOf(mx);
int iy = dy.ToList().IndexOf(my);
if (e.Button.HasFlag(MouseButtons.Right))
{ // move the grid with the right mouse button
Location = new Point(Left + e.X - mDown.X, Top + e.Y - mDown.Y);
}
else if (!e.Button.HasFlag(MouseButtons.Left))
{ // if we are close enough set cursor
Cursor = Cursors.Default;
if (mx < HandleSize) Cursor = Cursors.SizeWE;
if (my < HandleSize) Cursor = Cursors.SizeNS;
if (mx < HandleSize && my < HandleSize) Cursor = Cursors.SizeAll;
}
else
{ // else move grid line(s)
if (Cursor == Cursors.SizeWE || Cursor == Cursors.SizeAll)
Xs[ix] += e.X - mDown.X;
if (Cursor == Cursors.SizeNS || Cursor == Cursors.SizeAll)
Ys[iy] += e.Y - mDown.Y;
Invalidate();
mDown = e.Location;
// restore order in case we overshot
Xs = Xs.OrderBy(x => x).ToList();
Ys = Ys.OrderBy(x => x).ToList();
}
}
}
It was just a quick shot, so many things can and probably should be improved, like adding and removing columns and rows, validating etc..
I set it up to overlay a Panel panel1
like this:
Grid grid1 = new Grid();
panel1.Controls.Add(grid1);
//grid1.Size = panel1.ClientSize; // overlay full area..or..
grid1.Init(4, 3, new Size(99, 44)); // .. use the overload with size
grid1.Invalidate();
To let the user place and size it where he wants it you can use the usual mouse events instead..
Update: On re-reading I saw that you wanted to let the user resize the grid as well. Here is an example of how to expand the code to allow resizing from the left or right edge..:
{ // else move gridline or size grid
if (Cursor == Cursors.SizeWE || Cursor == Cursors.SizeAll)
{
int delta = mDown.X - e.X;
if (ix == 0) // left edge: resize
{
Width += delta;
Left -= delta;
Xs[Xs.Count - 1] = Width - 1;
}
else if (ix == Xs.Count - 1) // right edge resize
{
Width -= delta;
Xs[Xs.Count - 1] = Width - 1;
}
else Xs[ix] -= delta; // move gridline
}
The top & bottom edges will work the in the very same way. Like with the line crossings resizing will also work from the corners..
Update: Instead of a Panel
, which is a Container
control and not really meant to draw onto you can use a Picturebox
or a Label
(with Autosize=false
); both have the DoubleBuffered
property turned on out of the box and support drawing better than Panels
do.