I have a Form which contains several ComboBoxes.
I want one ComboBox of them to open the elements list when it gets the focus, both from keyboard and mouse.
The DroppedDown property of the ComboBox class manages the visibility of the elements list.
The event that most fits my needs is Enter, so the code I wrote is:
private void comboBox1_Enter(object sender, EventArgs e)
{
this.comboBox1.DroppedDown = true;
}
It works, but when directly clicking on the icon located on the right part of the ComboBox which does NOT have the focus, the elements list opens up and the suddenly disappears after its opening.
I've tried many ways to fix this weird behavior, checking the Focused property or using other events like DropDown or MouseClick, without getting any acceptable result.
A simple way (which doesn't force you to override a ComboBox derived Control's WndProc) is to simulate a HitTest, testing whether the MouseDown occurred on the ComboBox button area; then, set DroppedDown = true; only if it didn't.
Thus, when the Mouse is clicked on the Button, you won't cause a double effect, moving the Focus in an unexpected way (for the Control).
GetComboBoxInfo() is used to retrieve the correct bounds of the ComboBox Button, whether the current layout is (LTR or RTL).
private void comboBox1_Enter(object sender, EventArgs e)
{
var combo = sender as ComboBox;
if (!combo.DroppedDown) {
var buttonRect = GetComboBoxButtonInternal(combo.Handle);
if (!buttonRect.Contains(combo.PointToClient(Cursor.Position))) {
combo.DroppedDown = true;
Cursor = Cursors.Default;
}
}
}
Declarations for the GetComboBoxInfo() function:
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
[StructLayout(LayoutKind.Sequential)]
internal struct COMBOBOXINFO {
public int cbSize;
public Rectangle rcItem;
public Rectangle rcButton;
public int buttonState;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
}
internal static Rectangle GetComboBoxButtonInternal(IntPtr cboHandle) {
var cbInfo = new COMBOBOXINFO();
cbInfo.cbSize = Marshal.SizeOf<COMBOBOXINFO>();
GetComboBoxInfo(cboHandle, ref cbInfo);
return cbInfo.rcButton;
}
Create a new class the is inherited from ComboBox:
public class Combo : ComboBox
{
protected override void OnClick(EventArgs e)
{
if (!DroppedDown) base.OnClick(e);
}
}
And in its click call base.OnClick(e); if its not dropped down.
use this one instead of combobox. (Basically click event will be ignored if dropped down)
Use the following code on your form constructor:
this.comboBox1.GotFocus += (sender,args) => comboBox1.DroppedDown = true;
The MouseDown event isn't called when the mouse is over a child Control. I tried KeyPreview = true; but it doesn't help (though it does for KeyDown - keyboard clicks).
I'm looking for something like KeyPreview, but for mouse events.
I rather not use IMessageFilter and process the WinAPI message if there's a simpler. alternative (Also, IMessageFilter is set Application-wide. I want Form-wide only.) And iterating over all child Controls, subscribing each, has its own disadvantages.
You can still use MessageFilter and just filter for the ActiveForm:
private class MouseDownFilter : IMessageFilter {
public event EventHandler FormClicked;
private int WM_LBUTTONDOWN = 0x201;
private Form form = null;
[DllImport("user32.dll")]
public static extern bool IsChild(IntPtr hWndParent, IntPtr hWnd);
public MouseDownFilter(Form f) {
form = f;
}
public bool PreFilterMessage(ref Message m) {
if (m.Msg == WM_LBUTTONDOWN) {
if (Form.ActiveForm != null && Form.ActiveForm.Equals(form)) {
OnFormClicked();
}
}
return false;
}
protected void OnFormClicked() {
if (FormClicked != null) {
FormClicked(form, EventArgs.Empty);
}
}
}
Then in your form, attach it:
public Form1() {
InitializeComponent();
MouseDownFilter mouseFilter = new MouseDownFilter(this);
mouseFilter.FormClicked += mouseFilter_FormClicked;
Application.AddMessageFilter(mouseFilter);
}
void mouseFilter_FormClicked(object sender, EventArgs e) {
// do something...
}
The MouseDown event isn't called when the mouse is over a child Control. I tried KeyPreview = true; but it doesn't help (though it does for KeyDown - keyboard clicks).
I'm looking for something like KeyPreview, but for mouse events.
I rather not use IMessageFilter and process the WinAPI message if there's a simpler. alternative (Also, IMessageFilter is set Application-wide. I want Form-wide only.) And iterating over all child Controls, subscribing each, has its own disadvantages.
You can still use MessageFilter and just filter for the ActiveForm:
private class MouseDownFilter : IMessageFilter {
public event EventHandler FormClicked;
private int WM_LBUTTONDOWN = 0x201;
private Form form = null;
[DllImport("user32.dll")]
public static extern bool IsChild(IntPtr hWndParent, IntPtr hWnd);
public MouseDownFilter(Form f) {
form = f;
}
public bool PreFilterMessage(ref Message m) {
if (m.Msg == WM_LBUTTONDOWN) {
if (Form.ActiveForm != null && Form.ActiveForm.Equals(form)) {
OnFormClicked();
}
}
return false;
}
protected void OnFormClicked() {
if (FormClicked != null) {
FormClicked(form, EventArgs.Empty);
}
}
}
Then in your form, attach it:
public Form1() {
InitializeComponent();
MouseDownFilter mouseFilter = new MouseDownFilter(this);
mouseFilter.FormClicked += mouseFilter_FormClicked;
Application.AddMessageFilter(mouseFilter);
}
void mouseFilter_FormClicked(object sender, EventArgs e) {
// do something...
}
I am writing a custom control derived from Component.
In this control I need to be able somehow to get OS messages WM_DEVICEDCHANGED to create some events from.
Usually I would just override WndProc directly in the applications Form, but it is really important that this functionallity lays directly in the control instead.
Even though the control will always be used on a Form it is important that the OS messages are received on the control that is derived from Component so when dropping the control on a form there is no need to add functionality for it manually IN the form.
I have seen some examples mentioning NativeWindow and other solutions, but I have not been able to find head or tail in any of it, so I hope someone here can help me out.
Thanks...
i want to receive the WM_DEVICECHANGED message
Okay, that just requires subclassing the window of the form on which you drop the component. Any top-level window gets that message. Add a new class to your project and paste the code shown below. Build. Drop the new component from the top of the toolbox onto a form. Add an event handler for the DeviceChange event and add any code that's relevant to the kind of device change notification that you are interested in. You could also put that code in the OnDeviceChange() method to further specialize the notification and raise more specific events. It is up to you to take it from here.
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;
public class DeviceChangeNotifier : Component {
public delegate void DeviceChangeDelegate(Message msg);
public event DeviceChangeDelegate DeviceChange;
public DeviceChangeNotifier() {
// Add initialization here
}
public DeviceChangeNotifier(IContainer container) : this() {
// In case you need automatic disposal
container.Add(this);
}
public DeviceChangeNotifier(ContainerControl parentControl) : this() {
// In case you want to use it without the designer
this.ContainerControl = parentControl;
}
public ContainerControl ContainerControl {
// References the parent form
get { return this.parentControl; }
set {
this.parentControl = value;
this.parentControl.HandleCreated += parentControl_HandleCreated;
}
}
private void parentControl_HandleCreated(object sender, EventArgs e) {
// Subclass the form when its handle is created
snooper = new MessageSnooper(this, parentControl.Handle);
}
protected void OnDeviceChange(Message msg) {
// Raise the DeviceChange message
var handler = DeviceChange;
if (handler != null) handler(msg);
}
public override ISite Site {
// Runs at design time, ensures designer initializes ContainerControl
// so we'll have a reference to the parent form without it having to do any work
set {
base.Site = value;
if (value == null) return;
IDesignerHost service = value.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (service == null) return;
IComponent rootComponent = service.RootComponent;
this.ContainerControl = rootComponent as ContainerControl;
}
}
private ContainerControl parentControl;
private MessageSnooper snooper;
private const int WM_DESTROY = 0x0002;
private const int WM_DEVICECHANGE = 0x0219;
private class MessageSnooper : NativeWindow {
// Subclasses the parent window
public MessageSnooper(DeviceChangeNotifier owner, IntPtr handle) {
this.owner = owner;
this.AssignHandle(handle);
}
protected override void WndProc(ref Message m) {
if (m.Msg == WM_DESTROY) this.ReleaseHandle();
if (m.Msg == WM_DEVICECHANGE) owner.OnDeviceChange(m);
base.WndProc(ref m);
}
private DeviceChangeNotifier owner;
}
}
In Windows Forms, you can know, at any time, the current position of the cursor thanks to the Cursors class.
The same thing doesn't seem to be available for the keyboard. Is it possible to know if, for example, the Shift key is pressed?
Is it absolutely necessary to track down every keyboard notification (KeyDown and KeyUp events)?
if ((Control.ModifierKeys & Keys.Shift) != 0)
This will also be true if Ctrl+Shift is down. If you want to check whether Shift alone is pressed,
if (Control.ModifierKeys == Keys.Shift)
If you're in a class that inherits Control (such as a form), you can remove the Control.
The code below is how to detect almost all currently pressed keys, not just the Shift key.
private KeyMessageFilter m_filter = new KeyMessageFilter();
private void Form1_Load(object sender, EventArgs e)
{
Application.AddMessageFilter(m_filter);
}
public class KeyMessageFilter : IMessageFilter
{
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private bool m_keyPressed = false;
private Dictionary<Keys, bool> m_keyTable = new Dictionary<Keys, bool>();
public Dictionary<Keys, bool> KeyTable
{
get { return m_keyTable; }
private set { m_keyTable = value; }
}
public bool IsKeyPressed()
{
return m_keyPressed;
}
public bool IsKeyPressed(Keys k)
{
bool pressed = false;
if (KeyTable.TryGetValue(k, out pressed))
{
return pressed;
}
return false;
}
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == WM_KEYDOWN)
{
KeyTable[(Keys)m.WParam] = true;
m_keyPressed = true;
}
if (m.Msg == WM_KEYUP)
{
KeyTable[(Keys)m.WParam] = false;
m_keyPressed = false;
}
return false;
}
}
You can also look at the following if you use WPF or reference System.Windows.Input
if (Keyboard.Modifiers == ModifierKeys.Shift)
The Keyboard namespace can also be used to check the pressed state of other keys with Keyboard.IsKeyDown(Key), or if you are subscribing to a KeyDownEvent or similar event, the event arguments carry a list of currently pressed keys.
Most of these answers are either far too complicated or don't seem to work for me (e.g. System.Windows.Input doesn't seem to exist). Then I found some sample code which works fine:
http://www.switchonthecode.com/tutorials/winforms-accessing-mouse-and-keyboard-state
In case the page disappears in the future I am posting the relevant source code below:
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace MouseKeyboardStateTest
{
public abstract class Keyboard
{
[Flags]
private enum KeyStates
{
None = 0,
Down = 1,
Toggled = 2
}
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern short GetKeyState(int keyCode);
private static KeyStates GetKeyState(Keys key)
{
KeyStates state = KeyStates.None;
short retVal = GetKeyState((int)key);
//If the high-order bit is 1, the key is down
//otherwise, it is up.
if ((retVal & 0x8000) == 0x8000)
state |= KeyStates.Down;
//If the low-order bit is 1, the key is toggled.
if ((retVal & 1) == 1)
state |= KeyStates.Toggled;
return state;
}
public static bool IsKeyDown(Keys key)
{
return KeyStates.Down == (GetKeyState(key) & KeyStates.Down);
}
public static bool IsKeyToggled(Keys key)
{
return KeyStates.Toggled == (GetKeyState(key) & KeyStates.Toggled);
}
}
}
Since .NET Framework version 3.0, it is possible to use the Keyboard.IsKeyDown method from the new System.Windows.Input namespace. For instance:
if (((Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && Keyboard.IsKeyDown(Key.F))
{
// CTRL + F is currently pressed
}
Even though it's part of WPF, that method works fine for WinForm applications (provided that you add references to PresentationCore.dll and WindowsBase.dll). Unfortunately, however, the 3.0 and 3.5 versions of the Keyboard.IsKeyDown method did not work for WinForm applications. Therefore, if you do want to use it in a WinForm application, you'll need to be targeting .NET Framework 4.0 or later in order for it to work.
You can P/Invoke down to the Win32 GetAsyncKeyState to test any key on the keyboard.
You can pass in values from the Keys enum (e.g. Keys.Shift) to this function, so it only requires a couple of lines of code to add it.
if ((ModifierKeys == Keys.Control) && ((e.KeyChar & (char)Keys.F) != 0))
{
// CTRL+F pressed !
}
if (Control.ModifierKeys == Keys.Shift)
//Shift is pressed
The cursor x/y position is a property, and a keypress (like a mouse click/mousemove) is an event. Best practice is usually to let the interface be event driven. About the only time you would need the above is if you're trying to do a shift + mouseclick thing.
The best way I have found to manage keyboard input on a Windows Forms form is to process it after the keystroke and before the focused control receives the event. Microsoft maintains a built-in Form-level property named .KeyPreview to facilitate this precise thing:
public frmForm()
{
// ...
frmForm.KeyPreview = true;
// ...
}
Then the form's _KeyDown, _KeyPress, and / or _KeyUp events can be marshaled to access input events before the focused form control ever sees them, and you can apply handler logic to capture the event there or allow it to pass through to the focused form control.
Although not as structurally graceful as XAML's event-routing architecture, it makes management of form-level functions in Winforms far simpler. See the MSDN notes on KeyPreview for caveats.
if (Form.ModifierKeys == Keys.Shift)
does work for a text box if the above code is in the form's keydown event and no other control captures the keydown event for the key down.
Also one may wish stop further key processing with:
e.Handled = true;
In WinForms:
if( Form.ModifierKeys == Keys.Shift )
It sounds like a duplicate of Stack Overflow question Detect Shift key is pressed without using events in Windows Forms?.
If you need to listen to keys in any generic class what are pressed when a 'Form' Window, this is your code. It doesnt listen to global windows key events, so it cannot be used to see keys when the window is not active.
Form.cs
public partial class Form1 : Form
{
public Form1()
{
// Some other Code
// Register all Keys pressed
this.KeyPreview = true;
KeyHandler.Instance.Init();
this.KeyDown += Form1_KeyDown;
this.KeyUp += Form1_KeyUp;
// Some other Code in the constructor
}
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
// Fire event when a key is released
KeyHandler.Instance.FireKeyUp(sender, e);
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
// Fire event when a key is pressed
KeyHandler.Instance.FireKeyDown(sender, e);
}
}
KeyHandler.cs
KeyHandler is a Singleton Class and can be accessed in any other Object through Handler.Instance... Easy right.
public class KeyHandler
{
#region Singleton
private static KeyHandler instance;
private KeyHandler()
{
currentlyPressedKeys = new List<Keys>();
}
public static KeyHandler Instance
{
get
{
if (instance is null)
{
instance = new KeyHandler();
}
return instance;
}
}
#endregion Singleton
private List<Keys> currentlyPressedKeys;
public List<Keys> GetCurrentlyPressedKeys { get { return currentlyPressedKeys; } }
public void FireKeyDown(object sender, KeyEventArgs e)
{
if (!currentlyPressedKeys.Contains(e.KeyCode))
{
currentlyPressedKeys.Add(e.KeyCode);
KeyEventKeyPressed(sender, e);
}
}
public void FireKeyUp(object sender, KeyEventArgs e)
{
currentlyPressedKeys.Remove(e.KeyCode);
KeyEventKeyReleased(sender, e);
}
public event EventHandler<KeyEventArgs> KeyPressed;
protected virtual void KeyEventKeyPressed(object sender, KeyEventArgs e)
{
EventHandler<KeyEventArgs> handler = KeyPressed;
handler?.Invoke(sender, e);
}
public event EventHandler<KeyEventArgs> KeyReleased;
protected virtual void KeyEventKeyReleased(object sender, KeyEventArgs e)
{
EventHandler<KeyEventArgs> handler = KeyReleased;
handler?.Invoke(sender, e);
}
public void Init()
{
// Nothing to initialize yet
}
}
// In any other Class/Object its now possible to receive KeyEvents that are fired when the 'Form' is active. So its possible to listen to key events in any Control object or anything else. Its possible to see if e.g. multiple keys are pressed like Shift+Ctrl+Q or something like that.
public class SomeClass
{
public SomeClass()
{
KeyHandler.instance.KeyPressed += Instance_KeyPressed
KeyHandler.Instance.KeyReleased += Instance_KeyReleased;
}
public void SomeMethod()
{
if (KeyHandler.Instance.GetCurrentlyPressedKeys.Contains(Keys.ShiftKey))
{
// Do Stuff when the method has a key (e.g. Shift/Control...) pressed
}
}
private void Instance_KeyPressed(object sender, KeyEventArgs e)
{
// Any Key was pressed, do Stuff then
}
private void Instance_KeyReleased(object sender, KeyEventArgs e)
{
// Do Stuff when a Key was Released
}
}