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.

Jayme
  • 1,776
  • 10
  • 28
  • 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 after – Ňɏssa Pøngjǣrdenlarp Aug 27 '18 at 13:38
  • 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 – Jayme Aug 27 '18 at 13:41
  • What are you targetting: Winforms, WPF, ASP..? YOU should __always__ TAG your questions correctly so one can see it on the questions page! – TaW Aug 27 '18 at 13:43
  • 1
    You dont need grids. Just draw rects. It is easier – γηράσκω δ' αεί πολλά διδασκόμε Aug 27 '18 at 13:43
  • @TaW, im currently using winforms, updated – Jayme Aug 27 '18 at 13:48
  • @γηράσκωδ'αείπολλάδιδασκόμε, its tedious work to have to go draw rectangles over every future data for a cell, thats why im thinking of a resizable grid – Jayme Aug 27 '18 at 13:49
  • Draw 4 point's around each number in image then. It can't get easier than that. – γηράσκω δ' αεί πολλά διδασκόμε Aug 27 '18 at 13:55
  • @γηράσκωδ'αείπολλάδιδασκόμε, 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 grid – Jayme Aug 27 '18 at 14:02
  • 1
    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.. – TaW Aug 27 '18 at 14:04
  • @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 rows – Jayme Aug 27 '18 at 14:14
  • 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.. – TaW Aug 27 '18 at 15:16

1 Answers1

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.

TaW
  • 53,122
  • 8
  • 69
  • 111
  • 1
    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. – Jimi Aug 27 '18 at 20:40
  • 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.. – TaW Aug 27 '18 at 20:46
  • 1
    Thnaks for the kind words. I just added a few lines to implement resizing as well. – TaW Aug 29 '18 at 14:31