update datagridview very frequently - c#

I'm having trouble refreshing my DataGridView in a reasonable time in C# (which I am new to btw, I'm used to java...).
I'm getting data over a network with 20 packages sent per second. I'd like to parse the data and put it in a DataGridView. I would also like to adjust the interval in which the DataGridView is updated, from 0.1 seconds to 1 minute.
So I created an extra thread, which reads the packages and parses them to an Array. I also have a Timer, which I use to change the Interval. On every timer tick, I reassign the DataSource to the DataGridView.
Interestingly, when I do, even if I set the timer to 0.1 seconds, it is only triggered about once a second. If I do not refresh the DataGridView, it gets triggered 10 times a second, as it is supposed to.
So I am assuming that my method of updating the DataGridView is too time consuming. But what do I have to do to make it more efficient, so I can update it 10 times a second without any problems?
Here is the code I use:
public MyForm()
{
InitializeComponent();
timer = new System.Windows.Forms.Timer();
timer.Interval = (1 * 1000); // 1 secs
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
readNetworkValues = true;
networkReader = new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
byte[] data = new byte[1024];
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 49003);
UdpClient newsock = new UdpClient(ipep);
IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
while (readNetworkValues)
{
data = newsock.Receive(ref sender);
dataSet = parseData(data); //Decrypts the data
}
newsock.Close();
});
networkReader.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
if (dataSet != null)
{
lock (dataSet)
{
int currentRow = dataGrid.FirstDisplayedScrollingRowIndex;
dataGrid.DataSource = dataSet;
dataGrid.FirstDisplayedScrollingRowIndex = currentRow;
}
}
}

The number of cells you want to update and also the update rate you want are high enough to cause flicker and lagging.
To avoid it you can turn on DoubleBuffering for the DataGridView.
This property is not exposed by default. So have a have a choice of either
creating a subclass or
accessing it via reflection
Here is a post that demonstrates the former. It was written for a case of scrolling flicker but will help avoid update lags as well. The class can maybe look like this:
public class DBDataGridView : DataGridView
{
public new bool DoubleBuffered
{
get { return base.DoubleBuffered; }
set { base.DoubleBuffered = value; }
}
public DBDataGridView()
{
DoubleBuffered = true;
}
}
You can add this class to the project or simply to the form class (before the very last curly.) Compile and it will show up in the ToolBox.
The other option uses reflection; here is a general-purpose function that should work for for any type of control:
using System.Reflection;
static void SetDoubleBuffer(Control ctl, bool DoubleBuffered)
{
typeof(Control).InvokeMember("DoubleBuffered",
BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
null, ctl, new object[] { DoubleBuffered });
}
Both ways let you turn DoubleBuffering on and off at will; the former via the now exposed property, the latter by the bool param of the method.

Related

How to access Form textboxes inside EventHandler by string in C#?

