2

Hi based on the thread here: How to create a jpg image dynamically in memory with .NET?

I have this method:

        int maxVal = 50;
        int maxXCells = r.Next(maxVal);
        int maxYCells = r.Next(maxVal);
        int cellXPosition = r.Next(maxVal);
        int cellYPosition = r.Next(maxVal);
        int boxSize = 10;


        Graphics fg = this.CreateGraphics();


        using (var bmp = new System.Drawing.Bitmap(maxXCells * boxSize + 1, maxYCells * boxSize + 1))
        {


            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.Clear(Color.Yellow);
                Pen pen = new Pen(Color.Black);
                pen.Width = 1;

                //Draw red rectangle to go behind cross
                Rectangle rect = new Rectangle(boxSize * (cellXPosition - 1), boxSize * (cellYPosition - 1), boxSize, boxSize);
                g.FillRectangle(new SolidBrush(Color.Red), rect);

                //Draw cross
                g.DrawLine(pen, boxSize * (cellXPosition - 1), boxSize * (cellYPosition - 1), boxSize * cellXPosition, boxSize * cellYPosition);
                g.DrawLine(pen, boxSize * (cellXPosition - 1), boxSize * cellYPosition, boxSize * cellXPosition, boxSize * (cellYPosition - 1));

                //Draw horizontal lines
                for (int i = 0; i <= maxXCells; i++)
                {
                    g.DrawLine(pen, (i * boxSize), 0, i * boxSize, boxSize * maxYCells);
                }

                //Draw vertical lines            
                for (int i = 0; i <= maxYCells; i++)
                {
                    g.DrawLine(pen, 0, (i * boxSize), boxSize * maxXCells, i * boxSize);
                }
            }
            fg.DrawImage(bmp, 0, 0);
            fg.Dispose();

        }

It fires based on the random event (can be many times in a second). What should I do, to draw a new image only after current drawing has finished? At the moment, I can seen, that if the event to run this method fires faster than drawing has finished, the screen flickers. What are the common solution to avoiding drawing before previous drawing has finished?

