Forms.Panel and the KeyUp event (message)

  • Thread starter Thread starter sklett
  • Start date Start date
S

sklett

I know that Panel (and most of it's derivitives) don't raise keyboard
events. I *really* need to catch keyboard events though so I've been
googling the topic and have found quite a few suggestions. The one
suggestion I've found that makes the most sense isn't working for me and
after this most recent failure I'm really at a loss! ;0)

I'm overriding WndProc and checking the Message.Msg property for the
WM_KEYUP message.

If anyone has any ideas for me I would really appreciate it!


Here is a complete application that shows the failure.

<code>
using System;
using System.Collections.Generic;
using System.Windows.Forms;

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

private MyPanel panel1;

/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be
disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#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.panel1 = new MyPanel();
this.SuspendLayout();
//
// panel1
//
this.panel1.Location = new System.Drawing.Point(46, 49);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(200, 100);
this.panel1.TabIndex = 0;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.panel1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}

#endregion
}

static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}

public class MyPanel : Panel
{
const int WM_KEYUP = 0x0101;

protected override void WndProc(ref Message m)
{
if (m.Msg == WM_KEYUP)
{
Console.WriteLine("KeyUp message caught");
}

base.WndProc(ref m);
}
}
}
</code>
 
Hi,

Actually, you can trap key events in a panel just fine, but the problem is
the panel doesn't focus easily. One way to focus on a panel is to handle the
MouseDown event and set focus there.

Try this

public class MyPanel : Panel
{
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
}

protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);

Focus();
}
}


--
Happy Coding!
Morten Wennevik [C# MVP]


sklett said:
I know that Panel (and most of it's derivitives) don't raise keyboard
events. I *really* need to catch keyboard events though so I've been
googling the topic and have found quite a few suggestions. The one
suggestion I've found that makes the most sense isn't working for me and
after this most recent failure I'm really at a loss! ;0)

I'm overriding WndProc and checking the Message.Msg property for the
WM_KEYUP message.

If anyone has any ideas for me I would really appreciate it!


Here is a complete application that shows the failure.

<code>
using System;
using System.Collections.Generic;
using System.Windows.Forms;

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

private MyPanel panel1;

/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be
disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#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.panel1 = new MyPanel();
this.SuspendLayout();
//
// panel1
//
this.panel1.Location = new System.Drawing.Point(46, 49);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(200, 100);
this.panel1.TabIndex = 0;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.panel1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}

#endregion
}

static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}

public class MyPanel : Panel
{
const int WM_KEYUP = 0x0101;

protected override void WndProc(ref Message m)
{
if (m.Msg == WM_KEYUP)
{
Console.WriteLine("KeyUp message caught");
}

base.WndProc(ref m);
}
}
}
</code>
 
Hi Morten,

Thank you for the reply! I hadn't thought about the focus issue, I thought
that all messages would run the WndProc regardless of focus state. I will
give your suggestion a try. I'm not sure what effect it will have on the
operation of my control.

You seem to really know your WinForm and Control stuff from posts I've read
by you. If you don't mind I'd like to describe the control I have and the
issue I'm trying to solve.

The purpose of the control is to allow my user's to select pages from a
document, it's similar in function to the "thumbnails" in acrobat reader.

I have implemented the control as a derived FlowLayoutPanel that I populate
with CheckBox controls. Each checkbox control has it's Image property set
to one of the pages in my source image[].

It works great, fulfills the use case, quick, etc, etc.

I'm always trying to tweak my user interfaces, especially data entry
applications, so the user doesn't feel the urge to grab the mouse and click.
With my page selection control they tend to use the mouse to click on the
thumbnails. *I* know it's possible to tab and spacebar to select the
checkboxes, etc, etc but for my users that is a bit too awkward.

My plan was to handle the numeric key presses (1-9) to select the
corresponding pages. This is limited in that it will only support 9 pages,
but for 99% of the time this will be fine.

As my control is essentially a FlowLayoutpanel with child controls it seems
I need to handle the key presses on the FlowLayoutPanel.

In your opinion, am I on the right track?

I'm concerned that if I set focus to the *Panel programmatically (per your
suggestion) then if a user clicks on a checkbox control with the mouse the
focus will be removed from the FlowLayoutPanel. Then subsequent 1-9 presses
won't be caught by the Panel.

