Preventing double events in DataGridView - c#

I have a problem that I am not sure how to solve. I have a DataGridView (EditMode = EditProgrammatically). This grid has one checkbox column and one or more textbox columns. The functionality is as following (or should be at least):
When you click on a checkbox, the checkbox should be toggled
When a row (or many rows) are selected, and you press space, the checkboxes should be toggled.
I have these two event handlers:
private void grid_CellClick(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex >= 0 && e.ColumnIndex == useColumn.Index)
{
if (ModifierKeys != Keys.Shift && ModifierKeys != Keys.Control)
{
ToggleRows(grid.SelectedRows);
}
}
}
private void RowSelectorForm_KeyDown(object sender, KeyEventArgs e)
{
if (grid.Focused && e.KeyCode == Keys.Space)
{
ToggleRows(grid.SelectedRows);
e.Handled = true; // Not sure if this is needed or even does anything
e.SuppressKeyPress = true; // Or this for that matter...
}
}
This almost works. The problem is when you press space and a checkbox cell is active. When a textbox cell is active, it works like it should. The problem is that when you press space and a checkbox cell is active, both events gets fired. Which means it first selects and then deselects (or the reverse). So the checkboxes end up being like they was. How can I prevent this?
I have thought about using a flag, but not sure where I can put it, since I can't really know if it was a double event or if it was just the user using space and then clicking with the mouse. So that can't really be used I think. Is there a different event I should use? Is there a way to see if the cell was clicked by mouse or by space? Is there a way to disable the automatic checkbox toggling when space is pressed? What can I do?
Note: Reason for RowSelectorForm_KeyDown and not just grid_KeyDown was that I was trying to use KeyPreview and then suppress the keypress if it was space and the grid was focused. But that SuppressKeyPress doesn't really seem to do anything at all =/ Maybe I've just misunderstood it...

Well, I didn't want to do it, but I have now fixed it with a timer... but if anyone knows how to do it properly, please let me know!!
Current solution:
private DateTime lastClick = DateTime.MinValue;
and in both events:
if (DateTime.Now - lastClick > TimeSpan.FromMilliseconds(400))
{
lastClick = DateTime.Now;
ToggleRows(grid.SelectedRows);
}

It has been a time since I worked with C#, but I assume that you can disconnect the grid_CellClick event handler before calling ToggleGrid in RowSelectorForm_KeyDown.
After the call, you can reconnect the event handler.
Also, there might be some way to supress the event from being fired in the first place. In some API's certain methods are specially provided that don't trigger any events.

The frustrating problems are:
.Handled doesn't prevent the check box from changing
.SuppressKeyPress doesn't prevent the checkbox from changing
If you DoubleClick on the checkbox, the first click fires the Click() event (toggling the checkbox) and the second click fires the DoubleClick() event (toggling the checkbox yet again).
However, the KeyDown event fires before any of the grid events. Perhaps setting a flag to indicate the spacebar was pressed or if the row was already selected, reset the value of the checkbox.

I used the following and it seemed to work well:
private Keys _ClickSource = 0;
private void dgv_CellClick(object sender, System.Windows.Forms.DataGridViewCellEventArgs e)
{
if (_ClickSource == 0 || _ClickSource != Keys.Space)
{
dgv.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = ! (System.Convert.ToBoolean(dgv.Rows[e.RowIndex].Cells[e.ColumnIndex].Value));
}
_ClickSource = null;
}
private void dgv_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
_ClickSource = e.KeyCode;
}

Related

C#.NET Winform keypress event can't be cancelled

