SimoneApprendista
SimoneApprendista

Reputation: 25

PictureBox updates only on resize

Trying to display a graph on a Form application. Created a PictureBox control and initialized this class with its value. On resize, graph always updates; on mouse scroll, it hardly does. It's GraphBox , PictureBox control, inside a GraphBoxPanel, Panel control.

This the class:

public struct DLT_measure_item
{
    public DateTime ts;
    public float value;
    public int id;
    public int X;
    public int Y;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct dlt_ser_meas
{
    public byte msg_id;         // 'D'
    public byte meas_count;        // Number of measures
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
    public byte[] port;        // Module ID (4b) + Port ID (4b)
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
    public float[] meas;       // measure
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    public byte[] msg_end;
}

public class manageGraph
{
    private PictureBox box;
    public bool displayGrid = true;
    private int   horAxisMin_p = 0;
    private int   horAxisMax_p = 300;
    private float verAxisMin_p = 0;
    private float verAxisMax_p = 40;
    public int   horAxisMin
    {
        get { return this.horAxisMin_p; }
        set
        {
            if (value < horAxisMax_p)
            {
                this.horAxisMin_p = value;
                reDraw();
            }
        }
    }
    public int   horAxisMax      
    {
        get { return this.horAxisMax_p; }
        set
        {
            if (value > horAxisMin_p)
            {
                this.horAxisMax_p = value;
                reDraw();
            }
        }
    }
    public float verAxisMin
    {
        get { return this.verAxisMin_p; }
        set
        {
            if (value < verAxisMax_p)
            {
                this.verAxisMin_p = value;
                verPointPerUnit = graphArea.Height / (verAxisMax_p - this.verAxisMin_p);
            }
        }
    }
    public float verAxisMax
    {
        get { return this.verAxisMax_p; }
        set
        {
            if (value > verAxisMin_p)
            {
                this.verAxisMax_p = value;
                verPointPerUnit = graphArea.Height / (this.verAxisMax_p - verAxisMin_p);
            }
        }
    }
    Pen axes = new Pen(Color.Black, (float)1.5);
    public int horAxisSpacing = 30;
    public int verAxisSpacing = 20;
    public int horAxis = 20;
    public int verAxis = 20;
    private float horPointPerUnit = 1;
    private float verPointPerUnit = 1;
    public int horAxisTickLen = 5;
    public int verAxisTickLen = 5;
    public bool horAxisShowTime = false;
    private Rectangle graphArea = new Rectangle();


    public void reDraw()
    {
        box.Image.Dispose();
        Bitmap GraphBlankImage = new Bitmap(box.Width, box.Height);
        box.Image = GraphBlankImage;
        updatePointPerUnit();
        drawGrid();
        box.Refresh();

    }
    public manageGraph(PictureBox targetImageBoxbox)
    {
        box = targetImageBoxbox;
        horAxisMin_p = 0;
        horAxisMax_p = 300;
        verAxisMin_p = 0F;
        verAxisMax_p = 50F;
        updatePointPerUnit();
    }
    private Point measToPoint(DLT_measure_item measure) {
        Point coords = new Point();
        coords.X = graphArea.Width - (int)( 
         ((DateTime.Now - measure.ts).TotalSeconds  + horAxisMin_p) * horPointPerUnit  )   ;
        coords.Y = graphArea.Height - (int)(
         ((measure.value - verAxisMin_p) * verPointPerUnit));
        return coords;
    }
    public manageGraph(PictureBox targetImageBoxbox, 
                       int xmin, int xmax, float ymin, float ymax)
    {
        box = targetImageBoxbox;
        horAxisMin_p = xmin;
        horAxisMax_p = xmax;
        verAxisMin_p = ymin;
        verAxisMax_p = ymax;
        updatePointPerUnit();
    }

    private void updateGraphArea()
    {
        graphArea = new Rectangle(0, 0, box.Width - horAxis, box.Height - verAxis);
    }

    private void updatePointPerUnit()
    {
        updateGraphArea();
        horPointPerUnit = graphArea.Width / (horAxisMax_p - horAxisMin_p);
        verPointPerUnit = graphArea.Height / (verAxisMax_p - verAxisMin_p);
    }

