locked
How to invalidate a Panel in a Form? RRS feed

  • Question

  • I'm just learning C# -- although I have written Java, and I'm familiar with the general OOP approach.  (HP laid a bunch of us off a few days ago, and trying to develop some useful skills--C and assembly language are both dead, and for those who don't have five years of Java, it might as well be on life support.)

    I've used Visual Studio to put a menu and a panel in a form.  On the File menu is an Open command, which I use to open a CSV file containing some data.  The panel event handler calls a method that plots the data.  Everything works just fine except that after the  panel event handler draws the data in the panel, the File menu goes away, and clears part of the form that is occupied by the panel.  The result is that left end of the graph doesn't appear until I do a minimize/maximize to force an update.  (This sends an event to the panel event handler.)

    I'm guessing that there is some event associated with the form that indicates that components (such as the panel) are now invalid--but for some reason, that information isn't getting passed to the panel event handler.

    Feel free to comment or criticize my lack of style in areas not relevant to the problem that I trying to solve.  I'm too old to be arrogant!

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.IO;

    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }

            private List<NumbersToPlot> numbersArray = new List<NumbersToPlot>();
            private Legend legend = new Legend();
            private List<Color> colorChoices = new List<Color>();

            private void openToolStripMenuItem_Click(object sender, EventArgs e)
            {
                colorChoices.Add(Color.Beige);
                colorChoices.Add(Color.Aqua);
                colorChoices.Add(Color.Black);
                colorChoices.Add(Color.Brown);
                colorChoices.Add(Color.Chartreuse);
                OpenFileDialog openFileDialog1 = new OpenFileDialog();
                openFileDialog1.Title = "Data Input File (CSV)";
                openFileDialog1.DefaultExt = "*.csv";
                openFileDialog1.Filter = "CSV files|*.csv";
                if (openFileDialog1.ShowDialog() == DialogResult.OK)
                {
                    // This is where we specify the CSV file to open.
                    Stream myStream = new FileStream(openFileDialog1.FileName, FileMode.Open);
                    if (myStream != null)
                    {
                        string[] strArray;
                        char[] charArray = new char[] { ',' };

                        // Dispose of the old data set, if any
                        if (numbersArray.Count > 0)
                            numbersArray.Clear();

                        StreamReader sr = new StreamReader(myStream);
                        string strLine;
                        // Pull in the first line--the legend line.
                        strLine = sr.ReadLine();
                        if (strLine != null)
                        {
                            strArray = strLine.Split(charArray);
                            legend.legendStrings = new List<string>();
                            for (int i = 0; i < strArray.Length; i++)
                                legend.legendStrings.Add(strArray[i]);
                        }
                        // Now pull in the data that we are going to plot.
                        do
                        {
                            strLine = sr.ReadLine();
                            if (strLine != null)
                            {
                                System.Console.WriteLine("reading " + strLine);
                                strArray = strLine.Split(charArray);
                                NumbersToPlot numbers = new NumbersToPlot();
                                numbers.county = strArray[0];
                                numbers.category = strArray[1];
                                numbers.events = new List<float>();
                                // We need to keep track of the smallest and largest values we see.
                                numbers.min = numbers.max = 0;
                                int lastNumber;
                                for (int i = 2; i < strArray.Length - 1; i++)
                                {
                                    numbers.events.Add(float.Parse(strArray[i]));
                                    lastNumber = numbers.events.Count - 1;
                                    numbers.min = Math.Min(numbers.min, numbers.events[lastNumber]);
                                    numbers.max = Math.Max(numbers.max, numbers.events[lastNumber]);
                                }
                                numbersArray.Add(numbers);
                            }
                        }
                        while (strLine != null);
                        myStream.Close();
                    }
                }
            }

            public void PlotData(Graphics g, NumbersToPlot numbers, Legend legendStrings,
                                 Pen pen, Pen legendPen)
            {
                g.PageUnit = GraphicsUnit.Pixel;
                SizeF sizef = g.VisibleClipBounds.Size;
                // Calculate the scaling required.
                float yScaling;
                if (numbers.max == 0.0F)
                    yScaling = sizef.Height;
                else
                    yScaling = (float)(sizef.Height / numbers.max) / 1.1F;
                float xScaling = (float)Math.Round(Math.Round((sizef.Width / numbers.events.Count), 1) + 1.0F, 0);
                g.Clear(Color.White);
                for (int i = 1; i < numbers.events.Count; i++)
                {
                    float value = (float)numbers.events[i];
                    float lastValue = (float)numbers.events[i - 1];
                    Point prevValueCoord = new Point((int)((i - 1) * xScaling + xScaling / 2),
                                                     (int)(sizef.Height - (lastValue * yScaling)));
                    Point curValueCoord = new Point((int)(i * xScaling + xScaling / 2),
                                                    (int)(sizef.Height - (value * yScaling)));
                    g.DrawLine(legendPen, (float)(i - 1) * xScaling, sizef.Height,
                               (float)(i - 1) * xScaling, sizef.Height - 5);
                    g.DrawLine(legendPen, (float)(i - 1) * xScaling, 0,
                              (float)(i - 1) * xScaling, 5);
                    g.DrawLine(pen, prevValueCoord, curValueCoord);
                }
            }

            private void configureToolStripMenuItem_Click(object sender, EventArgs e)
            {
                // This is where we specify which lines to plot.
            }

            protected override void OnPaint(PaintEventArgs e)
            {
                base.OnPaint(e);
                Graphics g = e.Graphics;
                g.Clear(Color.Black);
            }

            private void panel1_Paint_1(object sender, PaintEventArgs e)
            {
                base.OnPaint(e);
                Graphics g = e.Graphics;
                if (numbersArray.Count > 0)
                {
                    for (int i = 0; i < 5; i++)
                    {
                        Pen pen = new Pen(colorChoices[i % 5], 1);
                        Pen legendPen = new Pen(Color.Black, 5);
                        PlotData(g, numbersArray[i], legend, pen, legendPen);
                    }
                }
                else
                    g.Clear(Color.White);
                this.Invalidate();
            }
        }
    }

    public class Legend
    {
        public List<string> legendStrings;
    }

    public class NumbersToPlot
    {
        public string category;
        public string county;
        public List<float> events;
        public float min, max;

        public NumbersToPlot()
        {
        }

        public NumbersToPlot(string Category, string County)
        {
            category = Category;
            county = County;
        }
    };


    Thursday, August 28, 2008 5:19 PM

