Click here to Skip to main content
16,018,394 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
In WinForms, I am attempting to create a 250x250 px main panel that is filled with 50x50 px subpanels. When a subpanel is clicked, I would like a MouseClick event handler to be executed. In the final version, the each subpanel will be processed depending on the contents of its Tag property (not shown here).

Although the main panel and subpanels are correctly drawn, when a subpanel is clicked, the MouseClick event does not seem to fire.

The code is
C#
// **************************************** patterns_PAN_Paint

void patterns_PAN_Paint ( object           sender, 
                          PaintEventArgs   e )
    {
    SolidBrush  back_brush = new SolidBrush ( 
                                     Color.LightSkyBlue );
    SolidBrush  black_brush = new SolidBrush ( Color.Black );
    Graphics    g = e.Graphics;
    SolidBrush  marker_brush = new SolidBrush ( Color.Gold );
    Panel       p = ( Panel ) sender;
    int         x = 0;
    int         y = 0;

    p.Controls.Clear ( );
    g.FillRectangle( back_brush, p.DisplayRectangle );

    for ( int i = 0; ( i < 5 ); i++ )
        {
        for ( int j = 0; ( j < 5 ); j++ )
            {
            Panel       panel = new Panel ( )
                            {
                            Location = new Point ( x, y ),
                            Size = new Size ( 50, 50 ),
                            };
            Rectangle   rectangle = new Rectangle 
                            {
                            Location = new Point ( x, y ),
                            Size = new Size ( 50, 50 ),
                            };

            g.FillRectangle ( black_brush, rectangle );
            rectangle.Inflate ( -1, -1 );
            g.FillRectangle ( back_brush, rectangle );
            rectangle.Inflate ( -5, -5 );
            g.FillEllipse ( marker_brush, rectangle );

            panel.MouseClick += 
                new MouseEventHandler ( panel_MouseClick );
                
            x += 50;
            }
        x = 0;
        y += 50;
        }
    
    back_brush.Dispose ( );
    black_brush.Dispose ( );
    marker_brush.Dispose ( );
    
    } // patterns_PAN_Paint

// ****************************************** panel_MouseClick

void panel_MouseClick ( object          sender, 
                        MouseEventArgs  e )
    {
    
    throw new NotImplementedException ( );
    
    } // panel_MouseClick


Note that I am drawing to the Graphics of the main panel (the background) and then creating and drawing each subpanel.

NOTE: This is a .Net v3.5 Framework project.

Any ideas as to why MouseClick is not firing?

What I have tried:

The code above is the current attempt. It produces the graphics of the main and sub panel. However clicking on any subpanel has no effect.
Posted
Updated 6-May-23 21:01pm
v3

Why a Panel control? Wouldn't a PictureBox control be more suitable?

Here is a solution for another question that I gave that does what I think that you are trying to do... The solution is for rendering cells (subpanels) without flickering and to enable them to be identified when clicked.

Create a new project and drop this code in... Then run and click away...

1. Form Designer:
C#
#region Windows Form Designer generated code

