How to prevent context menu from closing when holding the Alt key? - c#

I need to allow the Alt key to be used as a modifier when selecting certain menu entries in a ContextMenuStrip. This works fine with all other modifiers like Ctrl or Shift. However, for some reason Alt automatically closes the context menu.
I tried using the Closing event in the context menu, and the menu item AutoClose property, but this is proving to be more clunky than anticipated. Specifically, even though the context menu is kept open, the application MenuStrip is activated (which may explain why the context menu closes in the first place).
I've tried searching around but I found surprisingly few questions about this, and none on stack overflow, so I wonder whether there might be a better way to work around this that I have missed?
Example code showing that suppressing MenuStrip activation does not prevent context menu from closing:
class MainForm : Form
{
MenuStrip menuStrip;
ContextMenuStrip contextMenuStrip;
public MainForm()
{
KeyPreview = true;
menuStrip = new MenuStrip();
menuStrip.Items.Add("&File");
Controls.Add(menuStrip);
contextMenuStrip = new ContextMenuStrip();
contextMenuStrip.Items.Add("&OptionA");
contextMenuStrip.Items.Add("&OptionB");
ContextMenuStrip = contextMenuStrip;
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Alt)
{
e.Handled = true;
e.SuppressKeyPress = true;
}
else base.OnKeyDown(e);
}
}

This is by design, so you'll have to do the state tracking yourself. But this will definitely stop Alt from reaching the MenuStrip.
This is low level keyboard filtering, so you'll have to decide what to do when Alt is pressed entirely on your own, however.
You could also change the conditional to check for Alt plus some state.
In short, returning true from PreFilterMesssage will stop it from reaching your app.
static void Main()
{
//...SNIP...
Application.AddMessageFilter(new AltFilter());
//...SNIP...
}
public class AltFilter : IMessageFilter
{
private static ushort WM_SYSKEYDOWN = 0x0104;
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == WM_SYSKEYDOWN && Control.ModifierKeys == Keys.Alt)
{
//Do your own special thing instead
return true;
}
return false;
}
}

Related

Event that triggers BEFORE my form loses focus

I need to be alerted before my entire form loses focus. The Deactivate event only triggers after it loses focus. LostFocus and Leave are only for controls.
I have also tried overriding WndProc but this only triggers after the message has been processed.
overriding PreProcessMessage only can be used for keyboard stuff, not form deactivation.
Dodgy Method
Even though this is a quick and hacky way of doing it, changing Input Language is unnatural to start with..
private void Form1_Deactivate(object sender, EventArgs e)
{
((Form)sender).Activate();
System.Diagnostics.Debug.WriteLine(this.ActiveControl.Name);
//Change Input Language here..
//Alt TAB to set focus to the application selected 5 milliseconds ago
SendKeys.SendWait("%{TAB");
}
Correct and orthadox method
How to monitor focus changes? and C#: Detecting which application has focus
Its using the Automation framework, Add references to UIAutomationClient and UIAutomationTypes and use Automation.AddAutomationFocusChangedEventHandler, e.g.:
public class FocusMonitor
{
public FocusMonitor()
{
AutomationFocusChangedEventHandler focusHandler = OnFocusChanged;
Automation.AddAutomationFocusChangedEventHandler(focusHandler);
}
private void OnFocusChanged(object sender, AutomationFocusChangedEventArgs e)
{
AutomationElement focusedElement = sender as AutomationElement;
if (focusedElement != null)
{
int processId = focusedElement.Current.ProcessId;
using (Process process = Process.GetProcessById(processId))
{
Debug.WriteLine(process.ProcessName);
}
}
}
}
Got it, this hack works perfectly.
private void MyForm_Deactivate(object sender, EventArgs e)
{
Thread.Sleep(200); //delay to allow external tab time to open
Form f1 = new Form(); //create a new form that will take focus, switch input, then terminate itself
f1.Shown += new EventHandler((s, e1) => { f1.Activate(); InputLanguage.CurrentInputLanguage = InputLanguage.DefaultInputLanguage; f1.Close(); });
f1.Show();
}
EDIT: upon further testing I have found this to be equally unreliable. It doesn't seem like there is a good way to do this at all.
For now I am tracking the mouse and keyboard to detect when the user is about to deactivate it. Obviously a mouse and keyboard hook is a horrible solution but its the only reliable solution so far.