I have a (.NET 3.5) winform with a datagridview on which I added an event on checkboxes in the gridview like this. That post doesn't take into account that people can also use spacebar to toggle the checkbox, and because there is no CellKeyUp event like there is a CellMouseUp event, I enabled KeyPreview on the form and added this code to prevent toggling with the spacebar:
private void BulkOrderAddressDifferencesForm_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Space)
{
e.Handled = true;
e.SuppressKeyPress = true;
}
}
That works mostly, but there is a scenario in which the event is still handled, even though the debugger shows e.Handled is set to true.
If I click on a checkbox, then 1, then 2, I can toggle the checkbox with the space bar again. I have no idea why this happens, nor do I know how to fix it.
You can override Form's ProcessCmdKey method:
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Space && checkBox1.Focused)
{
//instead of checkBox1.Focused condition, you check if your DataGridView contains focus and active cell is of checkBox type
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
If the goal is to always react immediately when the check is changed, rather than preventing the use of the spacebar (Unless I'm mistaken, the problem is that the cellmouseup approach doesn't include (un)checking with space, rather than the goal is that space shouldn't be used at all? ), you could use the celldirtychanged approach instead of cellmouseup to catch both
//grid.CurrentCellDirtyStateChanged += grid_CurrentCellDirtyStateChanged;
void grid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (grid.IsCurrentCellDirty)
{
var cell = grid.CurrentCell;
if (cell is DataGridViewCheckBoxCell)
{
grid.EndEdit();
//you could catch the cellvaluechanged event (or a bound listchanged event), or handle the change immediately here, e.g.:
//Console.WriteLine("{0} value changed to {1}", cell.OwningColumn.HeaderText, cell.Value);
}
}
}
How about the DataGridView.EditMode Property which
Gets or sets a value indicating how to begin editing a cell.
where
The default is EditOnKeystrokeOrF2.
and
All DataGridViewEditMode values except for EditProgrammatically allow a user to double-click a cell to begin editing it.
You have several options to choose from the DataGridViewEditMode Enumeration
EditOnEnter - Editing begins when the cell receives focus. This mode is useful when pressing the TAB key to enter values across a row, or when pressing the ENTER key to enter values down a column.
EditOnF2 - Editing begins when F2 is pressed while the cell has focus. This mode places the selection point at the end of the cell contents.
EditOnKeystroke - Editing begins when any alphanumeric key is pressed while the cell has focus.
EditOnKeystrokeOrF2 - Editing begins when any alphanumeric key or F2 is pressed while the cell has focus.
EditProgrammatically - Editing begins only when the BeginEdit method is called.
Update for DataGridViewCheckBoxCell:
It turns out that the DataGridViewEditMode does not work for the DataGridViewCheckBoxColumn.
In this case you can create your own DataGridViewCheckBoxColumn & DataGridViewCheckBoxCell. This allows you to override the cell's OnKeyUp event handler and reset the EditingCellFormattedValue if Space was pressed.
public class MyCheckBoxColumn : DataGridViewCheckBoxColumn
{
public MyCheckBoxColumn()
{
CellTemplate = new MyCheckBoxCell();
}
}
public class MyCheckBoxCell : DataGridViewCheckBoxCell
{
protected override void OnKeyUp(KeyEventArgs e, int rowIndex)
{
if (e.KeyCode == Keys.Space)
{
e.Handled = true;
if (EditingCellValueChanged)
{
// Reset the value.
EditingCellFormattedValue = !(bool)EditingCellFormattedValue;
}
}
else
{
base.OnKeyUp(e, rowIndex);
}
}
}
After you rebuild your project the new column should appear in the designer:

Defocusing TextBox by clicking it again

I'm making a settings form, where user can assign custom hotkeys for the application. There's a TextBox, and by clicking it with mouse, it focuses and waits for one keypress and then defocuses (by focusing another label):
private void txtKey_KeyDown(object sender, KeyEventArgs e)
{
e.SuppressKeyPress = true;
}
private void txtKey_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
TextBox textBox = (TextBox)sender;
textBox.Text = e.KeyCode.ToString();
label1.Focus();
}
Is there a way to defocus focused TextBox (and cancel the key assinging process), by either clicking it again with mouse, or by clicking the GroupBox around it? I can't figure out how to check if TextBox was already focused when clicked (because when clicked, it gets focused before I can test if it's focused). Of course I can add a button "Cancel" next to the TextBox, but that's not what I want.
There is no Click-event for GroupBox, so I can't defocus TextBox by clicking GroupBox around it. Or can I somehow?
You can set/remove the Focus with
Keyboard.Focus = null;
You can also register to the following event:
public event MouseButtonEventHandler PreviewMouseLeftButtonDown
This event fires every time you click on the TextBox, thus you can set the Focus there if you want to.
For Winforms there is a way as well. I'm not proficient in it, but here would be a way:
Make a textBox (e.g. named textBoxFocus) that lies outside your window. Size it 1, 1 and move it to -10,-10 for example. Then you can register to the Click event and write
textBoxFocus.Focus();
It's a bit of a roundabout way, but should achieve what you want.
Thanks to private_meta for getting me to right direction (in comments)! I set the flag with click event, and before setting the flag, testing if flag is set. So first click does not find the flag, but second will. And flag is cleared within textbox Enter-event (which fires before Click-event). Now every other click focuses and every other defocuses textbox, as I wanted.
private void txtKey_Enter(object sender, EventArgs e)
{
TextBox textBox = (TextBox)sender;
textBox.Tag = null;
}
private void txtKey_Click(object sender, EventArgs e)
{
TextBox textBox = (TextBox)sender;
if (textBox.Tag != null) label1.Focus();
textBox.Tag = "clicked";
}
One of the simple way is that, you may use a bool flag here.
Algorithm:
By default, the bool value is 0;
If(Textbox Selected && flag = 0)
Do your task; and flag = 1;
I hope I could satisfy your query and you can follow this algorithm.