/// <summary>
///  Required method for Designer support - do not modify
///  the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
    this.butAdd = new System.Windows.Forms.Button();
    this.butHide = new System.Windows.Forms.Button();
    this.butShow = new System.Windows.Forms.Button();
    this.Canvas = new System.Windows.Forms.PictureBox();
    this.labSelected = new System.Windows.Forms.Label();
    this.label2 = new System.Windows.Forms.Label();
    ((System.ComponentModel.ISupportInitialize)(this.Canvas)).BeginInit();
    this.SuspendLayout();
    // 
    // butAdd
    // 
    this.butAdd.Anchor = ((System.Windows.Forms.AnchorStyles)
        ((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
    this.butAdd.Location = new System.Drawing.Point(676, 6);
    this.butAdd.Name = "butAdd";
    this.butAdd.Size = new System.Drawing.Size(112, 34);
    this.butAdd.TabIndex = 1;
    this.butAdd.Text = "ADD";
    this.butAdd.UseVisualStyleBackColor = true;
    this.butAdd.Click += new System.EventHandler(this.OnAddClick);
    // 
    // butHide
    // 
    this.butHide.Anchor = ((System.Windows.Forms.AnchorStyles)
        ((System.Windows.Forms.AnchorStyles.Top |
          System.Windows.Forms.AnchorStyles.Right)));
    this.butHide.Location = new System.Drawing.Point(676, 46);
    this.butHide.Name = "butHide";
    this.butHide.Size = new System.Drawing.Size(112, 34);
    this.butHide.TabIndex = 1;
    this.butHide.Text = "HIDE";
    this.butHide.UseVisualStyleBackColor = true;
    this.butHide.Click += new System.EventHandler(this.OnHideClick);
    // 
    // butShow
    // 
    this.butShow.Anchor = ((System.Windows.Forms.AnchorStyles)
        ((System.Windows.Forms.AnchorStyles.Top |
          System.Windows.Forms.AnchorStyles.Right)));
    this.butShow.Location = new System.Drawing.Point(676, 86);
    this.butShow.Name = "butShow";
    this.butShow.Size = new System.Drawing.Size(112, 34);
    this.butShow.TabIndex = 1;
    this.butShow.Text = "SHOW";
    this.butShow.UseVisualStyleBackColor = true;
    this.butShow.Click += new System.EventHandler(this.OnShowClick);
    // 
    // Canvas
    // 
    this.Canvas.Anchor = ((System.Windows.Forms.AnchorStyles)
        ((((System.Windows.Forms.AnchorStyles.Top |
            System.Windows.Forms.AnchorStyles.Bottom) |
            System.Windows.Forms.AnchorStyles.Left) |
            System.Windows.Forms.AnchorStyles.Right)));
    this.Canvas.BackColor = System.Drawing.SystemColors.ControlDark;
    this.Canvas.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
    this.Canvas.Location = new System.Drawing.Point(12, 12);
    this.Canvas.Name = "Canvas";
    this.Canvas.Size = new System.Drawing.Size(646, 426);
    this.Canvas.TabIndex = 2;
    this.Canvas.TabStop = false;
    // 
    // labSelected
    // 
    this.labSelected.Anchor = ((System.Windows.Forms.AnchorStyles)
        ((System.Windows.Forms.AnchorStyles.Bottom |
          System.Windows.Forms.AnchorStyles.Right)));
    this.labSelected.Location = new System.Drawing.Point(676, 400);
    this.labSelected.Name = "labSelected";
    this.labSelected.Size = new System.Drawing.Size(121, 38);
    this.labSelected.TabIndex = 3;
    this.labSelected.Text = "None";
    this.labSelected.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
    // 
    // label2
    // 
    this.label2.Anchor = ((System.Windows.Forms.AnchorStyles)
        ((System.Windows.Forms.AnchorStyles.Bottom |
          System.Windows.Forms.AnchorStyles.Right)));
    this.label2.Font = new System.Drawing.Font("Segoe UI", 9F,
        System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
    this.label2.Location = new System.Drawing.Point(664, 368);
    this.label2.Name = "label2";
    this.label2.Size = new System.Drawing.Size(133, 32);
    this.label2.TabIndex = 4;
    this.label2.Text = "Selected Cell:";
    this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
    // 
    // Form1
    // 
    this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 25F);
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    this.ClientSize = new System.Drawing.Size(800, 450);
    this.Controls.Add(this.label2);
    this.Controls.Add(this.labSelected);
    this.Controls.Add(this.Canvas);
    this.Controls.Add(this.butShow);
    this.Controls.Add(this.butHide);
    this.Controls.Add(this.butAdd);
    this.Name = "Form1";
    this.Text = "Form1";
    ((System.ComponentModel.ISupportInitialize)(this.Canvas)).EndInit();
    this.ResumeLayout(false);

}

#endregion
private Button butAdd;
private Button butHide;
private Button butShow;
private PictureBox Canvas;
private Label labSelected;
private Label label2;
}

2. Form code-behind
C#
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        this.Resize += Form1_Resize;
        
        Canvas.Paint += Canvas_Paint;
        Canvas.MouseClick += Canvas_MouseClick;
 
        Canvas.SetDoubleBuffered();
        Canvas.BackColor = Color.Gray;
        Canvas.BorderStyle = BorderStyle.FixedSingle;
        Canvas.Cursor = Cursors.Hand;
    }

    private bool showCells = true;

    private record Cell(int Row, int Column, string Text, bool Visible = true);

    private readonly List<Cell> cells = new();

    private readonly float cellWidth = 50f;
    private readonly float cellHeight = 50f;
    private readonly Font cellFont =  new Font( "Time New Roman", 8, FontStyle.Bold );

    private readonly StringFormat cellTextFormat = new StringFormat()
    {
        LineAlignment = StringAlignment.Center,
        Alignment = StringAlignment.Center
    };

    private readonly Color cellBackColor = Color.White;
    private readonly Color cellForeColor = Color.Black;
    private readonly Color cellBorderColor = Color.Black;

    private void Form1_Resize(object? sender, EventArgs e)
    {
        //Canvas.Invalidate();
    }

    private void Canvas_MouseClick(object? sender, MouseEventArgs e)
    {
        if (showCells)
            labSelected.Text =
                $@"{Math.Floor(e.X / cellWidth):N0},{Math.Floor(e.Y / cellHeight):N0}";
    }

    private void Canvas_Paint(object? sender, PaintEventArgs e)
    {
        Graphics gr = e.Graphics;

        foreach (Cell cell in cells)
        {
            RectangleF rect =
                new RectangleF(cell.Column * cellWidth,
                               cell.Row * cellHeight, cellWidth, cellHeight);
        
            // background
            using SolidBrush backBrush = new SolidBrush(cellBackColor);
            gr.FillRectangle(backBrush, rect);

            // are we hiding the cells?
            if (!showCells)
                continue;

            // border
            using Pen pen = new Pen(cellBorderColor, 1);
            pen.Alignment = PenAlignment.Outset;
            gr.DrawRectangle(pen, rect);

            // text
            using SolidBrush foreBrush = new SolidBrush(cellForeColor);
            gr.DrawString(cell.Text, cellFont, foreBrush, rect, cellTextFormat);
        }
    }

    private void OnAddClick(object sender, EventArgs e)
    {
        cells.Clear();
        
        FillCells();

        showCells = true;
        Canvas.Invalidate();
    }

    private void OnShowClick(object sender, EventArgs e)
    {
        if (showCells) return;

        showCells = true;
        Canvas.Invalidate();
    }

    private void OnHideClick(object sender, EventArgs e)
    {
        if (!showCells) return;

        showCells = false;
        Canvas.Invalidate();

        labSelected.Text = "None";
    }

    void FillCells()
    {
        int nLabelNum = 10;

        for (int j = 0; j < nLabelNum; j++)
        for (int i = 0; i < nLabelNum; i++)
            cells.Add(new Cell(j, i, $"{i} | {j}"));
    }
}