Answers

  • Change this.Invalidate() to this.Invalidate(true)

    The true indicates you wish to invalidate child controls as well.  All Invalidate() does under the hood is call Invalidate(false).
    David Morton - http://blog.davemorton.net/
    Thursday, August 28, 2008 5:29 PM
  • The panel event handler is simply a method within your Form1 class.  "this" still refers to Form1.  Try panel1.Invalidate(true) instead.


    David Morton - http://blog.davemorton.net/
    Thursday, August 28, 2008 5:51 PM
  • You could call "this" from any method within a class.  "this" always refers to the currently instantiated version of the class in which "this" resides, regardless of whether the method it is called from is an event handler or a regular method of some other kind.  It may make more sense to invalidate the individual controls instead of invalidating the whole form. 

    You could have another method of accomplishing this task.  Perhaps drawing an image instead, and inserting it into a picturebox, then .NET will handle the repainting for you.
    David Morton - http://blog.davemorton.net/
    Thursday, August 28, 2008 6:04 PM
  • Got it!  Add an event driver for the menu, VisibleChanged property.  Then panel1.Invalidate() does the trick.
    Thursday, August 28, 2008 6:44 PM
  • Your panel1_paint_1() event handler is definitely wrong.  It has no business calling base.OnPaint(), nor this.Invalidate().  It should only busy itself with painting the panel.
    Hans Passant.
    Friday, August 29, 2008 12:27 PM

All replies

  • Change this.Invalidate() to this.Invalidate(true)

    The true indicates you wish to invalidate child controls as well.  All Invalidate() does under the hood is call Invalidate(false).
    David Morton - http://blog.davemorton.net/
    Thursday, August 28, 2008 5:29 PM
  • Thanks.  This solves the problem--and now forces me to understand the problem of unnecessary invalidation.  (This causes flicker problems, because it keeps repainting.)
    Thursday, August 28, 2008 5:44 PM
  • What's wrong with just invalidating the Panel only?  Panels have Invalidate() methods too! :D
    David Morton - http://blog.davemorton.net/
    Thursday, August 28, 2008 5:48 PM
  • That's actually what I am doing above--I am running this.Invalidate() form the panel event handler.  The problem is that the panel event handler doesn't receive any events after the menu gets done clearing itself from the screen.
    Thursday, August 28, 2008 5:50 PM
  • The panel event handler is simply a method within your Form1 class.  "this" still refers to Form1.  Try panel1.Invalidate(true) instead.


    David Morton - http://blog.davemorton.net/
    Thursday, August 28, 2008 5:51 PM
  • Same flicker madness--except the lines aren't even appearing!  What confuses me is that panel1.Invalidate(true) should be sending an event to the child controls of the panel (since I am executing panel1.Invalidate(true) form the panel's event handler).  Wouldn't it make more sense to be doing this.Invalidate(true) form the Form's event handler?
    Thursday, August 28, 2008 6:01 PM
  • You could call "this" from any method within a class.  "this" always refers to the currently instantiated version of the class in which "this" resides, regardless of whether the method it is called from is an event handler or a regular method of some other kind.  It may make more sense to invalidate the individual controls instead of invalidating the whole form. 

    You could have another method of accomplishing this task.  Perhaps drawing an image instead, and inserting it into a picturebox, then .NET will handle the repainting for you.
    David Morton - http://blog.davemorton.net/
    Thursday, August 28, 2008 6:04 PM
  • Over at http://www.tech-archive.net/Archive/PocketPC/microsoft.public.pocketpc.developer/2007-11/msg00179.html there is a discussion of a similar problem--and it appears that "For some reason a panel is not a first class citizen when it comes to repainting. If you paint your own graphics into a panel and expect the panel to refresh after dialogs and menus are dismissed - you will be disappointed."

    It sounds like the problem is that Panel isn't the right solution.  I guess I will try your PictureBox approach instead.
    Thursday, August 28, 2008 6:06 PM
  • Very interesting.  If I put an event handler in for the menu, and have it execute panel1.Invalidate(), as soon as I mouse over the menu, boom, everything refreshes.  I need to hunt around and see if there's an event associated with menu for the situation where it is "done messing with the display."
    Thursday, August 28, 2008 6:23 PM
  • Got it!  Add an event driver for the menu, VisibleChanged property.  Then panel1.Invalidate() does the trick.
    Thursday, August 28, 2008 6:44 PM
  • You also want the menu event drivers for DropDownClosed and Paint to invalidate the panel as well.
    Thursday, August 28, 2008 7:24 PM
  • Your panel1_paint_1() event handler is definitely wrong.  It has no business calling base.OnPaint(), nor this.Invalidate().  It should only busy itself with painting the panel.
    Hans Passant.
    Friday, August 29, 2008 12:27 PM