I'm populating a combobox from a datasource and I have code for when the user changes the selection in the combobox. So obviously I don't want the code in the SelectedIndexChanged method to fire on form load.
This SO question was answered by suggesting two things:
1) Before and after loading the data to the combobox use this code:
private void LoadYourComboBox()
{
this.comboBox1.SelectedIndexChanged -= new EventHandler(comboBox1_SelectedIndexChanged);
// Set your bindings here . . .
this.comboBox1.SelectedIndexChanged += new EventHandler(comboBox1_SelectedIndexChanged);
}
I tried that with this code:
this.cboSelectCategory.SelectedIndexChanged -= new EventHandler(cboSelectCategory_SelectedIndexChanged);
However, the cboSelectCategory_SelectedIndexChanged part has a red error squiggly and hovering over it says: The name cboSelectCategory_SelectedIndexChanged does not exist in the current context. I tried that code in both the form_load and the method that actually populates the combobox.
2) That same SO question had the answer to use the event SelectedIndexChangeCommitted.
private void cboSelectCompany_SelectedIndexChangeCommitted(object sender, EventArgs e)
{
string selectedCat = cboSelectCategory.SelectedValue.ToString();
Console.WriteLine(selectedCat);
}
But that event isn't firing when I change the selection in the combobox.
Am I missing something somewhere? Is my code off or in the wrong place?
So obviously I don't want the code in the SelectedIndexChanged method to fire on form load.
If you bind your combobox in the form's constructor (after InitializeComponent()) then SelectedIndexChanged will fire before the form is visible, so you can simply return from the selectedindexchanged event if the form is invisible:
public MainForm()
{
InitializeComponent();
DataTable dt = new DataTable();
dt.Columns.Add("Name");
dt.Columns.Add("Code");
dt.Rows.Add("Milk", "MLK");
dt.Rows.Add("Bread", "BRD_WHITE");
dt.Rows.Add("Bread", "BRD_BROWN");
dt.Rows.Add("Coffee", "COFF");
comboBox1.DataSource = dt;
comboBox1.DisplayMember = "Name";
comboBox1.ValueMember = "Code";
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (!this.Visible)
return;
MessageBox.Show("a");
}
It's often easier to simply return from an event handler at an unwanted time than to mess around trying to remove and add event handlers
Side note: If you use a strongly typed dataset and create the bindings using the windows forms designer, the event doesn't fire, I believe because the forms designer InitializeComponent() calls Begin/EndInit on the components at the start and end
Related
When I fetch data from the database the rows are colorcoded. However when the user clicks on the columns to sort the color formatting is discarded and all the rows become white. I've searched for answers and found some people with the same issue as I. They have implemented some kind of eventhandler (such as DataBindingComplete or CellFormatting) in order to keep or re-instantiate the formatting after the sort. However I don't get this to work. Can someone explain why, or tell me another way I can solve this problem?
This is the code that fetch data from the database and fills the gridview
public static OdbcConnection DbConnection; // Create an object for the DB connection
public static MainWindow mw = Form.ActiveForm as MainWindow;
public static void TestSqlToGridView()
{
// https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.datagridview.datasource?view=netframework-4.7.1
//var mw = Form.ActiveForm as MainWindow;
ConnectToDB();
DbConnection.Open();
BindingSource bindingSource = new BindingSource();
// Automatically generate the DataGridView columns.
SuspendDrawing(mw.dataGridView); // wait with drawing until all data is read
bindingSource.DataSource = GetData( Laddstatus() );
mw.dataGridView.DataSource = bindingSource;
SetRowColor(); // Change the rows color
mw.dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.ColumnHeader; // Adjusting the size of header cells !!! AllCells = SLOW !!!
ResumeDrawing(mw.dataGridView); // draw all cells
// Set the DataGridView control's border.
mw.dataGridView.BorderStyle = BorderStyle.Fixed3D;
DbConnection.Close();
}
This is the way I tried to reinitiate the formatting
void dataGridView_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
// This event is raised when the contents of the data source
// change or when the value of the DataSource, DataMember, or BindingContext
// property changes.
Print("DatabindingComplete!"); //DEBUG
SetRowColor();
}
But for some reason when I press the column headers to sort it seem like the event is never called. Do I have to put in a specific location?
Thanks for the help!
[I]t seem[s] like the event is never called. Do I have to put in a specific location?
Yes-ish. You've added the event handler, but you need to subscribe (attach) to the event. This can be done multiple ways:
In Design mode by Double-clicking the DataBindingComplete event under Properties -> Events:
This stubs an empty handler to your Form.cs file, and attaches to the event in the Form.designer.cs file:
this.dataGridView1.DataBindingComplete += new System.Windows.Forms.DataGridViewBindingCompleteEventHandler(this.dataGridView1_DataBindingComplete);
Programmatically in the form constructor or Load event:
this.dataGridView1.DataBindingComplete += this.dataGridView1_DataBindingComplete;
This should give you the desired results:
WHY?
By subscribing to an event, you are attaching your handler to be run after the base event has finished. Otherwise, it will never be invoked. It is possible to subscribe multiple handlers multiple times. For example, the following:
this.dataGridView1.DataBindingComplete += DataGridView1_DataBindingComplete1;
this.dataGridView1.DataBindingComplete += DataGridView1_DataBindingComplete2;
this.dataGridView1.DataBindingComplete += DataGridView1_DataBindingComplete3;
this.dataGridView1.DataBindingComplete += DataGridView1_DataBindingComplete1;
private void DataGridView1_DataBindingComplete1(object sender, DataGridViewBindingCompleteEventArgs e)
{
Console.WriteLine("First");
}
private void DataGridView1_DataBindingComplete2(object sender, DataGridViewBindingCompleteEventArgs e)
{
Console.WriteLine("Second");
}
private void DataGridView1_DataBindingComplete3(object sender, DataGridViewBindingCompleteEventArgs e)
{
Console.WriteLine("Third");
}
Would produce the following output every time dataGridView1.DataBindingComplete triggers:
/*
First
Second
Third
First
*/
Take care to only subscribe (+=) to an event once - otherwise it may produce odd results, resource leaks, and/or bog down your run time (ex. When an expensive/large handler is attached repeatedly). This can be countered by unsubscribing (-=) from the event.
As you suggested yourself:
Private Sub dgwList_DataBindingComplete(sender As Object, e As DataGridViewBindingCompleteEventArgs) Handles dgwList.DataBindingComplete
Call ColorMyRows()
End Sub
C#:
Private void dgwList_DataBindingComplete(Object sender, DataGridViewBindingCompleteEventArgs e)
{
ColorMyRows();
}
This is a way I do it and it works as long as you fill the DataGridView using DataSource.
But looking at your code 2nd time, you have SuspendDrawing, then you do the data binding and then you ResumeDrawing again! That will disable this event.
I have a UserControl with a ComboBox and a Button which opens up a Form on top of the UserControl. In this Form you Add/Edit/Delete items from the UserContols' ComboBox.
I also have a Method RefreshData() in the UserControl's class that refreshes the data (By rebuilding the datatable) in the ComboBox when you press other controls within the UserControl. -Both this Method, and the ComboBox have Public access modifiers. (But not Static - which is possibly the issue?!)
RefreshData() works fine when it is being called from within its own class (ie, when its being called by controls on the same UserControl). However I also need to refresh the data in the UserControls' combobox when I Close the Form which edits the Data.
-The problem is that the FormClosing handler is calling RefreshData(), but it's not actually refreshing the data on the other form. -I have tried this in both FormClose and FormClosing event handlers.
(I know its being called because I can step into it whilst debugging)
This is my Code:
RefreshData() Method:
public void refreshdata()
{
SQLiteConnection sqlcon = new SQLiteConnection("data source =" + SqliteClass.dir + "\\SupportDash.sqlite");
sqlcon.Open();
SQLiteCommand getdataDesc = new SQLiteCommand("SELECT ID, URLDescription, URLAddress, Type from URLS", sqlcon);
SQLiteDataReader reader;
reader = getdataDesc.ExecuteReader();
DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(string));
dt.Columns.Add("URLDescription", typeof(string));
dt.Load(reader);
//Description ComboBox
Report.ValueMember = "ID";
Report.DisplayMember = "URLDescription";
Report.SelectedValue = "ID";
Report.DataSource = dt;
sqlcon.Close();
}
Calling it from the other Form:
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(AddReport_Close);
void AddReport_FormClosing(object sender, System.Windows.Forms.FormClosedEventArgs e)
{
RRunner r = new RRunner();
r.refreshdata();
}
As well as trying both event handlers, I've also tried: (All of these are in the Form_Closing event handler)
r.Invalidate(); Application.DoEvents();
r.Refresh();
I've also tried clearing all controls off the UserControl, then re-drawing them:
foreach (Control ctrl in splitContainer1.Panel2.Controls)
{
splitContainer1.Panel2.Controls.Clear();
break;
}
RRunner rrunner = new RRunner();
splitContainer1.Panel2.Controls.Add(rrunner);
Since none of these work, I believe my issue lies in the way I'm setting the EventHandlers for the `FormClosed' Event, however I just cannot see were I am going wrong?
You're creating new user control RRunner in FormClosing event and calling refreshdata. Obviously it's not going to work. You need to refresh the existing RRunner. If it was added to the form then following code should work.
void AddReport_FormClosing(object sender, System.Windows.Forms.FormClosedEventArgs e)
{
RRunner r = this.Controls
.OfType<RRunner>()
.First();
r.refreshdata();
}
The correct way of doing this is to include your cleanup code in a method that is being called in your FormClosing event handler (nothing ugly in that, in my opinion). Calling Application.Exit or closing the application the old-fashioned way then results in this event being generated. Which triggers the cleanup method.
private void Clicked(object sender, EventArgs e)
{
Application.Exit();
}
private void FormClosing(object sender, CancelEventArgs e)
{
Cleanup();
}
private void Cleanup()
{
// do cleanup here
}
I have a ComboBox and in the form's load event it's populated with data. Now when I select an Item form the ComboBox, it should perform some actions. So I know few events which can be used in this case like
SelectedIndexChanged
SelectedValueChanged
etc.
But the problem is those events are raised even when setting the DataSource of the ComboBox and selecting a default index etc. when the form is loaded.
ComboBox1.DataSource = dt;
ComboBox1.SelectedIndex = -1;
What am I trying to do is that I just want to execute an action only when I select an item form the combo box. Is there a mouse event that could be used in this case?
The comboBox.SelectionChangeCommitted Event seems to do that.
Otherwise you could set a boolean value before you bind the datasource which you can use inside the event to ignore it.
private bool blnIgnoreEvent = false;
// in Form_load
blnIgnoreEvent = true;
ComboBox1.DataSource = dt;
blnIgnoreEvent = false;
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (!blnIgnoreEvent)
{
// go ahead
}
}
I don't believe there is another event that better handles what you'd like to do. XIVSolutions has a neat work-around for the event firing when you bind the data source: How to prevent selectedindexchanged event when DataSource is bound?
Also, since SelectedIndexChanged works for all cases, why not just handle the first?
if (ComboBox1.SelectedIndex == -1)
{
return;
}
If -1 corresponds to a value you'd like to be able to select, just use a private field to store some bool that you check to determine whether or not it is the first time the action has been executed.
I'm filling a combobox with the datasource property in a c# winform app. In the other hand, I'm firing up an action with the SelectedIndexChanged of the same combo. The problem is that whenever the combo is filled with datasource the SelectedIndexChanged is called and I just want this event to be called when the user in fact does a selection.
Is there a way to avoid calling this event when filling the combo?
This is some of my code
//Filling the combo with some data
combo_cliente.DataSource = clientes;
combo_cliente.DisplayMember = "NomComp";
combo_cliente.ValueMember = "IDPersona";
private void combo_cliente_SelectedIndexChanged(object sender, EventArgs e)
{
// Here is the action to be triggered when user perfoms a selection
}
Thanks
Maybe unsubscribe and then subscribe again:
combo_cliente.SelectedIndexChanged -= combo_cliente_SelectedIndexChanged;
combo_cliente.DataSource = clientes;
combo_cliente.SelectedIndexChanged += combo_cliente_SelectedIndexChanged;
im assuming you assigned the event handler with the designer so they are bound when the control is instantiated. alternatively you could assign them in code after populating the controls.
Attach your event handler in your code behind instead of doing it on your aspx page and do it affer you have finished loading your control.
you need to add a blank record as the first of your combobox. Then in your code, you can write this;
private void combo_cliente_SelectedIndexChanged(object sender, EventArgs e)
{
if!(comboBox1.SelectedValue.ToString()== string.Empty)
{
//Here is the action to be triggered when user perfoms a selection
}
}
I have a DataGridView whose DataSource is a DataTable.
This DataTable has a boolean column, which is interpreted as a checkbox in the DataGridView.
employeeSelectionTable.Columns.Add("IsSelected", typeof(bool));
...
employeeSelectionTable.RowChanged += selectionTableRowChanged;
dataGridViewSelectedEmployees.DataSource = employeeSelectionTable;
...
private void selectionTableRowChanged(object sender, DataRowChangeEventArgs e)
{
if ((bool)e.Row["IsSelected"])
{
Console.Writeline("Is Selected");
}
else
{
Console.Writeline("Is Not Selected");
}
break;
}
When the user single-clicks on a checkbox, it gets checked, and selectionTableRowChanged will output "Is Selected."
Similarly, when the user checks it again, the box gets cleared, and selectionTableRowChanged outputs "Is Not Selected."
Here's where I have the problem:
When the user double-clicks on the checkbox, the checkbox gets checked, the RowChanged event gets called ("Is Selected"), and then the checkbox is cleared, and no corresponding RowChanged event gets called. Now the subscriber to the the RowChanged event is out of sync.
My solution right now is to subclass DataGridView and override WndProc to eat WM_LBUTTONDBLCLICK, so any double-clicking on the control is ignored.
Is there a better solution?
The reason that making an empty DoubleClick event method would not help would be that is executed in addition to the other operations that happen when a double click occurs.
If you look at the windows generated code or examples of programatically adding event handlers, you use += to assign the event handler. This means you are adding that event handler in addition to the others that already exist, you could have multiple event handlers being triggered on the save event.
My instinct would have been to override the DataGridView class, then override the OnDoubleClick method and not call the base OnDoubleClick method.
However, I have tested this real quick and am seeing some interesting results.
I put together the following test class:
using System;
using System.Windows.Forms;
namespace TestApp
{
class DGV : DataGridView
{
private string test = "";
protected override void OnDoubleClick(EventArgs e)
{
MessageBox.Show(test + "OnDoubleClick");
}
protected override void OnCellMouseDoubleClick(System.Windows.Forms.DataGridViewCellMouseEventArgs e)
{
MessageBox.Show(test + "OnCellMouseDoubleClick");
}
protected override void OnCellMouseClick(System.Windows.Forms.DataGridViewCellMouseEventArgs e)
{
if (e.Clicks == 1)
{
// Had to do this with a variable as using a MessageBox
// here would block us from pulling off a double click
test = "1 click ";
base.OnCellMouseClick(e);
}
else
{
MessageBox.Show("OnCellMouseClick");
}
}
}
}
Then inserted this into a windows form, adding a checkbox column and ran the program.
On a fresh run, double clicking on the checkbox causes the messagebox display to say "1 click OnDoubleClick".
This means that OnCellMouseClick executed on the first part of the double click and then OnDoubleClick executed on the second click.
Also, unfortunately, the removal of the call to the base methods doesn't seem to be preventing the checkbox from getting the click passed to it.
I suspect that for this approach to work it may have to be taken further and override the DataGridViewCheckBoxColumn and DataGridViewCheckBoxCell that ignores the double click. Assuming this works, you would be able to stop double click on the checkbox but allow it still on your other column controls.
I have posted an answer on another question that talks about creating custom DataGridView columns and cells at here.
In case you want a checkbox column inside a DataGridView, create something like this:
DataGridViewCheckBoxCell checkBoxCell = new MyDataGridViewCheckBoxCell();
...
DataGridViewColumn col = new DataGridViewColumn(checkBoxCell);
...
col.Name = "colCheckBox";
...
this.dgItems.Columns.Add(col);
where dgItems is DataGridView instance.
As you can see I have a MyDataGridViewCheckBoxCell class which is a subclass of the DataGridViewCheckBoxCell class. In this subclass I define:
protected override void OnContentDoubleClick(DataGridViewCellEventArgs e)
{
//This the trick to keep the checkbox in sync with other actions.
//base.OnContentDoubleClick(e);
}
When the user now double clicks a checkbox in the checkbox column the checkbox will behave as if it was single clicked. This should solve your sync problem.
This isn't exactly an elegant solution, but why not simply do the following?
private void dgv_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
dgv_CellClick(sender, e);
}
This would explicitly force the second CellClick event and avoid out of sync.
Is there some reason it needs to be done that low level? Can the DoubleClick Method just be an empty method that eats it?
I already tried overriding the OnDoubleClick method in the DataGridView subclass to do nothing, but it still allowed the checkbox to be changed a second time.
Ideally I was looking to have the DataTable's RowChanged event get called twice.
Is there a way to affect the underlying DataTable via the overridden OnDoubleClick method?
Why not just leave IsSelected column unbounded? Use CellContentClick event to push the data to underlying datatable and leave CellDoubleClick or RowChange event alone.
private void dgv_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if(e.ColumnIndex == <columnIndex of IsSelected>)
{
string value = dgv[e.ColumnIndex, e.RowIndex].EditedFormattedValue;
if( value == null || Convert.ToBoolean(value) == false)
{
//push false to employeeSelectionTable
}
else
{
//push true to employeeSelectionTable
}
}
}
Don't know why i had to, but i was able to put in a timer tick event attached to a datagridview refresh that just refreshed the dgv after the second click.
In cell click event
** _RefreshTimer = new Timer();
_RefreshTimer.Tick += new EventHandler(RefreshTimer_Tick);
_RefreshTimer.Interval = 100;
_RefreshTimer.Start();
}
}
}
void RefreshTimer_Tick(object sender, EventArgs e)
{
dgv.Refresh();
_RefreshTimer.Stop();
_RefreshTimer = null;
}**