How can I handle a customizable hotkey setting? - c#

I'm trying to let my app's users an option to set a keyboard hotkey for for some feature.
Right now I'm using a TextBox with the KeyDown event as follows:
Key Hotkey;
private void SetHotKey(object sender, KeyEventArgs e)
{
(sender as TextBox).Text = e.Key.ToString();
Hotkey = e.Key;
e.Handled = true;
}
The problem with this approach is I can't set complex shortcuts, like [Ctrl]+[F4]. Is there some 3rd-party control that helps with that? Or a better-suited event to subscribe to on a textbox?
UPDATE: I've changed my code but it seems I'm still doing something wrong.
Key Hotkey;
bool lControl = false;
bool lAlt = false;
bool lShift = false;
private void SetHotKey(object sender, KeyEventArgs e)
{
var k = e.Key;
if (e.IsDown)
{
var tb = sender as TextBox;
tb.Text = "";
lControl = Keyboard.IsKeyDown(Key.LeftCtrl);
lAlt = Keyboard.IsKeyDown(Key.LeftAlt);
lShift = Keyboard.IsKeyDown(Key.LeftShift);
if (lControl) tb.Text += "Ctrl+";
if (lAlt) tb.Text += "Alt+";
if (lShift) tb.Text += "Shift+";
tb.Text = e.Key.ToString();
Hotkey = e.Key;
}
e.Handled = true;
}
How can I make it work and look cleaner as well?

To get you started in the right direction.
First you will need the real key behind the System/ImeProcessed/DeadCharProcessed key. This can be done with extension method for easier access.
public static Key RealKey(this KeyEventArgs e)
{
switch (e.Key)
{
case Key.System:
return e.SystemKey;
case Key.ImeProcessed:
return e.ImeProcessedKey;
case Key.DeadCharProcessed:
return e.DeadCharProcessedKey;
default:
return e.Key;
}
}
And then you should format your modifiers to the shortcut, not just the Key which was pressed. You can use Keyboard.ModifierKeys to get flags and for easier formatting, gather those in a list. And also you should block just a modifier key (Ctrl, Alt and Shift) from updating the hotkey.
private void SetHotKey(object sender, KeyEventArgs e)
{
var nonShortcuttableKeys = new[] { Key.LeftAlt, Key.RightAlt, Key.LeftCtrl, Key.RightCtrl, Key.LeftShift, Key.RightShift };
var actualKey = e.RealKey();
if (e.IsDown && !nonShortcuttableKeys.Contains(actualKey))
{
var tb = sender as TextBox;
var modifiers = new List<ModifierKeys>();
if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
{
modifiers.Add(ModifierKeys.Control);
}
if (Keyboard.Modifiers.HasFlag(ModifierKeys.Alt))
{
modifiers.Add(ModifierKeys.Alt);
}
if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
{
modifiers.Add(ModifierKeys.Shift);
}
tb.Text = modifiers.Count == 0
? string.Format("{0}", actualKey)
: string.Format("{0} + {1}", string.Join(" + ", modifiers), actualKey);
Hotkey = actualKey;
}
e.Handled = true;
}

WinForms
You can check the KeyEventArgs.Modifiers if the Ctrl is pressed or not.
https://msdn.microsoft.com/en-us/library/system.windows.forms.keyeventargs.modifiers(v=vs.110).aspx
WPF
you can use this little snippet to check if the Control key is pressed:
Keyboard.IsKeyDown(Key.LeftCtrl)
If you want to use the MVVM pattern you can use the KeyBindings of the textbox (or any other control)
Create Key binding in WPF

Related

WPF TextBox - How to trap and deal with Backspace and Delete events

