I got three DataViews in my Program, where each has a DataTable connected that I full and update (via merge and deleting some rows) from an SqlAdapter. Updating is done in async Methods, where needed wrapped in my own Task.Run(...) Actions. After the updates the DataGridViews don't show the updated data although the combination of DataGridView, DataView and DataTable normally takes care of that.
This seems to break the automatic refresh of the DataGridViews that are bound to the DataViews. Observing the DataBindingComplete events of the DataGridViews reveals, that it is not properly fired when I update my underlying DataTables in async methods. When updating all three DataTables I only observe the event being fired for one of the DataGridViews at a state during the update process, where there is only one row in the DataTable... after the async method finishes the update there are far more rows that never get shown. My DataGridViews show the proper updated data as soon as I change the sorting of a column.
In the button's event that triggers the refresh I did another test:
private async void AddSomethingToolStripButton_Click(object sender, EventArgs e) {
if (sender is ToolStripButton button) {
int test1 = DataGridView1.RowCount; // 4
int test2 = ((DataGridView1.DataSource as BindingSource).DataSource as DataView).Count; // 4
/* Calls async method to write new value to DB and calls more async methods to refresh
* all the DataTables with the new data which also effects the one in this example */
await controller.AddSomethingToSqlDbAndRefreshDataTableAsync();
int test3 = DataGridView1.RowCount; // Still 4
int test4 = ((DataGridView1.DataSource as BindingSource).DataSource as DataView).Count; // 5
}
}
Those values are never in sync as they should be. After trying to manually refreshes by calling Refresh on the DataGridView or ReadValues on the Binding I managed to get it working by calling ResetBindings on the BindingSource which doesn't really feel right.
Is there something I am missing? Shouldn't I be using those data structures and mechanics in combination with the async/await pattern?
Edit - Example for a refresh method:
private async Task RefreshDataViewAsync(string query, DataView view) {
await Task.Run(() => {
using var con = new SqlConnection(sqlConnectionString);
using var adapter = new SqlDataAdapter();
adapter.SelectCommand = new SqlCommand(query, con);
var newtable = new DataTable();
adapter.FillSchema(newtable, SchemaType.Source);
foreach (DataColumn col in newtable.Columns) {
if (ColumnValueTypeMapping.TryGetValue(col.ColumnName, out Type coltype)) {
col.DataType = coltype;
}
}
adapter.Fill(newtable);
view.Table.Merge(newtable, false);
/* Remove rows that are not present anymore */
var rows2del = new List<DataRow>();
string[] primarykeys = view.Table.PrimaryKey.Select(keycol => keycol.ColumnName).ToArray();
foreach (DataRow row in view.Table.Rows) {
if (!newtable.Rows.Contains(primarykeys.Select(key => row.Field<object>(key)).ToArray())) {
rows2del.Add(row);
}
}
rows2del.ForEach(row => row.Delete());
view.Table.AcceptChanges();
});
}
You should split your UI code from your non-UI code.
private async void AddSomethingToolStripButton_Click(object sender, EventArgs e)
{
DataGridView1.DataSource = await GetDataAsync();
}
Your GetDataAsync method may use Task.Run. In fact, given that it's Windows Forms, it, probably, should.
I'm trying to load data from file to list and show that data immediately on Winforms' Datagridview. For that I've made the reading in another thread using Backgroundworker. The problem is, it only updates once and I can't get it to show more data. Not only that, when clicked, it tries to access element with -1 index, which of course doesn't exist, resulting in a crash.
Usually, from what I've seen, simply adding again same data to data source dataGridView1.DataSource = samelist; should work, but not in this case.
BackgroundWorker's work
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
//lotsofCode...
while (readData != null)
{
fooLists.Add(readData);
//someCalculations...
worker.ReportProgress();
}
}
BackgroundWorker's progressChanged
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.Invoke((MethodInvoker)delegate { UpdateGridView(); });
}
UpdateGridView Method
private void UpdateGridView()
{
if (fooLists.GetListById(1).calculatedList != null)
dataGridView1.DataSource = fooLists.GetListById(1).calculatedList;
}
Later on I've read some threads on stack, where one suggested using BindingSource as a "middleman", so now I have dataGridView1.DataSource = MyBindingSource; in the component initialization and tab1source.DataSource = fooLists.GetListById(1).calculatedList; instead of dataGridView1.DataSource. It certainly helped, as the list is now clickable as it should be, but still there are only few records on a list.
None of dataGridView1.Refresh(), dataGridView1.RefreshEdit() or dataGridView1.Update() helped, though made the list loading slightly fancier (probably due to the delay they introduced :) ).
I tried making some "protections" (semaphores, so the delegate isn't called again, while working; try-catches, though no exceptions are thrown there; data clearing before re-writing...) but the "better version" worked as poor as this one and it only darkened the code.
Am I missing a way to update the Datagridview control? Thanks in advance.
Although you didn't write it, but I think the reason that the items that you add to your dataSource are added to a collection that does not implement interface IBindingList. You probably use a simple list to hold your read data.
If your 'DataSourceimplements this interface, then after adding an item to your collection an event is raised. The class that holds theDataSource, whether it is aDataGridViewor aBindingSource` get notified about the changes in the list and update their contents accordingly.
Your solution would be to store your elements in an object of class System.ComponentModel.BindingList<T>.
Suppose the items you want to show are of class MyReadData
class MyForm : Form
{
public MyForm()
{
InitializeComponents();
this.myReadItems = new BindingList<MyReadData>();
this.MyBindingSource.DataSource = this.myReadItems;
// if not already done in InitializeComponents
this.MyDataGridView.DataSource = this.MyBindingSource;
}
private readonly BindingList<MyReadData> myReadItems;
// whenever needed, start the BackGroundWorker.
private void OnButtonReadFile_Click(object send, EventArgs e)
{
// create and start the backgroundworker
BackGroundWorkdr worker = ...
MyBackGroundWorkerParams params = ...
worker.RunWorkerAsync(params);
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
// I am certain the sender is my BackGroundWorker:
BackgroundWorker worker = (BackGroundWorker)sender;
MyBackGroundWorkerParams params = (MyBackGroundWorkerParams)e.Argument;
// do some work using the params
while (readData != null)
{
// some data read.
// dont't add the data to the list, just report the data that must been added to the list:
// someCalculations...
int percentProgress = ...
MyReadData dataToAddToGrid = ...
worker.ReportProgress(percentProgress, dataToAddToGrid);
}
private void bw_progressChanged(object sender, ProgressChangedEventArgs e)
{
// no need to call invoke, this is already the context of your forms thread
Debug.Assert(!This.InvokeReguired);
MyReadData dataToAdddToGrid = (MyReadData)e.UserState;
this.myReadItems.Add(dataToAddToGrid);
}
}
The main difference is that you should not let your BackgroundWorker to add data to the list of displayed data. The task of the BackGroundWorker is to read the data and to report to everyone who is interested what data has been read.
As it is the task of MyForm to display the read data, let MyForm decide which read data to display and in what format. This enhances reusage of both MyForm and MyBackGroundWorker: MyForm could display that that has been fetched in a different way, and MyBackGroundWorker could be used to inform others than MyForm to notify about read data.
Furthermore the display context of the progress changed event handler is the context of 'MyForm`, so an invoke is not needed.
You could also assign the IBindingList directly to the DataGridView, so without the use of a BindingSource. The only reason to keep a BindingSource is if you want access to the Current item, or if you want the freedom to fill your DataGridView with other items than the contents of your BindingList.
Finally: the most important part of the solution was that the items were added to an IBindingList.
System.Components.BindingList<T> is a class with limited functionality. If you want to order the rows in your DataGridView, or only show items that match some predicate, or combine items from several sources into one DataGridView, consider using Equin.ApplicationFramework.BindingListView
using Equin.ApplicationFramework;
public MyForm()
{
InitializeComponents();
this.myReadItems = new BindingListView<MyReadData>(this.components);
this.MyBindingSource.DataSource = this.myReadItems;
this.MyDataGridView.DataSource = this.MyBindingSource;
}
private readonly BindingListView<MyReadData> myReadItems;
private void bw_progressChanged(object sender, ProgressChangedEventArgs e)
{
MyReadData dataToAdddToGrid = (MyReadData)e.UserState;
this.myReadItems.Add(dataToAddToGrid);
// finished updating the list, DataGridView can be updated:
this.myReadItems.Refresh();
// this Refresh function allows you to change several items in the list
// without unnecessary intermediate updates of your BindingSource and DataGridView
}
Presto, that is all: free sorting or your columns by clicking on the column header. Consider examining their example project to see how filtering works and how to use several sources.
// Show only Brummies
this.myReadData.ApplyFilter(person => person.Address.City == "Birmingham");
// Remove the filter, show everyone again
this.myReadData.RemoveFilter();
I have a problem, with a DataGridView which get data from another thread.
I'm not the best in english, so here is the code:
private void TaskLoad()
{
List<taskafterdb> tasklist = new List<taskafterdb>();
this.tasklistTableAdapter.FillTaskBoard(sHADOWv5_testDataSet.tasklist)
var s = from x in sHADOWv5_testDataSet.tasklist
orderby x.DATE ascending
select x;
dgv_tasks.DataSource = s.AsDataView();
foreach (var task in sHADOWv5_testDataSet.tasklist)
{
tasklist.Add(new taskafterdb { DATE = task.DATE, COLOR = task.COLOR });
}
tasklist = tasklist.OrderBy(t => t.DATE).ToList();
dgv_tasks.DataSource = tasklist;
foreach (DataGridViewColumn column in dgv_tasks.Columns)
{
column.Frozen = false;
}
dgv_tasks.Columns["TASK"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dgv_tasks.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells;
dgv_tasks.DefaultCellStyle.WrapMode = DataGridViewTriState.True;
dgv_tasks.ScrollBars = ScrollBars.Both;
dgv_tasks.Controls[0].Enabled = true;
dgv_tasks.Controls[1].Enabled = true;
}
This is the TaskLoad() method, which fills the DGV with rows.
If I put this method to the main thread eg.: Form1_Load, the scrollbars are working perfectly.
The big problem is that I should refresh the data from another thread:
static public Thread dohardwork;
public void Form1_Load(object sender, EventArgs e)
{
dohardwork = new Thread(hardwork);
dohardwork.Start();
}
private void hardwork()
{
TaskLolad();
Form1_Resize(this, null);
IsHardworkDone = true;
fadeintimer.Enabled = true;
dohardwork.Abort();
}
And then, the scrollbars are gone... There is a little space for it, but nothing more.
I can scroll with mouse, or with arrow keys, but nothing more...
Thank you for any kind of help! :)
Here is what you need to do in order to improve performace and fix your issue
Refactor your TaskLoad method into 2 seperate methods. The first called GetTasks, which only fetch data from your database and nothing more. The second one called ShowTasks responsible for loading your tasks into the datagrid.
Your thread should call GetTasks only, upon receiving the result, use the BeginInvoke method of your DataGrid to run ShowTasks in the main thread passing it the result.
I'm writing this on the phone so can't show you a sample code. Let me know if you're still stuck and i'll show you some sample code later.
I have a DataGridView in which I load data from a SQL server database. When I load the data it takes quite long time.
I would like to give user information that the data is loading. May I ask you what is the best way connecting Progressbar when data is loading into the DataGridView?
I don't want anyone to make a fully working code for me. I just would like to know how it can be done.
I see someone awarded my question with bounty. I would like to say that at the moment Iam using this code which I would appriciate if it would fit.
DTGdataTable = new DataTable();
SqlDataAdapter SDA = new SqlDataAdapter
SDA.Fill(DTGdataTable);
dataGridView1.DataSource = DTGdataTable ;
Thank you everyone for your time.
If the problem is that it takes long to fetch the data from the database, i have a possible solution for you:
private void buttonLoad_Click(object sender, EventArgs e)
{
progressBar.Visible = true;
progressBar.Style = ProgressBarStyle.Marquee;
System.Threading.Thread thread =
new System.Threading.Thread(new System.Threading.ThreadStart(loadTable));
thread.Start();
}
private void loadTable()
{
// Load your Table...
DataTable table = new DataTable();
SqlDataAdapter SDA = new SqlDataAdapter();
SDA.Fill(table);
setDataSource(table);
}
internal delegate void SetDataSourceDelegate(DataTable table);
private void setDataSource(DataTable table)
{
// Invoke method if required:
if (this.InvokeRequired)
{
this.Invoke(new SetDataSourceDelegate(setDataSource), table);
}
else
{
dataGridView.DataSource = table;
progressBar.Visible = false;
}
}
Put the method loading the data into another thread and set the datasource when it's finished. There should be an invoke required. If you want to show percentage values in the progressbar, don't use the style 'Marquee' and add another function and delegate you can invoke for setting the value of the progress bar.
If binding the data to the grid is the problem, then you can not put the binding into another thread and you may show a progress-popup that runs in another thread.
I hope this helps.
Try this...This should be the quickest route...
1) Add a button control.
2) Add a datagridview control.
3) Add a single column into the datagridview control.
4) Add a progressbar control.
5) Leave them all with default names.
Under the button1_Click event, add this chunk of code...
int maxnumber = 1000;
progressBar1.Value = 0;
progressBar1.Maximum = maxnumber;
for (int x = 0; x <= maxnumber - 1; x++)
{
Application.DoEvents();
dataGridView1.Rows.Add(new string[] {Convert.ToString(x)});
progressBar1.Value += 1;
label1.Text = progressBar1.Value.ToString();
}
That's it. Edit to suit your needs. This isn't the complete exact code you want but it should get you started. I've taken the liberty to declare maxnumber to hold the limit of the progressbar. In your case, this should be the row count of your database and it's subtracted by 1 since index always starts with zero :)
The method above is single threaded, which means you still can't do multitasking while your loop still isn't done yet. In some cases, depending on the algorithm of the code, using the single threaded method may be faster than going for the multithreading route. Anyhow, the second method would be using a backgroundworker. This is most commonly preferred by people since the user can still do things while the list loads. Kind of like installers where you can cancel installation even if it's in the middle of doing something. The website below should get you started. The code is in VB.NET but can be easily converted to C# :)
http://social.msdn.microsoft.com/Forums/vstudio/en-US/efd7510c-43ed-47c4-86a3-1fa350eb0d30/fill-datagridview-using-backgroundworker-progressbar?forum=vbgeneral
The problem is all the data load together and return back from database.
You need to get the data loading progress. This can be done from database.
Get the count to data rows from database. (This query would return a single number, so it would be quicker).
Get the data in parts (Divide & conquer strategy), you can use OFFSET and FETCH database queries. (This is return part of data on each call)
OFFSET and FETCH is available on different database. In MSSQL, it was introduced in version 2012.
When we are getting the data in parts from server, we can calculate the progress.
Within your code, this line will take up most of the processing time:
SDA.Fill(DTGdataTable);
So I think the most important thing is to keep track of progress while this is executing. To do this, you first need to know how many rows you're expecting. The SQLDataAdapter can't provide this information, so you would need to run an extra COUNT(*) query first to get this number. You can then use this as the Maximum on your ProgressBar.
My actual loading code is based upon Michael Hofmeiers solution, but instead of using the ProgressBar in Marquee mode, I would feed it with real progress data from a Timer Control. So on your form, you add a Timer control (named progressTimer in my example), set its Interval to 100 msec and Enabled to false. The code then becomes:
private DataTable table = null;
private void buttonLoad_Click(object sender, EventArgs e)
{
// TODO: Select the row count here and assign it to progressBar.Maximum
progressBar.Visible = true;
System.Threading.Thread thread =
new System.Threading.Thread(new System.Threading.ThreadStart(loadTable));
thread.Start();
progressTimer.Enabled = true;
}
private void loadTable()
{
// Load your Table...
this.table = new DataTable();
SqlDataAdapter SDA = new SqlDataAdapter();
SDA.Fill(table);
setDataSource(table);
}
internal delegate void SetDataSourceDelegate(DataTable table);
private void setDataSource(DataTable table)
{
// Invoke method if required:
if (this.InvokeRequired)
{
this.Invoke(new SetDataSourceDelegate(setDataSource), table);
}
else
{
progressTimer.Enabled = false;
dataGridView.DataSource = table;
progressBar.Visible = false;
}
}
private void progressTimer_Tick(object sender, EventArgs e)
{
if (this.table != null)
progressBar.Value = this.table.Rows.Count;
}
When you run this, your application will stay responsive during the loading, and you'll see the ProgressBar changing to reflect the number of rows loaded. Only at the end (when you set the DataSource on the DataGridView) will the application seem to "hang", but I don't think there's a way to avoid that. You can only do this operation from the main thread, so it's unavoidable that the UI becomes unresponsive. But in my test, the DataGridView easily handles 300K+ rows in about a second, so that shouldn't be that much of a problem.
1.Download an ajax loader.gif from google.
2.replace that image with this code..
3.Set time accordingly..
<div id="spinner" style="display: none;">
<span id="ss" style="float: left; margin-left: 50% !Important; margin-top: 22% !Important;">
<img src="ajax-pink-loader.gif" alt="Loading..." />
</span>
</div>
$("#ssubmit").click(function () {
$("#spinner").show();
setInterval(function () {
$("#spinner").hide();
}, 20000);
});
In a WinForms application, I have a datagrid that is associated to a datasource. As and when data comes in through a background thread the dataset needs to be updated which in turn automatically updates the datagrid. Now, the updates can be in the order of say, 7000 updates per 20 seconds.
The probelm is that the UI hangs when such an update happens because it has to happen on the main thread. Is there a know solution for this problem?
Generally, how can you design highly performant enterprise applications in WinForms where the UI is being updated continuously without the application freezing?
Adding a scenario to explain this:
Consider this scenario. You have a tree view which you are using to represent some hierarchical data. Now the data update on the tree is async. The server can publish one or 1000 updates at the same same time. The update can be a modification of an existing item or addition of new nodes. The point to be noted is that the update cannot be delayed. The nodes represent a real time entity somewhere. Delaying the update will give the user the perception that the event itself was delayed. So this cant be done. If it was possible (from the business logic point of view) I would have done it a long time back.
There is a key point here: All the data need not be visible at the same time.
So that people dont suggest this anymore:
Adding a background worker thread WILL NOT HELP because the thread has to switch to the main thread to perform the update. The worker thread will make no difference.
You cannot, unless you want to use DirectX.
Windows Forms is not designed for real-time information display. As many others have pointed out, you can get very close, but due to the way the Windows message loop works, you absolutely cannot guarantee that what is on the screen will be "real-time", even if you create a Timer that ticks at 60hz. Even if you do this in an event-driven manner, Windows will still queue up a WM_PAINT message that will inevitably be late if you are looking for real-time display.
If you truly want a extremely-close-to-realtime display, you will need to implement something similar to a Game Loop.
For an explanation of why the Windows Message loop won't work for real-time display, and what a game loop is, see:
http://www.mvps.org/directx/articles/writing_the_game_loop.htm
A computer game cannot have any perceivable delay, so most computer games try to optimize performance so that they approach a framerate at or above the holy grail of 60hz. (Movies are only projected at 24hz, do you perceive them to be "delayed"?)
Writing an application with a real-time display is non-trivial and I would highly suggest considering compromising with what Windows provides in any of the following ways:
Create a timer that queues up screen updates at an acceptable rate (10 or more times per second). The user will not perceive the event as delayed, because a user cannot perceive delays that occur at a small fraction of a second.
Raise an event when the underlying data changes and let Windows to decide when to update the display (this will virtually always be acceptable).
If possible, come up with an alternate display that is not grid-based. Perhaps a scrolling console, or some other interface that displays the relevant information without overwriting old information. This may not be applicable, but coming up with another interface idea is often a good approach when the interface you want won't work.
If you really, really still want a very high-performance user interface, and write a game loop, you can do so in C# and paint a grid on your own to a DirectX surface. Once you get the hang of DirectX, drawing a grid is fairly easy, it's just a bunch of lines. With this approach you will avoid dealing with the Windows message loop, and potentially approach real-time performance.
Here's a great tutorial on how to use DirectX and how to render on a Windows form:
http://www.godpatterns.com/2005/02/using-directx-and-c-sharp-to-create.html
In your comment you say that your heavy processing is reporting progress very often and you can't drop any report (because the report is real data that needs showing).
What you should do is implement (double) buffering, report progress to a buffer and only synchronize the buffer with GUI every once in a while.
Pseudocode follows:
DataGrid Grid; // This displays the data
List<object> DataBuffer; // Frequent updates are performed on this list
void BackgroundThreadLoop()
{
while(true) // This loop iterates 7000 times in 20 seconds
{
var result = DoSomeHeavyCalculations();
// Depending on the nature of the result, you can either just add it to list
// or perhaps modify existing entries in the list in some way.
DataBuffer.Add(result); // The simple case
PerformSomeUpdating(DataBuffer, result); // The complicated case
}
}
Timer RefreshTimer;
override void OnLoad()
{
RefreshTimer = new Timer();
RefreshTimer.Interval = 500; // easy to experiment with this
RefreshTimer.Tick += (s, ea) => DrawBuffer(DataBuffer);
}
void DrawBuffer(List<object> DataBuffer)
{
// This should copy DataBuffer and put it in the grid as fast as possible.
// How to do this really depends on how the list changes and what it contains.
// If it's just a list of strings:
Grid.DataSource = DataBuffer.ToList(); // Shallow copy is OK with strings
// If it's a list of some objects that have meaningful Clone method:
Grid.DataSource = DataBuffer.Select(o => o.Clone).ToList();
// If the number of elements is like constant and only some values change,
// you could use some Dictionary instead of List and just copy values.
}
If you give more precise info, I might be able to help further.
UPDATE
With the new details, I'd suggest buffering individual changes made to objects. The most general way of representing a change to some objects structure would be a function (perhaps parameterless Action). While receiving changes, you construct the update-functions modifying directly the view-bound data and store them in the buffer:
List<Action> UpdateBuffer;
void OnUpdateReceived(MyType objToModify, object newValue)
{
// The point is to make the lambda (below) as efficient as you can;
// finding the object and preparing the update should be done here, so that
// no time is wasted during redraw in the main thread.
UpdateBuffer.Add(() => objToModify.ApplyNewValueInSomeWay(newValue));
// some other method should be constructed to add data to the view, but you get the point
}
Now the DrawBuffer (name no longer fully adequate, but no matter) method would be easy:
void DrawBuffer()
{
List<Action> bufferCopy;
lock(UpdateBuffer) // the other thread should also lock the buffer for adding
{
bufferCopy = UpdateBuffer.ToList();
UpdateBuffer.Clear();
}
view.SuspendLayout();
foreach(Action a in bufferCopy)
a();
view.ResumeLayout();
}
Obviously I have not tried this exact solution, but it gives you the ability to control redraw frequency and redraw whole batches instead of single updates.
I just made an sample application, that will fill its internal list through a BackgroundWorker and the data will be displayed within a DataGridView. You can change the speed of the inserts to find out if it meets your requirements:
The most interesting part should be the code within the form itself:
public partial class FormMain : Form
{
private List<Person> _Persons;
private Random _Random;
private int _TimeoutBetweenInserts;
public FormMain()
{
InitializeComponent();
// Initialize our private fields
_Random = new Random();
_Persons = new List<Person>();
_TimeoutBetweenInserts = (int)numericUpDownTimeoutBetweenInserts.Value;
// Attach the list to the binding source and get informed on list changes.
personBindingSource.DataSource = _Persons;
personBindingSource.ListChanged += (sender, e) => labelDataGridViewCount.Text = _Persons.Count.ToString();
}
private void OnBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
var spinner = new SpinWait();
var worker = (BackgroundWorker)sender;
// Should we abort our adding?
while (!worker.CancellationPending)
{
// Create a new entry ...
var person = new Person();
person.Index = _Persons.Count;
person.Born = new DateTime(_Random.Next(1950, 2012), _Random.Next(1, 13), _Random.Next(1, 28));
person.FirstName = "Hello";
person.LastName = "World";
// ... and add it to the list
_Persons.Add(person);
// Do a little waiting ... (to avoid blowing out the list)
for (int i = 0; i < _TimeoutBetweenInserts; i++)
{
spinner.SpinOnce();
}
spinner.Reset();
}
}
private void OnBackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Stop the gui updater, cause the background worker also stopped.
timerGuiUpdater.Stop();
}
private void OnCheckBoxToggleWorkerCheckedChanged(object sender, EventArgs e)
{
// Update the "button" according to the state
checkBoxToggleWorker.Text = checkBoxToggleWorker.Checked ? "&Pause" : "&Start";
if (checkBoxToggleWorker.Checked)
{
if (!backgroundWorker.IsBusy)
{
// Start the gui updater and the background worker
timerGuiUpdater.Start();
backgroundWorker.RunWorkerAsync();
}
}
else
{
// Stop the background worker
backgroundWorker.CancelAsync();
}
}
private void OnNumericUpDownTimeoutBetweenInsertsValueChanged(object sender, EventArgs e)
{
// Update the internal value, to let it propagate into the background worker
_TimeoutBetweenInserts = (int)numericUpDownTimeoutBetweenInserts.Value;
}
private void OnTimerGuiUpdaterTick(object sender, EventArgs e)
{
// Tell the BindingSource it should inform its clients (the DataGridView)
// to update itself
personBindingSource.ResetBindings(false);
}
}
To let you access all these fields within the form, here comes the Designer.cs:
partial class FormMain
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.dataGridView = new System.Windows.Forms.DataGridView();
this.Index = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.lastNameDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.firstNameDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.bornDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.ageDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.personBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.backgroundWorker = new System.ComponentModel.BackgroundWorker();
this.labelDataGridViewCountText = new System.Windows.Forms.Label();
this.labelDataGridViewCount = new System.Windows.Forms.Label();
this.labelSpinsBetweenInsertsText = new System.Windows.Forms.Label();
this.numericUpDownTimeoutBetweenInserts = new System.Windows.Forms.NumericUpDown();
this.checkBoxToggleWorker = new System.Windows.Forms.CheckBox();
this.timerGuiUpdater = new System.Windows.Forms.Timer(this.components);
((System.ComponentModel.ISupportInitialize)(this.dataGridView)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.personBindingSource)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numericUpDownTimeoutBetweenInserts)).BeginInit();
this.SuspendLayout();
//
// dataGridView
//
this.dataGridView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.dataGridView.AutoGenerateColumns = false;
this.dataGridView.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
this.dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.Index,
this.lastNameDataGridViewTextBoxColumn,
this.firstNameDataGridViewTextBoxColumn,
this.bornDataGridViewTextBoxColumn,
this.ageDataGridViewTextBoxColumn});
this.dataGridView.DataSource = this.personBindingSource;
this.dataGridView.Location = new System.Drawing.Point(12, 12);
this.dataGridView.Name = "dataGridView";
this.dataGridView.Size = new System.Drawing.Size(560, 212);
this.dataGridView.TabIndex = 0;
//
// Index
//
this.Index.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCells;
this.Index.DataPropertyName = "Index";
this.Index.HeaderText = "Index";
this.Index.Name = "Index";
this.Index.Width = 58;
//
// lastNameDataGridViewTextBoxColumn
//
this.lastNameDataGridViewTextBoxColumn.DataPropertyName = "LastName";
this.lastNameDataGridViewTextBoxColumn.HeaderText = "LastName";
this.lastNameDataGridViewTextBoxColumn.Name = "lastNameDataGridViewTextBoxColumn";
//
// firstNameDataGridViewTextBoxColumn
//
this.firstNameDataGridViewTextBoxColumn.DataPropertyName = "FirstName";
this.firstNameDataGridViewTextBoxColumn.HeaderText = "FirstName";
this.firstNameDataGridViewTextBoxColumn.Name = "firstNameDataGridViewTextBoxColumn";
//
// bornDataGridViewTextBoxColumn
//
this.bornDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCells;
this.bornDataGridViewTextBoxColumn.DataPropertyName = "Born";
this.bornDataGridViewTextBoxColumn.HeaderText = "Born";
this.bornDataGridViewTextBoxColumn.Name = "bornDataGridViewTextBoxColumn";
this.bornDataGridViewTextBoxColumn.Width = 54;
//
// ageDataGridViewTextBoxColumn
//
this.ageDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCells;
this.ageDataGridViewTextBoxColumn.DataPropertyName = "Age";
this.ageDataGridViewTextBoxColumn.HeaderText = "Age";
this.ageDataGridViewTextBoxColumn.Name = "ageDataGridViewTextBoxColumn";
this.ageDataGridViewTextBoxColumn.ReadOnly = true;
this.ageDataGridViewTextBoxColumn.Width = 51;
//
// personBindingSource
//
this.personBindingSource.DataSource = typeof(WindowsFormsApplication.Person);
//
// backgroundWorker
//
this.backgroundWorker.WorkerSupportsCancellation = true;
this.backgroundWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(this.OnBackgroundWorkerDoWork);
this.backgroundWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.OnBackgroundWorkerRunWorkerCompleted);
//
// labelDataGridViewCountText
//
this.labelDataGridViewCountText.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.labelDataGridViewCountText.Location = new System.Drawing.Point(12, 230);
this.labelDataGridViewCountText.Name = "labelDataGridViewCountText";
this.labelDataGridViewCountText.Size = new System.Drawing.Size(50, 23);
this.labelDataGridViewCountText.TabIndex = 1;
this.labelDataGridViewCountText.Text = "Count:";
this.labelDataGridViewCountText.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// labelDataGridViewCount
//
this.labelDataGridViewCount.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.labelDataGridViewCount.Location = new System.Drawing.Point(68, 230);
this.labelDataGridViewCount.Name = "labelDataGridViewCount";
this.labelDataGridViewCount.Size = new System.Drawing.Size(82, 23);
this.labelDataGridViewCount.TabIndex = 2;
this.labelDataGridViewCount.Text = "0";
this.labelDataGridViewCount.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// labelSpinsBetweenInsertsText
//
this.labelSpinsBetweenInsertsText.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.labelSpinsBetweenInsertsText.Location = new System.Drawing.Point(265, 230);
this.labelSpinsBetweenInsertsText.Name = "labelSpinsBetweenInsertsText";
this.labelSpinsBetweenInsertsText.Size = new System.Drawing.Size(155, 23);
this.labelSpinsBetweenInsertsText.TabIndex = 3;
this.labelSpinsBetweenInsertsText.Text = "Spins between inserts:";
this.labelSpinsBetweenInsertsText.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// numericUpDownTimeoutBetweenInserts
//
this.numericUpDownTimeoutBetweenInserts.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.numericUpDownTimeoutBetweenInserts.Increment = new decimal(new int[] {
10,
0,
0,
0});
this.numericUpDownTimeoutBetweenInserts.Location = new System.Drawing.Point(426, 233);
this.numericUpDownTimeoutBetweenInserts.Maximum = new decimal(new int[] {
500,
0,
0,
0});
this.numericUpDownTimeoutBetweenInserts.Minimum = new decimal(new int[] {
10,
0,
0,
0});
this.numericUpDownTimeoutBetweenInserts.Name = "numericUpDownTimeoutBetweenInserts";
this.numericUpDownTimeoutBetweenInserts.Size = new System.Drawing.Size(65, 20);
this.numericUpDownTimeoutBetweenInserts.TabIndex = 4;
this.numericUpDownTimeoutBetweenInserts.ThousandsSeparator = true;
this.numericUpDownTimeoutBetweenInserts.Value = new decimal(new int[] {
500,
0,
0,
0});
this.numericUpDownTimeoutBetweenInserts.ValueChanged += new System.EventHandler(this.OnNumericUpDownTimeoutBetweenInsertsValueChanged);
//
// checkBoxToggleWorker
//
this.checkBoxToggleWorker.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.checkBoxToggleWorker.Appearance = System.Windows.Forms.Appearance.Button;
this.checkBoxToggleWorker.Location = new System.Drawing.Point(497, 230);
this.checkBoxToggleWorker.Name = "checkBoxToggleWorker";
this.checkBoxToggleWorker.Size = new System.Drawing.Size(75, 23);
this.checkBoxToggleWorker.TabIndex = 6;
this.checkBoxToggleWorker.Text = "&Start";
this.checkBoxToggleWorker.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.checkBoxToggleWorker.UseVisualStyleBackColor = true;
this.checkBoxToggleWorker.CheckedChanged += new System.EventHandler(this.OnCheckBoxToggleWorkerCheckedChanged);
//
// timerGuiUpdater
//
this.timerGuiUpdater.Tick += new System.EventHandler(this.OnTimerGuiUpdaterTick);
//
// FormMain
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(584, 262);
this.Controls.Add(this.checkBoxToggleWorker);
this.Controls.Add(this.numericUpDownTimeoutBetweenInserts);
this.Controls.Add(this.labelSpinsBetweenInsertsText);
this.Controls.Add(this.labelDataGridViewCount);
this.Controls.Add(this.labelDataGridViewCountText);
this.Controls.Add(this.dataGridView);
this.MinimumSize = new System.Drawing.Size(600, 300);
this.Name = "FormMain";
this.Text = "DataGridView Performance Tester";
((System.ComponentModel.ISupportInitialize)(this.dataGridView)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.personBindingSource)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numericUpDownTimeoutBetweenInserts)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.DataGridView dataGridView;
private System.ComponentModel.BackgroundWorker backgroundWorker;
private System.Windows.Forms.BindingSource personBindingSource;
private System.Windows.Forms.Label labelDataGridViewCountText;
private System.Windows.Forms.Label labelDataGridViewCount;
private System.Windows.Forms.Label labelSpinsBetweenInsertsText;
private System.Windows.Forms.NumericUpDown numericUpDownTimeoutBetweenInserts;
private System.Windows.Forms.CheckBox checkBoxToggleWorker;
private System.Windows.Forms.Timer timerGuiUpdater;
private System.Windows.Forms.DataGridViewTextBoxColumn Index;
private System.Windows.Forms.DataGridViewTextBoxColumn lastNameDataGridViewTextBoxColumn;
private System.Windows.Forms.DataGridViewTextBoxColumn firstNameDataGridViewTextBoxColumn;
private System.Windows.Forms.DataGridViewTextBoxColumn bornDataGridViewTextBoxColumn;
private System.Windows.Forms.DataGridViewTextBoxColumn ageDataGridViewTextBoxColumn;
}
Last but not least my little person class that is used for the payload:
public class Person
{
public int Age
{
get
{
// ToDo: better algorithm to determine real age is left as an exercise to the reader. ;-)
var age = (int)((DateTime.Now - Born).TotalDays / 365);
return Math.Max(0, age);
}
}
public DateTime Born { get; set; }
public string FirstName { get; set; }
public int Index { get; set; }
public string LastName { get; set; }
}
Is the bottleneck in processing data from server or in actually putting it to DataGridView? If latter, VirtualMode can help you: http://msdn.microsoft.com/en-us/library/2b177d6d.aspx.
Are you using a BackgroundWorker? Put the code that makes you application to freeze in DoWork event:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
YourFreezingCodeHere
}
And start backgroundWorker like
backgroundWorker1.RunWorkerAsync();
You can do this using BackgroundWorker. In the DoWork method you can iterate update the datagrid.
To update datagrid from Non-UI thread you will need to as follows
Create an extension method like
public static class ControlExtensions
{
public static void Invoke(this Control control, Action action)
{
if (control.InvokeRequired) control.Invoke(new MethodInvoker(action), null);
else action.Invoke();
}
}
Update data grid as (assuming dataGrid is your control id and dataSource is your data source)
dataGrid.Invoke(() => { dataGrid.DataSource = dataSource; };
Hope this works for you.
The UI will always be updated by the main/UI thread. That is the way WinForms works.
What you can do is prevent the UI-thread from doing too much. To do that:
Make sure you execute all other processing on one or more different threads.
Only update the UI when useful for the user. I can't see/read a number that is changing every 3 ms so skip DISPLAYING the update.
Note that I use the terms ViewModel, View and Model is the remainder of this answer. I am not forcing you to use MVVM but it makes explaining things easier. You could use MVP or MVC in the same way.
You could create a special kind of ViewModel that raises an event after x milliseconds to check for a 'dirty bits' and raise appropriate PropertyChanged events. This would require you to set the dirty bits in the property setters and NOT raise the PropertyChanged event in the setters.
Perhaps even better might be to keep track of the last time a ViewModel was updated; when it is longer than x milliseconds ago, update the ViewModel from the Model, otherwise don't. This guarantees the UI to be in-sync with the ViewModel. But you have to realize that the ViewModel is not in-sync with the Model. Of course it is possible to create methods to sync the models explicitly.
The choice between these two might depend on how you think about the View-ViewModel relation and how much time this all is costing.
Application.DoEvents();
Use this method inside timer.
private void timer1_Tick(object sender, EventArgs e)
{
Application.DoEvents();
}
You should start timer where your UI freezes or you can start it in form_Load and set small number to your timer's interval to make it ticks frequently. For Example set it to ten.
timer1.Start();
timer1.Interval = 10;
I've done a lot of high volume data transfers (hundreds per second) like this and I think a DataGrid just isn't the control you want. It's designed to present data and let the user edit it, it's not really optimized to be an information stream. At this volume it won't do a user much good to view the data in real time, it's just going to be a stream of data too large and fast to make sense of.
I suggest you continue to use a background worker to do the work (like you said you are) and use the ReportProgress method to send a % done back to a progress bar. You can also update a label on the page with the file you're working on. The label will update automatically and not freeze your UI. To do this, create an instance variable in the class your background worker calls. On your UI, create an instance of that class and in the background worker's ProgressChanged method set your UI label to your class instance variable. It will update each time you call backgroundworker.ReportProgress()
Then put all the info in a log so someone can look at it later. It's just not that beneficial to try to visually take in 350 changes/second.
One solution to this problem is to update your data model periodically, i.e. to batch update them from the communication thread every x milliseconds. A little more information on how you are accessing the server data would be helpful in giving you a more explicit suggestion.
At the same time you should be using virtualised controls (especially the datagrid). Using a virtual grid basically means visible cells are rendered on the fly. Hence you only need to update the data that is currently displayed. As each cell becomes visible it will access the data model to obtain the relevent value at that time. See this link as a starting point to implementing a virtual grid.
By combining these two approaches you should be able to minimise the amount of updates to the grid.
re: The problem is that the UI hangs when such an update happens because it has to happen on the main thread. Is there a known solution for this problem?
no, as you're seeing
Generally, how can you design highly performant enterprise applications in WinForms where the UI is being updated continuously without the application freezing?
At the scale you're describing, you can't. Try limiting the scope of your UI control so it doesn't try to display everything happening everywhere at once, but forces the user to pick an area to focus on that you can update at an acceptable rate.
I recommend to use two layers for handling this scenario.
Non-UI Data Layer:
This layer can grab all updates from your background thread and generate final Data object (Let's call it ObjectX) which is the latest and most current state of data. This layer should run on it's own thread which will not effect UI at all. Also after receiving any update you can have one boolean variable (Let's call it NewUpdateExist) and set it to true which indicates that new changes has been received. Don't forget to use Thread-safe locking mechanism to set this variable to True, to avoid concurrency issues.
UI Sync Layer:
This layer also can run on separated thread. You can have a timer which will trigger on specific interval(*) to checks if there is any new data since last UI update by checking out NewUpdateExist variable and if there is, then Set NewUpdateExist to false and generate new sub-set of data which only required to display on Screen (***). Don't forget to use Thread-safe locking while generating sub-set of data and updating NewUpdateExist variable.
After generating your sub-set of data then you need to Invoke another method by control (In UI Thread) to apply this sub-set of data to the control. This is where UI thread will block until process done so you need to make this method as light as possible! All heavy stuff needs to be done outside of Invoke and only part that relate to UI control needs to be in that method.
(*) As "Hans Passant" mentioned in his comment, human eye only can process 50 milliseconds refresh, but I even suggest to increase this to 100 msec. You can get some ideas from this thread:
What is the shortest perceivable application response delay?
(**) Tricky part in this case is how to update your control with only data that is required instead of pushing all data at once to the UI. I really recommend to implement custom controls to handle this part instead of using standard controls; because you'll have full access to how and when to update UI and you can achieve best performance. For example on Grid you can find out first visible item and number of items that can be displayed on UI and just update that portion instead of trying to update control with all data.
Sorry, I know I suppose to explain solution in short message but this is the shortest version that I can came across. I hope this helps :-)
There is an article posted on MSDN regarding Asynchronous Call Pattern for Windows Forms. I hope this helps.
Use backgroundWorker, it will run inserted code in separated thread, so the application will not freeze.
public void backgroundWorkerPinger_DoWork(object sender, DoWorkEventArgs e)
{
Ping ping = new Ping();
try
{
PingReply pingreply = ping.Send("46.4.106.10", 500);
string active = pingreply.Status.ToString();
if (active == "Success")
{
//Pokud je spojení aktivni pak se nastavi barva labelu na zelenou a vypise se aktivni
ActiveOrNotLabel.ForeColor = Color.Green;
ActiveOrNotLabel.Text = "Aktivní";
// MessageBox.Show("vyjimka2");
if (connection_enabled == false)
{
admini.Enabled = true;
connection_enabled = true;
}
}
if (active != "Success") {
ActiveOrNotLabel.ForeColor = Color.Red;
ActiveOrNotLabel.Text = "Neaktivní";
admini.Enabled = false;
connection_enabled = false;
}
}
catch
{
//Jinak na cervenou a neaktivni
//MessageBox.Show("vyjimka");
ActiveOrNotLabel.ForeColor = Color.Red;
ActiveOrNotLabel.Text = "Neaktivní";
admini.Enabled = false;
connection_enabled = false;
}
}