All of my controls inherit from a base class that creates and assigns a OnAccept and OnCancel to the Enter and ESC keys.
private readonly Button _accept, _cancel;
public ViewUserControl()
{
_accept = new Button();
_cancel = new Button();
_accept.Click += (o, e) => OnAccept();
_cancel.Click += (o, e) => OnCancel();
}
// the base function depends on the child functions to implement a accept/cancel function, if it doesn't then those events will fire to the
// new button and not be used for anything
public virtual IButtonControl GetAcceptButton()
{
return _accept;
}
public virtual IButtonControl GetCancelButton()
{
return _cancel;
}
protected virtual void OnAccept() { }
protected virtual void OnCancel()
{
this.ClosingEvent();
}
However, when the user is in a multiline textbox, the enter key is kicking off the OnAccept of the form rather than putting a new line into the textbox (which is the expected behaviour).
Currently, to get around this, i have to find the focused control of the form and if it's a textbox, then manually put the newline in. However when I do this, the cursor resets to the start of the textbox.
protected override void OnAccept()
{
var focused = FindFocusedControl(this);
if (focused is TextBox)
{
focused.Text += Environment.NewLine;
}
else
{
base.OnAccept();
}
}
public static Control FindFocusedControl(Control control)
{
var container = control as ContainerControl;
while (container != null)
{
control = container.ActiveControl;
container = control as ContainerControl;
}
return control;
}
My questions are:
Is there a way to bypass the OnAccept event so the enter event is recognised by the textbox?
Is there a way to call the textbox's enter event manually?
How do I set the cursor to the end of the textbox after I manually put in a line break?
An answer to any of these questions will achieve the result that I'm after, ordered in preference of solution.
UPDATE:
I did find a way to move the caret (not cursor as I called it in the original question) to the end using RichTextBox.SelectionStart however, I'd prefer a more elegant solution.
UPDATE 2:
For anyone else with the same problem, this is what I now do:
From the child control:
txtDetails.GotFocus += (o,e) => AcceptButtonStatus(false);
txtDetails.LostFocus += (o, e) => AcceptButtonStatus(true);
From the base control:
protected void AcceptButtonStatus(bool enabled)
{
this.ParentForm.AcceptButton = enabled?_accept:null;
}
So whenever the textbox gets focus, I remove the accept button from the form.
Here is a post on how to externally call component events.
How can I programmatically generate keypress events in C#?
As for the Accept event, your dialog is intercepting that before the control ever sees it. The only thing that might work would be to add a form event that watches focus changes and if the focus is a multiline text control, you set the AcceptButton control for the form to null (assuming you are using AcceptButton and CancelButton to generate the Accept/Cancel events).
Related
I have created a UserControl called Toggle, this is my code for it
[DefaultEvent("Click")]
public partial class Toggle : UserControl {
public bool ToggleStatus { get { return toggleStatus; } }
private bool toggleStatus { get; set; }
public Toggle() {
InitializeComponent();
toggleStatus = true;
}
private void toggleClick(object sender, EventArgs e) {
if (toggleStatus) { // currently set as "true" or "on"
this.lblSwitch.Dock = DockStyle.Right;
this.pnlBackground.BackColor = System.Drawing.Color.Red;
toggleStatus = false;
} else { // currently set as "false" or "off"
this.lblSwitch.Dock = DockStyle.Left;
this.pnlBackground.BackColor = System.Drawing.Color.Green;
toggleStatus = true;
}
}
}
The toggleClick method is tied to the click event of controls within the UserControl; this fires off just fine.
However, when I put my Toggle control on a form and attempt to tie an event to the click of it, it won't fire off.
private void toggleSoundClick(object sender, EventArgs e) {
soundToggle = !soundToggle;
}
I've made sure that the proper method is tied to the click event in my Designer.cs file of both my UserControl and my form
UserControl:
this.lblSwitch.Click += new System.EventHandler(this.toggleClick);
this.pnlBackground.Click += new System.EventHandler(this.toggleClick);
(I have it tied to two controls on my Toggle since I want it to fire no matter where you click on the control)
Form:
this.tglSound.Click += new System.EventHandler(this.toggleSoundClick);
The expected behavior for the UserControl is to fire off toggleClick (which it does) then the form should fire off toggleSoundClick (which it doesn't). I have seen this behavior work fine for other UserControls I have designed and used in this same project.
To clarify:
I have a UserControl called ServerDisplay. I have a method tied to the click event of the background panel of ServerDisplay (in the code for ServerDisplay) that shows a random MessageBox:
private void ServerDisplay_Click(object sender, EventArgs e) {
MessageBox.Show("test");
}
Then, I have a ServerDisplay control contained within my form. I have a method tied to the click event of it as well (in the code for my form)
private void serverDisplayClick(object sender, EventArgs e) {
if (loaded) {
ServerDisplay display = (ServerDisplay)sender;
this.lblLastServer.Text = "Last server joined was " + display.Server.Name + " at " + DateTime.Now.ToString("h:mm tt");
centerControl(this.lblLastServer);
}
}
When I click on the ServerDisplay control in my form, it shows the MessageBox (code from within ServerDisplay), then updates the label I specified in the code (code from form). This is the intended behavior, but it is not working for my other UserControl.
I finally figured it out! The way I had the control set up, I had the control itself, a panel filling up the entire background (I used this for the color), and then another panel inside the first panel to act as the "switch".
When I got rid of the first panel and just used the background of the control for the color and a small panel for the switch, it works when I click the background, but not when I click the "switch" panel. I guess this opens up more questions that I'll have to ask separately from this one, but at least I got my answer.
Essentially I need the same thing that Form.ShowDialog() offers, but with a UserControl.
Inside a winform, I load a UserControl, which should allow a user to select an item from a list, and return it back to the caller.
For example:
var item = myUserControl.SelectItem();
Obviously, returning from a control's method is very simple. But how can I make it wait until user performs the required action with the control?
I can subscribe to an event of the control, but this path is not ideal.
Put simply, I want a UserControl's method to return after user clicks a specific button on it.
Simply put, a UserControl is really just a custom control and just like you drop a TextBox or a ListBox on your WinFrom, you drop your UserControl on the form.
Treat your UserControl just like you would treat any other control, like TextBox or ListBox.
So, just like you get the value from a TextBox through TextBox.Text or the SelectedValue or SelectedItem from a ListBox, you would call a method from your UserControl to return the SelectedItem.
Often times when the OK button is clicked or the form is closed is when in your code you would go through each of your form's controls getting their values. Presumably, you would do some validation to make sure proper values were entered, too.
Therefore, when your form is accepted is when you would call your UserControl's method to get the selected item. You don't have to subscribe to an event to wait for that to happen. Again, just treat it like you would treat a normal ListBox.
EDIT:
Knowing now more about the nature of your question this is my answer:
Say you have a UserControl that looks like this:
In the code behind you are going to have to set up an Event to monitor when the the OK button has been clicked inside the UserControl. This event will also notify a subscriber what the choice was that the user selected in your list:
public partial class SelectFromListUserControl : UserControl
{
public class SelectedItemEventArgs : EventArgs
{
public string SelectedChoice { get; set; }
}
public event EventHandler<SelectedItemEventArgs> ItemHasBeenSelected;
public SelectFromListUserControl()
{
InitializeComponent();
}
private void btnOK_Click(object sender, EventArgs e)
{
var handler = ItemHasBeenSelected;
if (handler != null)
{
handler(this, new SelectedItemEventArgs
{ SelectedChoice = listBox1.SelectedItem.ToString() });
}
}
}
On your main form you will have code patterned similar to the following.
There should be a routine to create or make visible this special user control.
It will hook the event in the user control so that the main form will be notified.
It will draw the user control.
The event handler will retrieve the value selected in the user control and then clear the user control and/or bring up another user control.
private void ShowSelectFromListWidget()
{
var uc = new SelectFromListUserControl();
uc.ItemHasBeenSelected += uc_ItemHasBeenSelected;
MakeUserControlPrimaryWindow(uc);
}
void uc_ItemHasBeenSelected(object sender,
SelectFromListUserControl.SelectedItemEventArgs e)
{
var value = e.SelectedChoice;
ClosePrimaryUserControl();
}
private void MakeUserControlPrimaryWindow(UserControl uc)
{
// my example just puts in in a panel, but for you
// put your special code here to handle your user control management
panel1.Controls.Add(uc);
}
private void ClosePrimaryUserControl()
{
// put your special code here to handle your user control management
panel1.Controls.Clear();
}
Embed it in a form and call the form modally (ShowDialog)?
But how can I make it wait until user performs the required action with the control?
The question is more about how to wait for the user to select item and click OK button without blocking entire user interface.
The answer is simple: Async/Await feature.
private readonly SelectCompletionSource as new TaskCompletionSource(of ResultType)
public async function SelectItemAsync() as ResultType
me.Visible = true
return await SelectComplectionSource.Task
end function
public function OK() as boolean
me.Visible = false
dim Result = me.SelectedItem
SelectComplectionSource.
SetResult(Result)
end function
To get an Item one calls
dim Item = await UserControl.SelectItemAsync
UserControl is shown to the user without blocking user interface. The selection task is started but paused until the result is ready.
By clicking OK button, user invokes OK function that queries selected item and makes selection task into completed state.
When coding a small game, I encountered a problem; my form's KeyDown and KeyUp events don't fire at all.
This is the form's code:
public class GameForm : Form
{
private ControllableBlock player;
public GameForm()
{
KeyDown += Game_KeyDown;
KeyUp += Game_KeyUp;
player = new ControllableBlock();
Controls.Add(player);
}
private void Game_KeyDown(object sender, KeyEventArgs e)
{
player.ReactToKey(e.KeyCode);
}
private void Game_KeyUp(object sender, KeyEventArgs e)
{
player.ReactToKey(e.KeyCode);
}
}
There's a lot more going on, but I only pasted the relevant code.
I've already tried setting this.KeyPreview = true; and calling this.Focus();, neither works.
The problem is not in ReactToKey() method, I've already set a breakpoint there and the event is never fired.
Edit: After some tests I've come to a conclusion that the problem is within my ControllableBlock.
Yet, I have no idea why, but I'm working on it.
If I comment out everything that's related to the player, the events start firing.
Edit 2: Seems like the problem is me inheriting my ControllableBlock from Control.
If I inherit it from Panel, it works fine.
Why is this?
Can't I fire an event if I inherit from control?
The ControllableBlock class is empty for now, so it doesn't even do anything other than inherits from Control.
Edit 3: Now that I've started a bounty, I'd like to clarify that I'm not looking for a solution on how to make the events fire, I'm looking for a reason on why they don't fire if I inherit from Control.
If your events should be application-wide try to set property KeyPreview to true - it will allow you to fire respective events regardless of focused control.
this.KeyPreview = true;
Otherwise you should attach these events directly to control that will process them.
Edit:
I removed InitializeComponent(); from my form and got behaviour identical to yours.
After implementing solution provided in this question all events started to qork perfectly.
Copy code snippet here:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
if (keyData == Keys.Left) {
// Do your staff for Left Key here
return true;
}
// you need to add if condition to every direction you want to handle
return base.ProcessCmdKey(ref msg, keyData);
}
I was able to reproduce a similar issue (which is hopefully related..)
Explanation:
Controls which return CanSelect==true are selectable for keyboard input
A blank descendent of Control() is selectable, one of Panel() is not
The first selectable control added to a form will get selected
A selected control will steal keyboard events from its parents by default
Certain keys used for navigation within a window require extra steps to be handleable
Check here for a good overview of how windows keyboard input works.
Code to reproduce it:
public class GameForm : Form
{
public GameForm()
{
this.KeyDown += Game_KeyDown;
var tests = new List<Control[]>() {
new[] { new Panel() },
new[] { new Panel(), new Panel() },
new[] { new Control() },
new[] { new Control(), new Panel() },
new[] { new Panel(), new Control() }
};
// When test index 2 to 4 used, keyboard input does not reach form level
Controls.AddRange(tests[0]);
// When uncommented, ensures all keyboard input reaches form level
/*this.KeyPreview = true;
// Additional plumbing required along with KeyPreview to allow arrow and other reserved keys
foreach (Control control in this.Controls)
{
control.PreviewKeyDown += control_PreviewKeyDown;
}*/
}
void control_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
e.IsInputKey = true;
}
private void Game_KeyDown(object sender, KeyEventArgs e)
{
// breakpoint here
Debug.WriteLine(e.KeyCode);
}
}
You need to make your control selectable before it can receive the focus.
Try adding the following to your constructor:
this.SetStyle(ControlStyles.Selectable, true);
And ensure that you give your form focus after it has been displayed. Or, override OnMouseDown() and call this.Focus() in it.
try moving the handler setup to the Form_Load event rather than the constructor. Should there not be a call to Initialize() in the constructor? I wouldn't particularly recommend removing it
If ControllableBlock inherits from Panel, it will have more event hookups and better UI interaction setup than a base Control object.
I have a comboxbox (okay, in real a have a ToolStripComboBox) where I want a cancleable event that is triggered under certain conditions:
Focus lost
Focus gained
Item selected from the box
pressing Enter
so a "normal" validation event, but when I do the following
this.speedSelector.Validating
+= new System.ComponentModel.CancelEventHandler(this.speedSelector_Validating);
This event is only triggered, when I try to close the application via [X]. Also I can't leave the application when a not valid text is present, that works, but how to trigger that event on my conditions above?
Regards,
You will probably need to store the initial value somewhere (like maybe in the Control's universal Tag field).
You could validate the control on any of the events: SelectedIndexChanged, SelectionChanged, TextUpdate, etc.
The value stored in the control should not change when the control gains or loses focus.
public Form1() {
InitializeComponent();
speedSelector.Tag = speedSelector.Text;
speedSelector.SelectedIndexChanged += new System.EventHandler(this.speedSelector_Changed);
speedSelector.SelectionChangeCommitted += new System.EventHandler(this.speedSelector_Changed);
speedSelector.TextUpdate += new System.EventHandler(this.speedSelector_Changed);
}
private void speedSelector_Changed(object sender, EventArgs e) {
if (validData(speedSelector.Text)) {
speedSelector.Tag = speedSelector.Text;
} else {
speedSelector.Text = speedSelector.Tag.ToString();
}
}
private static bool validData(string value) {
bool result = false;
// do your test here
return result;
}
Validating will be called when moving focus from a control on the dialog that has the CausesValidation property set to true to another control that has the CausesValidation property set to true, e.g. from a TextBox control to the OK button. Maybe your validation happens when you close the window because you have CausesValidation set on the window, and not on the appropriate controls?
You could also just move all the validation into an OnBlur event for your control and do it that way.
I am using c# winform.
I have 2dimensional array of text boxes I want them to accept only Letters from A-I I've created the method but that works for only one text box.
Here is my code:
textbox[i,j].Validated+=new EventHandler(TextBox_KeyPress);
private void TextBox_KeyPress(object sender, EventArgs e)
{
bool bTest = txtRegExStringIsValid(textbox[1,1].Text.ToString());
ToolTip tip = new ToolTip();
if (bTest == false)
{
tip.Show("Only A-I", textbox[1,1], 2000);
textbox[1,1].Text = " ";
}
}
private bool txtRegExStringIsValid(string textToValidate)
{
Regex TheRegExpression;
string TheTextToValidate;
string TheRegExTest = #"^[A-I ]+$";
TheTextToValidate = textToValidate;
TheRegExpression = new Regex(TheRegExTest);
if (TheRegExpression.IsMatch(TheTextToValidate))
{
return true;
}
else
{
return false;
}
}
Can anyone please guide what should I do make this code work for all text boxes?
if this works for textbox[1,1] you could register your private void TextBox_KeyPress(object sender, EventArgs e) as eventhandler for all your textboxes and instead of textbox[1,1] you could use ((TextBox)sender)
i want text boxes to accept only letters from a-i actually i am trying to make sudoku
There's a much simpler solution than regular expressions, and you don't even need to handle the Validated event to implement it.
In a situation like this, where there are only certain characters that you want to prevent the user from entering, handling the KeyDown event is a much better solution. The user gets immediate feedback that the letter they tried to enter was not accepted. The alternative (the Validating and Validated events) actually wait until the user tries to leave the textbox to rudely alert them that their input was invalid. Especially for a game, this tends to break concentration and isn't particularly user-friendly.
Doing it this way also makes it irrelevant which individual textbox raised the event. Instead, you will handle it the same way for all of the textboxes—by completely ignoring all invalid input.
Here's what I'd do:
First, attach a handler method to your textbox's KeyDown event. You can do this from the Properties window in the designer, or you can do it through code, as you have in the question:
textbox[i,j].KeyDown += TextBox_KeyDown;
Then, you need to put the logic into your event handler method that determines if the key that the user just pressed is in the allowed range (A through I), or outside of it:
private void TextBox_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
// Determine if the keystroke was a letter between A and I
if (e.KeyCode < Keys.A || e.KeyCode > Keys.I)
{
// But allow through the backspace key,
// so they can correct their mistakes!
if (e.KeyCode != Keys.Back)
{
// Now we've caught them! An invalid key was pressed.
// Handle it by beeping at the user, and ignoring the key event.
System.Media.SystemSounds.Beep.Play();
e.SuppressKeyPress = true;
}
}
}
If you want to restrict the user to typing in only one letter, you can add code to handle that in the above method, or you can take an even simpler route and let the textbox control handle it for you automatically. To do that, set the MaxLength property of the textbox to true, either in the designer or through code:
textbox[i,j].MaxLength = true;
Check the text of the sender instead of whatever textbox[1,1] is.
Use the sender parameter of the event handler to identify the textbox responsible for the event.
The first thing that will help you is casting the sender of your event to a TextBox like this:
(Also, as Cody Gray said, this is a TextBox_Validated event, not a KeyPress event so I've renamed it appropriately)
private void TextBox_Validated(object sender, EventArgs e)
{
TextBox tb = sender as TextBox()
if (sender == null)
return;
bool bTest = txtRegExStringIsValid(tb.Text.ToString());
ToolTip tip = new ToolTip();
if (bTest == false) {
tip.Show("Only A-I", tb, 2000);
tb .ext = " ";
}
Next you need to actually get into that code for every textbox. There are two obvious approaches to that, you can either assign the eventhandler to each textbox in the array or you can use a custom textbox which always does this validation and then add that to your array.
Assign eventhandler to textboxes
foreach(var tb in textbox)
{
tb.Validated += new EventHandler(TextBox_KeyPress);
}
Create custom textbox control
Create the custom text box control (Add a user control to the project) and then just use it exactly as you would a normal textbox.
public partial class ValidatingTextBox: TextBox
{
public ValidatingTextBox()
{
InitializeComponent();
}
protected override void OnValidating(CancelEventArgs e)
{
bool bTest = txtRegExStringIsValid(this.Text.ToString());
ToolTip tip = new ToolTip();
if (bTest == false)
{
tip.Show("Only A-I", this, 2000);
this.Text = " ";
}
}
private bool txtRegExStringIsValid(string textToValidate)
{
// Exactly the same validation logic as in the same method on the form
}
}