CurrentRow in Data Grid giving first row after Selecting differant Row - c#

I have a C# project (VS 2017) that uses a Data Grid View to show data in an object list. Using a contextMenuStrip I want to be able to right click on a row and be able to remove it from the datagridview and the underlying datasource.
I have the contextMenuStrip set in the Properties of the Datagridview with one item with the following to methods to handle the events.
private void dgv_Test_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
var hti = dgv_Test.HitTest(e.X, e.Y);
dgv_Test.ClearSelection();
dgv_Test.Rows[hti.RowIndex].Selected = true;
}
}
private void cms_DGV_Remove_Click(object sender, EventArgs e)
{
MessageBox.Show("Content Menu Clicked on Remove Option");
PersonModel temp = (PersonModel)dgv_Test.CurrentRow.DataBoundItem;
string msg = $"The index for the selected Person is {temp.Id}.";
MessageBox.Show(msg);
}
I expect this to sent the current row to the row that is right clicked on. This is not happening as the CurrentRow is staying on the top row. It does work if I first Left click on the row then right click the same row.

The problem you are describing is coming from the cms_DGV_Remove_Click event. When the user right-clicks on the grid, this is NOT going to make the cell/row underneath the cursor the grid’s CurrentRow. Even though the code in the dgv_Test_MouseDown method sets the row to “selected”…. it isn’t necessarily going to be the “current” row. The grids CurrentRow property is read only and you cannot directly set it from your code.
Given this, it is clear that getting the mouse coordinates “in relation to the grid” FROM the context menu will take a little effort since its coordinates are global. You have apparently noticed this from wiring up of the grids MouseDown event. This event makes it easy to capture the mouse position in relation to the grid. Problem is… you are NOT saving this info. By the time the context menu fires, that info is LOST.
Solution: make the DataGridView.HitTest info global. Then, set it every time the user right clicks into the grid. With this global variable set, when the context menu fires it will know which row the cursor is under.
DataGridView.HitTestInfo HT_Info; // <- Global variable
private void dgv_Test_MouseDown(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Right) {
HT_Info = dgv_Test.HitTest(e.X, e.Y);
if (HT_Info.RowIndex >= 0) {
dgv_Test.ClearSelection();
dgv_Test.Rows[HT_Info.RowIndex].Selected = true;
}
}
}
It does not appear that the posted code is actually removing the row, however below is how the context menu “remove” event may look…
Below should work for a non-data bound grid and also a grid with a data bound DataTable.
private void cms_DGV_Remove_Click(object sender, EventArgs e) {
if (HT_Info.RowIndex >= 0) {
dgv_Test.Rows.Remove(dgv_Test.Rows[HT_Info.RowIndex]);
}
}
If you are using a List<T>, the method to remove may look something like below...
private void cms_DGV_Remove_Click(object sender, EventArgs e) {
if (HT_Info.RowIndex >= 0) {
PersonModel targetPerson = (PersonModel)dgv_Test.Rows[HT_Info.RowIndex].DataBoundItem;
AllPersons.Remove(targetPerson);
dgv_Test.DataSource = null;
dgv_Test.DataSource = AllPersons;
}
}
I am guessing this is the behavior you are looking for. The user right-clicks into the grid, the row under the cursor is selected and the context menu “remove” pops up. The user can either “select” remove to remove the row or click away from the context menu to cancel the remove.
Hope that makes sense.

Related

How to fix listview checkbox behaviour?

I created Listbox with property checkboxes == true, but the problem that I had was that I was needed to click twice on the line in order to set it checked. I was needed to change it in such a way that I can click on the line just once and the line set as checked. What I did is added mouseClick event:
private void Cbl_folders_MouseDown(object sender, MouseEventArgs e)
{
SelectedListViewItemCollection selectedItemsList = Cbl_folders.SelectedItems;
if(selectedItemsList.Count > Constants.EMPTY_COUNT)
{
selectedItemsList[0].Checked = !selectedItemsList[0].Checked;
}
}
And everything works fine, first, click on the line set the line as checked the second click on the line set the line as unchecked. But then I found out that if you clicked on the line and set this line as checked and then you click on the checkbox on the other line, so your first line that was checked changes the state to unchecked. Why? Because I am tracking mouseDown event and even when I click on the checkbox on the other line mouse down event looks on selected items and the obviously selected item is the first line that was clicked.
I understand that it is possible to add some flags and look where was a click and so on, but it seems overcomplicated, I feel like there is should be a simpler solution.
Handle the MouseDown event to switch the ListViewItem.Checked property when you click over the label area. To get info about the clicked area, call the ListView.HitTest method which returns a ListViewHitTestInfo object and check the Location property, the property returns one of the ListViewHitTestLocations values.
private void Cbl_folders_MouseDown(object sender, MouseEventArgs e)
{
if (Cbl_folders.CheckBoxes)
{
var ht = Cbl_folders.HitTest(e.Location);
if (ht.Item != null && ht.Location == ListViewHitTestLocations.Label)
ht.Item.Checked = !ht.Item.Checked;
}
}
This way, the items are checked/unchecked by the mouse also when you click outside their check boxes areas (determined by the ListViewHitTestLocations.StateImage value).

