1

I'm creating a program that uses the flycapture camera. I've created a class that extends the pictureBox class in order to draw a crosshair, consisting of two lines, onto the screen. I want to be able to move the crosshair from the center to any other location on the screen.

The problem is when the form is resized, the crosshair moves to a different location as shown here. I want the crosshair to be pointing at the same part of the image as before it was resized (in the example it is no longer pointing at the grey mesh). I'm drawing the crosshair in relation to the height and width of the pictureBox. I want to be able to draw the lines on the image but the image height and width are always the same regardless of the image's size.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;


namespace FlyCapture2SimpleGUI_CSharp
{
    class IMSPictureBox : PictureBox
    {

        private Color colorSetting = Color.Black;
        private float width = 1.0f;

        public IMSPictureBox()
        {
            this.Paint += IMSPictureBox_Paint;
        }

        private void IMSPictureBox_Paint(object sender, PaintEventArgs e)
        {
            //Draw if image has loaded
            if (this.Image != null)
            {
                //Draw horizontal line
                e.Graphics.DrawLine(
                  new Pen(this.colorSetting, this.width),
                  new Point(0, this.Size.Height / 2 + 100),
                  new Point(this.Size.Width, this.Size.Height / 2 + 100));

                //Draw vertical line
                e.Graphics.DrawLine(
                  new Pen(this.colorSetting, this.width),
                  new Point(this.Size.Width / 2 + 100, 0),
                  new Point(this.Size.Width / 2 + 100, this.Size.Height));
            }
        }
    }
}

Edit: As DiskJunky suggested, I'm now drawing on the image itself and not using the Paint function above.

Here is the image getting set:

private void UpdateUI(object sender, ProgressChangedEventArgs e)
{
    UpdateStatusBar();

    pictureBox1.SetImage = m_processedImage.bitmap;
    pictureBox1.Invalidate();
}

Here are the lines drawing on the image:

public System.Drawing.Image SetImage
{
    set
    {
        using (Graphics g = Graphics.FromImage(value))
        {
            g.DrawLine(new Pen(Color.Red, 3.0f), new Point(0, 0), new Point(value.Width, value.Height));
            g.Dispose();
        }
        this.Image = value;
    }
    get
    {
        return this.Image;
    }
}

I now have a line that scales with the image, but now it's constantly flickering.