Handling arrow keys in a Form

I have just found out that we can't use the KeyDown event directly with a PictureBox. So I have to change my strategy.
I decided to add the Keydown event to the actual form:
private void FullColourPaletteForm_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Left:
{
MessageBox.Show("Left");
e.Handled = true;
return;
}
}
}
Doesn't get executed. I see no message box when I press the left allow. Instead (and rightly so) it just moves the cusor from control to control.
I was hoping to be able to mimic some kind of cursor support for the block of colour by intercepting the arrow keys inside the picture box.
I am not sure of the best way forward. I don't want to break the standard dialogue functionality of moving between controls, but I want to now include suipport for detectign keys so I can add my code to move my block of colour.
Can it be done? Not sure why my event is not getting triggered in the form anyway.
I saw this question. So I tried setting my form KeyPreview property. No joy. I also looked at ProcessCmdKey but it doesn't seem right for the issue in hand.
Update:
If I try to follow the idea in the comments and create a SelectablePictureBox control, it looks like this:
I have two issues. 1. I still can't seem to work out how to handle the keydown event on my pictureBox object itself. I am reluctant to manually add any handlers to the designer file incase my changes get lost.
Also, when doing general control nagivation on the form with cursor keys it does not seem to know about this control.
If you want to handle arrow keys at form level, you can override the form's ProcessCmdKey function this way:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Left)
{
MessageBox.Show("Left");
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
But in general it's better to create a custom paint selectable control like this rather than putting such logic at form level. Your control should contain such logic.
Note
OP: I have just found out that we can't use the KeyDown event directly
with a PictureBox
As mentioned by Hans in comments, the PictureBox control is not selectable and can not be focused by default and you can not handle keyboard events for the control.
But you can force it to be selectable and support keyboard events this way:
using System;
using System.Reflection;
using System.Windows.Forms;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.pictureBox1.SetStyle(ControlStyles.Selectable, true);
this.pictureBox1.SetStyle(ControlStyles.UserMouse, true);
this.pictureBox1.PreviewKeyDown +=
new PreviewKeyDownEventHandler(pictureBox1_PreviewKeyDown);
}
void pictureBox1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
if (e.KeyData == Keys.Left)
MessageBox.Show("Left");
}
}
public static class Extensions
{
public static void SetStyle(this Control control, ControlStyles flags, bool value)
{
Type type = control.GetType();
BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
MethodInfo method = type.GetMethod("SetStyle", bindingFlags);
if (method != null)
{
object[] param = { flags, value };
method.Invoke(control, param);
}
}
}
At least knowing this approach as a hack you can reuse the extension method to enable or disable some styles on controls in future.

Pressing Tab key does not select next control

