I have an application where the user wants F4 to be the "Process Orders" button. (There is a long history around that key performing this feature.)
Today I found out that if the focus is in a ComboBox then F4 makes the ComboBox perform a dropdown.
Is there a way to make that not happen?
Update: I tried this using Delphi and it happens there too. While I am still curious, this seems to be a "baked in" Windows thing. I am going to ask the users to pick another shortcut.
use this
cboTest.PreviewKeyDown += (o,e) => {
if (e.Key == Key.F4)
e.Handled = true;
};
cboTest is your ComboBox Name
Use a custom combo box like this:
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
namespace Your.Namespace
{
public class CustomComboBox : ComboBox
{
protected override void OnKeyDown(KeyRoutedEventArgs e)
{
if (e.Key != Windows.System.VirtualKey.F4)
{
base.OnKeyDown(e);
}
}
}
}
And then, in XAML, replace all the instances of the old combo box with the new custom combo box:
<CustomComboBox xmlns="using:Your.Namespace.Controls"
... />
In addition to the answers above, here is a reusable solution without the need to create a custom control:
public static class ComboBoxHelper
{
public static readonly DependencyProperty DisableF4HotKeyProperty =
DependencyProperty.RegisterAttached("DisableF4HotKey", typeof(bool),
typeof(ComboBoxHelper), new PropertyMetadata(false, OnDisableF4HotKeyChanged));
public static bool GetDisableF4HotKey(DependencyObject obj)
{
return (bool)obj.GetValue(DisableF4HotKeyProperty);
}
public static void SetDisableF4HotKey(DependencyObject obj, bool value)
{
obj.SetValue(DisableF4HotKeyProperty, value);
}
private static void OnDisableF4HotKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var box = d as ComboBox;
if (d == null) return;
box.PreviewKeyDown -= OnComboBoxKeyDown;
box.PreviewKeyDown += OnComboBoxKeyDown;
}
private static void OnComboBoxKeyDown(object _, KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.F4)
{
e.Handled = true;
}
}
}
In your xaml file, add a namespace reference to the ComboBoxHelper class and set the attached property on your ComboBox:
<ComboBox h:ComboBoxHelper.DisableF4HotKey="True" />
How are you catching the F4 key? If you use the keypreview, you can override it from bubbling down to the combo box:
private void Form1_Load(object sender, EventArgs e)
{
this.KeyPreview = true;
this.KeyDown += new KeyEventHandler(Form1_KeyDown);
}
void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.F4)
{
e.Handled = true;
MessageBox.Show("F4 Pressed");
}
}
By default the F4 key opens or closes the dropdown list of a combo box. This behavior can be altered to ignore the F4 key and open the list using the down arrow key instead. This is done by sending a CB_SETEXTENDEDUI to the combo box providing a TRUE parameter. This is assuming that WPF does in fact use the native common controls internally.
Related
So, apparently I had some problem when handling keys such as F10 or F11.
I want to move the focus from current textbox into another textbox, but not in one particular textbox. So, I wrote some code to handle key:
private void checkKeys(KeyEventArgs e)
{
if (e.KeyCode == Keys.F10)
{
buyerName.Focus();
}
else if (e.KeyCode == Keys.F11)
{
discount.Focus();
}
}
But, if I put this into individual textbox, which kinda hassle to me. Is there any method to listen key whether in global userControl or textbox?
Edit : here's my structure that I want to ask :
Form-
|-User Control
|-TextBox
Edit 2 : here's some image might help img
To use a global keyboard listener in Winforms, you just need to add a handler to KeyUp action for the main form itself:
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.F10)
{
textBox1.Focus();
e.Handled = true; //To use F10, you need to set the handled state to true
} else if (e.KeyCode == Keys.F11)
{
textBox2.Focus();
}
}
Then make sure that the KeyPreview property on the main form is set to True.
The issue with the application freezing when pressing F10 is because it is waiting for another consecutive action. To bypass this simply set the Handled property on the keyevent to TRUE. This releases the unresolved event.
This is my entire form class, refactored to use a helper method as you are refering to. This works fine. But you have to make sure that the KeyPreview property on your form is True, unless your keypresses will not be matched to your event handlers.
namespace KeyTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
CheckKeys(e);
}
private void CheckKeys(KeyEventArgs e)
{
if (e.KeyCode == Keys.F10)
{
textBox1.Focus();
e.Handled = true;
}
else if (e.KeyCode == Keys.F11)
{
textBox2.Focus();
e.Handled = true;
}
}
}
}
Now in your comment you are mentioning a UserControl, if you want that, then you need to create an instance method on your UserControl class, and pass the event to that from your global keyboard event handler on your main form.
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public void HandleKeys(KeyEventArgs e)
{
if (e.KeyCode == Keys.F10)
{
textBox1.Focus();
e.Handled = true;
}
else if (e.KeyCode == Keys.F11)
{
textBox2.Focus();
e.Handled = true;
}
}
}
Then on your main form:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void Form1_KeyUp(object sender, KeyEventArgs e)
{
CheckKeys(e);
}
private void CheckKeys(KeyEventArgs e)
{
uc1.HandleKeys(e); //Instance method on your user control.
}
}
This then works as intended.
As pointed out in one of the comments, a better way would be to override the ProcessCmdKey method on the Form base class. This would be done like so:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
userControl11.HandleKeys(keyData); // method on the userControl to handle the key code.
base.ProcessCmdKey(ref msg, keyData);
return true;
}
}
The handler on the UserControl stays more or less the same:
public void HandleKeys(Keys keys)
{
if (keys == Keys.F10)
{
nameTB.Focus();
} else if (keys == Keys.F11)
{
emailTB.Focus();
}
}
Whether this is a more correct way of doing it, I am unsure of. They certainly both accomplish the same result. The documentation shows the first method in for handling keyboard events at the form level here:
How to handle keyboard input
But states here that the ProcessCmdKey method is to provide additional handling of shortcuts and MDI accellerators.
ProcessCmdKey
I will leave that up to you to decide what is the best for your scenario. But keep it in to show how you would use it should you choose to.
You can hook up to the KeyUp event of your form.
That way, any key pressed while your form is focused will be send to you (if the control didn't handle the key).
Thanks to #Espen and #reza-aghaei for handling keys into main form. Unfortunately, I still didn't managed find a way to focus to designated textbox inside a UserControl. However, I make some dirty method which kinda crappy and un-efficient by searching child control from it's parent
//MainForm.cs
if(yourUserControl.Name)//Do some check for targeted userControl, if null can cause NullReferenceException
{
if (e.KeyCode == Keys.F10)
{
this.Controls.Find("textboxName", true).First().Focus();
e.Handled = true;
}
}
I'm fairly new to programming so sorry if this is simple, but I might just be missing the obvious! I have the following form which is automatically populated on load from settings stored in an INI file:
The whole form is working just as I want it to apart form one small part. The 'Close' button currently just closes the form so that if any values have changed in the text boxes since the form was loaded, the changes are lost. I want to instead prompt the user to use the Save button instead otherwise the changes will be lost.
I've been trying to do something along these lines on my close button where the value of the text boxes are checked against the variable values that they were originally populated with:
private void btnClose_Click(object sender, EventArgs e)
{
if (txtName.Text != name || txtSchName.Text != schname || txtServer1.Text != svr1 etc etc etc)
{
Warn User changes will be lost, ask user if really OK to close?
if (User chooses to to close)
{
this.Close();
}
else
{
Go back to the config form;
}
}
else
{
this.Close();
}
With over 21 text fields, I was not sure if this was the most "tidy way" of checking for changes? Any pointers would be appreciated.
You just add a global boolean variable and write an handler for the TextChanged event
// Declared at the form level
private bool _modified = false;
public Form1()
{
InitializeComponent();
// This could be done in the form designer of course
// o repeated here for every textbox involved....
txtName.TextChanged += OnBoxesChanged;
......
}
private void Form1_Load(object sender, EventArgs e)
{
.....
// Code that initializes the textboxes could raise the TextChanged event
// So it is better to reset the global variable to an untouched state
_modified = false;
}
private void OnBoxesChanged(object sender, EventArgs e)
{
// Every textbox will call this event handler
_modified = true;
}
private void btnClose_Click(object sender, EventArgs e)
{
if(_modified)
{
// Save, warning, whatever in case of changes ....
}
}
Just set the event handler OnBoxesChanged for every textbox you want to trigger the condition. You could do it through the Designer or manually after the InitializeComponent call
What you are looking for is Dirty Tracking. Dirty tracking is used to track states of your control. Here is a simple reusable approach to track your controls
public class SimpleDirtyTracker
{
private Form _frmTracked;
private bool _isDirty;
public SimpleDirtyTracker(Form frm)
{
_frmTracked = frm;
AssignHandlersForControlCollection(frm.Controls);
}
// property denoting whether the tracked form is clean or dirty
public bool IsDirty
{
get { return _isDirty; }
set { _isDirty = value; }
}
// methods to make dirty or clean
public void SetAsDirty()
{
_isDirty = true;
}
public void SetAsClean()
{
_isDirty = false;
}
private void SimpleDirtyTracker_TextChanged(object sender, EventArgs e)
{
_isDirty = true;
}
private void SimpleDirtyTracker_CheckedChanged(object sender, EventArgs e)
{
_isDirty = true;
}
// recursive routine to inspect each control and assign handlers accordingly
private void AssignHandlersForControlCollection(
Control.ControlCollection coll)
{
foreach (Control c in coll)
{
if (c is TextBox)
(c as TextBox).TextChanged
+= new EventHandler(SimpleDirtyTracker_TextChanged);
if (c is CheckBox)
(c as CheckBox).CheckedChanged
+= new EventHandler(SimpleDirtyTracker_CheckedChanged);
// ... apply for other desired input types similarly ...
// recurively apply to inner collections
if (c.HasChildren)
AssignHandlersForControlCollection(c.Controls);
}
}
and in your mainform
public partial class Form1 : Form
{
private SimpleDirtyTracker _dirtyTracker;
private void Form1_Load(object sender, EventArgs e)
{
_dirtyTracker = new SimpleDirtyTracker(this);
_dirtyTracker.SetAsClean();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
// upon closing, check if the form is dirty; if so, prompt
// to save changes
if (_dirtyTracker.IsDirty)
{
DialogResult result
= (MessageBox.Show(
"Would you like to save changes before closing?"
, "Save Changes"
, MessageBoxButtons.YesNoCancel
, MessageBoxIcon.Question));
}
}
If you want to save for example usersettings you could create Settings in your Properties.
You can get them like this:
string anyProperty = WindowsFormsApplication1.Properties.Settings.Default.TestSetting;
You save them using the Save-method:
WindowsFormsApplication1.Properties.Settings.Default.TestSetting = "Hello World";
WindowsFormsApplication1.Properties.Settings.Default.Save();
You can call the Save-method after you have clicked the close-button.
For saving several string properties you could create a property of the type System.Collections.Specialized.StringCollection, so that you just create one property and not 21.
One disadvantage of dirtytracer is, that it returns "dirty" even when changes revert to original state. If You use object in DataContext as Your data model, all this task simplifies to:
bool changed = dataContext.SomeTables.GetModifiedMembers(someRow).Any();
I have a TextChanged event on my ComboBox like;
private void comboBox1_TextChanged(object sender, EventArgs e)
{
foreach (var item in comboBox1.Items.Cast<string>().ToList())
{
comboBox1.Items.Remove(item);
}
foreach (string item in InputBox.AutoCompleteCustomSource.Cast<string>().Where(s => s.Contains(comboBox1.Text)).ToList())
{
comboBox1.Items.Add(item);
}
}
As an explanation, when I change the text of combobox, I want to get string values contains in AutoCompleteCustomSource on InputBox (which is TextBox).
It works fine when I search them but when I select the item, obviously TextChanged event triggered again and Text property of Combobox will reset.
How to solve this?
If I understood correctly then i think you want to hide the TextChange event of the combobox. If it is then you can create a custom control inherited by ComboBox and override the TextChange event.
public partial class MyCombo : ComboBox
{
public MyCombo()
{
InitializeComponent();
}
bool bFalse = false;
protected override void OnTextChanged(EventArgs e)
{
//Here you can handle the TextChange event if want to suppress it
//just place the base.OnTextChanged(e); line inside the condition
if (!bFalse)
base.OnTextChanged(e);
}
protected override void OnSelectionChangeCommitted(EventArgs e)
{
bFalse = true;
base.OnSelectionChangeCommitted(e);
}
protected override void OnTextUpdate(EventArgs e)
{
base.OnTextUpdate(e);
bFalse = false; //this event will be fire when user types anything. but, not when user selects item from the list.
}
}
EDITED:
Another simple soution is use TextUpdate event instead of TextChange and keep your combobox as it is without creating another custom control.
private void myCombo1_TextUpdate(object sender, EventArgs e)
{
foreach (var item in myCombo1.Items.Cast<string>().ToList())
{
myCombo1.Items.Remove(item);
}
foreach (string item in myCombo1.AutoCompleteCustomSource.Cast<string>().Where(s => s.Contains(myCombo1.Text)).ToList())
{
myCombo1.Items.Add(item);
}
}
TextUpdate event will call only when user types anything in combobox. But, not when user selects item from the drop down list. So, this will not resent the added items.
You can also change the where condition if you wish to return all matched items in both cases(Upper and Lower). Suppose you have a two items in the list 1. Microsoft Sql Server, 2. microsoft office then what would be the result if i type microsoft only.
Where(s => s.ToLower().Contains(comboBox1.Text.ToLower()))
Sample Code
As #Sayse already said:
Add a boolean:
private bool codeCalled = new bool();
In your textChanged:
if(codeCalled == true)
{
codeCalled = false;
return;
}
else
{
codeCalled = true;
//your foreachcode here
}
That should do the trick.
Tested and is working.
Also tested and working, also not elegant:
private void textBox_TextChanged(object sender, EventArgs e)
{
textBox.TextChanged -= textBox_TextChanged;
//yourcode
textBox.TextChanged += textBox_TextChanged;
}
Possible Duplicate:
How can I assign the 'Close on Escape-key press' behavior to all WPF windows within a project?
I want to close the windows in my wpf project when the user clicks the escape button. I don't want to write the code in every window but want to create a class which can catch the when the user press the escape key.
Option 1
Use Button.IsCancel property.
<Button Name="btnCancel" IsCancel="true" Click="OnClickCancel">Cancel</Button>
When you set the IsCancel property of a button to true, you create a
Button that is registered with the AccessKeyManager. The button is
then activated when a user presses the ESC key.
However, this works properly only for Dialogs.
Option2
You add a handler to PreviewKeyDown on the window if you want to close windows on Esc press.
public MainWindow()
{
InitializeComponent();
this.PreviewKeyDown += new KeyEventHandler(HandleEsc);
}
private void HandleEsc(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape)
Close();
}
Here is a button-less solution that is clean and more MVVM-ish. Add the following XAML into your dialog/window:
<Window.InputBindings>
<KeyBinding Command="ApplicationCommands.Close" Key="Esc" />
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close" Executed="CloseCommandBinding_Executed" />
</Window.CommandBindings>
and handle the event in the code-behind:
private void CloseCommandBinding_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
if (MessageBox.Show("Close?", "Close", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
this.Close();
}
One line to put after InitializeComponent():
PreviewKeyDown += (s,e) => { if (e.Key == Key.Escape) Close() ;};
Please note that this kind of code behind does not break MVVM pattern since this is UI related and you don't access any viewmodel data. The alternative is to use attached properties which will require more code.
You can create a custom DependencyProperty:
using System.Windows;
using System.Windows.Input;
public static class WindowUtilities
{
/// <summary>
/// Property to allow closing window on Esc key.
/// </summary>
public static readonly DependencyProperty CloseOnEscapeProperty = DependencyProperty.RegisterAttached(
"CloseOnEscape",
typeof(bool),
typeof(WindowUtilities),
new FrameworkPropertyMetadata(false, CloseOnEscapeChanged));
public static bool GetCloseOnEscape(DependencyObject d)
{
return (bool)d.GetValue(CloseOnEscapeProperty);
}
public static void SetCloseOnEscape(DependencyObject d, bool value)
{
d.SetValue(CloseOnEscapeProperty, value);
}
private static void CloseOnEscapeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Window target)
{
if ((bool)e.NewValue)
{
target.PreviewKeyDown += Window_PreviewKeyDown;
}
else
{
target.PreviewKeyDown -= Window_PreviewKeyDown;
}
}
}
private static void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (sender is Window target)
{
if (e.Key == Key.Escape)
{
target.Close();
}
}
}
}
And use it your windows' XAML like this:
<Window ...
xmlns:custom="clr-namespace:WhereverThePropertyIsDefined"
custom:WindowUtilities.CloseOnEscape="True"
...>
The answer is based on the content of the gist referenced in this answer.
The InputBinding options here are nice and flexible.
If you want to use an event handler, be aware that the Preview events happen quite early. If you have a nested control that should take the Esc key for its own purposes, stealing it at the window level may brake that control's functionality.
Instead you can handle the event at the window level only if nothing else wants to with:
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (!e.Handled && e.Key == Key.Escape && Keyboard.Modifiers == ModifierKeys.None)
{
this.Close();
}
}
I have implemented an undo system based on the Memento pattern. I disable the built in Undo on TextBox and was wondering how to do this on a ComboBox. The Combobox I have is editable, so it contains a TextBox, how do I access this to disable the Undo on it as well.
I know I can derive from ComboBox add a property and override the control template and set the property on the TextBox, but I would like a way to do this on the standard ComboBox from the xaml.
You can look it up from the template like this:
public Window1()
{
this.InitializeComponent();
comboBox1.Loaded += new RoutedEventHandler(comboBox1_Loaded);
}
void comboBox1_Loaded(object sender, RoutedEventArgs e)
{
var textBox = comboBox1.Template.FindName("PART_EditableTextBox", comboBox1) as TextBox;
}
I know this is 3+ years old but maybe it'll help someone. It is basically Rick's answer as a Behavoir that decyclone mentioned:
public class ComboBoxDisableUndoBehavoir : Behavior<ComboBox>
{
public ComboBoxDisableUndoBehavoir()
{
}
protected override void OnAttached()
{
if (AssociatedObject != null)
{
AssociatedObject.Loaded += AssociatedObject_Loaded;
}
base.OnAttached();
}
void AssociatedObject_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
var tb = AssociatedObject.Template.FindName("PART_EditableTextBox", AssociatedObject) as TextBox;
if (tb != null)
{
tb.IsUndoEnabled = false;
}
}
}