Trigger Grid row validation when selection of cell editor LookUpEdit changes

I have a DevExpress.XtraGrid.GridControl which has a column using DevExpress.XtraEditors.Repository.RepositoryItemLookUpEdit as cell editors.
When I select an item from the LookUpEdit, this does not cause any validation but does nothing until I click anywhere to make the Grid lose focus.
My desired behaviour would be that changing the LookUpEdit's selection immediately triggers a row validation event of the Grid.
How could this be achieved?
The official DevExpress Documentation tells to call the GridView's UpdateCurrentRow method:
There may be cases when you need to implement row validation. [...] To do so, handle the ColumnView.ValidateRow event. Note that you can also initiate row validation manually by calling the ColumnView.UpdateCurrentRow method.
I did this in the GridView's ValidatingEditor event handler:
private void gridView1_ValidatingEditor(object sender,BaseContainerValidateEditorEventArgs e)
{
(sender as GridView).UpdateCurrentRow();
}
However, now I can't add new rows to the Grid any more as end-user.
What would be the correct approach?
Update:
I am now listening to the GridView's CellValueChanging event and handling it like this:
private void gridView_CellValueChanging(object sender, CellValueChangedEventArgs e)
{
GridView gv = (sender as GridView);
gv.CellValueChanging -= gridView_CellValueChanging; // detach this event handler
gv.ActiveEditor.EditValue = e.Value;
gv.CellValueChanging += gridView_CellValueChanging; // re-attach handler
gv.CloseEditor();
}
I am sure this is not the way how one should do it, but it works for existing rows. However, it does not work on the "new row" - I still have to click anywhere else to apply changes that create a new entry. I want a new row to be created as soon as any cell of the "new row" got edited.
The editor in the cell is still open, you have to close it.
private void lookup_Closed(object sender, EventArgs e)
{
gvMyGrid.CloseEditor();
gvMyGrid.UpdateCurrentRow();
}

C# - DatagridView and ContextMenuStrip

I have a datagridview with five columns and context menu strip which have items and sub items. When I right click on last column I want to open context menu.
I tried this code, but it's open context menu strip without sub items.
dataGrid.Columns[dataGrid.Columns.Count].HeaderCell.ContextMenuStrip = contextMenuStrip1;
It looks like you want to open your ContextMenuStrip if your user right clicks the header of your DataGridView's last column. I would use the DataGridView MouseDown event and in that event check for these conditions and if they're met call the Show method of your ContextMenuStrip.
Like this:
private void dataGridView1_MouseDown(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Right) {
var ht = dataGridView1.HitTest(e.X, e.Y);
// See if the user right-clicked over the header of the last column.
if (( ht.ColumnIndex == dataGridView1.Columns.Count - 1)
&& (ht.Type == DataGridViewHitTestType.ColumnHeader)) {
// This positions the menu at the mouse's location.
contextMenuStrip1.Show(MousePosition);
}
}
}
If you mean you want to attach context menu to header of your last column, your direction is probably right. But index of last column is dataGrid.Columns.Count - 1. So, this code works fine for me:
dataGrid.Columns[dataGrid.Columns.Count - 1].HeaderCell.ContextMenuStrip = contextMenuStrip1; ?
Subitems are in place.

Open dropdown(in a datagrid view) items on a single click

How can i avoid the double click on a DropDownButton used within a DataGridView? Right now I am able to view the drop down items within the DataGridView by clicking two or more times. First time it selects the cell and second time when I click on the DropDownButton arrow, it shows the list. How can I achieve the same in a single click?
Set EditMode property of the DataGridView to EditOnEnter: link
DataGridView.EditMode - Gets or sets a value indicating how to begin editing a cell.
EditOnEnter - Editing begins when the cell receives focus.
You can achieve this by subscribing for the EditingControlShowing event of the grid and there for control of type ComboBox
ComboBox ctl = e.Control as ComboBox;
ctl.Enter -= new EventHandler(ctl_Enter);
ctl.Enter += new EventHandler(ctl_Enter);
And in the Enter event, use the property
void ctl_Enter(object sender, EventArgs e)
{
(sender as ComboBox).DroppedDown = true;
}
DroppedDown indicates as the name suggests whether the dropdown area is shown or not, so whenever the control is entered this will set it to true and display the items without the need of further clicks.
The "set EditMode property of the DataGridView to EditOnEnter" worked for me, but I found another problem: user can't delete a row by just selecting and pressing DEL key. So, a google search gave me another way to do it. Just catch the event CellEnter and check if the cell is the appropriated type to perform appropriated action like this sample code:
private void Form_OnLoad(object sender, EventArgs e){
dgvArmazem.CellEnter += new DataGridViewCellEventHandler(dgvArmazem_CellEnter);
}
void dgvArmazem_CellEnter(object sender, DataGridViewCellEventArgs e)
{
DataGridView dg = (DataGridView)sender;
if (dg.CurrentCell.EditType == typeof(DataGridViewComboBoxEditingControl))
{
SendKeys.Send("{F4}");
}
}
Now the ComboBox drops down faster and the user still delete a row by selecting a row and pressing DEL key.
That's it.