This what I included in OnPaint method:

   protected override void OnPaint(System.Windows.Forms.PaintEventArgs pe)
    {
        finishedInvalidating = false;
        fg = this.CreateGraphics();
        lock (bmp)
        {
            fg.DrawImage(bmp, 0, 0);
        }
        fg.Dispose();
        finishedInvalidating = true;

But this does not solve the problem

---------------------- update ------------------

 public partial class LadderFrm : Form
{
    Bitmap bmp;

    int numCols = 3;
    int colWidth = 100;
    int numRows = 30;
    int rowHeight = 20;
    bool finishedInvalidating = false;

    decimal lastprice;

    public LadderFrm()
    {
        bmp = new System.Drawing.Bitmap(numCols * colWidth + 1, numRows * rowHeight + 1);
        prepareLadderGraphics();
    }



    // prepare initial ladder background
    private void prepareLadderGraphics()
    {

        using (Graphics g = Graphics.FromImage(bmp))
        {
            g.Clear(Color.LightGray);
            Pen pen = new Pen(Color.Black);
            pen.Width = 1;

            // drawCells
            for (int i = 0; i < numCols; i++)
            {
                for (int j = 0; j < numRows; j++)
                {
                    Rectangle rect = new Rectangle(i * colWidth, j * rowHeight, colWidth, rowHeight);
                    g.DrawRectangle(pen, rect);
                }
            }
            g.Dispose();
        }

    }


    protected override void OnPaint(System.Windows.Forms.PaintEventArgs pe)
    {
        Graphics fg = this.CreateGraphics();
        lock (fg)
        {
            lock (bmp)
            {
                fg.DrawImage(bmp, 0, 0);
            }
        }
        fg.Dispose();
    }

    public void OrderBookUpdateFn(OrderBookEvent orderBookEvent)
    {



        if (lastprice != orderBookEvent.ValuationAskPrice)
        {
            lastprice = orderBookEvent.ValuationAskPrice;
            lock (bmp)
            {
                using (Graphics g = Graphics.FromImage(bmp))
                {
                    Pen pen = new Pen(Color.Black);
                    pen.Width = 1;
                    for (int i = 0; i < numRows; i++)
                    {
                        Rectangle rect = new Rectangle(colWidth + 1, i*rowHeight + 1, colWidth - 1, rowHeight - 1);
                        g.FillRectangle(new SolidBrush(Color.LightGray), rect);
                        g.DrawString(lastPrice.ToString(), new Font(FontFamily.GenericSansSerif, 10), new SolidBrush(Color.Black), new Point(colWidth + 1, i*rowHeight + 1));
                    }
                    g.Dispose();
                }
                this.Invalidate(new Rectangle(0, 0, 1, 1));
            }
    }
}

Above code works, pretty well, however I am not sure if its correct. This is just for test purpose. Method OrderBookUpdateFn(OrderBookEvent orderBookEvent) gets fired very often (sometimes tens of times a second).

Community
  • 1
  • 1
Macin
  • 391
  • 2
  • 6
  • 20
  • use a lock. This of course would discard the event, if the previous event is still processing, its far better then trying to process two events at the sametime. – Security Hound Jun 02 '11 at 13:01
  • You need to share more code for the entire method. How are you calling this method? Where is the random event that calls this method? – NakedBrunch Jun 02 '11 at 13:03

3 Answers3

3

The event handlers all run in the main thread of the application, so it's not possible that a method to handle an event is started before the method that handles the previous event is finished.

If you experience flickering, it's not because of overlapping event handlers.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • My best guess is that repainting occurs so fast, that before previous painting has finished, new begins. – Macin Jun 02 '11 at 13:22
  • @Macin: You have to look for a different cause, because that is simply not possible. The updates to the user interface runs in a single thread. – Guffa Jun 02 '11 at 13:39
  • I found some clue: bmp is the global representation of the screen. I tried to ivalidate only the portion of the bmp which has changed, but then in OnPaint method I was drawing whole bitmap to the screen. Now, when I invalidated the form this way: this.Invalidate(new Rectangle(0,0, 1,1)); flickering disappeared. Maybe the problem was that I was invalidating only a portion but repainted whole bitmap? – Macin Jun 02 '11 at 13:49
  • Yes, only when I run: Invalidate(new Rectangle(0,0, 1, 1)); flickering dissapear. I tried using larger values, but as soon as I change it to something larger, flickering returns. Why is that? – Macin Jun 02 '11 at 14:03
  • @Macin: If you are drawing this in the `Paint` event, you are doing it wrong. In the event arguments there is a `Graphics` object that you should use. It's clipped to the area that is to be updated, so that you don't draw somewere you shouldn't (for example on top of another window that is overlapping your window). You can even use that clipping information so that you only create a bitmap that is large enough to draw what you need, further reducing the work that has to be done. – Guffa Jun 02 '11 at 15:06
  • @Guffa: Thank you for your help. I have little experience with drawing in windows and I am aware I am doing some things wrong. Where should I draw updates then, if not in the onPaint? I have edited my question again, and pasted updated code which suits much more my requirements than example I gave at first. Thanks for help again – Macin Jun 02 '11 at 15:23
  • @Macin: You should do the update in the `Paint` event, but you should use the object in the `pe.Graphics` property instead of creating a new `Graphics` object. – Guffa Jun 02 '11 at 15:45
1

You could try overriding the OnPaint method, call base.OnPaint(); and then do your manual painting after.

protected override void OnPaint(PaintEventArgs e)
{
   // call base
   base.OnPaint(e);

   // Do your stuff after the rest has been painted
   if(this.picture != null && this.pictureLocation != Point.Empty)
   {
      e.Graphics.DrawImage(this.picture, this.pictureLocation);
   }
}
jimplode
  • 3,474
  • 3
  • 24
  • 42
1

to avoid flickering of selfpainted controls you can DoubleBuffer the drawing of the form / control. Add this to the constructor:

this.SetStyle(
  ControlStyles.AllPaintingInWmPaint |
  ControlStyles.UserPaint |
  ControlStyles.DoubleBuffer,true);

some anti-flicker techniques

fixagon
  • 5,506
  • 22
  • 26
  • when I use above code, nothing is being drawn, only when I resize the form. – Macin Jun 02 '11 at 13:18
  • so remove the SetStyle(ControlStyles.UserPaint, true); <-- means that all calls of the paint method are user controlled. look at the link i edited inside – fixagon Jun 02 '11 at 13:27
  • and as i assume that you use at least .net 2.0 you better use the ControlStyles.OptimizedDoubleBuffer than the ControlStyles.DoubleBuffer – fixagon Jun 02 '11 at 13:28
  • You might need to add on a "this.UpdateStyles()" after the above code. – Harag Jun 03 '11 at 08:26