问题
In my project there is a Form mainForm
in which there are two textBoxes txtUserName
and txtPassword
and also a button btnLogin
.
I have given the following txtUserName
properties:
txtUserName Properties
AutoCompleteCustomSource - Collection
--> Administrator
--> Clerk
AutoCompleteMode - Suggest
AutoCompleteSource - CustomSource
btnLogin_Click Event
if (txtUserName.Text.Equals("Administrator") && txtPassword.Text.Equals("123"))
{
//function to access admin features
}
else if (txtUserName.Text.Equals("Clerk") && txtPassword.Text.Equals("123"))
{
//function to access clerk features
}
else
{
MessageBox.Show("Please Enter correct details", "Login Error");
}
I have setted the mainForm
keypreview
to true
and implemented function to keyDown event of mainForm
which is shown in the below code:
mainForm_KeyDownEvent
if (e.KeyCode.Equals(Keys.Enter)) //Invokes whenever Enter is pressed
{
btnLogin_Click(sender,e); //login
}
Now my problem is that whenever the focus is on txtUserName
and pressing A
, dropdown is showing to select "Administrator" (which is defined in collections as I shown in above properties). When I click Enter
on keyboard it is showing MessageBox instead of selecting "Administrator". I know that is invoking the keydown event of mainForm
. How to disable the keyDown event, when it is on textbox dropdown thing so that I can press enter
?
EDIT:
I tried the below code in public form()
:(not working)
InitializeComponent();
if (txtUserName.AutoCompleteMode) { /* showing red scribbles */
this.KeyDown -= mainForm_KeyDown;
}
回答1:
You should not be handling the Enter key at all. You can remove your KeyDown
handler, and instead use the AcceptButton property of the form to set the button that gets "clicked" when Enter is pressed. This is already supposed to not "click" the button when another control has already handled the Enter key.
That isn't enough for your situation, because standard Windows behaviour is for the Enter key to press the default button. Press Win+R, for example, to get the Run... dialog, start typing C:\Use, press Down to select C:\Users, press Enter, and see what happens.
In order to override that behaviour, you need to make the text box tell the form that it will be handling the Enter key itself, so that the form won't send it to the default button. This can be done by creating a derived class and overriding IsInputKey
:
public class MyTextBox : TextBox
{
protected override bool IsInputKey(Keys keyData)
{
return base.IsInputKey(keyData) || ((keyData & ~Keys.Shift) == Keys.Enter && IsDroppedDown);
}
}
However, TextBox
implements autocompletion using the SHAutoComplete function, which automatically creates an IAutoComplete object behind the scenes. That object cannot be accessed, and because of that, the IsDroppedDown
property that I used in IsInputKey
cannot be created. It would be implemented using IAutoCompleteDropDown.GetDropDownStatus, but since the object is inaccessible, you cannot (reliably) determine whether the dropdown list is showing.
You would need to either implement the auto completion without using the built-in AutoComplete*
properties, or you would need to always suppress the Enter key (remove the && IsDroppedDown
in the above IsInputKey
).
Update: here's how to create an IAutoComplete
object manually. The strings Administrator and Clerk are hardcoded. The GetDropDownStatus function is used to suppress any default button's handling of Enter when the drop down list is visible. Feedback welcome.
IAutoComplete.cs:
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
[ComImport]
[Guid("00bb2762-6a77-11d0-a535-00c04fd7d062")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[CoClass(typeof(IAutoCompleteClass))]
interface IAutoComplete
{
void Init(HandleRef hwndEdit, IEnumString punkACL, string pwszRegKeyPath, string pwszQuickComplete);
void Enable(bool fEnable);
}
IAutoComplete2.cs:
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
[Guid("EAC04BC0-3791-11d2-BB95-0060977B464C")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IAutoComplete2
{
void Init(HandleRef hwndEdit, IEnumString punkACL, string pwszRegKeyPath, string pwszQuickComplete);
void Enable(bool fEnable);
void SetOptions(AutoCompleteOptions dwFlag);
AutoCompleteOptions GetOptions();
};
AutoCompleteOptions.cs:
using System;
[Flags]
enum AutoCompleteOptions : int
{
None = 0x00,
AutoSuggest = 0x01,
AutoAppend = 0x02,
Search = 0x04,
FilterPrefixes = 0x08,
UseTab = 0x10,
UpDownKeyDropsList = 0x20,
RtlReading = 0x40,
WordFilter = 0x80,
NoPrefixFiltering = 0x100,
}
IAutoCompleteDropDown.cs:
using System;
using System.Runtime.InteropServices;
using System.Text;
[Guid("3CD141F4-3C6A-11d2-BCAA-00C04FD929DB")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IAutoCompleteDropDown
{
void GetDropDownStatus(out AutoCompleteDropDownFlags dwFlags, out StringBuilder wszString);
void ResetEnumerator();
}
AutoCompleteDropDownFlags.cs:
using System;
[Flags]
enum AutoCompleteDropDownFlags : int
{
None = 0x00,
Visible = 0x01
}
IAutoCompleteClass.cs:
using System;
using System.Runtime.InteropServices;
[ComImport]
[Guid("00BB2763-6A77-11D0-A535-00C04FD7D062")]
class IAutoCompleteClass
{
}
EnumString.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
class EnumString : IEnumString
{
const int E_INVALIDARG = unchecked((int)0x80070057);
const int S_OK = 0;
const int S_FALSE = 1;
int current;
string[] strings;
public EnumString(IEnumerable<string> strings)
{
this.current = 0;
this.strings = strings.ToArray();
}
public void Clone(out IEnumString ppenum)
{
ppenum = new EnumString(strings);
}
public int Next(int celt, string[] rgelt, IntPtr pceltFetched)
{
if (celt < 0)
return E_INVALIDARG;
int num = 0;
while (current < strings.Length && celt != 0)
{
rgelt[num] = strings[current];
current++;
num++;
celt--;
}
if (pceltFetched != IntPtr.Zero)
Marshal.WriteInt32(pceltFetched, num);
if (celt != 0)
return S_FALSE;
return S_OK;
}
public void Reset()
{
current = 0;
}
public int Skip(int celt)
{
if (celt < 0)
return E_INVALIDARG;
if (strings.Length - current > celt)
{
current = strings.Length;
return S_FALSE;
}
current += celt;
return S_OK;
}
}
MyTextBox.cs:
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
public class MyTextBox : TextBox
{
IAutoComplete2 autoComplete;
IAutoCompleteDropDown autoCompleteDropDown;
public bool IsDroppedDown
{
get
{
if (autoCompleteDropDown == null)
return false;
AutoCompleteDropDownFlags dwFlags;
StringBuilder wszString;
autoCompleteDropDown.GetDropDownStatus(out dwFlags, out wszString);
return (dwFlags & AutoCompleteDropDownFlags.Visible) != AutoCompleteDropDownFlags.None;
}
}
protected override void CreateHandle()
{
base.CreateHandle();
autoComplete = (IAutoComplete2)new IAutoComplete();
autoCompleteDropDown = (IAutoCompleteDropDown)autoComplete;
autoComplete.SetOptions(AutoCompleteOptions.AutoSuggest);
autoComplete.Init(new HandleRef(this, this.Handle), new EnumString(new string[] { "Administrator", "Clerk" }), null, null);
}
protected override void DestroyHandle()
{
ReleaseAutoComplete();
base.DestroyHandle();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
ReleaseAutoComplete();
}
base.Dispose(disposing);
}
protected override bool IsInputKey(Keys keyData)
{
return base.IsInputKey(keyData) || ((keyData & ~Keys.Shift) == Keys.Enter && IsDroppedDown);
}
void ReleaseAutoComplete()
{
if (autoComplete != null)
{
Marshal.ReleaseComObject(autoComplete);
autoComplete = null;
autoCompleteDropDown = null;
}
}
}
回答2:
You need to override the keydown event handler.
protected override void OnKeyDown(KeyEventArgs e)
{
//call original event handler. Remove it if you don't need it at all.
base.OnKeyDown(e);
//Insert your code here....
}
回答3:
Try this. Hopefully it will not cause any problem on pressing enter while your focus is on txtUsername or else where
If you write a
in txtUserName
and press enter, Your Admministrator
choice will be selected from your autocompletecustomsource
using regular expression
and focus will go to txtPassword
. My regular expression is very flexible you can made it bit restricted as following to match strictly from beginning and also can remove ignore case
Regex rg = new Regex("^" + txtUserName.Text);
private void mainForm_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode.Equals(Keys.Enter))// && !txtUserName.Focus())// && intFlag.Equals(0))
{
if (txtUserName.Text.Length > 0)
{
if (txtUserName.Focused)
{
Regex rg = new Regex(txtUserName.Text, RegexOptions.IgnoreCase);
for (int i = 0; i < txtUserName.AutoCompleteCustomSource.Count; i++)
{
if (rg.IsMatch(txtUserName.AutoCompleteCustomSource[i]))
{
txtUserName.Text = txtUserName.AutoCompleteCustomSource[i];
txtPassword.Focus();
return;
}
}
}
if (txtPassword.Text.Length > 0)
{
btnLogin_Click(null, null); //login
}
else
{
//MessageBox.Show("Please Give a Password");
txtPassword.Focus();
}
}
else
{
//MessageBox.Show("Please Give a username");
txtUserName.Focus();
}
}
//if (txtPassword.ContainsFocus)
//{
// btnLogin_Click(sender, e); //login
//}
//else
//{
// this.txtPassword.Focus();
//}
}
回答4:
Actually, you have two issues.
First off, set the AutoCompleteMode property of txtUserName to "SuggestAppend" instead of simply "Suggest." This way, if the user types the first letter or two, the correct entry will automatically be appended to the txtUSerName.Text.
Next, modify your Form code as follows:
void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode.Equals(Keys.Enter)) //Invokes whenever Enter is pressed
{
if (txtPassword.ContainsFocus)
{
btnLogin_Click(sender, e); //login
}
else
{
this.txtPassword.Focus();
}
}
}
private void btnLogin_Click(object sender, EventArgs e)
{
if (txtUserName.Text.Equals("Administrator") && txtPassword.Text.Equals("123"))
{
MessageBox.Show("Administrator");
}
else if (txtUserName.Text.Equals("Clerk") && txtPassword.Text.Equals("123"))
{
MessageBox.Show("Clerk");
}
else
{
MessageBox.Show("Please Enter correct details", "Login Error");
}
}
In the above, the Key Down event handling code tests to see if the password text box has the focus (meaning, the user has presumable entered a user name already, and a password, and is ready to submit the data). If so, the btnLogin_Click event is called. Otherwise, (meaning, txtUserName probably has the focus) control is passed to txtPassword to continue data entry.
UPDATE: re - Your comment:
Simply kill the logic in the Key Down Event handler like so:
Revised Event Handling Code:
void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode.Equals(Keys.Enter)) //Invokes whenever Enter is pressed
{
btnLogin_Click(sender, e); //login
}
}
Note, another minor improvement (considering the overall structure of your code) would be to use a combo box for selection of UserName, and set the autocomplete source to "ListItems" then enter your options the same as with the text box. This requires the user to select from the pre-defined list. This still has scaleability issues similar to the previous, but eliminates an unnecessary step for the user if they simply make a typo while entering User Name data.
Remember that users tend to dislike unnecessary interruption by pop-up messages. Allow them to select the appropriate "user name" from a drop down, type in the proper password, and move on.
There are some better ways to do all of this, but this should tune up what you have into working order.
On a final note, let me observe that eventually you will probably want to find a more robust way of performing this sort of validation. Any time you need to add users (which, in your code, appear to be defined more as "groups" you will need to add to your conditional event handling tree.
You might check into persisting usernames and passwords in an encrypted file or database, and load them into a dictionary or something at run time. Then perform a key/value lookup on user/Password.
Or something.
Anyway, hope that helps.
UPDATE 2: The complete code all in one shot. This should behave the way you are asking:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.KeyDown +=new KeyEventHandler(Form1_KeyDown);
}
void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode.Equals(Keys.Enter)) //Invokes whenever Enter is pressed
{
btnLogin_Click(sender, e); //login
}
}
private void btnLogin_Click(object sender, EventArgs e)
{
if (txtUserName.Text.Equals("Administrator") && txtPassword.Text.Equals("123"))
{
MessageBox.Show("Administrator");
}
else if (txtUserName.Text.Equals("Clerk") && txtPassword.Text.Equals("123"))
{
MessageBox.Show("Clerk");
}
else
{
MessageBox.Show("Please Enter correct details", "Login Error");
}
}
}
}
来源:https://stackoverflow.com/questions/12759177/disable-key-event-when-the-focus-is-on-autocompletion-box-of-textbox