CheckedListBox Control - Only checking the checkbox when the actual checkbox is clicked

I'm using a CheckedListBox control in a small application I'm working on. It's a nice control, but one thing bothers me; I can't set a property so that it only checks the item when I actually check the checkbox.
What's the best way to overcome this?
I've been thinking about getting the position of the mouseclick, relative from the left side of the checkbox. Which works partly, but if I would click on an empty space, close enough to the left the current selected item would still be checked. Any ideas regarding this?
I know this thread's a bit old, but I don't think it's a problem to offer another solution:
private void checkedListBox1_MouseClick(object sender, MouseEventArgs e)
{
if ((e.Button == MouseButtons.Left) & (e.X > 13))
{
this.checkedListBox1.SetItemChecked(this.checkedListBox1.SelectedIndex, !this.checkedListBox1.GetItemChecked(this.checkedListBox1.SelectedIndex));
}
}
(With the value of CheckOnClick = True).
You could use that thingy with the rectangle, but why make it more complex the it needs to.
Well, it is quite ugly, but you could calculate mouse hit coordinates against rectangles of items by hooking on CheckedListBox.MouseDown and CheckedListBox.ItemCheck like the following
/// <summary>
/// In order to control itemcheck changes (blinds double clicking, among other things)
/// </summary>
bool AuthorizeCheck { get; set; }
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
if(!AuthorizeCheck)
e.NewValue = e.CurrentValue; //check state change was not through authorized actions
}
private void checkedListBox1_MouseDown(object sender, MouseEventArgs e)
{
Point loc = this.checkedListBox1.PointToClient(Cursor.Position);
for (int i = 0; i < this.checkedListBox1.Items.Count; i++)
{
Rectangle rec = this.checkedListBox1.GetItemRectangle(i);
rec.Width = 16; //checkbox itself has a default width of about 16 pixels
if (rec.Contains(loc))
{
AuthorizeCheck = true;
bool newValue = !this.checkedListBox1.GetItemChecked(i);
this.checkedListBox1.SetItemChecked(i, newValue);//check
AuthorizeCheck = false;
return;
}
}
}
Another solution is to simply use a Treeview.
Set CheckBoxes to true, ShowLines to false, and ShowPlusMinus to false and you have basically the same thing as a CheckedListBox. The items are only checked when the actual CheckBox is clicked.
The CheckedListBox is much more simplistic, but the TreeView offers a lot of options that can potentially be better suited for your program.
I succesfully used this property:
CheckedBoxList.CheckOnClick
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.checkedlistbox.checkonclick?view=netframework-4.7.2
The text for a checkbox in a CheckedListBox is rendered by default is to place an HTML label after the checkbox input and set the label's "for" attribute to the ID of the checkbox.
When a label is denoting an element that it is "for," clicking on that label tells the browser to focus on that element, which is what you're seeing.
Two options are to render your own list with separate CheckBox controls and text (not as the Text property of the CheckBox, as that does the same thing as the CheckBoxList) if the list is static or to use something like a Repeater if the list is dynamic.
Try this. Declare iLastIndexClicked as a form-level int variable.
private void chklst_MouseClick(object sender, MouseEventArgs e)
{
Point p = chklst.PointToClient(MousePosition);
int i = chklst.IndexFromPoint(p);
if (p.X > 15) { return; } // Body click.
if (chklst.CheckedIndices.Contains(i)){ return; } // If already has focus click anywhere works right.
if (iLastIndexClicked == i) { return; } // native code will check/uncheck
chklst.SetItemChecked(i, true);
iLastIndexClicked = i;
}
Just checking to see if the user clicked in the leftmost 15 pixels of the checked list (the check box area) works at all times except re-checking a currently selected item. Storing the last index and exiting without changing lets the native code handle that properly, trying to set it to checked in that case turns it on and it just turns back off when the "ItemCheck" code runs.

Categories