    public void drawGrid()
    {

        //updatePointPerUnit();
        using (Graphics g = Graphics.FromImage(box.Image))
        {
            // X axis
            g.DrawLine(axes, graphArea.Left, graphArea.Bottom, box.Width, graphArea.Bottom);
            // Y axis
            g.DrawLine(axes, graphArea.Right + 1, graphArea.Top, graphArea.Right +1, graphArea.Bottom);
            using (Font ArialFont = new Font("Arial", 10))
            {
                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
                // Put x labels 
                for (int i = 1; i <= (graphArea.Width / horPointPerUnit); i = i + (int)(horAxisSpacing / horPointPerUnit ) + 1)
                {
                    g.DrawString((i).ToString(), ArialFont, Brushes.Black, graphArea.Width - ( i * horPointPerUnit) - 5, graphArea.Bottom +5);
                    g.DrawLine(axes, graphArea.Width - (i * horPointPerUnit), graphArea.Bottom + (horAxisTickLen / 2), graphArea.Width - (i * horPointPerUnit), graphArea.Bottom - (horAxisTickLen / 2));
                }
                // Put y labels 
                for (int i = 1; i <= (graphArea.Height / verPointPerUnit); i = i + (int)(verAxisSpacing / verPointPerUnit) +1)
                {
                    g.DrawString((i).ToString(), ArialFont, Brushes.Black, graphArea.Right + 1 , graphArea.Height - (i * verPointPerUnit) - 8);
                    g.DrawLine(axes, graphArea.Width - (verAxisTickLen / 2), (i * verPointPerUnit), graphArea.Width + (verAxisTickLen / 2), (i * verPointPerUnit));
                }
            }
            /*Put some random data*/
            DLT_measure_item testmeas = new DLT_measure_item();
            Point testGraphPoint = new Point();
            testmeas.ts = DateTime.Now;
            testmeas.value = 0;
            testGraphPoint = measToPoint(testmeas);
            g.FillEllipse(Brushes.Blue, testGraphPoint.X, testGraphPoint.Y, 4, 4);
            for (double i = 0; i < 300; i++)
            {
                double x = i;
                double freq = 10;
                double y = 30 - (i/10);
                testmeas.value = (float)y;
                testmeas.ts = DateTime.Now.AddSeconds(-1 * i);
                testGraphPoint = measToPoint(testmeas);
                g.FillEllipse(Brushes.Red, testGraphPoint.X, testGraphPoint.Y, 2,2);
            }
        }
    }
}

The initialization:

    public DLThermalogMainForm()
    {
        InitializeComponent();
        Bitmap GraphBlankImage = new Bitmap(GraphBox.Width, GraphBox.Height);
        GraphBox.Image = GraphBlankImage;
        myGraph = new manageGraph(GraphBox);
        myGraph.drawGrid();

    }

Those the handlers:

    private void Form1_ResizeEnd(object sender, EventArgs e)
    {
        myGraph.reDraw();
        OutputTextBox.AppendText("Resize." + Environment.NewLine);
    }

    private void GraphBox_MouseMove(object sender, MouseEventArgs e)
    {
        //Get the focus to have the wheel working
        GraphBoxPanel.Focus();
    }

    private void GraphBoxPanel_MouseWheel(object sender, MouseEventArgs e)
    {
        // Change x axis max value to zoom in/out the graph
        myGraph.horAxisMax += e.Delta/ 120;
        myGraph.reDraw();
    }

On resize event, it always redraws; on mouse wheel, it does quickly only with small horAxisMax values (does it makes sense???), but for larger, it takes many seconds to update, or doesn't at all. Thank you very much

Upvotes: 2

Views: 790

Answers (2)

TaW
TaW

Reputation: 54433

Change reDraw like this:

public void reDraw()
{
    box.Image.Dispose();
    Bitmap GraphBlankImage = new Bitmap(box.ClientSize.Width, box.ClientSize.Height);
    updatePointPerUnit();
    drawGrid(GraphBlankImage);
    box.Image = GraphBlankImage;
}

and drawGrid like this:

public void drawGrid(Bitmap bmp)
{
    //updatePointPerUnit();  //??
    using (Graphics g = Graphics.FromImage(bmp))
    {
     ...
     ...
     ...
    }
}

Now the Bitmap with the grid should immediately show up in the PictureBox.

As mentioned a Graphics object is a tool to change an associated Bitmap. To pick it up the Bitmap should be assigned to the PictureBoxe's Image.

Also note, that unless your PictureBox has no Border, there is a small difference between the outer size (aka Bounds) and the inner size, the ClientRectangle / ClientSize. The Image should have the ClientSize

You may wonder why your original code doesn't work? After all an Image is a reference type, so changing it, as you did should be enough..

But looking deeper into the source code we find the reason:

The PictureBox's Image is a property and in its setter there is a call to InstallNewImage:

    public Image Image {
        get {
            return image;
        }
        set {
            InstallNewImage(value, ImageInstallationType.DirectlySpecified);
        }
    }

The same call is also in a few other places like Load or in the setter of ImageLocation. But changing the Image behind the scene alone will not force the PictureBox to make that call. A Refresh() should also do it.. And, as you found out, resizing it will also cause the PictureBox to pick up the changed data in the Image Bitmap..

Upvotes: 1

Ciprian Khlud
Ciprian Khlud

Reputation: 444

The easiest way to force updating is simply to invalidate the control.

timer = new Timer();
timer.Interval = 200; //refreshes every 200 ms
timer.Tick += (sender,e) => targetImageBoxbox.Invalidate();
timer.Start();

Upvotes: 0

Related Questions