Creating a resizable grid to overlay on a image using c#
Asked Answered
J

1

3

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 something like this ? I have not been able to find anything similar online.

I want to have the user drag a grid over the image and resize it to fit the image.

They drag the below grid over the image (being the numbers) Before

and end up like this, after resizing the table After

So essentially, we are having a image in the form and then a draggable and resizable grid to use over the image.

In the end I want to have the user drag a grid over data in an image i then want to use OCR to read the data in each area corresponding to the cell on the image. That way I can then choose say col 2 in row 2 and read specifically just that data.

Jecho answered 27/8, 2018 at 13:30 Comment(11)
If your app is showing the image, the size is known so there is no need for the user to drag or resize anything. Not sure what you are afterOfficialese
I want to have the user drag a grid over data in an image i then want to use OCR to read the data in each area corresponding to the cell on the imageJecho
What are you targetting: Winforms, WPF, ASP..? YOU should always TAG your questions correctly so one can see it on the questions page!Tessellated
You dont need grids. Just draw rects. It is easierRallentando
@TaW, im currently using winforms, updatedJecho
@γηράσκωδ'αείπολλάδιδασκόμε, its tedious work to have to go draw rectangles over every future data for a cell, thats why im thinking of a resizable gridJecho
Draw 4 point's around each number in image then. It can't get easier than that.Rallentando
@γηράσκωδ'αείπολλάδιδασκόμε, the numbers are just an example, the data could be anything anywhere on a image, usually in a grid format, and i want to add the gridJecho
There is no out-of-the box control that let's you do that. DGV let's you create the grid but won't display an image or allow transparency; TableLayoutPanel will allow that and also show a nice grid. But it will not allow directly changing the grid. You could add a moveable control to drag the gridlines. Not very hard..Tessellated
@TaW, could you direct me to an example? the issue i am having is not being able to drag a control around the image, but how to create or use existing controls that allow transparency and resizing of the columns and rowsJecho
Well, as I wrote there is no controls that does it both. TLP shows a nice grid but resizing it will take some effort Here is a weird workaround I', playing with: Nest the image control in a DGV, leave a little space at top and left for the headers. Then nest a TLP in the image control and make it display the cell borders. Then couple the row/cell height with the TLP Row/CellStyles. I got some offsets but other than that it kind of works.. - But a real virtual grid would be a lot cleaner..Tessellated
T
8

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:

enter image description here

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.

Tessellated answered 27/8, 2018 at 17:54 Comment(4)
As a note, the InitializeComponent(); method is not "initialized" (yes, I saw the partial keyword) and I think Init(x, y, s) should be called from the costructor (or InitializeComponent()) with default values, so you can use it as a custom control (dragging it from the ToolBox, as one would be tempted to try it). Details. I tested it and it works quite well.Haste
Yes, if one wants to add it from the toolbox default values make sense. One can still use the properties, though, I guess but didn't test further. It was just a quick one and I like it a lot better than the hack with a dgv plus FLP I tried before..Tessellated
Thnaks for the kind words. I just added a few lines to implement resizing as well.Tessellated
Thanks. You have to create a UserControl (not only a simple Class) so you have the InitializeComponent() call that can compile. A little needed fix is: c# if (Ys[rows] == Height) Ys[rows]--; Seibert

© 2022 - 2024 — McMap. All rights reserved.