It has 3 buttons, Add, Hide, Show. Press Add to render the clickable grid. The other buttons are self explanatory. There is also a label to display which cell was clicked.


UPDATE

Here is a version that corrects your use of dynamic panels. I've also added a mock paint event to show that the HostPanel can be painted behind the child controls (panels). What you want to render is up to you...

C#
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        InitializeChildPanels();

        HostPanel.Paint += OnPaint;
        this.Closing += OnClosing;

        HostPanel.Invalidate();
    }

    private void OnPaint(object sender, PaintEventArgs e)
    {
        _thread = new Thread(() =>
        {
            Thread.Sleep(500);
            HostPanel.Invoke((MethodInvoker)delegate
            {
                HostPanel.BackColor = _colors[_colorIndex];
                _colorIndex = _colorIndex < _colors.Length - 1 ?
                    _colorIndex + 1 : 0;
            });
        });

        _thread.Start();
    }

    private Thread _thread;

    private Color[] _colors = new Color[]
    {
        Color.Red,
        Color.Green,
        Color.Yellow,
        Color.Blue, 
        Color.MediumPurple,
        Color.White,
        Color.Gray, 
    };

    private int _colorIndex = 0;

    private void InitializeChildPanels()
    {
        int y = 0;

        for (int i = 0; i < 5; i++)
        {
            int x = 0;

            for (int j = 0; j < 5; j++)
            {
                Point point = new Point(x, y);
                Panel panel = new Panel()
                {
                    Location = point,
                    Size = new Size(50, 50),
                    BackColor = Color.Red,
                    BorderStyle = BorderStyle.FixedSingle,
                    Tag = point
                };
                panel.MouseClick += OnMouseClick;
                HostPanel.Controls.Add(panel);
                x += 50;
            }

            y += 50;
        }
    }

    private void OnMouseClick(object sender, MouseEventArgs e)
    {
        Panel panel = (Panel)sender;
        Point point = (Point)panel.Tag;
        Debug.WriteLine($"Index: {HostPanel.Controls.IndexOf(panel)} | Location: X:{point.X} Y:{point.Y} was clicked");
    }

    // cleanup to avoid memory leaks
    private void OnClosing(object sender, CancelEventArgs e)
    {
        _thread.Abort();
        _thread = null;

        Closing -= OnClosing;
        HostPanel.Paint -= OnPaint;
        foreach (Panel panel in HostPanel.Controls)
        {
            panel.MouseClick -= OnMouseClick;
        }
    }
}