I am trying to warn the user when they select and delete text in a wpf textbox.
I am able to trap the delete event using the previewkeydown event, but its is canceling out the delete event. Even when you press ok in the code below - deletion does not happen. I am missing something ...
private void TextBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Delete)
{
var textbox = (TextBox)sender;
if (textbox.SelectionLength > 1)
{
var result = MessageBox.Show("Delete selected?", "MyApp", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.Cancel)
{
e.Handled = true;
}
}
}
}
This does not seem to be the proper usage of the PreviewKeyDown event handler. That handler seems to be meant to redirect non-standard input key events to do custom behavior. The delete key is not considered non-standard/special.
You've got the right idea with your current code otherwise, but now you just need to actually delete the text in the textbox.
private void TextBox_KeyDownHandler(object sender, KeyEventArgs e)
{
switch(e.KeyCode)
{
case Keys.Delete:
if (sender is TextBox tb)
{
if(tb.SelectionLength > 1 && MessageBox.Show("Delete?", "MyApp", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
{
tb.SelectedText = "";
}
}
e.Handled = true;
break;
}
}
Lastly, make sure you're actually subscribing your handlers
public MainWindow()
{
InitializeComponent();
MyTextBox.KeyDown += new KeyEventHandler(TextBox_KeyDownHandler);
}

Real Hint in TextBox Visual Studio C# [duplicate]

This question already has answers here:
Watermark TextBox in WinForms
(11 answers)
Closed 4 years ago.
I'm currently making a Windows Forms Application on Visual Studio in C# and I'm trying to find a way to have a real hint.
I've found a lot of answers online on how to have some text preset there, Some examples even show how to grey out the text to look like a placeholder, but that's not what I'm looking for.
I want a grayed out text that you don't have to backspace to type something there. So I want it to behave like an HTML placeholder like the "Search Q&A" search bar on stack Overflow.
Is there an easy way to do this, like configuring a property of the textbox in the designer on Visual Studio?
This might be the ugliest code but I think you can improve it.
This following class is merely an extension of the standard TextBox
class PHTextBox : System.Windows.Forms.TextBox
{
System.Drawing.Color DefaultColor;
public string PlaceHolderText {get;set;}
public PHTextBox(string placeholdertext)
{
// get default color of text
DefaultColor = this.ForeColor;
// Add event handler for when the control gets focus
this.GotFocus += (object sender, EventArgs e) =>
{
this.Text = String.Empty;
this.ForeColor = DefaultColor;
};
// add event handling when focus is lost
this.LostFocus += (Object sender, EventArgs e) => {
if (String.IsNullOrEmpty(this.Text) || this.Text == PlaceHolderText)
{
this.ForeColor = System.Drawing.Color.Gray;
this.Text = PlaceHolderText;
}
else
{
this.ForeColor = DefaultColor;
}
};
if (!string.IsNullOrEmpty(placeholdertext))
{
// change style
this.ForeColor = System.Drawing.Color.Gray;
// Add text
PlaceHolderText = placeholdertext;
this.Text = placeholdertext;
}
}
}
Copy/paste to new cs file entitled PHTextBox.cs.
Go to your graphic designer and add a TextBox.
Go to the designer and change the instiantion line for the textbox as follow:
Now compile but before you do, just make sure the textbox is not the first element to get the focus. Add button for that matter.
Have you tried overlapping a label on the textbox?
On the textbox keypress event you can check the length of the textbox.text and set the label.
On the keypress event..
MyLabel.Visible = String.IsNullOrEmpty(MyTextBox.Text);
Of course you might want to set the default text of the label as well as grey it out too.
Issue with this is if your form is re sizable.
What you want to achieve is not native to windows forms.
I know that this is an old question; However I was searching for a way and I found my answer to be best answer to display a prompt text in a TextBox:
1) Create a class .cs file called for example MyExtensions.cs having a namespace called for example 'Extensions'.
2) Create a method in the TextBox called Init(string prompt) that takes the prompt text you want to display inside the TextBox.
3) Let me stop talking and give you the rest of the code for MyExtensions.cs (The entire code):
MyExtensions.cs
using System.Drawing;
using System.Windows.Forms;
namespace Extensions
{
public static class MyExtensions
{
public static void Init(this TextBox textBox, string prompt)
{
textBox.Text = prompt;
bool wma = true;
textBox.ForeColor = Color.Gray;
textBox.GotFocus += (source, ex) =>
{
if (((TextBox)source).ForeColor == Color.Black)
return;
if (wma)
{
wma = false;
textBox.Text = "";
textBox.ForeColor = Color.Black;
}
};
textBox.LostFocus += (source, ex) =>
{
TextBox t = ((TextBox)source);
if (t.Text.Length == 0)
{
t.Text = prompt;
t.ForeColor = Color.Gray;
return;
}
if (!wma && string.IsNullOrEmpty(textBox.Text))
{
wma = true;
textBox.Text = prompt;
textBox.ForeColor = Color.Gray;
}
};
textBox.TextChanged += (source, ex) =>
{
if (((TextBox)source).Text.Length > 0)
{
textBox.ForeColor = Color.Black;
}
};
}
}
}
Now Assume that you have three TextBox's : tbUsername, tbPassword, tbConfirm:
In your Form_Load(object sender, EventArgs e) method initialize your three TextBox's to have their appropriate Prompt Text Messages:
using Extensions;
namespace MyApp{
public partial class Form1 : Form{
private void Form1_Load(object sender,
EventArgs e){
tbUsername.Init("Type a username");
tbPassword.Init("Type a password");
tbConfirm.Init("Confirm your password");
}
}
}
Enjoy! :)
What about this
private bool hasTextBeenTyped;
private void Form1_Load(object sender, EventArgs e)
{
this.ActiveControl = label1;
textBox1.ForeColor = Color.LightGray;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
hasTextBeenTyped = !String.IsNullOrEmpty(textBox1.Text);
if (hasTextBeenTyped)
{
textBox1.ForeColor = Color.Black;
}
}
private void textBox1_Click(object sender, EventArgs e)
{
if (!hasTextBeenTyped)
{
textBox1.Text = "";
}
}
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
hasTextBeenTyped = true;
}
the this.ActiveControl = label1; is just to take focus away from the text box initially. If something else already does, don't worry about that line.
Please, take a look at my ControlHintManager class, ControlHintInfo type and ControlHintType enumeration. They are written in Vb.Net, but you can get the idea analyzing the source-code, or compile the library to use it in your C# project without any more effort.
My solution has even better behavior than the StackOverflows hint that you mentioned, taking into account that when the control leave focus, the hint-text should be restored if the string remains empty.
Usage is so friendlly:
ControlHintInfo hint1 =
new ControlHintInfo("I'm a hint text.", font (or nul), Color.Gray,
ControlHintType.Persistent);
ControlHintManager.SetHint(TextBox1, hint1);
To acchieve this by your own, one way is calling the Win32 SendMessage function with the EM_SETCUEBANNER message, however, that will produce a too basic hint with poor behavior, not recommendable,
so a proper way to acchieve this is by taking control of the edit-control text by yourself, handling the Control.HandleCreated, Control.Enter, Control.Leave, Control.MouseDown, Control.KeyDown and Control.Disposed events (as you can see in my linked source-code).
Just use a object to keep track of the control's state (forecolor, text, and optionally the font), then use properlly the event-handlers mentioned to set or restore the text and color.
This is a online C# translation of the most-important code of the linked urls if this help you understand it better:
private static void Control_HandleCreated(object sender, EventArgs e) {
InstanceControlHintFields();
Control ctrl = (Control)sender;
ControlHintInfo hintInfo = controlHintsB(ctrl);
SetProperties(ctrl, hintInfo);
}
private static void Control_Enter(object sender, EventArgs e) {
InstanceControlHintFields();
Control ctrl = (Control)sender;
string ctrlText = ctrl.Text;
ControlHintInfo ctrlDefaults = controlHintsDefaults(ctrl);
ControlHintInfo hintInfo = controlHintsB(ctrl);
switch (hintInfo.HintType) {
case ControlHintType.Normal:
if ((ctrlText.Equals(hintInfo.Text, StringComparison.OrdinalIgnoreCase))) {
RestoreProperties(ctrl, ctrlDefaults);
}
break;
}
}
private static void Control_Leave(object sender, EventArgs e) {
InstanceControlHintFields();
Control ctrl = (Control)sender;
string ctrlText = ctrl.Text;
ControlHintInfo ctrlDefaults = controlHintsDefaults(ctrl);
ControlHintInfo hintInfo = controlHintsB(ctrl);
switch (hintInfo.HintType) {
case ControlHintType.Normal:
if ((ctrlText.Equals(hintInfo.Text, StringComparison.OrdinalIgnoreCase))) {
RestoreProperties(ctrl, ctrlDefaults);
} else if (string.IsNullOrEmpty(ctrlText)) {
SetProperties(ctrl, hintInfo);
}
break;
case ControlHintType.Persistent:
if (string.IsNullOrEmpty(ctrlText)) {
SetProperties(ctrl, hintInfo);
}
break;
}
}
private static void Control_MouseDown(object sender, MouseEventArgs e) {
InstanceControlHintFields();
Control ctrl = (Control)sender;
string ctrlText = ctrl.Text;
ControlHintInfo hintInfo = controlHintsB(ctrl);
switch (hintInfo.HintType) {
case ControlHintType.Persistent:
if ((ctrlText.Equals(hintInfo.Text, StringComparison.OrdinalIgnoreCase))) {
// Get the 'Select' control's method (if exist).
MethodInfo method = sender.GetType.GetMethod("Select", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, {
typeof(int),
typeof(int)
}, null);
if ((method != null)) {
// Select the zero length.
method.Invoke(ctrl, new object[] {
0,
0
});
}
}
break;
}
}
private static void Control_KeyDown(object sender, KeyEventArgs e) {
Control ctrl = (Control)sender;
string ctrlText = ctrl.Text;
ControlHintInfo ctrlDefaults = controlHintsDefaults(ctrl);
ControlHintInfo hintInfo = controlHintsB(ctrl);
switch (hintInfo.HintType) {
case ControlHintType.Persistent:
if ((ctrlText.Equals(hintInfo.Text, StringComparison.OrdinalIgnoreCase))) {
RestoreProperties(ctrl, ctrlDefaults);
} else if (string.IsNullOrEmpty(ctrlText)) {
RestoreProperties(ctrl, ctrlDefaults, skipProperties: { "Text" });
}
break;
case ControlHintType.Normal:
if (string.IsNullOrEmpty(ctrlText)) {
RestoreProperties(ctrl, ctrlDefaults);
}
break;
}
}
private static void Control_Disposed(object sender, EventArgs e) {
RemoveHint((Control)sender);
}
PS: It is Reflection based to support more variety of controls.

