I found the following solution to limit textbox to numbers. I have 20 textBoxes in my GUI is there a cleaner way than making 20 of these functions?
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = !char.IsDigit(e.KeyChar) && !char.IsControl(e.KeyChar);
}
My suggestion is to replace the standard TextBox Controls that need this feature, with a Custom Control that can also filter the User input when text is pasted in the Control or when the Control's Text Property is set in the Designer, using the PropertyGrid.
If you only handle the KeyPress event, you cannot prevent a bad paste.
I think it's also better to filter what is set in the Text Property, to avoid a misunderstanding.
Test this simple Custom TextBox Control: it handles direct User input and text pasted at run-time.
Setting the UserPaste Property to Disallow, pastes are ignored, while setting it to NumbersOnly (default) allows just numbers: if mixed chars are pasted in the Control, only the numbers are preserved.
To also allow the input of comma and dot, change the Regex in [^0-9,.\b].
The Text property set in the Designer is also filtered.
To replace the existing TextBox Controls with the this Custom Control, you can use the Find/Replace function (usually activated with CTRL+H) in Visual Studio:
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Windows.Forms;
[ToolboxItem(true)]
[DesignerCategory("Code")]
public class TextBoxNumbers : TextBox
{
private Regex regex = new Regex(#"[^0-9\b]", RegexOptions.Compiled);
public TextBoxNumbers() { }
public override string Text {
get => base.Text;
set { if (!base.Text.Equals(value)) base.Text = regex.Replace(value, ""); }
}
public enum PasteAction {
NumbersOnly,
Disallow
}
[DefaultValue(PasteAction.NumbersOnly)]
public PasteAction UserPaste { get; set; }
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (regex.IsMatch(e.KeyChar.ToString())) {
e.Handled = true;
}
base.OnKeyPress(e);
}
protected override void WndProc(ref Message m)
{
switch (m.Msg) {
case NativeMethods.WM_PASTE:
switch (UserPaste) {
case PasteAction.Disallow:
return;
case PasteAction.NumbersOnly:
string text = Clipboard.GetText(TextDataFormat.UnicodeText);
text = regex.Replace(text, "");
NativeMethods.SendMessage(this.Handle, NativeMethods.EM_REPLACESEL, 1, text);
return;
}
break;
}
base.WndProc(ref m);
}
}
NativeMethods class:
using System.Runtime.InteropServices;
private class NativeMethods
{
internal const int WM_PASTE = 0x0302;
internal const int EM_REPLACESEL = 0xC2;
[DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, uint uMsg, int wParam, string lParam);
}
My simple and go-to solution would be to wirte the methode once, after that go to the desginer and select every of your texboxes that need it, click on the event-button in the propperties, scroll to the KeyPress Event and click on it once, now a dropdown-arrow should apear, select your methode there - done.
private void textBox_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = !char.IsDigit(e.KeyChar) && !char.IsControl(e.KeyChar);
}
Related
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...
}
I want to create a small control that allows the users of my application to define key combinations, and display them in a human readable format.
For example, I currently have a text box and if the user has focus and then presses a key, it will record and display the pressed key within the text box, my issues are when it comes to key combinations, or special keys (CTRL, ALT, BACKSPACE etc.)
Here is the simple code I have at the moment, which I was using just to experiment:
private void tboxKeyCombo_KeyPress(object sender, KeyPressEventArgs e)
{
if (tboxKeyCombo.Focused)
{
string sKeyboardCombo = String.Empty;
if (char.IsLetterOrDigit(e.KeyChar))
{
sKeyboardCombo += e.KeyChar.ToString();
}
else if (char.IsControl(e.KeyChar))
{
sKeyboardCombo += "CTRL";
}
tboxKeyCombo.Text += sKeyboardCombo + "+";
}
}
At the moment it behaves very weirdly, if I was to press "CTRL+O" it would display "CTRL+" in the text box. Even if I press BACKSPACE it just prints CTRL anyway.
I think I'm misunderstanding some of the parts of deciphering the keyboard input, so any help would be brilliant - thank you.
As an option, you can create a control based on TextBox and make it read-only, then override some key functions like ProcessCmdKey and convert pressed keys to string using KeysConverter class.
Example
using System.Windows.Forms;
public class MyTextBox : TextBox
{
public MyTextBox() { this.ReadOnly = true; }
public Keys ShortcutKey { get; set; }
public new bool ReadOnly
{
get { return true; }
set { base.ReadOnly = true; }
}
KeysConverter converter = new KeysConverter();
protected override bool ProcessCmdKey(ref Message m, Keys keyData)
{
ShortcutKey = keyData;
this.Text = converter.ConvertToString(keyData);
return false;
}
}
Is there any way of creating a tool window in WinForms that as long as the hosting form has focus, the tool window does as well? An example of this is in Paint.NET:
I'm using C# as the backend application language, under .Net 4.0.
The source code for an old version of Paint.Net is available at openpdn Fork of Paint.NET 3.36.7
I tried to extract their methods from that source code into the most concise working example I could muster:
Pinvoking class:
internal static class Win32 {
public const int WM_ACTIVATE = 0x006;
public const int WM_ACTIVATEAPP = 0x01C;
public const int WM_NCACTIVATE = 0x086;
[DllImport("user32.dll", SetLastError = false)]
internal static extern IntPtr SendMessageW(IntPtr hWnd, uint msg,
IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal extern static bool PostMessageW(IntPtr handle, uint msg,
IntPtr wParam, IntPtr lParam);
}
Base Form:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private bool ignoreNcActivate = false;
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
switch (m.Msg) {
case Win32.WM_NCACTIVATE:
if (m.WParam == IntPtr.Zero) {
if (ignoreNcActivate) {
ignoreNcActivate = false;
} else {
Win32.SendMessageW(this.Handle, Win32.WM_NCACTIVATE, new IntPtr(1), IntPtr.Zero);
}
}
break;
case Win32.WM_ACTIVATEAPP:
if (m.WParam == IntPtr.Zero) {
Win32.PostMessageW(this.Handle, Win32.WM_NCACTIVATE, IntPtr.Zero, IntPtr.Zero);
foreach (Form2 f in this.OwnedForms.OfType<Form2>()) {
f.ForceActiveBar = false;
Win32.PostMessageW(f.Handle, Win32.WM_NCACTIVATE, IntPtr.Zero, IntPtr.Zero);
}
ignoreNcActivate = true;
} else if (m.WParam == new IntPtr(1)) {
Win32.SendMessageW(this.Handle, Win32.WM_NCACTIVATE, new IntPtr(1), IntPtr.Zero);
foreach (Form2 f in this.OwnedForms.OfType<Form2>()) {
f.ForceActiveBar = true;
Win32.SendMessageW(f.Handle, Win32.WM_NCACTIVATE, new IntPtr(1), IntPtr.Zero);
}
}
break;
}
}
protected override void OnShown(EventArgs e) {
base.OnShown(e);
Form2 f = new Form2();
f.Show(this);
}
}
Always active Form2 (unless app is not active):
public partial class Form2 : Form {
internal bool ForceActiveBar { get; set; }
public Form2() {
InitializeComponent();
this.ShowInTaskbar = false;
this.ForceActiveBar = true;
}
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
if (m.Msg == Win32.WM_NCACTIVATE) {
if (this.ForceActiveBar && m.WParam == IntPtr.Zero) {
Win32.SendMessageW(this.Handle, Win32.WM_NCACTIVATE, new IntPtr(1), IntPtr.Zero);
}
}
}
}
There is no need to set TopMost to true for Form2 since it should be owned by the main form when it gets displayed. Also, Form2 is not an MDI child form.
The tool windows in Paint.NET are just that—tool windows. In Win32 terms, you achieve this by creating the window with the WS_EX_TOOLWINDOW extended window style:
The window is intended to be used as a floating toolbar. A tool window has a title bar that is shorter than a normal title bar, and the window title is drawn using a smaller font. A tool window does not appear in the taskbar or in the dialog that appears when the user presses ALT+TAB.
In WinForms, this is controlled by the FormBorderStyle property. Set it to either FormBorderStyle.FixedToolWindow or FormBorderStyle.SizableToolWindow in your form's constructor.
You also need to make sure that you specify an owner window for the tool window. Its owner should be your main form, the one for which it serves as a tool palette. You generally do this when showing the form, using the overload of the Show method that allows you to specify an owner window.
Finally, another cool effect that Paint.NET has (I think, if I remember correctly) is that the tool windows can never actually receive the focus. You can interact with them, clicking on buttons to select tools, but you can't actually set the focus to a floating palette. It always goes back to the main window. A naive attempt to emulate this behavior might be to reset the focus in one of the focus-changing notifications (e.g., the Activate event), but that's not a good idea for numerous reasons. A better solution would be to add the WS_EX_NOACTIVATE extended window style. I'm not aware of any property that exposes this functionality in WinForms, but you can set it manually during the window's creation by overriding the CreateParams property. For example:
public class MyForm : Form
{
// ... other code ...
protected override CreateParams CreateParams {
get {
const int WS_EX_NOACTIVATE = 0x08000000;
CreateParams cp = base.CreateParams;
cp.ExStyle |= WS_EX_NOACTIVATE;
return cp;
}
}
}
I don't know if Windows Forms has a built-in feature for this, but you can achieve what you want with the code below:
For main form:
private ToolForm m_toolForm;
private void MainForm_Load(object sender, EventArgs e)
{
m_toolForm = new ToolForm ();
m_toolForm.Show();
}
private void MainForm_Resize(object sender, EventArgs e)
{
switch (WindowState)
{
case FormWindowState.Minimized:
m_toolForm.Hide();
break;
case FormWindowState.Maximized:
m_toolForm.Show();
break;
}
}
For tool form:
You don't need any code, just set the property "TopMost" to true.
For those using the DevExpress RibbonForm, please use the solution in the below link to solve the focus problem. The zip file is in a comment to the solution, not in the solution itself:
http://www.devexpress.com/Support/Center/Question/Details/Q498321
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
}
}