C# - DatagridView and ContextMenuStrip - c#

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.

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).

CurrentRow in Data Grid giving first row after Selecting differant Row

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.

CellMouseDoubleClick fires multiple times on DataGridView c#

I have WinForms application in c#.
I want to add event on my dynamically created DataGridView.
The action that i want to add on double click is deleting the clicked row.
The problem is that the events fires multiple times, for each row index higher that the one that is clicked.
For example I have 5 items in my DataGridView and i click on the second one.
The event fires 4 times and deletes 2nd, 3rd, 4th and 5th element.
If i click on the last one, it works ok (because he is the higher index).
Any ideas ...
Here is example of that, and I left some space for oportunity to remove one day multiple selected rows in case you need it
if (e.Button == MouseButtons.Left)
{
var hti = dataGridView1.HitTest(e.X, e.Y);
if (hti.RowIndex != -1)
{
dataGridView1.ClearSelection();
dataGridView1.Rows[hti.RowIndex].Selected = true;
}
}
foreach (DataGridViewRow row in DataGridView1.SelectedRows)
{
DataGridView1.Rows.Remove(row);
}

Click and Drag to Multiselect Checkboxes in WPF/C#

The problem:
My application requires a user to be able to select multiple entries in a datagrid via a column of checkboxes. The desired behavior is that when you click on a checkbox in the column, it behaves like a normal checkbox, but if you drag over it while the left mouse button is down, its selection state changes to the opposite of what is was before.
What I have tried so far:
I have tried subclassing CheckBox and handling OnMouseEnter, but the first checkbox that is clicked seems to capture the mouse so no other checkboxes fire the OnMouseEnter event.
I have tried implementing a drag-and-drop hack, where the user clicks to select a checkbox, and then drags that checkbox over the others so the others recieve a DragOver event and can switch states. This solution causes the cursor to display as a circle with a slash when not over another checkbox during the drag and drop, which is not acceptable for this application.
What I would like:
I would like a method to implement a checkbox that has the functionality I describe, ideally in a xaml style or subclass that I can reuse, as this functionality is needed in multiple places in my application.
Is there an elegant way to achieve this effect?
I did this in my application, very handy when you have to select, say, 30 checkBoxes.
To do this, i handled the preview mouse event myself : PreviewMouseLeftButtonDown, PreviewMouseMove, PreviewMouseLeftButtonUp.
In PreviewMouseLeftButtonDown : i get the mouse position relative to the control.
In PreviewMouseMove : i draw a rectangle from start to current position if i am far enough from firstPoint. then i iterate in CheckBoxes, see if they intersect with rectangle, and highlight them if so (so the user know whiwh chexboxes will swap)
In PreviewMouseLeftButtonUp : i do the swap for intersecting CheckBoxes.
if it can help you, here's the code i use. it is not MVVM (:-)) but works fine, it might give you ideas. it is an automatic translation from vb.net code.
To make it work, you need a Canvas on top of your CheckBoxes (=within the same grid cell for instance), with property IsHitTestVisible="False" .
Within this Canvas, put a Rectangle nammed "SelectionRectangle" with proper fill and stroke, but with 0.0 Opacity.
// '' <summary>
// '' When Left Mouse button is pressed, remember where the mouse move start
// '' </summary>
private void EditedItems_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
StartPoint = Mouse.GetPosition(this);
}
// '' <summary>
// '' When mouse move, update the highlight of the selected items.
// '' </summary>
private void EditedItems_PreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e) {
if ((StartPoint == null)) {
return;
}
PointWhereMouseIs = Mouse.GetPosition(this);
Rect SelectedRect = new Rect(StartPoint, PointWhereMouseIs);
if (((SelectedRect.Width < 20)
&& (SelectedRect.Height < 20))) {
return;
}
// show the rectangle again
Canvas.SetLeft(SelectionRectangle, Math.Min(StartPoint.X, PointWhereMouseIs.X));
Canvas.SetTop(SelectionRectangle, Math.Min(StartPoint.Y, PointWhereMouseIs.Y));
SelectionRectangle.Width = Math.Abs((PointWhereMouseIs.X - StartPoint.X));
SelectionRectangle.Height = Math.Abs((PointWhereMouseIs.Y - StartPoint.Y));
foreach (CheckBox ThisChkBox in EditedItems.Children) {
object rectBounds = VisualTreeHelper.GetDescendantBounds(ThisChkBox);
Vector vector = VisualTreeHelper.GetOffset(ThisChkBox);
rectBounds.Offset(vector);
if (rectBounds.IntersectsWith(SelectedRect)) {
((TextBlock)(ThisChkBox.Content)).Background = Brushes.LightGreen;
}
else {
((TextBlock)(ThisChkBox.Content)).Background = Brushes.Transparent;
}
}
}
// '' <summary>
// '' When Left Mouse button is released, change all CheckBoxes values. (Or do nothing if it is a small move -->
// '' click will be handled in a standard way.)
// '' </summary>
private void EditedItems_PreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e) {
PointWhereMouseIs = Mouse.GetPosition(this);
Rect SelectedRect = new Rect(StartPoint, PointWhereMouseIs);
StartPoint = null;
SelectionRectangle.Opacity = 0;
// hide the rectangle again
if (((SelectedRect.Width < 20)
&& (SelectedRect.Height < 20))) {
return;
}
foreach (CheckBox ThisEditedItem in EditedItems.Children) {
object rectBounds = VisualTreeHelper.GetDescendantBounds(ThisEditedItem);
Vector vector = VisualTreeHelper.GetOffset(ThisEditedItem);
rectBounds.Offset(vector);
if (rectBounds.IntersectsWith(SelectedRect)) {
ThisEditedItem.IsChecked = !ThisEditedItem.IsChecked;
}
((TextBlock)(ThisEditedItem.Content)).Background = Brushes.Transparent;
}
}
Edit : i used that code within a user control. This control takes a list of booleans and a list of strings (caption) as argument, and builds (with a WrapPanel) an array of CheckBoxes having the right caption. And so you can select/unselect with the rectangle, and there are also two buttons to check all/uncheck all. I tried also to keep good column/rows ratio to handle selection of 7 to 200 booleans with a good column/row balance.
This topic is a few months old but I think I have the elegant answer you are looking for.
Since you describe dragging and checking as two separate behaviors, first set your datagrid MouseDown handler...
yourdatagrid.MouseDown += DragCheck_MouseDownHandler;
This will allow a drag start from the datagrid background (but not a control in the grid.)
private void DragCheck_MouseDownHandler(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
Control dgrid = sender as Control;
foreach (CheckBox box in dgrid.Controls.OfType<CheckBox>())
{
box.Tag = null;
}
dgrid.MouseMove += DragMove_MouseMoveHandler;
}
This uses the checkbox.Tag as a toggle for one pass only dragging while the mouse is down. If you use the CheckBox tags for something else I am sure you can find your own way to identify the boxes. The datagrid.MouseMove is set for the next handler....
private void DragMove_MouseMoveHandler(object sender, MouseEventArgs e)
{
Control dgrid = sender as Control;
Point now = dgrid.PointToClient(Cursor.Position);
if (e.Button == MouseButtons.Left)
{
Control under = dgrid.GetChildAtPoint(now);
if (under != null && under.GetType() == typeof(CheckBox))
{
//if the point has a valid CheckBox control under it
CheckBox box = under as CheckBox;
if (box.Tag == null)// not yet been swiped
{
box.Checked = !box.Checked;
box.Tag = true;
}
}
}
else
{
//if MouseButtons no longer registers as left
//remove the handler
dgrid.MouseMove -= DragMove_MouseMoveHandler;
}
}

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.

Categories