Jaitnium
  • 621
  • 1
  • 13
  • 27
  • 2
    Your calculation is drawing based on the size of the picture box, not the size of the image. Resizing the form resizes the image which triggers the `Paint` event and now the coordinates are different. You could store the `Point` details as a class variable rather than recalculating on every `Paint` – DiskJunky Sep 22 '17 at 16:31
  • In your paint event **this** is your form not the picturebox. – Ralf Sep 22 '17 at 16:35
  • Dispose your Pens. – LarsTech Sep 22 '17 at 16:36
  • The code is ignoring the PictureBox.SizeMode property. That is only ever valid if it is Normal, surely it is not. – Hans Passant Sep 22 '17 at 16:37
  • @Ralf **this** does not refer to the form in this example. This is an inherited PictureBox control. – LarsTech Sep 22 '17 at 16:39
  • @DiskJunky Could you go into more detail? How does storing the Point help me know how to adjust the lines? – Jaitnium Sep 22 '17 at 16:40
  • @LarsTech What do you mean by disposing of them? – Jaitnium Sep 22 '17 at 16:40
  • See [What happens if I don't call Dispose on the pen object?](https://stackoverflow.com/q/4267729/719186) – LarsTech Sep 22 '17 at 16:46
  • @Jaitnium you don't adjust the lines - that's the point (no pun intended). You need to redraw the lines with the same measurements on each `Paint` event. If you look at your code for `new Point()`, you're using `this` which is the `PictureBox`'s height and width. `Width` and `Height` change when you resize and so do your line positions as a result – DiskJunky Sep 22 '17 at 17:02

1 Answers1

0

This isn't exact but modifying to something like the following will keep the position static as the picture box resizes;

class IMSPictureBox : PictureBox
{

    private Color colorSetting = Color.Black;
    private float width = 1.0f;
    private Tuple<Point, Point> _verticalLine;
    private Tuple<Point, Point> _horizontalLine;

    public IMSPictureBox()
    {
        this.Paint += IMSPictureBox_Paint;
    }

    private void IMSPictureBox_Paint(object sender, PaintEventArgs e)
    {
        //Draw if image has loaded
        if (this.Image != null)
        {
            //Draw vertical line
            if (_verticalLine == null)
            {
                _verticalLine = new Tuple<Point, Point>(new Point(100, 0), new Point(100, this.Size.Height);
            }
            e.Graphics.DrawLine(
              new Pen(this.colorSetting, this.width),
              _verticalLine.Item1,
              _verticalLine.Item2);

            //Draw horizontal line
            if (_horizontalLine == null)
            {
                _horizontalLine = new Tuple<Point, Point>(new Point(0, 100), new Point(this.Size.Width, 100);
            }
            e.Graphics.DrawLine(
              new Pen(this.colorSetting, this.width),
              _horizontalLine.Item1,
              _horizontalLine.Item2);
        }
    }
}

Edit The above solution outlines the concept of preserving the line position. As per discussion in comments below, this developed into a more involved solution for the OP as additional requirements came out of investigating and testing the solution above for the original intent - to cleanly draw a a coordinate marker on an image.

To that end a manual double buffering mechanism is recommended in that scenario as the PictureBox control supports limited drawing capability itself. An example of manually implementing a double buffering solution can be found here; https://learn.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-manually-render-buffered-graphics

Rather than calling DrawEllipse() there'll be calls to DrawLine() to display the coordinate marker. One caveat is that if the image is still being displayed in the PictureBox then the PictureBoxSizeMode.Zoom value may still have to be accounted for.

DiskJunky
  • 4,750
  • 3
  • 37
  • 66
  • Oh, I see what you're saying now. This is my fault that because I've worded the question ambiguously regarding "I want the crosshair to stay at the same location"... What I mean is: If the crosshair was over a balloon, and then I resized the form, my current code would have the crosshair offset from the balloon, when I actually want it to still be on top of the balloon (same location on the image). I've edited my question. – Jaitnium Sep 22 '17 at 17:23
  • 1
    @Jaitnium in order to do that, I'd need to know how the picture was sized. In the screenshot it looks to be stretched, is that correct? – DiskJunky Sep 22 '17 at 18:15
  • I'm new to this so I'm not entirely sure. I'm using one of the example program that comes with the flycapture SDK. If you're asking about the size mode, it's using PictureBoxSizeMode.Zoom. – Jaitnium Sep 22 '17 at 18:31
  • 1
    OK, so that sounds like it's doing a crop depending on the aspect ratio. At this point, I'd actually recommend keeping the image in memory and whenever you need to resize/redrect that you copy the original, draw directly onto the (copied) image and set that to be the `PictureBox` source. The reason being is that if you draw as you are, you not only need to calculate the difference in sizes after a resize in order to preserve the relative location, you'll also have to deal with the `PictureBoxSizeMode` algorithm. Drawing directly onto the image will actually be easier and isolate it from the PB – DiskJunky Sep 22 '17 at 18:38
  • 1
    I'm drawing the line on the image itself and now it's scaling properly as the image is resized. The problem now is that it's flickering. I've editing my question to include the new code. – Jaitnium Sep 22 '17 at 20:44
  • @Jaitnium it's flickering as when you call `Invalidate()` what happens with the `PictureBox` is that it draws the background and then draws the source image. The way around this is a technique (which you're nearly there with) called double buffering. You can find an example on how to do this here - https://www.codeproject.com/Articles/12870/Don-t-Flicker-Double-Buffer. The idea is that you use the `Graphics` object to paint directly into the `PictureBox` rather than using the native `PictureBox` methods which do a fair bit of extra drawing that you don't need in this scenario – DiskJunky Sep 22 '17 at 20:57
  • I put this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true); before the drawLine, but I'm not seeing any difference. – Jaitnium Sep 22 '17 at 21:19
  • In the example it's drawing in the paint function. If I do this in the paint function, wouldn't the lines not scale with the image? – Jaitnium Sep 22 '17 at 21:23
  • The PictureBox control has the DoubleBuffered property true by default. – LarsTech Sep 22 '17 at 22:42
  • @LarsTech the `PictureBox` doesn't have a `DoubleBuffered` property - you're thinking of a `Form` – DiskJunky Sep 26 '17 at 09:16
  • 1
    Forms inherit from Controls, and Controls have a protected DoubleBuffered property, so it isn't public. You can see from the source code for yourself if you look in the constructor: [PictureBox](http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/PictureBox.cs,594784cfbb39d6e8). If you still don't believe me, try inheriting from a PictureBox and examine its DoubleBuffered property. – LarsTech Sep 26 '17 at 14:06