How to set Textbox.Enabled from false to true on TextChange?

I programmatically create a Form with two textboxes. My goal is to disable one textbox if I type something in the second one and contrariwise. I managed to disable second textbox on first textbox textchange,but can't figure out how enable it when the first textbox.Text is empty.
Here is the code :
private void metaName_TextChanged(object sender,EventArgs e)
{
var ctrl = (Control)sender;
var frm = ctrl.FindForm();
TextBox metaTxt = null;
foreach (var ctr in frm.Controls)
{
if (ctr is TextBox)
{
metaTxt = (TextBox)ctr;
if (metaTxt.Name == "metaHTTPEquiv")
{
metaTxt.Enabled = false;
}
else
if (?)
{
}
}
}
}
I want to make something like this :
if(textBox3.Text == String.Empty)
{
textBox4.Enabled = true;
}
else
if(textBox3.Text != String.Empty)
{
textBox4.Enabled = false;
}
You can check only the textchanged event for each one like the following:
private void textBox1_TextChanged(object sender, EventArgs e)
{
textBox2.Enabled = !(textBox1.Text.Length >= 1);
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
textBox1.Enabled = !(textBox2.Text.Length >= 1);
}
The self textbox has some values, then the enabled will be false for the other one
First set a flag to enable or disable the second control based on the content of the metaName textbox that raises the event, then search for the second textbox using a bit of Linq.
private void metaName_TextChanged(object sender,EventArgs e)
{
TextBox ctrl = sender as TextBox;
if(ctrl != null)
{
bool enable = !string.IsNullOrEmpty(ctrl.Text);
TextBox secondOne = this.Controls
.OfType<TextBox>()
.FirstOrDefault(x => x.Name == "metaHTTPEquiv");
if(secondOne != null)
secondOne.Enabled = enable;
}
}
The same code, reversing the textboxes roles, could be used as the event handler of the second textbox.
Forget about control events and use data binding.
Take the following helper method
static void Bind(Control target, string targetProperty, object source, string sourceProperty, Func<object, object> expression)
{
var binding = new Binding(targetProperty, source, sourceProperty, true, DataSourceUpdateMode.Never);
binding.Format += (sender, e) => e.Value = expression(e.Value);
target.DataBindings.Add(binding);
}
and just add something like this in your form load event
Bind(textBox2, "Enabled", textBox1, "Text", value => string.IsNullOrEmpty((string)value));

