Reputation: 6778
I have a class that derives from UserControl
(below). The user can have more than one instance of this control open at a time.
I want to be able to detect when the user switches from one to the other, i.e. has clicked on one and then clicks on another.
OnEnter
and OnLoad
get called only when first displaying the control
OnLeave
is never called.
OnGotFocus
and OnLostFocus
are never called but apparently their use is discouraged.
What do I need to do?
public partial class MyView : System.Windows.Forms.UserControl
{
// Before `OnLoad`
// WM_NCCREATE, WM_NCCALCSIZE WM_CREATE, WM_SIZE, WM_MOVE,WM_REFLECT, WM_SHOWWINDOW, WM_PARENTNOTIFY
// After `OnLoad`, Before `OnEnter`
// WM_WINDOWPOSCHANGING, WM_NCCALCSIZE, WM_WINDOWPOSCHANGED, WM_SIZE, WM_GETTEXTLENGTH, WM_GETTEXT, (WM_WINDOWPOSCHANGING, WM_NCCALCSIZE, WM_WINDOWPOSCHANGED, WM_SIZE, WM_CHILDACTIVATE, WM_MOVE, WM_SHOWWINDOW
private const int WM_ACTIVATE = 0x006;
protected override void WndProc(ref Message message)
{
if (message.Msg == WM_ACTIVATE)
{
System.Diagnostics.Trace.WriteLine("MyView.WndProc(): message: " + message);
}
base.WndProc(ref message);
}
// called before OnEnter
protected override void OnLoad(System.EventArgs eventArgs)
{
base.OnLoad(eventArgs);
Form form1 = this.FindForm(); // null
Form parentForm = this.ParentForm; // null
}
// called only on first display, not when the user switches between the 2 controls
protected override void OnEnter(EventArgs eventArgs)
{
base.OnEnter(eventArgs);
}
protected override void OnLeave(EventArgs eventArgs)
{
base.OnLeave(eventArgs);
}
// Never called
protected override void OnGotFocus(EventArgs eventArgs)
{
base.OnGotFocus(eventArgs);
}
// Never called
protected override void OnLostFocus(EventArgs eventArgs)
{
base.OnGotFocus(eventArgs);
}
}
Upvotes: 0
Views: 122
Reputation: 30464
Requirement: "I want to be able to detect when the operator ...has clicked on one and then clicks on another."
Apparently you want to get notified if the control is clicked, or on any of its child controls. For this you'll have to subscribe to the Click events of all your child controls.
protected override void OnControlAdded(ControlEventArgs e)
{
// subscribe to click event of e.Control
e.Control.Click += OnChildClicked;
}
// TODO: de-subscribe in OnControlRemoved
public void OnChildClicked(object Sender, EventArgs e)
{
// Clicked on child control, act as if Clicked on me
this.OnClick(e);
}
The click event will be called by any click-like event, even if it is done by the keyboard, for instance by pressing the enter key.
You wrote you wanted mouse clicks. If you really want only mouse clicks, use the MouseClick event. If an operator uses TAB to select one of your child controls and presses ENTER to click it, you won't get notified.
Upvotes: 1
Reputation: 1552
This is the complete source code the for the user derived control. It works as is.
On the form, I created two instances of the control and can click on them. As I do, the squares switch from red to blue.
You should be able to set breakpoints in the OnGotFocus and OnLostFocus methods. The invalidates are there to force a repaint.
The magic is that there are two controls (as you have also) but in addition, the OnButtonclicked override sets the focus. I believe this is the part that's missing from your example.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace DesktopApp1
{
public partial class MyView : System.Windows.Forms.UserControl
{
protected Color SelectedColor { get; set; } = Color.Red;
protected Color NormalColor { get; set; } = Color.Blue;
protected override void OnPaint(PaintEventArgs e)
{
using (SolidBrush blueBrush = new SolidBrush(this.Focused?SelectedColor:NormalColor))
using (Pen blackPen = new Pen(Color.Black, 3))
{
e.Graphics.FillRectangle(blueBrush, ClientRectangle);
Rectangle inset = new Rectangle(this.ClientRectangle.X + 1, this.ClientRectangle.Y + 1, this.ClientRectangle.Width -3 , this.ClientRectangle.Height - 3);
e.Graphics.DrawRectangle(blackPen, inset);
}
base.OnPaint(e);
}
private void OnButton1Clicked(object sender, EventArgs e)
{
this.Select();
}
protected override void OnGotFocus(EventArgs e)
{
Invalidate();
base.OnGotFocus(e);
}
protected override void OnLostFocus(EventArgs e)
{
base.OnLostFocus(e);
Invalidate();
}
}
}
To complete the answer here is the form1.Designer.cs
namespace DesktopApp1
{
partial class Form1
{
/// <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.myView2 = new DesktopApp1.MyView();
this.myView1 = new DesktopApp1.MyView();
this.SuspendLayout();
//
// myView2
//
this.myView2.Location = new System.Drawing.Point(189, 12);
this.myView2.Name = "myView2";
this.myView2.Size = new System.Drawing.Size(150, 150);
this.myView2.TabIndex = 1;
//
// myView1
//
this.myView1.Location = new System.Drawing.Point(9, 12);
this.myView1.Name = "myView1";
this.myView1.Size = new System.Drawing.Size(150, 150);
this.myView1.TabIndex = 0;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(355, 175);
this.Controls.Add(this.myView2);
this.Controls.Add(this.myView1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
private MyView myView1;
private MyView myView2;
}
}
Upvotes: 1