I have four text boxes in my winform, and I've already ordered their tab index sequentially. Also, I set their TabStop properties as true. But when I press Tab key while in filling my first textbox, it does not move to next one. For that, I even added the following part for each of them in the code:
firstTextbox.KeyDown += (sender, args) =>
{
if(args.KeyCode == Keys.Tab)
{
firstTextbox.SelectNextControl(this, true, true, true, true);
}
}
But this didn't help either. Any suggestion?
Responding to tab buttons is handled by the message loop. If you're running the form as modal, or are calling
Application.Run(myForm);
then you've got a message loop. If you're only doing
Form myForm = new Form();
myForm.Visible = true;
Then you do not have a message loop and therefore tab navigation won't work. (If you are not running the form modally, then try using the ShowDialog() method to show the form, and see if tabbing works in this case.)
If this is your issue, then this MSDN article below suggests that you either
Run each Form in its own thread (so that you can call Application.Run(myForm) on it). Running each form in its own thread is a huge can of worms and should not be undertaken by the faint of heart (especially if you have preexisting or poorly designed forms).
Show the dialog modally.
I've had some success with a horrible hack of setting KeyPreview on the form to true and then listening for System.Windows.Forms.Keys.Tab within the Form.OnKeyDown method (and setting KeyEventArgs.SuppressKeyPress and KeyEventArgs.Handled to true if I do handle it). (Also remember to check the KeyEventArgs.Modifiers property to see if the user is doing a [Shift]+[Tab].) The downside of this is that you rob the Control that has a focus on the opportunity to respond to the keypress since Form.KeyPreview causes the Form to get a chance to handle the key before the Control gets a chance. (I'm also not sure what would happen if you implemented this logic and had the message loop going.)
As far as what you should do when you detect the Tab Key, check out the Control.SelectNextControl function. I've been calling it like...
e.SuppressKeyPress = SelectNextControl(
ActiveControl, //Current Control
true, //Move Forward? (You want false for a Shift-Tab to go backward.)
true, //Only stop at controls where Control.TabStop == true?
true, //Consider controls nested within other controls?
true); //Wrap to the beginning of the form if you reach the end?
A person who is smarter than me might be able to set up a shared message loop or something else, but this is all I've figured out so far.
The only way I was able to fire textBox1_KeyDown with the Tab key was by overriding IsInputKey method.
class TabTextBox : TextBox
{
protected override bool IsInputKey(Keys keyData)
{
if (keyData == Keys.Tab)
{
return true;
}
else
{
return base.IsInputKey(keyData);
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyData == Keys.Tab)
{
this.SelectedText = " ";
}
else
{
base.OnKeyDown(e);
}
}
and then modify InitializeComponent():
private void InitializeComponent()
{
...
this.textBox1 = new TabTextBox();
...
}
Forms.Control.isInputKey
Every control in windows forms application have property
this.button1.TabIndex = 3;
you can use it to select next control if you order it correctly.
If your textboxes are multiline you need to set the AcceptsTab property to false.
Perhaps your event handler is not being registered. Make sure textboxes are not ReadOnly=true, TabStop=false. Try this
public Form1()
{
InitializeComponent();
this.textBox1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBox1_KeyDown);
}
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Tab)
{
textBox1.SelectNextControl(sender as Control, true, true, true, true);
}
}
I ran into a similar problem with tab order completely ignored. After two hours of digging I finally found my dumb mistake: "If mybutton.Focus Then ..." when I meant "If myButton.Focused Then...". Oops. Figured I'd share in case anyone else has this experience too.

How to set hotkeys for a Windows Forms form

I would like to set hotkeys in my Windows Forms form. For example, Ctrl + N for a new form and Ctrl + S for save. How would I do this?
Set
myForm.KeyPreview = true;
Create a handler for the KeyDown event:
myForm.KeyDown += new KeyEventHandler(Form_KeyDown);
Example of handler:
// Hot keys handler
void Form_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.S) // Ctrl-S Save
{
// Do what you want here
e.SuppressKeyPress = true; // Stops other controls on the form receiving event.
}
}
You can also override ProcessCmdKey in your Form derived type like this:
protected override bool ProcessCmdKey(ref Message message, Keys keys)
{
switch (keys)
{
case Keys.B | Keys.Control | Keys.Alt | Keys.Shift:
// ... Process Shift+Ctrl+Alt+B ...
return true; // signal that we've processed this key
}
// run base implementation
return base.ProcessCmdKey(ref message, keys);
}
I believe it's more suitable for hotkeys. No KeyPreview needed.
If your window has a menu, you can use the ShortcutKeys property of System.Windows.Forms.ToolStripMenuItem:
myMenuItem.ShortcutKeys = Keys.Control | Keys.S;
In Visual Studio, you can set it in the property page of the menu item, too.
I'd like a KeyDown event for the Form and some code like this:
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == (Keys.Control | Keys.N))
{
CreateNew();
}
}
If you are trying to link them to menu items in your application, then you don't need any code. On the menu item, you can simply setup the shortcut key property and it will run the same event that you have configured for your menu item click.
I thought I'd put an update here since the newest answer is 5 years old. Specifically addressing the question portion regarding Menu hotkeys, you can manipulate the properties of your MenuStrip.MenuItem object, by setting the ShortcutKeys property. In Visual Studio you can do this in the form design window by opening the properties of your MenuStrip object. Once scrolled down to the the ShortcutKeys property, you can use VS interface to set your hot keys.
If you want a MenuStrip to underline a menu item, prefix the ampersand (&) char to the char of the desired hotkey. So for example if you want the "x" of Exit to be underlined, the property setting should be E&xit.
These property manipulations should yield a result similar to this*:
*Note: To display the shortcut key "Ctrl+N" change ShowShortcutKeys property to true.
You can set it using a hidden menu too, if you want. Just set the property of menu.visible = false;
First, you need to handle the KeyDown event, and then you can start watching out for your modifiers:
private void Form1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
if (Control.ModifierKeys == Keys.Control && e.KeyCode == Keys.S)
{
//Do whatever
}
}
Of course, you need to make sure your form subscribes to the KeyDown event.

