Is there a doubleclick event for a datagrid? I'm trying to use this code to open a details form when the user doubleclicks on a row.
http://www.codeproject.com/KB/grid/usingdatagrid.aspx
I tried adding it by doubleclicking on the control, but it gives dataGrid1_Navigate instead.
What you get when you double click a control in design mode is the event the designers of the control thought would be the most used, in this case it's Navigate.
But yes, this control has two double click events:
public partial class Form1 : Form
{
DataGrid grid = new DataGrid();
public Form1()
{
InitializeComponent();
grid.DoubleClick += new EventHandler(grid_DoubleClick);
grid.MouseDoubleClick += new MouseEventHandler(grid_MouseDoubleClick);
grid.Dock = DockStyle.Fill;
this.Controls.Add(grid);
}
void grid_MouseDoubleClick(object sender, MouseEventArgs e)
{
}
void grid_DoubleClick(object sender, EventArgs e)
{
}
}
However, both of these events run when you double click anywhere on the control and they don't directly give you information on what row was selected. You might be able to retrieve the row double clicked in the grid_MouseDoubleClick handler by getting it from the control based on the point being clicked (e.Location), that's how it works in the TreeView control for example. At a quick glance I didn't see if the control has such a method. You might want to consider using DataGridView instead, if you don't have a particular reason to use this control.
Sounds like you need a way to get a list of all the events for a given control, rather than finding the default event (which is what VS gives you when you double click a control in the designer)
There are a few ways of doing this:
One way Select the grid.
Then click the events icon to turn the properties window into a list of events, then doubel click the event you want to strart coding the event.
Alternatively, switch to code view, select the grid in the drop down list of objects at the top left of the code window, then select the event you want from the list of all the events for that control in the event list (top right of the code window)
I tried #steve76's code, but had to tweak it slightly to work in a Windows Embedded CE 6.0 system. Here is what worked for me.
private void dataGrid1_DoubleClick(object sender, EventArgs e)
{
Point pt = dataGrid1.PointToClient(Control.MousePosition);
DataGrid.HitTestInfo info = dataGrid1.HitTest(pt.X, pt.Y);
int row;
int col;
if (info.Column < 0)
col = 0;
else
col = info.Column;
if (info.Row < 0)
row = 0;
else
row = info.Row;
object cellData = dataGrid1[row, col];
string cellString = "(null)";
if (cellData != null)
cellString = cellData.ToString();
MessageBox.Show(cellString, "Cell Contents");
}
Perhaps you can use the DataGridView.CellContentDoubleClick event.
Example:
private void DataGridView1_CellContentDoubleClick(Object sender, DataGridViewCellEventArgs e) {
System.Text.StringBuilder messageBoxCS = new System.Text.StringBuilder();
messageBoxCS.AppendFormat("{0} = {1}", "ColumnIndex", e.ColumnIndex );
messageBoxCS.AppendLine();
messageBoxCS.AppendFormat("{0} = {1}", "RowIndex", e.RowIndex );
messageBoxCS.AppendLine();
MessageBox.Show(messageBoxCS.ToString(), "CellContentDoubleClick Event" );
}
If that is not what you are looking for, you can find other events in the reference:
http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview_events.aspx
Related
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.
Here is a short program that reproduces the problem I just encountered. This was compiled under MS Windows 7 with .NET 4.0, just in case that makes a difference.
using System;
using System.Drawing;
using System.Windows.Forms;
// Compile with "csc /target:exe /out:comboboxbug.exe /r:System.dll /r:System.Drawing.dll /r:System.Windows.Forms.dll comboboxbug.cs"
// in a Visual Studio command prompt.
static class Program
{
[STAThread]
static void Main()
{
//Create a label.
Label oLabel = new Label();
oLabel.Location = new Point (10, 10);
oLabel.Size = new Size (100, 15);
oLabel.Text = "Combo box bug:";
// Create a combo-box.
ComboBox oComboBox = new ComboBox();
oComboBox.Location = new Point (10, 50);
oComboBox.Size = new Size (150, 21);
oComboBox.Items.AddRange (new object[]
{ "A", "A B", "A C", "A B C", "A C B", "A B C D", "A C B D" });
oComboBox.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
oComboBox.AutoCompleteSource = AutoCompleteSource.ListItems;
oComboBox.SelectionChangeCommitted
+= new EventHandler (comboBox_SelectionChangeCommitted);
// Create a form.
Form oForm = new Form();
oForm.Size = new Size (200, 150);
oForm.Controls.Add (oLabel);
oForm.Controls.Add (oComboBox);
// Run this form.
Application.Run (oForm);
}
static void comboBox_SelectionChangeCommitted (object sender,
EventArgs e)
{
MessageBox.Show ("SelectionChangeCommitted");
}
}
Click in the text portion of the combo-box and type "A". You will get a list of autocomplete suggestions. Click one of the selections with your mouse. The SelectionChangeCommitted event doesn't happen!
Select a menu-item without using autocomplete. You'll get a message-box showing that the SelectionChangeCommitted event happened!
Given that the selection was changed by the user in both cases, shouldn't SelectionChangeCommitted be called in both cases?
Using the SelectedIndexChanged event is not an option, because for the application behind this canned example, I only want it to happen when the user makes a selection, not when it's set programmatically.
EDIT 2020-Oct-28: I found another case where SelectionChangeCommitted doesn't get called! Auto-complete doesn't even need to be set for the problem to happen! Click to open the combo-box, press a key that matches the beginning of one of the combo-box items, and then press Tab to leave. The combo-box item gets selected, but SelectionChangeCommitted is not called! My revised answer is below.
Using the SelectedIndexChanged event is not an option, because for the application behind this canned example, I only want it to happen when the user makes a selection, not when it's set programmatically.
You could also accomplish this by writing a wrapper method for changing the selection that temporarily disables your event.
Unfortunately I do not know off hand a solution to the issue that SelectionChangeCommitted not being started for the more general case (such as where you don't control the ComboBox or how it is accessed).
EDIT:
I made a streamer of all the events that ComboBox calls, and it doesn't appear that any other event will do what you are looking for. The only solution I can think of would involve hooking into the events that the AutoComplete triggers. The difficulty is knowing what those events are, since they don't appear to trigger off the ComboBox from what my minor testing shows.
FYI, here was the best solution I ever came up with. Obviously, this is a Leave event-handler on a ComboBox subclass. The SelectionChangeCommitted event doesn't happen on the mouse-click, but at least it happens during the normal flow of GUI interaction.
private void this_Leave (object sender, EventArgs e)
{
// If this is an autocomplete combo-box, select the
// item that was found by autocomplete.
// This seems like something that ComboBox should be
// doing automatically...I wonder why it doesn't?
if (this.AutoCompleteMode != AutoCompleteMode.None)
{
// Determine which combo-box item matches the text.
// Since IndexOf() is case-sensitive, do our own
// search.
int iIndex = -1;
string strText = this.Text;
ComboBox.ObjectCollection lstItems = this.Items;
int iCount = lstItems.Count;
for (int i = 0; i < iCount; ++i)
{
string strItem = lstItems[i].ToString();
if (string.Compare (strText, strItem, true) == 0)
{
iIndex = i;
break;
}
}
// If there's a match, and this isn't already the
// selected item, make it the selected item.
//
// Force a selection-change-committed event, since
// the autocomplete was driven by the user.
if (iIndex >= 0
&& this.SelectedIndex != iIndex)
{
this.SelectedIndex = iIndex;
OnSelectionChangeCommitted (EventArgs.Empty);
}
}
}
If someone got this problem, I suggest a solution that works fine to me...
Think with me, to accept the suggest of Combo-box, generally the user needs to key down with an Enter key.
You can write into KeyDown event of Combo-box property, something like this:
private void cboProperty_SelectionChangeCommitted(object sender, EventArgs e)
{
//Call here the event of SelectionChangeCommitted
cboProperty_SelectionChangeCommitted(sender,null);
}
It will raise the SelectionChangeCommitted on the right time.
This workaround worked fine for me and hope for you too. When use Autocomplete by typing data in your combo box to get an item through keyboard or mouse selection you need _KeyDown event. From inside invoke _SelectionChangeCommitted method which contains code for other operations. See code below:
private void YourComboBox_KeyDown(object sender, KeyEventArgs e)
{
//Works also when user select and click on autocomplete list.
if (e.KeyCode == Keys.Enter && YourComboBox.SelectedItem != null)
YourComboBox_SelectionChangeCommitted(sender, e);
}
For the non-auto-complete case mentioned above (i.e. my 2020-Oct-28 edit), this Leave event-handler on a ComboBox subclass incorporates the new case as well as the old one, as long as your SelectionChangeCommitted event-handler is idempotent. Compared to my previous answer, it removes the test for auto-complete, and always calls OnSelectionChangeCommitted().
private void this_Leave (object sender, EventArgs e)
{
// Determine which combo-box item matches the text.
// Since IndexOf() is case-sensitive, do our own
// search.
int iIndex = -1;
string strText = this.Text;
ComboBox.ObjectCollection lstItems = this.Items;
int iCount = lstItems.Count;
for (int i = 0; i < iCount; ++i)
{
string strItem = lstItems[i].ToString();
if (string.Compare (strText, strItem, true) == 0)
{
iIndex = i;
break;
}
}
// If there's a match, and this isn't already the
// selected item, make it the selected item.
if (iIndex >= 0
&& this.SelectedIndex != iIndex)
this.SelectedIndex = iIndex;
// Force a selection-change-committed event, since
// the autocomplete was driven by the user.
OnSelectionChangeCommitted (EventArgs.Empty);
}
If I have multiple TextBoxes and other text insertion controls and I want to create some buttons that insert special characters into whichever TextBox is in focus, what is the best control for this and what properties should be set?
Requirements for the buttons:
Buttons do not steal focus when clicked.
Buttons can insert text (e.g. special characters) into any control that accepts keyboard input.
The cursor should move as if the user had entered the text on the keyboard.
If #2 is not possible, it will suffice to limit the controls to only TextBoxes.
NOTE: I do not want to make the buttons unfocusable, only such that they do not steal focus when clicked.
I'm not aware of any button that is not stealing focus when it is clicked, but in button click event handle you can return focus to previous owner. If I had to implement this I'd create an behavior that is attached to parent panel of all special textboxes and all buttons that are to insert some text.
<StackPanel>
<i:Interaction.Behaviors>
<local:TextBoxStateTracker/>
</i:Interaction.Behaviors>
<TextBox />
<Button Content="description" Tag="?" />
</StackPanel>
For sample simplicity I've put text that is to be inserted to textbox in Tag property.
public class TextBoxStateTracker : Behavior<Panel>
{
private TextBox _previouslySelectedElement;
private int _selectionStart;
private int _selectionLength;
protected override void OnAttached()
{
//after control and all its children are created find textboxes and buttons
AssociatedObject.Initialized += (x, y) =>
{
var textBoxElements = FindChildren<TextBox>(AssociatedObject);
foreach (var item in textBoxElements)
{
item.LostFocus += new RoutedEventHandler(item_LostFocus);
}
var buttons = FindChildren<Button>(AssociatedObject);
foreach (var item in buttons)
{
item.Click += new RoutedEventHandler(item_Click);
}
};
}
private void item_Click(object sender, RoutedEventArgs e)
{
if (_previouslySelectedElement == null) return;
//simply replace selected text in previously focused textbox with whatever is in tag property
var button = (Button)sender;
var textToInsert = (string)button.Tag;
_previouslySelectedElement.Text = _previouslySelectedElement.Text.Substring(0, _selectionStart)
+ textToInsert +
_previouslySelectedElement.Text.Substring(_selectionStart + _selectionLength);
_previouslySelectedElement.Focus();
_previouslySelectedElement.SelectionStart = _selectionStart + textToInsert.Length;
}
private void item_LostFocus(object sender, RoutedEventArgs e)
{
//this method is fired when textboxes loose their focus note that this
//might not be fired by button click
_previouslySelectedElement = (TextBox)sender;
_selectionStart = _previouslySelectedElement.SelectionStart;
_selectionLength = _previouslySelectedElement.SelectionLength;
}
public List<TChild> FindChildren<TChild>(DependencyObject d)
where TChild : DependencyObject
{
List<TChild> children = new List<TChild>();
int childCount = VisualTreeHelper.GetChildrenCount(d);
for (int i = 0; i < childCount; i++)
{
DependencyObject o = VisualTreeHelper.GetChild(d, i);
if (o is TChild)
children.Add(o as TChild);
foreach (TChild c in FindChildren<TChild>(o))
children.Add(c);
}
return children;
}
}
This does more or less what you described but it is far from perfect I think it is enough to get you started.
You need to do override the template of a Label and a TextBox.
requirements 1 and 2 - can be done inside the template for the Label which will act as a button.
requirement 3 - can be done inside the the template for the Textbox.
It's not easy...
you might need to learn alot of WPF styling, XAML and overriding the Control Template. maybe even creating a custom control.
I want to initialize some of the rows of my DataGridView in red, base on a certain condition. The thing is, I've been playing around but I can't get this to work when the DataGridView is shown. . I try to do this in the constructor of the MainForm but no luck at all.
private void UpdateSoldOutProducts ()
{
for (int i = 0; i < productsTable.Rows.Count; i++)
if ((int)productsTable.Rows [i] ["Quantity"] == 0)
dataGridViewProducts.Rows [i].DefaultCellStyle.BackColor = Color.Red;
}
This method is calle in the constructor of the MainForm.
Try RowPostPaint event, it works for me :
private void dataGridViewProducts_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
{
if ((int)dataGridViewProducts.Rows[e.RowIndex].Cells["Quantity"].Value == 0)
dataGridViewProducts.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.Red;
}
You can paint DataGridView Rows and Cells using Custom Painting. it is done with
the DataGridView.RowPostPaint Event and DataGridView.RowPrePaint Event.
The another expect is Paint Event.
private void dataGridViewProducts_Paint(object sender, PaintEventArgs e)
{
foreach (DataGridViewRow row in dataGridViewProducts.Rows)
{
int value = Convert.ToInt32(row.Cells["Quantity"].Value);
if (value == 0)
row.DefaultCellStyle.BackColor = Color.Red;
}
}
You can use DataGridViewRowPostPaintEventArgs or DataGridViewRowPrePaintEventArgs to set the Row or Cell style on the basis of condition..
You can handle this event alone or in combination with the RowPrePaint event to customize the appearance of rows in the control. You can paint entire rows yourself, or paint specific parts of rows and use the following methods of the DataGridViewRowPostPaintEventArgs class to paint other parts:
DrawFocus
PaintCells
PaintCellsBackground
PaintCellsContent
PaintHeader
Check this example on MSDN link and try to put you code one of these events.. here you you will get the Currrent row index using the DataGridViewRowPostPaintEventArgs
int value = Convert.ToInt32(dataGridViewProducts.Rows[e.RowIndex].Cells["Quantity"].Value);
if (value == 0)
dataGridViewProducts.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.Red;
Edit:
Put you code on Form Load event or DataBinding Completed event. may this solve your problem.
I'm trying to update this DataGridView object such that if a value == "bob" there will be a button in a column next to its name, otherwise I don't want any button to appear.
DataGridViewTextBoxColumn valueColumn = new DataGridViewTextBoxColumn();
DataGridViewButtonColumn buttonColumn = new DataGridViewButtonColumn();
buttonColumn.ReadOnly = true;
buttonColumn.Visible = false;
this.dgv.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
valueColumn,
buttonColumn,
});
//elsewhere...
if(value == "bob")
{
Button button = new Button()
{
Text = "null",
};
index = dgv.Rows.Add(value, button);
DataGridViewButtonCell buttonCell = dgv.Rows[index].Cells[2] as DataGridViewButtonCell;
buttonCell.Visible = true;
}
else
{
dgv.Rows.Add(value);
}
But, since I can't set Visible on a cell, this doesn't work. Is there a way to add a button to only the rows were Value == "bob"?
Here is a neat little hack that I've used before to accomplish this:
Instead of using a DataGridViewButtonColumn, use the DataGridViewTextBoxColumn and add a DataGridViewButtonCell where appropriate.
e.g.
private void button1_Click(object sender, EventArgs e)
{
// Iterate through each of the rows.
for (int i = 0; i < dgv.RowCount - 1; i++)
{
if (dgv.Rows[i].Cells[0].Value.ToString() == "bob")
{
// Here is the trick.
var btnCell = new DataGridViewButtonCell();
dgv.Rows[i].Cells[1] = btnCell;
}
}
}
In the example above, I have two DataGridViewTextBoxColumns and iterate through each of the rows on a button click event. I check the first column to see if it contains "bob" and if it does, I add a button in the column next to it. You can use this trick however you want (i.e. button clicks, RowsAdded event, CellEndEdit event, etc.). Experiment in different ways. Hope this helps someone!
There are two possibilities here, one ugly and one from MSDN.
The Ugly: Add a button to your DGV at runtime
Do the following:
- Add an unbound DataGridViewTextBoxColumn to your DGV. Note it's index value in your DGV; this is where you'll put your button.
- Use your DGV's CellFormatting event like so:
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) {
if (e.ColumnIndex == 0) { // Assumes column 0 has the data that determines if a button should be displayed.
if (e.Value.ToString() == "bob") { // Test if a button should be displayed on row.
// Create a Button and add it to our DGV.
Button cellButton = new Button();
// Do something to identify which row's button was clicked. Here I'm just storing the row index.
cellButton.Tag = e.RowIndex;
cellButton.Text = "Hello bob";
cellButton.Click += new EventHandler(cellButton_Click);
dataGridView1.Controls.Add(cellButton);
// Your ugly button column is shown here as having an index value of 3.
Rectangle cell = this.dataGridView1.GetCellDisplayRectangle(3, e.RowIndex, true);
cellButton.Location = cell.Location;
}
}
}
When a user clicks the button the cellButton_Click event will fire. Here's some test code:
void cellButton_Click(object sender, EventArgs e) {
Console.WriteLine("Hello from row: {0}", ((Button) sender).Tag);
}
As you can see this isn't very refined. I based it on an even uglier sample I found. I'm sure you can modify it to suit your needs.
From MSDN: Roll your own (extend) DataGridViewButtonColumn that conditionally displays a disabled button.
For this option see How to: Disable Buttons in a Button Column in the Windows Forms DataGridView Control
Of course this option doesn't actually remove any buttons, only conditionally disables them. For your application however, this might be better.
You can handle cell painting on cell painting event:
private void dgv_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if(e.RowIndex>=0 && e.ColumnIndex == indexOfButtonColumn && value[e.RowIndex] != "bob")
{
e.Paint(e.ClipBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.ContentForeground & ~DataGridViewPaintParts.ContentBackground);
e.Handled = true;
}
}