DataGridView - how can I make a checkbox act as a radio button?

I have a Windows Forms app that displays a list of objects in a DataGridView.
This control renders bool values as checkboxes.
There's a set of three checkboxes in the object properties that are mutually exclusive. At most one of them can be true. Accordingly I'd like the checkboxes to act like a set of radio buttons.
Just a side remark from the old guy: I think people these days don't even know why these are called radio buttons. In the old days a radio in a car had 4 or 5 buttons, and depressing any one of them caused all the others to pop out. They were mutually exclusive. "Radio button" is probably not a helpful description these days because radios don't have buttons like that anymore, I don't think.
How can I do it? I figure if I attach a "CheckedChanged" event to the checkboxes, and I know the row, I will be able find all the other checkboxes.
What event can I hook to grab the checkbox control when it is first rendered, so that I Can attach the CheckedChanged event to it? I know about DataGridView.CellFormatting, but I think that's wrong because it gets called every time the DataGridView paints. I really need an event that is called only the first time the DGV is rendered.
Thanks to KeithS for the helpful answer.
When I looked in the doc for CellValueChanged, I found this helpful bit:
The DataGridView.CellValueChanged event occurs when the user-specified value is committed, which typically occurs when focus leaves the cell.
In the case of check box cells, however, you will typically want to handle the change immediately. To commit the change when the cell is clicked, you must handle the DataGridView.CurrentCellDirtyStateChanged event. In the handler, if the current cell is a check box cell, call the DataGridView.CommitEdit method and pass in the Commit value.
This is the code I used to get the radio behavior:
void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
// Manually raise the CellValueChanged event
// by calling the CommitEdit method.
if (dataGridView1.IsCurrentCellDirty)
{
dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
public void dataGridView1_CellValueChanged(object sender,
DataGridViewCellEventArgs e)
{
// If a check box cell is clicked, this event handler sets the value
// of a few other checkboxes in the same row as the clicked cell.
if (e.RowIndex < 0) return; // row is sometimes negative?
int ix = e.ColumnIndex;
if (ix>=1 && ix<=3)
{
var row = dataGridView1.Rows[e.RowIndex];
DataGridViewCheckBoxCell checkCell =
(DataGridViewCheckBoxCell) row.Cells[ix];
bool isChecked = (Boolean)checkCell.Value;
if (isChecked)
{
// Only turn off other checkboxes if this one is ON.
// It's ok for all of them to be OFF simultaneously.
for (int i=1; i <= 3; i++)
{
if (i != ix)
{
((DataGridViewCheckBoxCell) row.Cells[i]).Value = false;
}
}
}
dataGridView1.Invalidate();
}
}
The one you want is CellContentClick, on the DGV itself. Attach a handler that checks to see if that column of the DGV is a CheckBoxCell, and if so, uncheck all other checkboxes on the row.
Just a note though, for a CheckBoxCell, this event fires before the checkbox value actually changes. This means that regardless of what you do to the current cell, it will be overridden by events that fire later. The behavior that will shake out of this is that you can have none of the cells on a row checked, by checking one box on the row and then checking it again (whether you try to set the checkbox value in the handler or not, the checkbox will end up cleared after the second click). To overcome that and force one of the checkboxes to be checked, you can handle CellValueChanged instead, and if the cell that changed is the current cell and is unchecked, check it.
private void dataGridViewProduit_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if ((sender as DataGridView).CurrentCell is DataGridViewCheckBoxCell)
{
if (Convert.ToBoolean(((sender as DataGridView).CurrentCell as DataGridViewCheckBoxCell).Value))
{
foreach (DataGridViewRow row in (sender as DataGridView).Rows)
{
if (row.Index != (sender as DataGridView).CurrentCell.RowIndex && Convert.ToBoolean(row.Cells[e.ColumnIndex].Value) == true)
{
row.Cells[e.ColumnIndex].Value = false;
}
}
}
}
}
private void dataGridViewClient_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (this.dataGridViewClient.IsCurrentCellDirty)
{
dataGridViewClient.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
It would get too easy here:
Conisder your checkbox column is the 2nd column in your datagridview.
private void YourDatagridview_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (IsHandleCreated)
{
if (YourDatagridview.CurrentCell == YourDatagridview.Rows[e.RowIndex].Cells[1])
{
if (Convert.ToBoolean(YourDatagridview.CurrentCell.Value) == true)
{
for (int i = 0; i < YourDatagridview.RowCount; i++)
{
if (YourDatagridview.Rows[i].Cells[1] != YourDatagridview.CurrentCell)
{
YourDatagridview.Rows[i].Cells[1].Value = false;
}
}
}
}
}
}
And call this too:
private void YourDatagridview_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (this.YourDatagridview.IsCurrentCellDirty)
{
YourDatagridview.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
and Voila!!

How to detect if an event is sent by an argument

I don't know if it is called an argument (i.e. textbox1.text = "Hello";).
I have a control and there is a text box in it. It has a dropdown box that opens when the text is changed. But when I update the text in the text box that box drops down.
I need a way to make it so it only drops down if someone manually does it.
TBAddressBar.ABText.Text = getCurrentBrowser().Source.ToString();
and
public void ABText_TextChanged(object sender, TextChangedEventArgs e)
{
if (sender == 1*)
{
ABDropDown.Visibility = Visibility.Visible;
}
else
{
ABDropDown.Visibility = Visibility.Collapsed;
}
}
If someone manually does it, presumably they are using keypresses to do so. In that case, use KeyDown or KeyUp events to show the dropdown instead.
What I have done in the past is use a boolean variable that I set when I update my textboxes programically to bypass the TextChangedEvent.
i.e.
bool loading;
....
loading =true;
TBAddressBar.ABText.Text = getCurrentBrowser().Source.ToString();
loading = false;
public void ABText_TextChanged(object sender, TextChangedEventArgs e)
{
if(loading) return;
....
}
Simple, just remove the code from your TextChanged Event.
Anyway you got the basic idea.. Now do your dropdown logic in KeyPress event, since it accepts only characters and not the modifiers. So it behaves closer to your requirement. Not that you cant handle the same using KeyDown and KeyUp, you can, but more code..

Validate on text change in TextBox

I have implemented validation rules on a textBox in my WinForm and it works well. However it checks the validation only when I tab out of the field. I would like it to check as soon as anything is entered in the box and everytime the content changes. Also I'd like it to check validation as soon as the WinForm opens.
I remember doing this fairly recently by setting some events and whatnot, but I can't seem to remember how.
If you're using databinding, go to the Properties of the textbox. Open (DataBindings) at the top, click on the (Advanced) property, three dots will appear (...) Click on those. The advanced data binding screen appears. For each property of the TextBox that is bound, in your case Text, you can set when the databinding, and thus the validation, should "kick in" using the combobox Data Source Update mode. If you set it to OnPropertyChanged, it will re-evaluate as you type (the default is OnValidation which only updates as you tab).
TextChanged event
in the future you can find all of the events on the MSDN library, here's the TextBox class reference:
http://msdn.microsoft.com/en-us/library/system.windows.forms.textbox(VS.80).aspx
How will your data be valid if it isn't finished? i.e. a user types a number and you try and validate it as a date?
When binding your textbox to a bindingSource go to Advanced and select validation type
"On Property Changed". This will propagate your data to your entity on each key press.
Here is the screen shot
You should be checking on KeyPress or KeyDown events and not just your TextChanged event.
Here is a C# Example direct from the MSDN documentation:
// Boolean flag used to determine when a character other than a number is entered.
private bool nonNumberEntered = false;
// Handle the KeyDown event to determine the type of character entered into the control.
private void textBox1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
// Initialize the flag to false.
nonNumberEntered = false;
// Determine whether the keystroke is a number from the top of the keyboard.
if (e.KeyCode < Keys.D0 || e.KeyCode > Keys.D9)
{
// Determine whether the keystroke is a number from the keypad.
if (e.KeyCode < Keys.NumPad0 || e.KeyCode > Keys.NumPad9)
{
// Determine whether the keystroke is a backspace.
if(e.KeyCode != Keys.Back)
{
// A non-numerical keystroke was pressed.
// Set the flag to true and evaluate in KeyPress event.
nonNumberEntered = true;
}
}
}
//If shift key was pressed, it's not a number.
if (Control.ModifierKeys == Keys.Shift) {
nonNumberEntered = true;
}
}
// This event occurs after the KeyDown event and can be used to prevent
// characters from entering the control.
private void textBox1_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
{
// Check for the flag being set in the KeyDown event.
if (nonNumberEntered == true)
{
// Stop the character from being entered into the control since it is non-numerical.
e.Handled = true;
}
}

Categories