I have a TabControl, with a few TabPages. What I am trying to do is change tabs, and have it call a richtextbox on a different tab. I will try to word the question better after code...
void tabControl1_SelectedIndexChanged(object sender, System.EventArgs e)
{
switch(tabControl1.SelectedTab.Name)
{
case "tabPage_ProdOrders":
dostuff();
break;
}
//throw new System.NotImplementedException();
}
void dostuff()
{
while (tabControl1.SelectedTab == tabControl1.TabPages["tabPage_ProdOrders"])
{
safeCall("testing...\n");
Thread.Sleep(10000);
}
}
delegate void SetTextCall(string s);
public void safeCall(string s)
{
if (this.richTextBox1.InvokeRequired)
{
SetTextCall d = new SetTextCall(safeCall);
this.Invoke(d, new object[] { s });
}
else this.richTextBox1.AppendText(string.Format(":{0}:\n",s));
}
I know it looks sloppy- sorry. So the richtextbox resides on tabpage "tabPage_DEV". What I am trying to have happen is when I change to tabpage "tabPage_ProdOrders" have it append text to the richtextbox that is on "tabPage_DEV". This is moreover a test to make sure everything runs smooth, what I eventually will have happening is once "tabPage_ProdOrders" is selected I will have a gridview bound to my database, and so long as that page is selected, it will refresh from the database every X seconds. The problem is that as soon as I select the "tabPage_ProdOrders" the whole app freezes. I am assuming this is due to the Thread.Sleep() that I have within the while loop (my assumption is that since that void is part of the windows from that when it is .Sleep() the whole form is .Sleep() correct? Any guidance on a work around would be stellar.
Add a Timer to your form and code its Tick event to do all the DB stuff.
Change the dostuff method like this:
void dostuff()
{
if (tabControl1.SelectedTab == tabControl1.TabPages["tabPage_ProdOrders"])
{
dbTimer.Interval = 10000; // your interval in ms
dbTimer.Start();
}
else dbTimer.Stop();
}
With Thread.Sleep(..) you send the current, that is the GUI thread to sleep. Suerely not what you want..
I used similar to TaW's suggestions...
delegate void populateProdOrders(DataTable dt);
public void safePopulate(DataTable dt)
{
if (this.dataGridView2.InvokeRequired)
{
populateProdOrders d = new populateProdOrders(safePopulate);
this.Invoke(d, new object[] { dt });
}
else this.dataGridView2.DataSource = dt;
}
public Form1()
{
// stuff
System.Timers.Timer refreshProdOrders = new System.Timers.Timer(5000);
refreshProdOrders.Elapsed += refreshProdOrders_Elapsed;
refreshProdOrders.AutoReset = true;
refreshProdOrders.Enabled = true;
}
void refreshProdOrders_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
//dostuff();
var globes = SharedResourceVariables.globals;
DataTable dt = new DataTable();
System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection(globes.SQLConn);
using (conn)
{
System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("development_usp_viewProdductionOrders", conn);
cmd.CommandType = CommandType.StoredProcedure;
using (cmd)
{
conn.Open();
System.Data.SqlClient.SqlDataReader dr = cmd.ExecuteReader();
dt.Load(dr);
conn.Close();
}
}
safePopulate(dt);
}
Related
private void btnLoad_click(object sender, EventArgs e)
{
string filename = txtfile.Text;
progressBar.Visible = true;
progressBar.Style = ProgressBarStyle.Marquee;
DataTable dt= GetDt(filename);
datagridview1.DataSource = dt;
datagridview1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells;
progressBar.Visible = false;
}
}
On button click I am trying to display a progress bar, do the load (takes about 10 seconds), AutoSizeRowsMode that takes about 5 seconds, and then hide the progress bar. However, while the load is happening, I cannot see the progress bar - it looks like the app has frozen. Is this because everything runs on single thread?
To counter this possible problem, I tried to put the grid load into separate thread using Task.Run:
await Task.Run(() =>
{
DataTable dt= GetDt(filename);
datagridview1.DataSource = dt;
datagridview1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells;
});
Above code gives error because the datagridview is on UI thread where as the Task.Run is on separate thread. What is the simples solution here?
Not sure if this is wpf or winforms but you can use the Progress class for this it synchronizes with the base context where it is initialized. So it will have no issues communicating with the Main/UI thread.
Progress can be initialized with an Action and this action can be really helpful in updating your UI components.
basic click event example tested on WPF forms:
private void btn_Click(object sender, RoutedEventArgs e)
{
IProgress<int> p = new Progress<int>((x)=>
{
pg1.Value = x;
if (x == 100)
{
//update data grid here
MessageBox.Show("Completed!");
}
});
Task.Run(() =>
{
//Your heavy tasks can go here
var x = 0;
for (int i = 0; i < 10; i++)
{
System.Threading.Thread.Sleep(500);
x += 10;
p.Report(x); //report back progress
}
});
}
Here pg1 (Progress bar component of WPF) is updated on button click event which triggers the heavy process you need in the new task run.
Updated code where the grid data loading can be done.
See if the following will fulfill your basic requirements.
The Progressbar is on a panel which once done loading is hidden, alternate is to show a child form with a progressbar and close once data is loaded.
Data is read from a SQL-Server database
Using a separate class
Code responsible to load data
using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading.Tasks;
namespace LoadDataGridViewProgressBar.Classes
{
public class Operations
{
private static string _connectionString =
"Data Source=.\\sqlexpress;Initial Catalog=NorthWind2020;Integrated Security=True";
public static async Task<(Exception exception, DataTable dataTable)> LoadCustomerData()
{
var customersDataTable = new DataTable();
using (var cn = new SqlConnection() { ConnectionString = _connectionString })
{
using (var cmd = new SqlCommand() { Connection = cn })
{
try
{
cmd.CommandText =
"SELECT C.CustomerIdentifier, C.CompanyName, C.ContactId, CT.ContactTitle, C.City, CO.[Name] " +
"FROM Customers AS C INNER JOIN ContactType AS CT ON C.ContactTypeIdentifier = CT.ContactTypeIdentifier " +
"INNER JOIN Countries AS CO ON C.CountryIdentifier = CO.CountryIdentifier ";
await cn.OpenAsync();
customersDataTable.Load(await cmd.ExecuteReaderAsync());
customersDataTable.Columns["Name"].AllowDBNull = false;
customersDataTable.Columns["CustomerIdentifier"].ColumnMapping = MappingType.Hidden;
customersDataTable.Columns["ContactId"].ColumnMapping = MappingType.Hidden;
}
catch (Exception exception)
{
return (exception, customersDataTable);
}
}
}
return (null, customersDataTable);
}
}
}
In the form I use a BindingSource for working with data which is used as the DataSource for the DataGridView.
using System;
using System.Data;
using System.Windows.Forms;
using LoadDataGridViewProgressBar.Classes;
namespace LoadDataGridViewProgressBar
{
public partial class Form1 : Form
{
public readonly BindingSource _customersBindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
Shown += OnShown;
}
private async void OnShown(object sender, EventArgs e)
{
var (exception, dataTable) = await Operations.LoadCustomerData();
try
{
if (exception == null)
{
_customersBindingSource.DataSource = dataTable;
customerDataGridView.DataSource = _customersBindingSource;
customerDataGridView.ExpandColumns(true);
}
else
{
CurrentButton.Enabled = false;
MessageBox.Show(exception.Message);
}
}
finally
{
progressBar1.Style = ProgressBarStyle.Continuous;
progressBar1.MarqueeAnimationSpeed = 0;
panel1.Hide();
}
}
private void Form1_Load(object sender, EventArgs e)
{
progressBar1.Style = ProgressBarStyle.Marquee;
progressBar1.MarqueeAnimationSpeed = 30;
}
private void CurrentButton_Click(object sender, EventArgs e)
{
if (_customersBindingSource.Current == null) return;
// access fields from the DataRow
var data = ((DataRowView) _customersBindingSource.Current).Row;
MessageBox.Show($"{data.Field<int>("CustomerIdentifier")}");
}
}
}
Resize DataGridView columns
using System.Windows.Forms;
namespace LoadDataGridViewProgressBar.Classes
{
public static class DataGridViewExtensions
{
public static void ExpandColumns(this DataGridView sender, bool sizable = false)
{
foreach (DataGridViewColumn col in sender.Columns)
{
// ensure we are not attempting to do this on a Entity
if (col.ValueType.Name != "ICollection`1")
{
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
}
}
if (!sizable) return;
for (int index = 0; index <= sender.Columns.Count - 1; index++)
{
int columnWidth = sender.Columns[index].Width;
sender.Columns[index].AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
// Set Width to calculated AutoSize value:
sender.Columns[index].Width = columnWidth;
}
}
}
}
I cannot see the progress bar - it looks like the app has frozen. Is
this because everything runs on single thread?
Correct
Above code gives error because the datagridview is on UI thread where
as the Task.Run is on separate thread.
Also correct
What is the simples solution here?
The simplest solution would be to use MVVM architecture and bind the DataSource property to an ObservableCollection in the view-model, but if we ignore that, the most straight-forward way would be to GetDt on a worker thread (using Task.Run), but to set the DataSource on the UI thread.
So instead of this:
await Task.Run(() =>
{
DataTable dt= GetDt(filename);
datagridview1.DataSource = dt;
datagridview1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells;
});
Do this:
DataTable dt = await Task.Run(() => GetDt(filename));
datagridview1.DataSource = dt;
datagridview1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells;
I have a windows form app written in C#. I took all the code that, (does a restapi call,builds a list, puts the list into a datagridview) into a thread so I can check the thread and update the customer as it is running. When doing this everything works except the scroll bars are greyed out.
Is there a problem with scrollbars only?
If your code still in main thread, your form isn't able to do anything.(you need double check)
But it is true, you can use Application.DoEvents(); after finish bind data to gridView.
This method will stop all thread and update view, please make sure this is not a perfect solution, this method will bring irreversible error sometimes when cross thread condition.
Anyway, I hope to use System.Windows.Forms.Timer to make it simple.
Here is sample.
After starting form, timer will work 100 times and interval is 5 seconds.
private int loopCnt = 1;
// you don't need to this, just drag timer component from a "tool box"
// to your form any where at design time(mode).
// private System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
private void Form1_Load(object sender, EventArgs e)
{
//it is up to you start timer at form-load or triggering after some button click.
//this.loopCnt = 1;
this.timer1.Enabled = true;
this.timer1.Interval = 1000 * 5;
this.timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
CreateDataAndBind(loopCnt++);
if(loopCnt > 100)
{
this.timer1.Stop();
}
}
public void CreateDataAndBind(int loopCount)
{
DataSet dsTemp = new DataSet();
#region : Define data Table and columns
DataTable dtTemp = new DataTable();
DataColumn dColKey = new DataColumn("key", typeof(int));
DataColumn dColName = new DataColumn("name", typeof(String));
dtTemp.Columns.Add(dColKey);
dtTemp.Columns.Add(dColName);
dtTemp.Columns.Add(new DataColumn("role"));
dtTemp.Columns.Add(new DataColumn("timeText", typeof(String)));
dtTemp.Columns.Add(new DataColumn("timeValue", typeof(DateTime)));
#endregion
dsTemp.Tables.Add(dtTemp);
for (int index = 0; index < 10; index++)
{
DataRow dRow = dtTemp.NewRow();
dRow[0] = loopCount;
dRow[1] = "jornathan";
dRow[2] = "Developer";
dRow[3] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
dRow[4] = DateTime.Now;
dtTemp.Rows.Add(dRow);
}
this.BindDataSet(dsTemp);
}
public delegate void updateDataSetDelegate(DataSet ds);
private void BindDataSet(DataSet ds)
{
if (this.dataGridView1.InvokeRequired)
{
this.Invoke(new updateDataSetDelegate(BindDataSet), new object[] { ds });
}
else
{
this.dataGridView1.DataSource = ds.Tables[0];
}
}
I have two WinForms (Setting and frmMain). I have a TreeView in Setting's form and I want to call its FillTree method in the second form frmMain.
I'm using the TreeView.Invoke for the threading purposes.
Here is my code for TreeView filling data in Setting's form :
TreeNode parentNode;
public void FillTree(DataTable dtGroups, DataTable dtGroupsChilds)
{
treeViewGroups.Nodes.Clear();
if (dtGroups == null) return;
foreach (DataRow rowGroup in dtGroups.Rows)
{
parentNode = new TreeNode
{
Text = rowGroup["Groupname"].ToString(),
Tag = rowGroup["Groupid"]
};
treeViewGroups.Invoke(new Add(AddParent), new object[] { parentNode });
if (dtGroupsChilds == null) continue;
foreach (DataRow rowUser in dtGroupsChilds.Rows)
{
if (rowGroup["Groupid"] == rowUser["Groupid"])
{
TreeNode childNode = new TreeNode
{
Text = rowUser["Username"].ToString(),
Tag = rowUser["Phone"]
};
treeViewGroups.Invoke(new Add(AddParent), new object[] { childNode });
System.Threading.Thread.Sleep(1000);
}
}
}
treeViewGroups.Update();
}
public delegate void Add(TreeNode tn);
public void AddParent(TreeNode tn)
{
treeViewGroups.Nodes.Add(tn);
}
public void AddChild(TreeNode tn)
{
parentNode.Nodes.Add(tn);
}
FillTree method from above code, I wants call it in my second form frmMain which I tried like so:
Settings settingsWindow;
public frmMain()
{
InitializeComponent();
settingsWindow = new Settings(this);
}
private void SomeMethod()
{
//Two DataTables (dt1 and dt2) are passed from frmMain form
settingWindow.FillTree(dt1, dt2);
}
When I call FillTree method it show me error like this:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
I real wants to know, where should be handle TreeView's Invoke method in my second winform frmMain?
I'm following these links (but no avail):
1) Populating TreeView on a Background Thread
2) How to add object in treeview from another thread
3) How can i invoke a method called from backgroundworker dowork event?
Edited for Visualizing Problem
I have tried it with TreeView its not working then I Tried it with ListBox but the problem is still same.
Problem: I've a method (which populate my ListBox) in WinForm settingsWindow and I want to call that method in my second WinForm frmMain.
Settings form screenshot:
frmMain form screenshot:
Problem GIF :
Settings form Code for ListBox Populating :
public void PopulateGroupListData(DataTable dt)
{
listGroups.DataSource = null;
listGroups.DisplayMember = "GroupName";
listGroups.ValueMember = "Groupid";
listGroups.DataSource = dt;
if (listGroups.Items.Count > 0)
listGroups.SelectedItem = listGroups.Items[0];
}
Calling PopulateGroupListData in second form frmMain 's method:
void onCompleteReadFromServerStream(IAsyncResult iar)
{
/... some code
String[] arr1 = ServerMessage[1].Split('&');
string Groupid1 = arr1[0];
string GroupName1 = arr1[1];
GroupsList.Rows.Add(Groupid1, GroupName1);
settingsWindow.PopulateGroupListData(GroupsList);
/... some code
}
Subscribe to HandleCreated event and fill the tree in the event handler.
public partial class Form1 : Form
{
Form2 settingsWindow;
public Form1()
{
InitializeComponent();
}
private void SettingsWindow_HandleCreated(object sender, EventArgs e)
{
var dt1 = new SampleTable1();
var dt2 = new SampleTable2();
settingsWindow.FillTree(dt1, dt2);
}
private void button1_Click(object sender, EventArgs e)
{
settingsWindow = new Form2();
settingsWindow.HandleCreated += SettingsWindow_HandleCreated;
settingsWindow.ShowDialog();
}
}
As a bonus, we also fixed a couple problems with FillTree method, so the tree can be built correctly.
public void FillTree(DataTable dtGroups, DataTable dtGroupsChilds)
{
treeViewGroups.Nodes.Clear();
if (dtGroups == null) return;
foreach (DataRow rowGroup in dtGroups.Rows)
{
parentNode = new TreeNode
{
Text = rowGroup["Groupname"].ToString(),
Tag = rowGroup["Groupid"]
};
treeViewGroups.Invoke(new Add(AddParent), new object[] { parentNode });
if (dtGroupsChilds == null) continue;
foreach (DataRow rowUser in dtGroupsChilds.Rows)
{
if ((int)rowGroup["Groupid"] == (int)rowUser["Groupid"])
{
TreeNode childNode = new TreeNode
{
Text = rowUser["Username"].ToString(),
Tag = rowUser["Phone"]
};
treeViewGroups.Invoke(new Add(AddChild), new object[] { childNode });
//System.Threading.Thread.Sleep(1000);
}
}
}
treeViewGroups.Update();
}
Answering your follow-up question: if Form2 is already visible, the exception you reported wouldn't happen because at this point the window's handle has been created, as we show below.
public partial class Form1 : Form
{
Form2 settingsWindow;
SampleTable1 dt1;
SampleTable2 dt2;
int groupid = 1;
int userid = 101;
public Form1()
{
InitializeComponent();
dt1 = new SampleTable1();
dt2 = new SampleTable2();
dt1.AddGroup(groupid);
dt2.AddUser(groupid, userid++);
dt2.AddUser(groupid, userid++);
dt1.AddGroup(++groupid);
dt2.AddUser(groupid, userid++);
dt2.AddUser(groupid, userid++);
dt2.AddUser(groupid, userid++);
}
private void SettingsWindow_HandleCreated(object sender, EventArgs e)
{
settingsWindow.FillTree(dt1, dt2);
}
private void button1_Click(object sender, EventArgs e)
{
settingsWindow = new Form2(this);
settingsWindow.HandleCreated += SettingsWindow_HandleCreated;
settingsWindow.ShowDialog();
}
public void UpdateData(string groupname)
{
dt1.AddGroup(++groupid, groupname);
dt2.AddUser(groupid, userid++);
dt2.AddUser(groupid, userid++);
settingsWindow.FillTree(dt1, dt2);
}
}
Form2 stays the same you already have, just adding an event handler for the new button:
private void button1_Click(object sender, EventArgs e)
{
form1.UpdateData(textBox1.Text);
}
Answering your 2nd follow-up question: UpdateData was created for the specific use case of the user submitting a new group through Form2. I would rather have specific code for a different use case. Note that it would be fairly elementary to have UpdateData to consult with a server, or even better, with some abstract interface, that could be in a remote server or not (good design dictates it should be irrelevant/transparent...) and then return a string message to be presented in Form2. However, doing so wouldn't add any insight into the purposes of the limited scope of this sample. It would actually mud the waters about the root problem you're reporting and its corresponding solution. Let's not forget that without it, the reported exception in your original question comes right back at you...:O)
The code which you have shared doesn't help us to reproduce the problem. The only thing that someone can do is creating an example, showing you how to load a TreeView in another form in another thread (if thread is really necessary).
There are some points that you should consider:
You should first show the form, then call Invoke method of the form or one of its controls. Otherwise you will receive 'Invoke or BeginInvoke cannot be called on a control until the window handle has been created.'
When adding a lot of nodes to a TreeView, first call BeginUpdate then add all the nodes then call EndUpdate. This way it will be quite faster with less UI rendering.
Condiser using async/await pattern to have a non-blocking UI.
When you want to update UI thread from another thread, use Invoke.
Don't do blocking time-consuming non-UI tasks in the UI thread.
When using Invoke, keep in mind that the code is running in the UI thread. So don't call blocking time-consuming non-UI tasks in the Invoke. Just call the UI code in Invoke.
In the following example, I've created a form having a button. On click of button, I load data and then open another window which has a TreeView inside. Then loading tree with 10000 nodes:
private async void button1_Click(object sender, EventArgs e)
{
DataTable table = null;
//Load data
this.Text = "Loading ...";
await Task.Run(async () => {
await Task.Delay(2000); //Simulating a delay for loading data
table = new DataTable();
table.Columns.Add("C1");
for (int i = 0; i < 10000; i++)
table.Rows.Add(i.ToString());
});
this.Text = "Load data successfully.";
//Show the other form
var f = new Form();
var tree = new TreeView();
tree.Dock = DockStyle.Fill;
f.Controls.Add(tree);
f.Show();
//Load Tree
f.Text = "Loading tree...";
await Task.Run(async () => {
await Task.Delay(2000); //Simulating a delay for processing
Invoke(new Action(() => { tree.BeginUpdate(); }));
foreach (DataRow row in table.Rows) {
//DO NOT processing using invoke, just call UI code in INvoke.
Invoke(new Action(() => { tree.Nodes.Add(row[0].ToString()); }));
}
Invoke(new Action(() => { tree.EndUpdate(); }));
});
f.Text = "Load tree successfully.";
}
I have a form which is running on the other laptop, this is the kitchen side where all the orders will go through after buying in my POS (which is running on the other laptop also) side.
Now my listview in the kitchen side refreshes after 5 seconds 'using the timer', are there any options or ways to refresh the listview without using the timer so that the 'Focus' when I select an item inside the listview will not disappear?
This is the my code:
public Kitchen()
{
InitializeComponent();
listView2.Columns.Add("ORDERS", 800);
listView2.View = View.Details;
System.Windows.Forms.Timer timer_1 = new System.Windows.Forms.Timer();
timer1.Interval = 5000;
timer1.Tick += new System.EventHandler(timer1_Tick);
timer1.Start();
}
private void dinein(String tblnmber)
{
String[] row = { tblnmber };
listView2.Items.Add(new ListViewItem(row));
}
public void loaddinein()
{
listView2.Items.Clear();
string sq = "select tblnmber as [ORDERS] FROM Kitchen Group By tblnmber";
cmd = new SqlCommand(sq,con);
try
{
con.Open();
adp = new SqlDataAdapter(cmd);
adp.Fill(dt);
foreach (DataRow row in dt.Rows)
{
dinein(row[0].ToString());
}
con.Close();
dt.Rows.Clear();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
con.Close();
}
}
private void timer1_Tick(object sender, EventArgs e)
{
loaddinein();
}
Well your question is really how do I refresh an existing list of items without changing order or focus? not anything to do with timers.
To accomplish that when you fetch the data from the DB again, you need to determine which items have changed in the list (compare the text of your model to an item in the list) and simply update the Text of those items. Overwriting a ListViewItem's Text property won't change selection/focus or re-ordering by default.
Don't clear out the ListView and repopulate otherwise focus/selection will change.
A timer is still perfectly valid to use.
Pooling continuously to the DB server is not a good approach, you can go for either of the two approaches.
Raise a change message using trigger to MSMQ, and subscribe to the
MSMQ for new messages. For more details on the implementation you
can check here
Monitoring the data changes using SqlDependency, for more details you can check here
Note: Both the approaches have it own pro/cons. For example if the number of listeners are more, in that case SQL Dependency will hit the performance.
Hi guys i've already watch and read some tutorials , and implement it, unfortunately sql dependency is not firing to my datagridview nor listview. I've also enabled the broker.
This is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data.SqlClient;
//ALTER DATABASE HOB SET ENABLE BROKER
namespace DemoSQL
{
public partial class Form1 : Form
{
public string m_connect = #"Data Source=DESKTOP-3B561M1;Initial Catalog=Users;Integrated Security=True";
SqlConnection con = null;
public delegate void NewHome();
public event NewHome OnNewHome;
public Form1()
{
InitializeComponent();
try
{
SqlClientPermission ss = new SqlClientPermission(System.Security.Permissions.PermissionState.Unrestricted);
ss.Demand();
}
catch (Exception)
{
throw;
}
SqlDependency.Stop(m_connect);
SqlDependency.Start(m_connect);
con = new SqlConnection(m_connect);
}
private void Form1_Load(object sender, EventArgs e)
{
OnNewHome+=new NewHome(Form1_OnNewHome);//tab
//load data vao datagrid
LoadData();
}
public void Form1_OnNewHome()
{
ISynchronizeInvoke i = (ISynchronizeInvoke)this;
if (i.InvokeRequired)//tab
{
NewHome dd = new NewHome(Form1_OnNewHome);
i.BeginInvoke(dd, null);
return;
}
LoadData();
}
//Ham load data
void LoadData()
{
DataTable dt = new DataTable();
if (con.State==ConnectionState.Closed)
{
con.Open();
}
SqlCommand cmd = new SqlCommand("SELECT FirstName,LastName from dbo.Uss", con);
cmd.Notification = null;
SqlDependency de = new SqlDependency(cmd);
de.OnChange += new OnChangeEventHandler(de_OnChange);
dt.Load(cmd.ExecuteReader(CommandBehavior.CloseConnection));
dataGridView1.DataSource = dt;
}
public void de_OnChange(object sender, SqlNotificationEventArgs e)
{
SqlDependency de = sender as SqlDependency;
de.OnChange -= de_OnChange;
if (OnNewHome!=null)
{
OnNewHome();
}
}
}
}
I have a windows form with many controls. A tiny part of it is logging in to an SQL server and fetching the list of database names and assigning the collection to a combobox.
private void InitializeComponent()
{
//...
//...
this.ServerTB = new System.Windows.Forms.TextBox();
this.UserNameTB = new System.Windows.Forms.TextBox();
this.PasswordTB = new System.Windows.Forms.TextBox();
//...
//...
this.ServerTB.TextChanged += new System.EventHandler(this.OnSQLServerChanged);
this.UserNameTB.TextChanged += new System.EventHandler(this.OnSQLServerChanged);
this.PasswordTB.TextChanged += new System.EventHandler(this.OnSQLServerChanged);
this.DatabaseCmbBox = new System.Windows.Forms.ComboBox();
//...
//...
this.DatabaseCmbBox.MouseClick += new System.Windows.Forms.MouseEventHandler(this.DatabaseCmbBox_Click);
//....
}
private void DatabaseCmbBox_Click(object sender, MouseEventArgs e)
{
MessageBox.Show(sender.GetType().ToString());
this.Cursor = Cursors.IBeam;
List<string> dbList = new List<string>();
dbList = GetDatabaseList();
DatabaseCmbBox.DataSource = dbList;
DatabaseCmbBox.SelectedIndex = -1;
if (dbList.Count > 0)
{
DatabaseCmbBox.MouseClick -= DatabaseCmbBox_Click;
}
DatabaseCmbBox.Focus();
this.Cursor = Cursors.Default;
}
protected List<string> GetDatabaseList()
{
List<string> list = new List<string>();
string conString = "server=" + ServerTB.Text + ";uid=" + UserNameTB.Text + ";pwd=" + PasswordTB.Text + "; database=master";
try
{
using (SqlConnection con = new SqlConnection(conString))
{
con.Open();
using (SqlCommand cmd = new SqlCommand("select name from sys.databases where name not in ('master', 'model', 'tempdb', 'msdb') ", con))
{
using (IDataReader dr = cmd.ExecuteReader())
{
while (dr.Read())
{
list.Add(dr[0].ToString());
}
dr.Close();
}
cmd.Dispose();
}
con.Close();
}
}
catch(SqlException ex)
{
DatabaseCmbBox.MouseClick -= DatabaseCmbBox_Click;
MessageBox.Show(ex.Message);
ServerTB.Focus();
}
return list;
}
private void OnSQLServerChanged(object sender, EventArgs e)
{
DatabaseCmbBox.MouseClick += DatabaseCmbBox_Click;
}
I don't have a problem in fetching the list of Databases. It takes about 10 seconds to do so. With the messagebox in the event handler, I found out that the MouseClick event, for no apparent reason, gets fired like 38 times. The same thing happens even if I use the Enter event or Click event, instead of the MouseClick event.
Why does this happen, and how does one go about using these kind of events?
Thanks in advance,
RPS.
If you want to get list of Databases, why don't you do so on Combobox value changed event?
Anyways,I see that you are doing
private void OnSQLServerChanged(object sender, EventArgs e)
{
DatabaseCmbBox.MouseClick += DatabaseCmbBox_Click;
}
This should solve your problem
private void OnSQLServerChanged(object sender, EventArgs e)
{
DatabaseCmbBox.MouseClick -= DatabaseCmbBox_Click;
DatabaseCmbBox.MouseClick += DatabaseCmbBox_Click;
}
This is a potential error: you should subscribe DatabaseCmbBox.MouseClick only once, not every time OnSQLServerChanged. Correct your code correspondingly, and the issue with multiple click events will be fixed:
private void OnSQLServerChanged(object sender, EventArgs e)
{
DatabaseCmbBox.MouseClick += DatabaseCmbBox_Click;
}