How can I sync two TextBox control's Text properties?

If I have two text boxes on a form, how can I make it so that their text properties are perfectly synced? Similar to what would happen if they both processed the same KeyDown event.
I would do it this way:
textBox1.TextChanged += (s, _) =>
{
if (!textBox2.Focused && textBox1.Text != textBox2.Text)
{
textBox2.Text = textBox1.Text;
}
};
textBox2.TextChanged += (s, _) =>
{
if (!textBox1.Focused && textBox2.Text != textBox1.Text)
{
textBox1.Text = textBox2.Text;
}
};
Basically I'm responding to the TextChanged even on each text box, but making sure that the target text box doesn't have focus and that the text actually has changed. This prevents infinite back-and-forth loop trying to update the text and it makes sure that the current insertion point isn't changed by the text being overwritten.
I would say that you partially answered your own question, have them both assigned to the same TextChanged EventHandler check which textbox has changed then update the Text property of the other one, something like this.
private void textBox_TextChanged(object sender, EventArgs e)
{
if (((TextBox)sender).Equals(textBox1))
textBox2.Text = ((TextBox)sender).Text;
else
textBox1.Text = ((TextBox)sender).Text;
}
Modified Code to Keep Carat Position Synced between the two TextBox's , see if this is what you are wanting.
private void textBox_TextChanged(object sender, EventArgs e)
{
TextBox tb = (TextBox)sender;
if (tb.Equals(textBox1))
{
if (textBox2.Text != tb.Text)
{
textBox2.Text = tb.Text;
textBox2.SelectionStart = tb.SelectionStart;
textBox2.Focus();
}
}
else
{
if (textBox1.Text != tb.Text)
{
textBox1.Text = tb.Text;
textBox1.SelectionStart = tb.SelectionStart;
textBox1.Focus();
}
}
}
I would simply do as follows:
bool flag1, flag2;
private void t1_TextChanged(object sender, EventArgs e)
{
if (flag2) return;
flag1 = true;
t2.Text = t1.Text;
flag1 = false;
}
private void t2_TextChanged(object sender, EventArgs e)
{
if (flag1) return;
flag2 = true;
t1.Text = t2.Text;
flag2 = false;
}