I still prefer the PictureBox (first) method.

Enjoy!
 
Share this answer
 
v5
Comments
gggustafson 5-May-23 23:32pm    
Unfortunately I did not mention that I am using VS 2008 and Framework 3.5. Your solution attaches the event handlers to buttons. I need to attach a single event handler to the subpanels.

Thanks for your thoughts.
Graeme_Grant 6-May-23 0:16am    
That works with 3.5, just need to un-modernise the code. 3.5 is over 11 years ago... If you look at the form designer code generated, the control events are hooked up exactly the same, I'm just doing it in code-behind so that it is not hidden.

And yes, I'm not using individual panels as that is overkill. This will do the same thing more efficiently.

The issue that you have is that the Paint triggers many times, so you are constantly adding new panels to the host control. Eventually you will exhaust your resources. Whilst you are creating panels (subPanels), and hooking the Mouse event, you're not actually adding the newly created panels to a parent control. The event handler is holding onto the Panel and not letting the GC to clean it up - ie: a memory leak.

Do seriously have a look at the solution provided. It is very fast and memory efficient and does what you need.

UPDATE: I have added a panel version to the solution above that was built using v3.5 Framework.
Graeme_Grant 7-May-23 1:46am    
How did you go? Was the second/update more inline with what you were trying to do?
gggustafson 7-May-23 10:36am    
As a matter of fact, I was about to give you a 5 and mark your solution as the accepted one.

Thank you for your assistance.
Graeme_Grant 7-May-23 18:07pm    
Glad to hear that it helped!
The mouse clicks will work when you do it like this:
panel.MouseClick += new MouseEventHandler(panel_MouseClick);
patterns_PAN.Controls.Add(panel);

Problem with this solution is that the main panel graphics are not visible anymore as it is overdrawn by the individual panel graphics.
Here is an alternative:
using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;

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

        private void FillPanel()
        {
            int x = 0;
            int y = 0;

            for (int i = 0; (i < 5); i++)
            {
                for (int j = 0; (j < 5); j++)
                {
                    Panel panel = new Panel()
                    {
                        Location = new Point(x, y),
                        Size = new Size(50, 50),
                    };

                    panel.MouseClick += new MouseEventHandler(panel_MouseClick);
                    panel.Paint += PanelPaint;
                    patterns_PAN.Controls.Add(panel);
                    x += 50;
                }

                x = 0;
                y += 50;
            }
        }

        void PanelPaint(object sender, PaintEventArgs e)
        {
            SolidBrush back_brush = new SolidBrush(Color.LightSkyBlue);
            SolidBrush black_brush = new SolidBrush(Color.Black);
            Graphics g = e.Graphics;
            SolidBrush marker_brush = new SolidBrush(Color.Gold);
            Panel p = (Panel)sender;

            g.FillRectangle(back_brush, p.DisplayRectangle);

            Rectangle rectangle = new Rectangle
            {
                Location = new Point(0, 0),
                Size = new Size(50, 50),
            };

            g.FillRectangle(black_brush, rectangle);
            rectangle.Inflate(-1, -1);
            g.FillRectangle(back_brush, rectangle);
            rectangle.Inflate(-5, -5);
            g.FillEllipse(marker_brush, rectangle);

            back_brush.Dispose();
            black_brush.Dispose();
            marker_brush.Dispose();
        }

        void panel_MouseClick(object sender, MouseEventArgs e)
        {
            Debug.Print("panel_MouseClick()");
        }
    }
}
 
Share this answer
 
v3

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900