I am using Visual Studio 2017. There is a form with textboxes. These textboxes need a refresh every 10 seconds. For achieving this I use a Timer event.
public partial class status_window : Form
{
public status_window()
{
InitializeComponent();
shutdown_button.Click += new EventHandler(shutdown_click);
Timer timer = new Timer();
timer.Interval = (1 * 10000); // 10 secs
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
}
The timer_tick function is member of the status_window class. Inside that eventhandler I can access the textboxes by their name as expected. But how to do that if the textbox "adress" is i variable. Look:
private void timer_Tick(object sender, EventArgs e)
{
Int32 unixtime = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
// for all boxes per exname
for (int i = 0; i < something.Count() ; i++)
{
// try to find textbox 1 -> embty result
Console.WriteLine( this.Controls.Find("nam1_last_event", true) );
Console.WriteLine( this.Controls.Find("nam2_last_event", true) ); // also empty result
// this works and fills the formbox as ecxpected
nam1_last_event.Text = "somevalue";
nam1_event_count.Text = "anothervale";
nam2_last_event.Text = "somemorevalue";
nam2_event_count.Text = "andsoon";
// thats what i want later to use my for loop for those:
// something[i] exuals to nam1,nam2 and so on
this.Controls.Find( String.Format("{0}_last_event", something[i].ToLower()) , true)[0].Text = "somevalue"; // this fails cause array returned by find is empty
this.Controls.Find(String.Format("{0}_last_event", ex_name.ToLower()), true)[0].Text = "anothervale"; // same
}
}
So I stuck here somehow limited by my own knowledge. Most results on Google suggest to use the controls Find Method.
This works for me:
var inPanel = this.Controls.Find("inPanel", true).OfType<TextBox>().FirstOrDefault();
inPanel?.Text = "Found it";
In your code you use equally name nam1_last_event as member of class status_window and name of control. Please check in designer if your control relay has name nam1_last_event.
Function Controls.Find use key which is value of property Name of control.
Create a list or Dictionary variable to save those textboxs, and get it in the timer_Tick.

auto scroll in an ultragridview for infragistics

I am using an Infragistics UltraGridView in my program. Is it possible to set it to automatically scroll the UltraGridView starting at top to the bottom and then resetting it back to the top? Also the UltraGridView is to be set as AutoRefresh. Any ideas?
You can simply build a tight loop as this
foreach (UltraGridRow row in grid.Rows)
{
row.Activate();
}
But it is unclear what is your purpose for this code. Your user probably will not be able to understand anything of the data while it scrolls on the grid.
Instead if your point is to set a particular row as the first one in the grid area then you should work along the line of this
grid.ActiveRowScrollRegion.FirstRow = grid.Rows[500];
(Assuming that you have more than 500 rows of course)
If you want to slow down the scrolling then you could add a Timer and in the Tick event run the Activate call. In this context you could write a class like this
public class SlowScroller
{
private UltraGridRow current = null;
private UltraGrid grd = null;
private System.Windows.Forms.Timer t = null;
public SlowScroller(UltraGrid grid)
{
grd = grid;
t = new System.Windows.Forms.Timer();
}
public void Start(int interval)
{
t.Interval = interval;
t.Tick += onTick;
t.Start();
}
public void Stop()
{
if (t.Enabled)
t.Stop();
}
private void onTick(object sender, EventArgs e)
{
if(current == null)
current = grd.Rows[0];
else
current = current.GetSibling(SiblingRow.Next);
current.Activate();
}
}
And call it with
SlowScroller ss = new SlowScroller(grid);
ss.Start(500); // Scroll every 500 milliseconds
Note the presence of the Stop method. This is necessary because you don't want this class to continue fire the Tick event even when you discard your form. Thus you need to call the Stop in the Form_Closing event handler

C# Trouble Using Safe Thead or Background Worker

Fairly frustrating since this seems to be well documented and the fact that I accomplished this before, but can't duplicate the same success. Sorry, I'll try to relate it all clearly.
Visual Studio, C# Form, One Main Form has text fields, among other widgets.
At one point we have the concept that we are "running" and therefore gathering data.
For the moment, I started a one second timer so that I can update simulated data into some fields. Eventually that one second timer will take the more rapid data and update it only once per second to the screen, that's the request for the application right now we update at the rate we receive which is a little over 70 Hz, they don't want it that way. In addition some other statistics will be computed and those should be the field updates. Therefore being simple I'm trying to just generate random data and update those fields at the 1 Hz rate. And then expand from that point.
Definition and management of the timer: (this is all within the same class MainScreen)
System.Timers.Timer oneSecondTimer;
public UInt32 run_time = 0;
public int motion = 5;
private void InitializeTimers()
{
this.oneSecondTimer = new System.Timers.Timer(1000);
this.oneSecondTimer.Elapsed += new System.Timers.ElapsedEventHandler(oneSecondTimer_elapsed);
}
public void start_one_second_timer()
{
run_time = 0;
oneSecondTimer.Enabled = true;
}
public void stop_one_second_timer()
{
oneSecondTimer.Enabled = false;
run_time = 0;
}
Random mot = new Random();
private void oneSecondTimer_elapsed(object source, System.Timers.ElapsedEventArgs e)
{
run_time++;
motion = mot.Next(1, 10);
this.oneSecondThread = new Thread(new ThreadStart(this.UpdateTextFields));
this.oneSecondThread.Start();
}
private void UpdateTextFields()
{
this.motionDisplay.Text = this.motion.ToString();
}
motionDisplay is just a textbox in my main form. I get the Invalid Operation Exception pointing me towards the help on how to make Thread-Safe calls. I also tried backgroundworker and end up with the same result. The details are that motionDisplay is accessed from a thread other than the thread it was created on.
So looking for some suggestions as to where my mistakes are.
Best Regards. I continue to iterate on this and will update if I find a solution.
Use a System.Forms.Timer rather than a System.Timers.Timer. It will fire it's elapsed event in the UI thread.
Don't create a new thread to update the UI; just do the update in the elapsed event handler.
Try this
private void UpdateTextFields()
{
this.BeginInvoke(new EventHandler((s,e)=>{
this.motionDisplay.Text = this.motion.ToString();
}));
}
This will properly marshall a call back to the main thread.
The thing with WinForm development is that all the controls are not thread safe. Even getting a property such as .Text from another thread can cause these type of errors to happen. To make it even more frustrating is that sometimes it will work at runtime and you won't get an exception, other times you will.
This is how I do it:
private delegate void UpdateMotionDisplayCallback(string text);
private void UpdateMotionDisplay(string text) {
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.motionDisplay.InvokeRequired) {
UpdateMotionDisplayCallback d = new UpdateMotionDisplayCallback(UpdateMotionDisplay);
this.Invoke(d, new object[] { text });
} else {
this.motionDisplay.Text = text;
}
}
When you want to update the text in motionDisplay just call:
UpdateMotionDisplay(this.motion.ToString())