I will try it and see what happens, I just wanted to run the design my you
for any thoughts you might have or suggestions.

Thanks again,
Steve


Morten Wennevik said:
Hi,

Actually, you can trap key events in a panel just fine, but the problem is
the panel doesn't focus easily. One way to focus on a panel is to handle
the
MouseDown event and set focus there.

Try this

public class MyPanel : Panel
{
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
}

protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);

Focus();
}
}


--
Happy Coding!
Morten Wennevik [C# MVP]


sklett said:
I know that Panel (and most of it's derivitives) don't raise keyboard
events. I *really* need to catch keyboard events though so I've been
googling the topic and have found quite a few suggestions. The one
suggestion I've found that makes the most sense isn't working for me and
after this most recent failure I'm really at a loss! ;0)

I'm overriding WndProc and checking the Message.Msg property for the
WM_KEYUP message.

If anyone has any ideas for me I would really appreciate it!


Here is a complete application that shows the failure.

<code>
using System;
using System.Collections.Generic;
using System.Windows.Forms;

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

private MyPanel panel1;

/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be
disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#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.panel1 = new MyPanel();
this.SuspendLayout();
//
// panel1
//
this.panel1.Location = new System.Drawing.Point(46, 49);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(200, 100);
this.panel1.TabIndex = 0;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.panel1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}

#endregion
}

static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}

public class MyPanel : Panel
{
const int WM_KEYUP = 0x0101;

protected override void WndProc(ref Message m)
{
if (m.Msg == WM_KEYUP)
{
Console.WriteLine("KeyUp message caught");
}

base.WndProc(ref m);
}
}
}
</code>
 
I'm concerned that if I set focus to the *Panel programmatically (per your
suggestion) then if a user clicks on a checkbox control with the mouse the
focus will be removed from the FlowLayoutPanel. Then subsequent 1-9
presses won't be caught by the Panel.

Turns out this is indeed the case. When my control is shown, I click in the
background space of the FlowLayouPanel which fires the mouse down event and
I set focus. I can then press the numeric keys and everything works great.

If I then click on a child control of the FlowLayoutPanel the focus is lost
and my numeric key events are not handled. I understand why this is
happening, I just don't understand what I can do about it.

I can imagine some hacks that might work but I'm not happy with any of them.
This requirement of mine is something I will need to have in other controls
I plan to create. It's really surprising to me that a Panel doesn't get all
messages! ;0(

If anyone can think of a solution to get my events or messages to always
make it to my FlowLayoutPanel I would really appreciate it.

My ideas for possible solutions are:

1) Anonymous handlers added to each child control that is created. Maybe
for the click event, keyDown, ?? - this handler would first service the
child controls event then set focus to the parent (FlowLayoutPanel). This
is nasty and limited, for example, if I decided to add a control that had
it's own nested control this solution would fail. I could recurse up the
tree until I find the FLP but this is just more nastiness.

I feel like I'm leaving a more elegant solution behind, there must be a way
to do this.

2) .... actually that's all I have right now ;0)

-Steve
 
Hi Steve,

The easiest way to prevent the checkboxes stealing focus is handling the
GotFocus or Enter event and then programmatically set focus back to the
panel.

In your panel try adding this code

protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);

e.Control.Enter += new EventHandler(Control_Enter);
}

void Control_Enter(object sender, EventArgs e)
{
Focus();
}

If you need to perform some action against the selected 'page' you can just
use the sender reference to determine which page was clicked.

You probably need to
 
Morten Wennevik said:
Hi Steve,

The easiest way to prevent the checkboxes stealing focus is handling the
GotFocus or Enter event and then programmatically set focus back to the
panel.

In your panel try adding this code

protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);

e.Control.Enter += new EventHandler(Control_Enter);
}

void Control_Enter(object sender, EventArgs e)
{
Focus();
}

If you need to perform some action against the selected 'page' you can
just
use the sender reference to determine which page was clicked.

You probably need to

Hi Morten,

Thanks again for the continued help! Your suggestion worked very well. I
needed to add a call to Focus() in my control's parent OnShown event and now
it works as I would like it to.

I'm now that much closer to keeping my user's hands OFF the mouse! ;0)

-Steve
 
Back
Top