Select Tab Page in TabControl without stealing focus

Using TabControl.SelectTab("...") shows the tab but it also gives the tab focus. I would like to show a particular tab, but keep focus where it is.
I have data rows in a grid. Based on properties of the selected row, I show a different tab page to have a different UI layout. But when using arrow keys to scroll through rows, the focus switches to the selected tab -- which I don't want to happen.
Thanks.
You can try disabling the TabControl before setting the selected tab, then re-enabling it. This will prevent it from taking focus. I tested this on a tab control with a few controls on it, and didn't see any visual change, but you'll have to try it in your UI and see whether it's ok for you.
tabControl1.Enabled = false;
tabControl1.SelectTab("tabPage4");
tabControl1.Enabled = true;
To be safe, you could put the line to re-enable the TabControl in a finally block to make sure it doesn't get left disabled.
I don't think there's a built-in function, but you can do in this way:
private bool skipSelectionChanged = false;
private void dataGridView_SelectionChanged(object sender, EventArgs e)
{
if (skipSelectionChanged)
return;
// supposing we decide tab[0] has to be selected...
this.SelectTabWithoutFocus(this.tabControl1.TabPages[0]);
}
private void SelectTabWithoutFocus(TabPage tabPage)
{
this.skipSelectionChanged = true;
// "this" is the form in my case, so you get the current focused control
// (ActiveControl), backup it, and re-set it after Tab activation
var prevFocusedControl = this.ActiveControl;
if (this.ActiveControl != null)
{
this.tabControl1.SelectedTab = tabPage;
prevFocusedControl.Focus();
}
this.skipSelectionChanged = false;
}
Here, I backup the current focused control, select the desired tab, and finally set the focus to the original control.
Skipping boolean is necessary, because giving the focus to the grid you trigger SelectionChanged event again, causing infinite looping.
This selects the tab pages while keeping the focus on top, as asked here above:
tc.TabPages[0].Enabled = false;
tc.SelectTab(0);
tc.TabPages[0].Enabled = true;
tc is here my instance for the TabControl type (i. e. it IS my tab control, and it has a few "tab pages"). This works properly for me. My purpose is to loop through these tab pages with the Left and Right keys (arrows) i. e. when I go forwards (by Key.Right) and reach the last tabpage I want to have my focus on [0] without activating the DataGridView which I have in that page, and when I go backwards (by Key.Left) and reach [0] I want to have [tc.TabCount - 1] enabled, which is the last one. The code for this case is:
tc.TabPages[tc.TabCount - 1].Enabled = false;
tc.SelectTab(tc.TabCount - 1);
tc.TabPages[tc.TabCount - 1].Enabled = true;
The complete piece of code is:
private bool KeyTc(System.Windows.Forms.Keys keyData)
{
if (keyData == K.Left && tc.SelectedIndex == 0)
{
tc.TabPages[tc.TabCount - 1].Enabled = false;
tc.SelectTab(tc.TabCount - 1);
tc.TabPages[tc.TabCount - 1].Enabled = true;
return true;
}
else if (keyData == K.Right && tc.SelectedIndex == tc.TabCount - 1)
{
tc.TabPages[0].Enabled = false;
tc.SelectTab(0);
tc.TabPages[0].Enabled = true;
return true;
}
return false;
}
This bool KeyTc is returned to a case in a switch statement for key evaluation in:
protected override bool ProcessCmdKey(ref Message keyMsg, Keys keyData)
{ switch keyData { ... } }
Base on the solution proposed by "Jeff Ogata : You can try disabling the TabControl before setting the selected tab, then re-enabling it. This will prevent it from taking focus", here bellow my solution:
tabMain.SelectedPageChanging += (s, e) =>
{
tabMain.Enabled = false;
};
tabMain.SelectedPageChanged += (s, e) =>
{
tabMain.Enabled = true;
};
Note: this code is using DevExpress "DevExpress.XtraTab.XtraTabControl".

Categories