ObjectDisposedException after closing application on Timer

Im working on a Network application. First I wanted to make DataGridView to refresh its data on every second. My implementation:
public partial class Form1 : Form
{
BindingList<Wifi> Networks = new BindingList<Wifi>();
System.Timers.Timer NetworksRefreshThread;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
NetshScanner netshScanner = new NetshScanner();
NetworksRefreshThread = new System.Timers.Timer(1000);
dataGridView1.DataSource = Networks;
NetworksRefreshThread.Elapsed += delegate
{
BindingList<Wifi> tmp = new BindingList<Wifi>(netshScanner.StartAndParseOutput());
this.Invoke((MethodInvoker)delegate
{
Networks.Clear();
foreach (Wifi net in tmp)
{
Networks.Add(net);
}
});
};
}
After closing the form, I get ObjectDisposedException inside the this.Invoke. Any advice?
What's probably happening is that the timer has fired and is currently busy refreshing your DataGridView. This happens on a seperate thread.
Then when you are closing the form, the GUI thread starts destroying its objects.
After that, the timer is done retrieving its new data but has no object left to put it.
To solve this. Add a boolean to your class called 'Updating' or something similar. Then set its value when you are updating to true.
NetworksRefreshThread.Elapsed += delegate
{
Updating = true;
BindingList<Wifi> tmp = new BindingList<Wifi>(netshScanner.StartAndParseOutput());
this.Invoke((MethodInvoker)delegate
{
Networks.Clear();
foreach (Wifi net in tmp)
{
Networks.Add(net);
}
});
Updating = false;
};
Now create a new method which you bind to the Form closing down event. In this method, do a Thread.Sleep() while the timer is updating:
while (Updating) Thread.Sleep(100);

How do I safely populate with data and Refresh() a DataGridView in a multi-threaded application?

