I have a UserControl that has inside some other controls and then a Panel that I use to paint custom shapes on mouse events. I would like to add some other functionalities but for that I would need to detect KeyDown and KeyUp while the mouse is drawing shapes on the Panel (in this situation UserControl.KeyDown/KeyUp never fire). How can be this achieved?
EDIT
Here is a minimum version for the UserControl where the problem can be seen:
public partial class UserControl1 : UserControl
{
TextBox textBox1;
Panel panel1;
public UserControl1()
{
InitializeComponent();
textBox1 = new System.Windows.Forms.TextBox();
textBox1.Location = new System.Drawing.Point(0, 0);
this.Controls.Add(this.textBox1);
panel1 = new System.Windows.Forms.Panel();
panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
panel1.Location = new System.Drawing.Point(0, 25);
panel1.Size = new System.Drawing.Size(300, 150);
panel1.MouseEnter += new System.EventHandler(this.panel1_MouseEnter);
this.Controls.Add(this.panel1);
this.Size = new System.Drawing.Size(300, 200);
this.KeyDown += UserControl1_KeyDown;
}
private void panel1_MouseEnter(object sender, EventArgs e)
{
panel1.Focus();
}
void UserControl1_KeyDown(object sender, KeyEventArgs e)
{
MessageBox.Show("Key down");
}
}
When this control is inside a Form the "Key down" message is never displayed.
I took the code from Hans Passant here (great hint, many thanks!) and I modified it to suit my needs as follow:
class SelectablePanel : Panel
{
public SelectablePanel()
{
this.SetStyle(ControlStyles.Selectable, true);
this.TabStop = true;
}
protected override bool IsInputKey(Keys keyData)
{
if (keyData == Keys.Up || keyData == Keys.Down) return true;
if (keyData == Keys.Left || keyData == Keys.Right) return true;
return base.IsInputKey(keyData);
}
protected override void OnKeyDown(KeyEventArgs e)
{
var handler = KeyDown;
if (handler != null) handler(this, e);
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
var handler = KeyPress;
if (handler != null) handler(this, e);
}
protected override void OnKeyUp(KeyEventArgs e)
{
var handler = KeyUp;
if (handler != null) handler(this, e);
}
protected override void OnMouseEnter(EventArgs e)
{
this.Focus();
base.OnMouseEnter(e);
}
protected override void OnEnter(EventArgs e)
{
this.Invalidate();
base.OnEnter(e);
}
protected override void OnLeave(EventArgs e)
{
this.Invalidate();
base.OnLeave(e);
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
if (this.Focused)
{
var rc = this.ClientRectangle;
rc.Inflate(-2, -2);
ControlPaint.DrawFocusRectangle(pe.Graphics, rc);
}
}
[Browsable(true)]
public new event EventHandler<KeyEventArgs> KeyDown;
[Browsable(true)]
public new event EventHandler<KeyEventArgs> KeyUp;
[Browsable(true)]
public new event EventHandler<KeyPressEventArgs> KeyPress;
}
Finally I modified my original code like this:
public partial class UserControl1 : UserControl
{
TextBox textBox1;
SelectablePanel selectablePanel;
public UserControl1()
{
InitializeComponent();
textBox1 = new System.Windows.Forms.TextBox();
textBox1.Location = new System.Drawing.Point(0, 0);
this.Controls.Add(this.textBox1);
selectablePanel = new SelectablePanel();
selectablePanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
selectablePanel.Location = new System.Drawing.Point(0, 25);
selectablePanel.Size = new System.Drawing.Size(300, 150);
selectablePanel.KeyDown += panel1_KeyDown;
this.Controls.Add(this.selectablePanel);
this.Size = new System.Drawing.Size(300, 200);
}
void panel1_KeyDown(object sender, KeyEventArgs e)
{
MessageBox.Show("Key down");
}
}
and now it works for what I need.
Related
I am trying to make an app to generate a tierlist.
Each tier consists of a flowlayout and you basically move the controls around by drag-and-drop.
The problem is that even though I can move the elements with drag-and-drop, the element is always added to the end of the list, and not where you drop the pointer.
Is there any way to maintain order during drag-and-drop?
These are the methods that are in the flowlayout
private void flow_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void flow_DragDrop(object sender, DragEventArgs e)
{
((UserControl1)e.Data.GetData(typeof(UserControl1))).Parent = (Panel)sender;
}
While these are from the usercontrol:
private void UserControl1_MouseDown(object sender, MouseEventArgs e)
{
this.DoDragDrop(this, DragDropEffects.Move);
}
You can opt this solution to reorder the dropped controls into a FlowLayoutPanel control. The part that utilizes the Control.ControlCollection.GetChildIndex and Control.ControlCollection.SetChildIndex methods.
Let's say you have a custom Control or UserControl named DragDropControl:
public class DragDropControl : Control
{
public DragDropControl()
{
AllowDrop = true;
BackColor = Color.LightSteelBlue;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
TextRenderer.DrawText(e.Graphics, Text, Font,
ClientRectangle, Color.Black,
TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter);
}
}
Note: From what I see in the images, just use a simple Label control instead.
Let's create a custom FlowLayoutPanel and encapsulate all the required functionalities to not repeat that in your implementation for each FLP.
public class DragDropFlowLayoutPanel : FlowLayoutPanel
{
public DragDropFlowLayoutPanel()
{
AllowDrop = true;
}
[DefaultValue(true)]
public override bool AllowDrop
{
get => base.AllowDrop;
set => base.AllowDrop = value;
}
The custom FLP implements the mouse, drag and drop events of its children as well.
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
if (e.Control is DragDropControl)
{
e.Control.DragOver += OnControlDragOver;
e.Control.DragDrop += OnControlDragDrop;
e.Control.MouseDown += OnControlMouseDown;
}
}
protected override void OnControlRemoved(ControlEventArgs e)
{
base.OnControlRemoved(e);
e.Control.DragOver -= OnControlDragOver;
e.Control.DragDrop -= OnControlDragDrop;
e.Control.MouseDown -= OnControlMouseDown;
}
Handle the child controls MouseDown event to do the drag:
private void OnControlMouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var control = sender as DragDropControl;
DoDragDrop(control, DragDropEffects.Move);
}
}
Handle the DragEnter and DragOver methods and events of the FLP and its children to set the drop effect.
protected override void OnDragEnter(DragEventArgs e)
{
base.OnDragEnter(e);
if (e.Data.GetDataPresent(typeof(DragDropControl)))
e.Effect = DragDropEffects.Move;
}
protected override void OnDragOver(DragEventArgs e)
{
base.OnDragOver(e);
if (e.Data.GetDataPresent(typeof(DragDropControl)))
e.Effect = DragDropEffects.Move;
}
private void OnControlDragOver(object sender, DragEventArgs e)
{
if (e.Data.GetData(typeof(DragDropControl)) is DragDropControl ddc)
{
var p = PointToClient(new Point(e.X, e.Y));
if (GetChildAtPoint(p) == ddc)
e.Effect = DragDropEffects.None;
else
e.Effect = DragDropEffects.Move;
}
}
Finally, call from the DragDrop method override and event the DropControl method and pass the DragEventArgs param.
protected override void OnDragDrop(DragEventArgs e)
{
base.OnDragDrop(e);
DropControl(e);
}
private void OnControlDragDrop(object sender, DragEventArgs e)
{
DropControl(e);
}
The dropped control takes the index of the control under the mouse position if any otherwise it will be inserted at the end of the Controls collection.
private void DropControl(DragEventArgs e)
{
if (e.Data.GetData(typeof(DragDropControl)) is DragDropControl ddc)
{
var p = PointToClient(new Point(e.X, e.Y));
var child = GetChildAtPoint(p);
var index = child == null
? Controls.Count
: Controls.GetChildIndex(child);
ddc.Parent = this;
Controls.SetChildIndex(ddc, index);
}
}
}
Put it all together.
public class DragDropFlowLayoutPanel : FlowLayoutPanel
{
public DragDropFlowLayoutPanel()
{
AllowDrop = true;
}
[DefaultValue(true)]
public override bool AllowDrop
{
get => base.AllowDrop;
set => base.AllowDrop = value;
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
if (e.Control is DragDropControl)
{
e.Control.DragOver += OnControlDragOver;
e.Control.DragDrop += OnControlDragDrop;
e.Control.MouseDown += OnControlMouseDown;
}
}
protected override void OnControlRemoved(ControlEventArgs e)
{
base.OnControlRemoved(e);
e.Control.DragOver -= OnControlDragOver;
e.Control.DragDrop -= OnControlDragDrop;
e.Control.MouseDown -= OnControlMouseDown;
}
protected override void OnDragEnter(DragEventArgs e)
{
base.OnDragEnter(e);
if (e.Data.GetDataPresent(typeof(DragDropControl)))
e.Effect = DragDropEffects.Move;
}
protected override void OnDragOver(DragEventArgs e)
{
base.OnDragOver(e);
if (e.Data.GetDataPresent(typeof(DragDropControl)))
e.Effect = DragDropEffects.Move;
}
protected override void OnDragDrop(DragEventArgs e)
{
base.OnDragDrop(e);
DropControl(e);
}
private void OnControlDragOver(object sender, DragEventArgs e)
{
if (e.Data.GetData(typeof(DragDropControl)) is DragDropControl ddc)
{
var p = PointToClient(new Point(e.X, e.Y));
if (GetChildAtPoint(p) == ddc)
e.Effect = DragDropEffects.None;
else
e.Effect = DragDropEffects.Move;
}
}
private void OnControlDragDrop(object sender, DragEventArgs e)
{
DropControl(e);
}
private void OnControlMouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var control = sender as DragDropControl;
DoDragDrop(control, DragDropEffects.Move);
}
}
private void DropControl(DragEventArgs e)
{
if (e.Data.GetData(typeof(DragDropControl)) is DragDropControl ddc)
{
var p = PointToClient(new Point(e.X, e.Y));
var child = GetChildAtPoint(p);
var index = child == null
? Controls.Count
: Controls.GetChildIndex(child);
ddc.Parent = this;
Controls.SetChildIndex(ddc, index);
}
}
}
I have a custom component for WinForms, on which graphics are drawn.
Using the Ctrl+right/left mouse buttons, I can add or remove objects.
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.KeyCode == Keys.ControlKey)
this.EditorMode = true;
}
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
if (e.KeyCode == Keys.ControlKey)
this.EditorMode = false;
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (!this.EditorMode)
{
base.OnMouseDown(e);
return;
}
if (e.Button == MouseButtons.Left)
{
// adding new object
}
else if (e.Button == MouseButtons.Right)
{
// deleting object
}
}
Everything works fine until I add something else to the custom control.
The problem is that pressing the Ctrl key will no longer be handled by the controller, but by the element on which the focus is currently set.
And I need my keyboard shortcut to work regardless of which element the focus is on...
What is the best way to do this?
I tried to redefine Processcmdkey, but it does not allow me to know if the key was pressed or released
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == (Keys.ControlKey | Keys.Control))
{
MessageBox.Show("ctrl");
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
what should I do to get the desired result: regardless of focusing on child controls, I can always add new objects to the drawing?
My suggestion is to implement IMessageFilter in your MainForm and intercept
the WM_KEYDOWN message for Control-Left and Control-Right. The example below is a guideline for a UserControl that will add and remove buttons based on Control-Right and Control-Left respectively.
Adding and Removing the MessageFilter
The message filter will be added on the OnHandleCreated override and removed in the Dispose method of MainForm.
public partial class MainForm : Form, IMessageFilter
{
public MainForm() => InitializeComponent();
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if(!(DesignMode ) || _isHandleInitialized)
{
_isHandleInitialized = true; ;
Application.AddMessageFilter(this);
}
}
bool _isHandleInitialized = false;
// In MainForm.Designer.cs
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
Application.RemoveMessageFilter(this);
}
base.Dispose(disposing);
}
}
Handling PreFilterMessage
The MainForm will provide three event hooks for ControlLeft, ControlRight, and NoCommand. These will be fired in the PreFilterMessage method.
public partial class MainForm : Form, IMessageFilter
{
const int WM_KEYDOWN = 0x100;
public bool PreFilterMessage(ref Message m)
{
if (Form.ActiveForm == this)
{
switch (m.Msg)
{
case WM_KEYDOWN:
var key = (Keys)m.WParam | ModifierKeys;
switch (key)
{
case Keys.Control | Keys.Left:
ControlLeft?.Invoke(this, EventArgs.Empty);
Text = "Control.Left";
break;
case Keys.Control | Keys.Right:
ControlRight?.Invoke(this, EventArgs.Empty);
Text = "Control.Right";
break;
default:
// Don't event if it's "just" the Control key
if(ModifierKeys == Keys.None)
{
Text = "Main Form";
NoCommand?.Invoke(this, EventArgs.Empty);
}
break;
}
break;
}
}
return false;
}
public event EventHandler ControlLeft;
public event EventHandler ControlRight;
public event EventHandler NoCommand;
}
Responding to events in the UserControl
The MainForm events will be subscribed to in the OnHandleCreated override of UserControlResponder.
public partial class UserControlResponder : UserControl
{
public UserControlResponder()
{
InitializeComponent();
}
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if(!(DesignMode || _isHandleInitialized))
{
_isHandleInitialized = true;
var main = (MainForm)Parent;
main.ControlLeft += onControlLeft;
main.ControlRight += onControlRight;
main.NoCommand += onNoCommand;
}
}
private bool _isHandleInitialized = false;
char _tstCount = 'A';
private void onControlRight(object sender, EventArgs e)
{
BackColor = Color.LightBlue;
BorderStyle = BorderStyle.None;
var button = new Button
{
Text = $"Button {_tstCount++}",
Size = new Size(150, 50),
};
button.Click += onAnyButtonClick;
flowLayoutPanel.Controls.Add(button);
}
private void onControlLeft(object sender, EventArgs e)
{
BackColor = Color.LightGreen;
BorderStyle = BorderStyle.None;
if(flowLayoutPanel.Controls.Count != 0)
{
var remove = flowLayoutPanel.Controls[flowLayoutPanel.Controls.Count - 1];
if(remove is Button button)
{
button.Click -= onAnyButtonClick;
}
flowLayoutPanel.Controls.RemoveAt(flowLayoutPanel.Controls.Count - 1);
}
}
private void onNoCommand(object sender, EventArgs e)
{
BackColor = Color.Transparent;
BorderStyle = BorderStyle.FixedSingle;
}
private void onAnyButtonClick(object sender, EventArgs e)
{
((MainForm)Parent).Text = $"{((Button)sender).Text} Clicked";
}
}
I have a custom UI element that inherits from System.Windows.Controls.Primitives.ToggleButton. I'm also routing my mouse events through a custom TouchDevice that raises touch events instead.
For some reason, the ManipulationCompleted event never fires. First, the custom TouchDevice can be found here: http://blakenui.codeplex.com/SourceControl/changeset/view/67526#Blake.NUI.WPF/Touch/MouseTouchDevice.cs
Here are the relevant parts of my class:
public class ToggleSwitch: ToggleButton {
private Grid _root;
private readonly IList<int> _activeTouchDevices;
private const double UNCHECKED_TRANSLATION = 0;
private TranslateTransform _backgroundTranslation;
private TranslateTransform _thumbTranslation;
private Grid _root;
private Grid _track;
private FrameworkElement _thumb;
private double _checkedTranslation;
private double _dragTranslation;
private bool _wasDragged;
private bool _isDragging;
public override void OnApplyTemplate()
{
...
MouseTouchDevice.RegisterEvents(_root);
_root.IsManipulationEnabled = true;
_root.TouchDown += OnTouchDown;
_root.TouchUp += OnTouchUp;
_root.GotTouchCapture += OnGotTouchCapture;
_root.LostTouchCapture += OnLostTouchCapture;
_root.ManipulationStarted += OnManipulationStarted;
_root.ManipulationDelta += OnManipulationDelta;
_root.ManipulationCompleted += OnManipulationCompleted;
}
private void OnTouchDown(object sender, TouchEventArgs e)
{
e.TouchDevice.Capture(_root);
}
private void OnGotTouchCapture(object sender, TouchEventArgs e)
{
if (e.TouchDevice.Captured == _root)
{
Manipulation.AddManipulator(_root,e.TouchDevice);
_activeTouchDevices.Add(e.TouchDevice.Id);
}
}
private void OnManipulationStarted(object sender, ManipulationStartedEventArgs e)
{
e.Handled = true;
_isDragging = true;
_dragTranslation = Translation;
ChangeVisualState(true);
Translation = _dragTranslation;
}
private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
e.Handled = true;
var horizontalChange = e.DeltaManipulation.Translation.X;
var direction = Math.Abs(horizontalChange) >= Math.Abs(e.DeltaManipulation.Translation.Y) ? Orientation.Horizontal : Orientation.Vertical;
if (direction == Orientation.Horizontal && horizontalChange != 0.0)
{
_wasDragged = true;
_dragTranslation += horizontalChange;
Translation = Math.Max(UNCHECKED_TRANSLATION, Math.Min(_checkedTranslation, _dragTranslation));
}
}
private void OnManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
e.Handled = true;
_isDragging = false;
var click = false;
if (_wasDragged)
{
var edge = (IsChecked ?? false) ? _checkedTranslation : UNCHECKED_TRANSLATION;
if (Translation != edge)
{
click = true;
}
}
else
{
click = true;
}
if (click)
{
OnClick();
}
_wasDragged = false;
}
}
The OnManipulationCompleted method is never entered.
When I am making a lot of buttons, is this the best way, or is there a better way? This code feels sort of clunky.
Button button = new Button();
button.MouseEnter += Button_MouseEnter;
button.MouseLeave += Button_MouseLeave;
Button button2 = new Button();
button2.MouseEnter += Button2_MouseEnter;
button2.MouseLeave += Button2_MouseLeave;
void Button_MouseEnter(object sender, EventArgs e)
{
BackgroundImage = Image.FromFile("buttonHover");
}
void Button_MouseLeave(object sender, EventArgs e)
{
BackgroundImage = Image.FromFile("button");
}
void Button2_MouseEnter(object sender, EventArgs e)
{
BackgroundImage = Image.FromFile("button2Hover");
}
void Button2_MouseLeave(object sender, EventArgs e)
{
BackgroundImage = Image.FromFile("button2");
}
I think there isn't better way. I would create a custom control with properties "button" and "buttonHover".
Something like this (not tested yet):
public class MyBytton : Button
{
public Image MainImage { get; set; }
public Image HoverImage { get; set; }
protected override void OnMouseEnter (EventArgs e)
{
if (HoverImage != null)
{
this.BackgroundImage = HoverImage;
}
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
if (MainImage != null)
{
this.BackgroundImage = MainImage;
}
base.OnMouseLeave(e);
}
}
Use:
MyBytton my = new MyBytton();
my.Location = new Point(10, 10); ;
my.Name = "button1";
my.Size = new System.Drawing.Size(141, 61);
my.TabIndex = 0;
my.Text = "test";
my.UseVisualStyleBackColor = true;
my.BackgroundImage = Image.FromFile("img1.jpg");
my.MainImage = Image.FromFile("img1.jpg");
my.HoverImage = Image.FromFile("img2.jpg");
I am trying to change the BackColor property of a User Control and ForeColor of a label inside it. Following is my code:
private void NRow_MouseLeave(object sender, EventArgs e)
{
BackColor = Color.White;
label1.ForeColor = Color.Black;
}
private void NRow_MouseEnter(object sender, EventArgs e)
{
BackColor = Color.Lime;
label1.ForeColor = Color.White;
}
But its not working. Even I tried to add breakpoint on BackColor changing line but control is not reaching there. I also checked the event binding, its ok. The user control is added to a panel like this:
notContainer.Controls.Add(new NRow());
I don't know what is happening. Please help.
UPDATE:
Event handlers are attached like this:
this.MouseEnter += new System.EventHandler(this.NRow_MouseEnter);
this.MouseLeave += new System.EventHandler(this.NRow_MouseLeave);
If your label1 placed inside your user control (UC) NRow, you should be handle MouseEnter and MouseEvent of label1 too. Because, your label1 inside your UC can be handles your mouse events instead your UC when the mouse moves over it.
this.MouseEnter += new System.EventHandler(this.NRow_MouseEnter);
this.MouseLeave += new System.EventHandler(this.NRow_MouseLeave);
label1.MouseEnter += new System.EventHandler(this.NRow_MouseEnter);
label1.MouseLeave += new System.EventHandler(this.NRow_MouseLeave);
Note: all of above lines should be placed inside your UC NRow.
I was able to get it working by overriding the UserControl's OnMouseLeave and OnMouseEnter and using the PointToClient Method to determine if the mouse coordinates are still within the UserControl before reverting, see if something like this works for you.
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
protected override void OnMouseEnter(EventArgs e)
{
BackColor = Color.Lime;
label1.ForeColor = Color.White;
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
if (! Bounds.Contains(PointToClient( MousePosition)))
{
BackColor = Color.White;
label1.ForeColor = Color.Black;
base.OnMouseLeave(e);
}
}
}
You can try this code to pass messages from child controls to your UserControl, in your case you need to pass the message WM_MOUSEMOVE plus some little code to make it work as expected:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
Dictionary<Control,NativeControl> controls = new Dictionary<Control,NativeControl>();
protected override void OnLostFocus(EventArgs e)
{
base.OnLostFocus(e);
OnMouseLeave(e);
}
protected override void OnControlAdded(ControlEventArgs e)
{
e.Control.HandleCreated += ControlsHandleCreated;
base.OnControlAdded(e);
}
protected override void OnControlRemoved(ControlEventArgs e)
{
e.Control.HandleCreated -= ControlsHandleCreated;
base.OnControlRemoved(e);
}
private void ControlsHandleCreated(object sender, EventArgs e)
{
Control control = sender as Control;
NativeControl nc;
if(!controls.TryGetValue(control, out nc)) {
nc = new NativeControl(this);
controls[control] = nc;
}
nc.AssignHandle(control.Handle);
}
public class NativeControl : NativeWindow
{
public NativeControl(UserControl1 parent)
{
Parent = parent;
}
UserControl1 Parent;
bool entered;
protected override void WndProc(ref Message m)
{
//WM_MOUSEMOVE = 0x200
//WM_LBUTTONDOWN = 0x201
//WM_LBUTTONUP = 0x202
//WM_NCHITTEST = 0x84
if (m.Msg == 0x200 || m.Msg == 0x201 || m.Msg == 0x202 || m.Msg == 0x84){
//Check if Parent is not nul, pass these messages to the parent
if (Parent != null){
m.HWnd = Parent.Handle;
Parent.WndProc(ref m);
}
if (m.Msg == 0x200 && !entered){
entered = true;
Parent.OnMouseEnter(EventArgs.Empty);
}
else entered = false;
}
else if (entered) entered = false;
base.WndProc(ref m);
}
}
}