I have a problem with populating my comboBoxes in a WinForm application that I'm writing. The data that I use to populate these comboBoxes is pulled from database. The problem is that there is a lot of data that needs to be binded to the comboBoxes, so this process takes a very long time during which the entire application is locked (the entire process of binding data takes over 9 seconds, while the process of pulling the data from the database takes only 400 milliseconds). I'm trying to speed things up by splitting the processes of creating the controls (main thread) and populating the comboBoxes (background worker), but naturally I get the cross thread error.
This is the part of code that I use:
private void Populate()
{
comboBox1.BindingContext = new System.Windows.Forms.BindingContext();
comboBox1.DataSource = MyClass.dtMyDataTable;
comboBox1.DisplayMember = "TitleColumn";
.//I repeat the same code for each comboBox
.//I use the BiningContext because some of the comboBoxes have to display the
.//same data.
}
I created a class that contains all DataTables that I need in this form - there are multiple forms that use the same data from the database, so I created a class and created an object that fills all of these DataTables on the parent Form.Load(), and then I pass them to the children forms when I create them. This way I load the data when the application loads (it doesen't even take that long), so it should be ready to use when I call it from child forms. I've tried to call the Populate() method from backgroundWorker.DoWork() method, and there I get the cross thread error.
My question is - is there a way to make this work, and if not, what could I use as alternative solution.
Thank you
I'm not a full bottle on invoking but try this:
PopulateData()
{
if (combobox1.InvokeRequired)
{
combobox1.Invoke(new EventHandler(delegate(object o, EventArgs a)
{
PopulateData();
}
));
}
else
{
// Do your updates here...
}
}
I believe this will find the thread responsible for combobox1, which is going to be the same thread for the other combo's, and then run.
I'm sure someone else will chime in with a better way to invoke perhaps at the form level ?
I've found a good alternative, and it sped things up from 9 seconds to 1.5 seconds. The solution is to place the comboBox.DisplayMember BEFORE the 'comboBox.DataSource' line because when you change the DisplayMember (or ValueMember) the data source repopulates itself. So if the comboBox.DisplayMember is after the 'comboBox.DataSource' line, the data source populates itself 2 times (I think that ClearBeforeFill is enabled by default when binding data sources, that is why there are no duplicates in the binded data).
Thanks anyway.
Related
I have inherited a form and have been tasked with speeding it up. Basically, it is a form which makes a WCF call to a service for some data, and then displays the data in a DataGridView.
I originally thought the bottleneck was the WCF call, so I moved that to a BackgroundWorker, to stop the UI from freezing up.
That helped a little bit, but it turns out that the bottleneck is actually this line:
DumpInfoGrid.DataSource = dumpGridBinding;
where the data retrieved from the WCF is assigned to the DataSource property.
The code basically boils down to this:
private BackgroundWorker dataGatherer;
dataGatherer.DoWork += dataGatherer_DoWork;
dataGatherer.RunWorkerCompleted += dataGatherer_RunWorkerCompleted;
private void dataGatherer_DoWork(object sender, DoWorkEventArgs e)
{
dumpGridBinding = WCF.GetDataFromService();
}
private void dataGatherer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
DumpInfoGrid.DataSource = dumpGridBinding;
}
Is there anything I can do to speed this up? Or any part I can move to the BackgroundWorker ? The assignment freezes the UI for far too long.
One possible solution for this would be to use FastDataGridView (or FastDataListView) which is found in ObjectListView, which greatly improves the speed of the DataGridView control by not attempting to process all the records at one time.
Another solution would be to page the DataGridView you currently have, spreading out the load time across the pages as you use them as seen here
You could use virtual mode -
MSDN: http://msdn.microsoft.com/en-us/library/15a31akc.aspx
This mode shows just the rows the user is looking at through the viewable region - you only load the rows actually being viewed - which is fast.
You handle the CellValueNeeded event and provide data from your list when the user scrolls through the list. Tex example shows you how to do that, but it's easy to write a linq statement to skip, take whatever is requested from your datasource (returned from the WCF service).
I have a weird issue with the DataGridView control in virtual mode and threading. I use a Cache class to stored 2 pages of data. When I scroll down and I have to load a new page in cache, there is a little delay before data is display (it's ok). But, instead of having
the next row displayed(199th,200th,201th), the control skip many rows (CellValueNeeded). By example, I cached 1 to 200 rows, when I scrolling at 199, I'm loading a page to 200 à 299. However, in the datagrid it's display the row 320.. instead of 300. (I kept the mouse button pressed 2-3 secondes, before releasing it). I notice I have the problem only when I load the cache in a particular thread. Does anyone have a idea how I can fix this?
private void datagridView_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
e.Value = m_rfiCache.RetrieveElement(e.RowIndex, e.ColumnIndex);
}
....
public DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage)
{
e : " + lowerPageBoundary);
DataTable dt = new DataTable();
if (m_useThreading) //THIS DOESN'T WORK WELL
{
Thread MulThread = new Thread(delegate()
{
dt = this.LoadData(lowerPageBoundary, rowsPerPage);
});
MulThread.Start();
MulThread.Join();
}
else //OK.
{
dt = this.LoadData(lowerPageBoundary, rowsPerPage);
}
return dt;
}
P.S. : This is only a snippet, I used an external API to extract data from ODB. This API used thread to load data.
Does anyone have a idea or I can fix this?
You can load the data in a background thread, but you can't add it to your main DataTable in the background thread itself.
One potential approach would be to load the data as you're doing, but into a separate DataTable. You could then, on the UI thread, call DataSet.Merge to merge this data in with your bound data correctly (which will be reasonable fast).
Finally, I found the solution. When the tread is processing and the scroll down button is pressed, my event for scrolling aren't sent to the datagrid unless my thread is finished. So I received a bunch of calls for drawing cells and the index was wrong. To avoid this, I removed the vs scrollbar from the datagridview and added a VSscrollbar control. When I'm loading data in my thread, I set to false the enable property on my scrollbar. When the thread is completed, I enable the scrollbar. Is not the BEST solution, but at least, there is now row skipped.
Note : Virtual Mode works very well if you don't load your data in a separate thread.
I have written a class that fills a treeview for me. IN my project I need this treeview several times and I don;t want to copy paste my code, so I decided to create a class that fills the treeview for me.
On some forms I want to use a thread to fill the treeview. This is because sometimes it can take some time to load the data and fill the treeview.
In my treeview-class I pass the treeview in the constructor. At the moment I want to fill the treeview, I call the LoadTreeview() method.
I'd like to call the LoadTreeview method on a thread, but when I do this I get the exception that the treeview is created on another thread. Which is logic off course. But I was wondering, what is the best way to create a custom class that works with controls and you want to use this class in a thread?
Do I need to write this code on every 'GUI-action'?
treeview.Invoke((MethodInvoker)delegate
{
treeview.Nodes.Add(MyNode);
})
Or are there other (smarter) ways?
Both your and Levisaxos' solutions will prevent the crash but you should really benchmark the runtime performance of this. The problem is that if you insert lots of nodes to the treeview and each node is inserted through Control.Invoke your code will not be doing much but synchronizing to the UI thread. If this is the case you should consider to separate loading the data that is needed to create the nodes for the treeview from the actual insertion of the nodes. Instead load the data asynchronously and then synchronously insert all nodes at once.
public delegate TreeView updateLabelDelegate(TreeView view);
private TreeView InvokeTreeView(TreeView view)
{
if (view.InvokeRequired)
{
view.Invoke(new updateLabelDelegate(InvokeTreeView), new object[] { view });
return null;
}
else
{
return view;
}
}
I hope this helps you. This is how I am handeling Async operations.
[edit]
Depending on how you want to use it.
In a thread:
public TreeView thistreeviewsucks;
void SomeThread()
{
TreeView tv = new TreeView();
tv.Items.Add("something");
//upon completion
this.thistreeviewsucks = InvokeTreeView(tv);
}
As far as I know this will work.
private void RevenueSummary_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'accountingDataSet1.Revenue' table. You can move, or remove it, as needed.
this.revenueTableAdapter.Fill(this.accountingDataSet1.Revenue);
string strGEC = GEClabel.Text;
fillByToolStripButton.PerformClick();
}
hi, I am having problems displaying the data after I add a new revenue item. It will be display in the database but not in the datagridview. It will only appear if I close the application and run it again.
As i can see in your code you are calling the fill method only when the form is Loaded (I am assuming RevenueSummary_Load method is to handle the load event of the form), this makes the accountingDataSet1 to be filled only when your Form is loaded and thats why when you update the database its not reflected in the dataset.
There are two things you can do to solve this.
1) Employ a ServiceBroker that will notify your application when the database is updated via SqlDependency (I assume your Database has this facility).
2) The other simple method would be have a refresh button where you fill the dataset again.
3) Finally you can have a timer that fills the dataset on every interval
NOTE: Both SQL Server and Oracle support Change Data Capture.
Update
I dont know if MS Access has any push notification feature. As i have searched over internet your only options are either to go with timer based database polling or Implement a FileSystemWatcher object to monitor whether your MS Access database file gets changed.
here is an example to this approach.
I am creating an array of ListViewItems on a background thread and then i populate my listview with it on the ui thread. The problem is that if the array is too big the ui blocks while the listview is updated.
Is there a way to populate the listview with a small impact on the ui?
If you have a lot of data going into it you might want to use it in virtual mode, by setting the VirtualMode property of the ListView control to true. That means that the ListView will not be populated in the traditional sense, but you will hook up event handlers where you deliver the information to the list view in small chunks as the items are being displayed.
Very simple example:
private List<string> _listViewData = new List<string>();
private void toolStripButton1_Click(object sender, EventArgs e)
{
_listViewData = GetData(); // fetch the data that will show in the list view
listView1.VirtualListSize = _listViewData.Count; // set the list size
}
// event handler for the RetrieveVirtualItem event
private void listView_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
if (e.ItemIndex >= 0 && e.ItemIndex < _listViewData.Count)
{
e.Item = new ListViewItem(_listViewData[e.ItemIndex]);
}
}
You should also look into using the CacheVirtualItems event.
You can also use BeginUpdate and EndUPdate methods
listView1.BeginUpdate();
//Add Values
listView1.EndUpdate();
My comments got quite long, so I decided to make this a separate post.
First, +1 for Fredrik Mörk, using VirtualMode is the way to go. However, you do lose some functionality, e.g. column autosize, and sorting is easier handled on your own.
If that is a problem, populating from a worker thread may sound sounds tempting. However, the population will still take place in the thread that owns the list control (i.e. virtually always the main thread) - .NET makes that visible by enforcing you to use (Begin)Invoke. Additionally, the context switches will notably increase the total time you need to fill all items if you fill one by one, so you want to fill in chunks (say 50 items at a time, or even better fill as many as you can in 20 milliseconds). Add to that the additional synchronization required when contents changes, you have a quite complex solution for a not-so-stellar result.