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
}
Related
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
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 am working on a librarian application (in Visual Studio 2013, C#) which allows employers to loan, return and reserve books for customers. There is a small problem with creating, firing and listening for custom events.
There is one form which adds data to a so-called Reservartion table, in my database. It adds a record with current date, itemID and other data.
When the adding of this done, I want to fire a custom event, which will be listened to from a different form. When this event is fired, I need the total data in the Reservation Table to be shown when the event is fired.
I use an onclick method in my main form, which opens a new form where data will be entered and afterwards, as described above, added into database. When the data has been added, the form closes and the main form receives focus again.
The only problem now is, when all this is done, I need the entire data in the Reservartion Table to be shown onscreen (it needs to refresh itself). Which is where I need an event to be raised.
How do I create a custom event in one form, which will be listen for INSIDE an onclick method?
I included some of the code for reference.
This is the code from the main form buttonclick:
private void btnToevoegR_Click(object sender, EventArgs e)
{
Forms.HandAddReserveer HAR = new Forms.HandAddReserveer();
HAR.Show();
// listen for event raised
// When event is raised do this
DataTable DT = DBReserveer.getAllReserveerItems();
gvUitleen.DataSource = DT;
}
And in the other form, where the event should be raised
private void button1_Click(object sender, EventArgs e)
{
int pasID;
int itemID;
if (int.TryParse(tbItemID.Text, out itemID))
{
if (int.TryParse(tbPasID.Text, out pasID))
{
if ((DBReserveer.ReserveerPasCheck(itemID, pasID)) != 0)
{
MessageBox.Show("Je hebt dit item al gereserveerd!");
}
if ((DBReserveer.ReserveerPasCheck(itemID, pasID)) == 0)
{
if (MessageBox.Show("Weet je zeker dat je dit item wilt reserveren?", "Reserveren?", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
DBReserveer.ReserveerItem(itemID, pasID);
if (DBReserveer.QueryStatus == true)
{
MessageBox.Show("Het item is gereserveerd!");
// Event should be raised from here
}
}
}
}
}
}
You could subscribe to the FormClosed Event of HAR.
private void btnToevoegR_Click(object sender, EventArgs e)
{
Forms.HandAddReserveer HAR = new Forms.HandAddReserveer();
HAR.FormClosed += new FormClosedEventHandler(HAR_FormClosed);
HAR.Show();
}
private void Har_FormClosed(Object sender, FormClosedEventArgs e) {
DataTable DT = DBReserveer.getAllReserveerItems();
gvUitleen.DataSource = DT;
}
Another way would be to create your on event, like this in the form, that is being closed:
public event EventHandler<EventArgs> ReservationComplete;
protected virtual void OnReservationComplete()
{
EventHandler<EventArgs> handler = this.ReservationComplete;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
Raise the event, by adding a call to OnReservationComplete()
if (DBReserveer.QueryStatus)
{
MessageBox.Show("Het item is gereserveerd!");
this.OnReservationComplete();
// Event should be raised from here
}
and listen to the event like this (I'm not sure if the += syntax is correct. I'm writing the code from the top of my head. Feel free to correct):
private void btnToevoegR_Click(object sender, EventArgs e)
{
Forms.HandAddReserveer HAR = new Forms.HandAddReserveer();
HAR.ReservationComplete += Har_ReservationComplete;
HAR.Show();
}
private void Har_ReservationComplete(Object sender, EventArgs e) {
DataTable DT = DBReserveer.getAllReserveerItems();
gvUitleen.DataSource = DT;
}
Edit:
Third option. You could display the 2nd form as modal.
private void btnToevoegR_Click(object sender, EventArgs e)
{
Forms.HandAddReserveer HAR = new Forms.HandAddReserveer();
HAR.ShowDialog();
DataTable DT = DBReserveer.getAllReserveerItems();
gvUitleen.DataSource = DT;
}
You could do it with an event. But why so cumbersome? Why not simply refreshing the list when reservation succeeded. You're already in the correct method for that anyway.
Edit
Like this:
...
if (MessageBox.Show("Je hebt dit item al gereserveerd!") == DialogResult.OK)
{
<Refresh list>
}
...
Edit 2Seems like I misinterpreted the question a bit. If the list-to-refresh resides in the other form then you would give the second form a reference to it on opening.
In the second form, you would declare a property that takes a reference to Form 1:
public partial class Form2
{
public Form1 CallingForm { get; set; }
...
... set this reference upon opening Form2 ...
private void btnToevoegR_Click(object sender, EventArgs e)
{
Forms.HandAddReserveer HAR = new Forms.HandAddReserveer();
HAR.CallingForm = this;
...
... finally the second Form will call the Refresh operation on Form1 on closing:
if (MessageBox.Show("Je hebt dit item al gereserveerd!") == DialogResult.OK)
{
CallingForm.<Refresh list>
}
I have the following code that load my windows form:
private void Panou_Load(object sender, EventArgs e)
{
List<string>[] list;
list = Conexiune.Select();
dataGridView1.Rows.Clear();
(dataGridView1.Columns[3] as DataGridViewComboBoxColumn).DataSource = new List<string> { "", "activ", "inactiv", "neverificat", "blocat" };
for (int i = 0; i < list[0].Count; i++)
{
int number = dataGridView1.Rows.Add();
dataGridView1.Rows[number].Cells[0].Value = list[0][i];
dataGridView1.Rows[number].Cells[1].Value = list[1][i];
dataGridView1.Rows[number].Cells[2].Value = list[2][i];
dataGridView1.Rows[number].Cells[3].Value = list[3][i];
dataGridView1.Rows[number].Cells[4].Value = list[4][i];
dataGridView1.Rows[number].Cells[5].Value = list[5][i];
dataGridView1.Rows[number].Cells[6].Value = list[6][i];
}
}
Everything works normally but now I want to make some updates in database when I remove a row so I use: dataGridView1_RowsRemoved .
Example:
private void dataGridView1_RowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
{
MessageBox.Show("deleted");
}
Why the message "deleted" is showing up when the form load?
dataGridView1.Rows[number].Cells[0].Value contain the id from database. How I retrieve this id in the dataGridView1_RowsRemoved function?
The actual binding of the data source (your list) to the DataGridView happens after the form load event, and so a lot of events that your would not expect to be fired are fired in the process of data binding.
I don't know why that is - would need to dig through the code or the DataGridView component itself, but there is fortunately a work around - attach all such events during the event handler of the DataBindingComplete event.
void dataGridView1_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
dataGridView1.RowsRemoved += new DataGridViewRowsRemovedEventHandler(dataGridView1_RowsRemoved);
}
That way you will not see RowsRemoved fired at form load.
Even better in your case, the answer to the second part of your question involves using a different event, that behaves as expected, even when not attached during DataBindingComplete.
The problem with RowsRemoved is that it fires after the row has been removed, so even though you have the (old) index of the row from the event args, getting to the data is very tricky (maybe impossible?)
The fix is to instead handle the UserDeletingRow event:
void dataGridView1_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e)
{
var msg = e.Row.Cells[0].Value.ToString();
MessageBox.Show(msg);
}
This event is fired once for every row that is deleted - it would probably make sense to aggregate these events and then perform a single database action from within the RowsRemoved handler.
This method correctly loads the data and binds it to the infragistics control as long as I remove the object source, EventArgs e and call the method from page_load.
Is that a good idea to remove the object source, eventArgs e?
protected void dgvAppts_NeedDataSource(object source, EventArgs e)
{
if (Session.IsNewSession == false)
{
DataTable ApptTable = new DataTable();
ApptTable = objGatewayFunctions.GetAppointmentReCap(Session["LoginID"].ToString(), RecapDate.ToShortDateString(), "R", ConfigurationManager.AppSettings["Connection"].ToString());
this.dgvAppts.DataSource = ApptTable;
//if (ApptTable.Rows.Count == 0)
//{
// this.uwtTabs.Tabs(0).Style.ForeColor = System.Drawing.Color.Gray;
//}
//else
//{
// this.uwtTabs.Tabs(0).Style.ForeColor = System.Drawing.Color.Black;
//}
}
}
If it's better to have the object source, EventArgs e present in the method. How do I call that function so it can load the WebDataGrid?
I don't think Infragistics WebDataGrid has NeedDataSource event; it sounds like the code was being converted from the use of a Telerik control.
If that's the case then there's no need to call the method
protected void dgvAppts_NeedDataSource(object source, EventArgs e)
You can call it instead something like
protected void BindMyGrid()
and call it from the page load indeed (with possible check to do it only if page is not in a postback mode)
You need to assign your method as event handler to NeedDataSource event in your grid control.
Select your grid in the designer, open Properties window and select your method as event handler for NeedDataSource event.
Looks like the method got detached from the event and that's why you have to call it yourself. It should be the grid control that calls your method when it needs the data source by raising the event.