C# TextBox NewLine simulate Ctrl+Enter on KeyDownEvent

I have a TextBox (multiline) and if I press Ctrl+Enter it just makes a new line. But I would also like to do this on Shift+Enter.
I tried:
messageTextBox_KeyDown(null, new KeyEventArgs(Keys.Control | Keys.Enter));
But this is not working.
I think that I can do this:
int lastSelectionStart = messageTextBox.SelectionStart;
messageTextBox.Text = messageTextBox.Text.Insert(messageTextBox.SelectionStart, Environment.NewLine);
messageTextBox.SelectionStart = lastSelectionStart;
But I don't like this.
How can I make it so that Shift+Enter will be the same as Ctrl+Enter?
My current code:
KeyDownEvent
if (e.KeyCode == Keys.Enter)
{
e.Handled = true;
if (e.Shift)
{
}
else if (!e.Control)
{
sendButton_Click(null, null);
}
}
In KeyPress event:
if (e.KeyChar == (char)13)
{
e.Handled = true;
return;
}
This is because I want to use the Enter key alone as Send button.
Sounds to me like the real problem you are fighting is the form's AcceptButton property. You cannot fix this with the KeyDown event handler, the keystroke is processed before it ever gets to the event.
This can be fixed by overriding the form's ProcessCmdKey() method but the cleanest way is to just create your own TextBox derived control. You can override its IsInputKey() method to tell Winforms that you want to see the Ctrl+Enter key. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox. Note how you can alter the class to your convenience to make it more adept at being a chat input box.
using System;
using System.Windows.Forms;
class ChatBox : TextBox {
public ChatBox() {
this.Multiline = true;
}
protected override bool IsInputKey(Keys keyData) {
if (keyData == (Keys.Shift | Keys.Enter)) return true;
return base.IsInputKey(keyData);
}
protected override void OnKeyDown(KeyEventArgs e) {
if (e.KeyData == (Keys.Shift | Keys.Enter)) {
int pos = this.SelectionStart;
this.SelectedText = Environment.NewLine;
this.SelectionStart = pos;
e.Handled = e.SuppressKeyPress = true;
return;
}
base.OnKeyDown(e);
}
}

Categories