My app has a DataGridView object and a List of type MousePos. MousePos is a custom class that holds mouse X,Y coordinates (of type "Point") and a running count of this position. I have a thread (System.Timers.Timer) that raises an event once every second, checks the mouse position, adds and/or updates the count of the mouse position on this List.
I would like to have a similar running thread (again, I think System.Timers.Timer is a good choice) which would again raise an event once a second to automatically Refresh() the DataGridView so that the user can see the data on the screen update. (like TaskManager does.)
Unfortunately, calling the DataGridView.Refresh() method results in VS2005 stopping execution and noting that I've run into a cross-threading situation.
If I'm understanding correctly, I have 3 threads now:
Primary UI thread
MousePos List thread (Timer)
DataGridView Refresh thread (Timer)
To see if I could Refresh() the DataGridView on the primary thread, I added a button to the form which called DataGridView.Refresh(), but this (strangely) didn't do anything. I found a topic which seemed to indicate that if I set DataGridView.DataSource = null and back to my List, that it would refresh the datagrid. And indeed this worked, but only thru the button (which gets handled on the primary thread.)
So this question has turned into a two-parter:
Is setting DataGridView.DataSource to null and back to my List an acceptable way to refresh the datagrid? (It seems inefficient to me...)
How do I safely do this in a multi-threaded environment?
Here's the code I've written so far (C#/.Net 2.0)
public partial class Form1 : Form
{
private static List<MousePos> mousePositionList = new List<MousePos>();
private static System.Timers.Timer mouseCheck = new System.Timers.Timer(1000);
private static System.Timers.Timer refreshWindow = new System.Timers.Timer(1000);
public Form1()
{
InitializeComponent();
mousePositionList.Add(new MousePos()); // ANSWER! Must have at least 1 entry before binding to DataSource
dataGridView1.DataSource = mousePositionList;
mouseCheck.Elapsed += new System.Timers.ElapsedEventHandler(mouseCheck_Elapsed);
mouseCheck.Start();
refreshWindow.Elapsed += new System.Timers.ElapsedEventHandler(refreshWindow_Elapsed);
refreshWindow.Start();
}
public void mouseCheck_Elapsed(object source, EventArgs e)
{
Point mPnt = Control.MousePosition;
MousePos mPos = mousePositionList.Find(ByPoint(mPnt));
if (mPos == null) { mousePositionList.Add(new MousePos(mPnt)); }
else { mPos.Count++; }
}
public void refreshWindow_Elapsed(object source, EventArgs e)
{
//dataGridView1.DataSource = null; // Old way
//dataGridView1.DataSource = mousePositionList; // Old way
dataGridView1.Invalidate(); // <= ANSWER!!
}
private static Predicate<MousePos> ByPoint(Point pnt)
{
return delegate(MousePos mPos) { return (mPos.Pnt == pnt); };
}
}
public class MousePos
{
private Point position = new Point();
private int count = 1;
public Point Pnt { get { return position; } }
public int X { get { return position.X; } set { position.X = value; } }
public int Y { get { return position.Y; } set { position.Y = value; } }
public int Count { get { return count; } set { count = value; } }
public MousePos() { }
public MousePos(Point mouse) { position = mouse; }
}
You have to update the grid on the main UI thread, like all the other controls. See control.Invoke or Control.BeginInvoke.
UPDATE! -- I partially figured out the answer to part #1 in the book "Pro .NET 2.0 Windows Forms and Customer Controls in C#"
I had originally thought that Refresh() wasn't doing anything and that I needed to call the Invalidate() method, to tell Windows to repaint my control at it's leisure. (which is usually right away, but if you need a guarantee to repaint it now, then follow up with an immediate call to the Update() method.)
dataGridView1.Invalidate();
But, it turns out that the Refresh() method is merely an alias for:
dataGridView1.Invalidate(true);
dataGridView1.Update(); // <== forces immediate redraw
The only glitch I found with this was that if there was no data in the dataGridView, no amount of invalidating would refresh the control. I had to reassign the datasource. Then it worked fine after that. But only for the amount of rows (or items in my list) -- If new items were added, the dataGridView would be unaware that there were more rows to display.
So it seems that when binding a source of data (List or Table) to the Datasource, the dataGridView counts the items (rows) and then sets this internally and never checks to see if there are new rows/items or rows/items deleted. This is why re-binding the datasource repeatedly was working before.
Now to figure out how to update the number of rows to display in dataGridView without having to re-bind the datasource... fun, fun, fun! :-)
After doing some digging, I think I have my answer to part #2 of my question (aka. safe Multi-threading):
Rather than using System.Timers.Timer, I found that I should be using System.Windows.Forms.Timer instead.
The event occurs such that the method that is used in the Callback automatically happens on the primary thread. No cross-threading issues!
The declaration looks like this:
private static System.Windows.Forms.Timer refreshWindow2;
refreshWindow2 = new Timer();
refreshWindow2.Interval = 1000;
refreshWindow2.Tick += new EventHandler(refreshWindow2_Tick);
refreshWindow2.Start();
And the method is like this:
private void refreshWindow2_Tick(object sender, EventArgs e)
{
dataGridView1.Invalidate();
}
Looks like you have your answer right there!
Just in cawse you're curious about how to do cross thread calls back to ui:
All controls have a Invoke() method (or BEginInvoke()- in case you want to do things asynchronously), this is used to call any method on the control within the context of the main UI thread.
So, if you were going to call your datagridview from another thread you would need to do the following:
public void refreshWindow_Elapsed(object source, EventArgs e)
{
// we use anonymous delgate here as it saves us declaring a named delegate in our class
// however, as c# type inference sometimes need a bit of 'help' we need to cast it
// to an instance of MethodInvoker
dataGridView1.Invoke((MethodInvoker)delegate() { dataGridView1.Invalidate(); });
}

Categories